diff --git a/Cargo.lock b/Cargo.lock index bf3bb9620..401393d9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8122,6 +8122,7 @@ dependencies = [ "parity-scale-codec", "rustc-hex", "scale-info", + "schnorrkel", "serde", "serde_json", "sp-core 21.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", diff --git a/Cargo.toml b/Cargo.toml index e4248d0a9..e74b27678 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,5 +309,7 @@ rpc-trace = { path = "client/rpc/trace" } rpc-txpool = { path = "client/rpc/txpool" } evm-tracer = { path = "standalone/runtime/evm_tracer", default-features = false } +schnorrkel = { verison = "11.4", default-features = false } + [profile.release] panic = "unwind" diff --git a/pallets/claims/Cargo.toml b/pallets/claims/Cargo.toml index 59f3af5ba..82eda1d7c 100644 --- a/pallets/claims/Cargo.toml +++ b/pallets/claims/Cargo.toml @@ -16,6 +16,7 @@ pallet-evm = { workspace = true } rustc-hex = { workspace = true } scale-info = { workspace = true } serde = { workspace = true } +schnorrkel = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -51,5 +52,6 @@ std = [ "pallet-vesting/std", "pallet-balances/std", "pallet-evm/std", + "schnorrkel/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/claims/src/lib.rs b/pallets/claims/src/lib.rs index b5e0e9233..2e81bc7d9 100644 --- a/pallets/claims/src/lib.rs +++ b/pallets/claims/src/lib.rs @@ -24,33 +24,38 @@ mod mock; #[cfg(test)] mod tests; +mod utils; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +use crate::utils::{ + ethereum_address::{EcdsaSignature, EthereumAddress}, + MultiAddress, MultiAddressSignature, +}; use frame_support::{ ensure, - traits::{Currency, Get, IsSubType, VestingSchedule}, + traits::{Currency, Defensive, Get, IsSubType, VestingSchedule}, weights::Weight, }; pub use pallet::*; use pallet_evm::AddressMapping; use parity_scale_codec::{Decode, Encode}; use scale_info::{ - prelude::{format, string::String}, TypeInfo, }; use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; use sp_core::H160; use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; use sp_runtime::{ - traits::{CheckedSub, DispatchInfoOf, SignedExtension, Zero}, + traits::{CheckedSub, SignedExtension, Zero}, transaction_validity::{ - InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, + InvalidTransaction, TransactionValidity, ValidTransaction, }, - RuntimeDebug, + AccountId32, RuntimeDebug, }; use sp_std::{convert::TryInto, fmt::Debug, prelude::*, vec}; - +use utils::Sr25519Signature; /// Custom validity errors used in Polkadot while validating transactions. #[repr(u8)] pub enum ValidityError { @@ -102,16 +107,6 @@ impl WeightInfo for TestWeightInfo { } } -#[derive( - Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo, Serialize, Deserialize, -)] -pub enum ClaimerType { - /// Claimer is Ethereum address - EthereumAddress, - /// Claimer is Substrate address - SubstrateAddress(A), -} - /// The kind of statement an account needs to make for a claim to be valid. #[derive( Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo, Serialize, Deserialize, @@ -145,66 +140,6 @@ impl Default for StatementKind { } } -/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account). -/// -/// This gets serialized to the 0x-prefixed hex representation. -#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct EthereumAddress([u8; 20]); - -impl Serialize for EthereumAddress { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]); - serializer.serialize_str(&format!("0x{}", hex)) - } -} - -impl<'de> Deserialize<'de> for EthereumAddress { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let base_string = String::deserialize(deserializer)?; - let offset = if base_string.starts_with("0x") { 2 } else { 0 }; - let s = &base_string[offset..]; - if s.len() != 40 { - Err(serde::de::Error::custom( - "Bad length of Ethereum address (should be 42 including '0x')", - ))?; - } - let raw: Vec = rustc_hex::FromHex::from_hex(s) - .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; - let mut r = Self::default(); - r.0.copy_from_slice(&raw); - Ok(r) - } -} - -impl From for H160 { - fn from(a: EthereumAddress) -> Self { - let mut r = Self::default(); - r.0.copy_from_slice(&a.0); - r - } -} - -#[derive(Encode, Decode, Clone, TypeInfo)] -pub struct EcdsaSignature(pub [u8; 65]); - -impl PartialEq for EcdsaSignature { - fn eq(&self, other: &Self) -> bool { - &self.0[..] == &other.0[..] - } -} - -impl sp_std::fmt::Debug for EcdsaSignature { - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - write!(f, "EcdsaSignature({:?})", &self.0[..]) - } -} - #[frame_support::pallet] pub mod pallet { use super::*; @@ -234,13 +169,17 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Someone claimed some native tokens. - Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf }, + Claimed { recipient: T::AccountId, source: MultiAddress, amount: BalanceOf }, } #[pallet::error] pub enum Error { /// Invalid Ethereum signature. InvalidEthereumSignature, + /// Invalid Native (sr25519) signature + InvalidNativeSignature, + /// Invalid Native account decoding + InvalidNativeAccount, /// Ethereum address has no claim. SignerHasNoClaim, /// Account ID sending transaction has no claim. @@ -256,7 +195,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn claims)] - pub(super) type Claims = StorageMap<_, Identity, EthereumAddress, BalanceOf>; + pub(super) type Claims = StorageMap<_, Identity, MultiAddress, BalanceOf>; #[pallet::storage] #[pallet::getter(fn total)] @@ -265,7 +204,7 @@ pub mod pallet { /// Expiry block and account to deposit expired funds #[pallet::storage] #[pallet::getter(fn expiry_time)] - pub(super) type ExpiryConfig = StorageValue<_, (BlockNumberFor, T::AccountId)>; + pub(super) type ExpiryConfig = StorageValue<_, (BlockNumberFor, MultiAddress)>; /// Vesting schedule for a claim. /// First balance is the total amount that should be held for vesting. @@ -274,22 +213,17 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn vesting)] pub(super) type Vesting = - StorageMap<_, Identity, EthereumAddress, (BalanceOf, BalanceOf, BlockNumberFor)>; + StorageMap<_, Identity, MultiAddress, (BalanceOf, BalanceOf, BlockNumberFor)>; /// The statement kind that must be signed, if any. #[pallet::storage] - pub(super) type Signing = StorageMap<_, Identity, EthereumAddress, StatementKind>; - - /// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to. - #[pallet::storage] - pub(super) type Preclaims = StorageMap<_, Identity, T::AccountId, EthereumAddress>; + pub(super) type Signing = StorageMap<_, Identity, MultiAddress, StatementKind>; #[pallet::genesis_config] pub struct GenesisConfig { - pub claims: - Vec<(EthereumAddress, BalanceOf, Option, Option)>, - pub vesting: Vec<(EthereumAddress, (BalanceOf, BalanceOf, BlockNumberFor))>, - pub expiry: Option<(BlockNumberFor, T::AccountId)>, + pub claims: Vec<(MultiAddress, BalanceOf, Option)>, + pub vesting: Vec<(MultiAddress, (BalanceOf, BalanceOf, BlockNumberFor))>, + pub expiry: Option<(BlockNumberFor, MultiAddress)>, } impl Default for GenesisConfig { @@ -302,18 +236,13 @@ pub mod pallet { impl BuildGenesisConfig for GenesisConfig { fn build(&self) { // build `Claims` - self.claims - .iter() - .map(|(a, b, _, _)| (a.clone(), b.clone())) - .for_each(|(a, b)| { - println!("a: {:?}, b: {:?}", a, b); - Claims::::insert(a, b); - }); + self.claims.iter().map(|(a, b, _)| (a.clone(), b.clone())).for_each(|(a, b)| { + println!("a: {:?}, b: {:?}", a, b); + Claims::::insert(a, b); + }); // build `Total` Total::::put( - self.claims - .iter() - .fold(Zero::zero(), |acc: BalanceOf, &(_, b, _, _)| acc + b), + self.claims.iter().fold(Zero::zero(), |acc: BalanceOf, &(_, b, _)| acc + b), ); // build `Vesting` self.vesting.iter().for_each(|(k, v)| { @@ -322,17 +251,10 @@ pub mod pallet { // build `Signing` self.claims .iter() - .filter_map(|(a, _, _, s)| Some((a.clone(), s.clone()?))) + .filter_map(|(a, _, s)| Some((a.clone(), s.clone()?))) .for_each(|(a, s)| { Signing::::insert(a, s); }); - // build `Preclaims` - self.claims - .iter() - .filter_map(|(a, _, i, _)| Some((i.clone()?, a.clone()))) - .for_each(|(i, a)| { - Preclaims::::insert(i, a); - }); // build expiryConfig ExpiryConfig::::set(self.expiry.clone()) } @@ -347,7 +269,12 @@ pub mod pallet { if current_block > expiry_config.0 { let unclaimed_amount = Total::::take(); frame_support::log::info!("Claims : Expiry block passed, sweeping remaining amount of {:?} to destination", unclaimed_amount); - CurrencyOf::::deposit_creating(&expiry_config.1, unclaimed_amount); + let expiry_destination = + match Self::convert_multi_address_to_account_id(expiry_config.1) { + Ok(a) => a, + Err(_) => return, + }; + CurrencyOf::::deposit_creating(&expiry_destination, unclaimed_amount); // clear the expiry detail ExpiryConfig::::take(); } @@ -357,7 +284,7 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Make a claim to collect your native tokens. + /// Make a claim to collect your tokens. /// /// The dispatch origin for this call must be _None_. /// @@ -385,16 +312,14 @@ pub mod pallet { #[pallet::call_index(0)] pub fn claim( origin: OriginFor, - dest: Option, - ethereum_signature: EcdsaSignature, + dest: Option, + signature: MultiAddressSignature, ) -> DispatchResult { ensure_none(origin)?; let data = dest.using_encoded(to_ascii_hex); - let signer = Self::eth_recover(ðereum_signature, &data, &[][..]) - .ok_or(Error::::InvalidEthereumSignature)?; + let signer = Self::get_signer_multi_address(dest.clone(), signature, data, vec![])?; ensure!(Signing::::get(&signer).is_none(), Error::::InvalidStatement); - Self::process_claim(signer, dest)?; Ok(()) } @@ -418,7 +343,7 @@ pub mod pallet { #[pallet::call_index(1)] pub fn mint_claim( origin: OriginFor, - who: EthereumAddress, + who: MultiAddress, value: BalanceOf, vesting_schedule: Option<(BalanceOf, BalanceOf, BlockNumberFor)>, statement: Option, @@ -427,9 +352,9 @@ pub mod pallet { >::mutate(|t| *t += value); println!("mint_claim: who: {:?}, value: {:?}", who, value); - >::insert(who, value); + >::insert(who.clone(), value); if let Some(vs) = vesting_schedule { - >::insert(who, vs); + >::insert(who.clone(), vs); } if let Some(s) = statement { Signing::::insert(who, s); @@ -468,79 +393,35 @@ pub mod pallet { #[pallet::call_index(2)] pub fn claim_attest( origin: OriginFor, - dest: Option, - ethereum_signature: EcdsaSignature, + dest: Option, + signature: MultiAddressSignature, statement: Vec, ) -> DispatchResult { ensure_none(origin)?; let data = dest.using_encoded(to_ascii_hex); println!("claim_attest: data: {:?}", data); - let signer = Self::eth_recover(ðereum_signature, &data, &statement) - .ok_or(Error::::InvalidEthereumSignature)?; - if let Some(s) = Signing::::get(signer) { + let signer = Self::get_signer_multi_address(dest.clone(), signature, data, statement.clone())?; + println!("claim_attest: signer: {:?}", signer); + if let Some(s) = Signing::::get(signer.clone()) { ensure!(s.to_text() == &statement[..], Error::::InvalidStatement); } Self::process_claim(signer, dest)?; Ok(()) } - /// Attest to a statement, needed to finalize the claims process. - /// - /// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a - /// `SignedExtension`. - /// - /// Unsigned Validation: - /// A call to attest is deemed valid if the sender has a `Preclaim` registered - /// and provides a `statement` which is expected for the account. - /// - /// Parameters: - /// - `statement`: The identity of the statement which is being attested to in the - /// signature. - /// - /// - /// The weight of this call is invariant over the input parameters. - /// Weight includes logic to do pre-validation on `attest` call. - /// - /// Total Complexity: O(1) - /// - #[pallet::weight(( - 3, - DispatchClass::Normal, - Pays::No - ))] - #[pallet::call_index(3)] - pub fn attest(origin: OriginFor, statement: Vec) -> DispatchResult { - let who = ensure_signed(origin)?; - let signer = Preclaims::::get(&who).ok_or(Error::::SenderHasNoClaim)?; - if let Some(s) = Signing::::get(signer) { - ensure!(s.to_text() == &statement[..], Error::::InvalidStatement); - } - Self::process_claim(signer, Some(who.clone()))?; - Preclaims::::remove(&who); - Ok(()) - } - #[pallet::weight({4})] #[pallet::call_index(4)] pub fn move_claim( origin: OriginFor, - old: EthereumAddress, - new: EthereumAddress, - maybe_preclaim: Option, + old: MultiAddress, + new: MultiAddress, ) -> DispatchResultWithPostInfo { T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; Claims::::take(&old).map(|c| Claims::::insert(&new, c)); Vesting::::take(&old).map(|c| Vesting::::insert(&new, c)); Signing::::take(&old).map(|c| Signing::::insert(&new, c)); - maybe_preclaim.map(|preclaim| { - Preclaims::::mutate(&preclaim, |maybe_o| { - if maybe_o.as_ref().map_or(false, |o| o == &old) { - *maybe_o = Some(new) - } - }) - }); Ok(Pays::No.into()) } @@ -551,7 +432,7 @@ pub mod pallet { pub fn force_set_expiry_config( origin: OriginFor, expiry_block: BlockNumberFor, - dest: T::AccountId, + dest: MultiAddress, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; ExpiryConfig::::set(Some((expiry_block, dest))); @@ -570,19 +451,30 @@ pub mod pallet { // // The weight of this logic is included in the `claim` dispatchable. // - Call::claim { dest: account, ethereum_signature } => { + Call::claim { dest: account, signature } => { let data = account.using_encoded(to_ascii_hex); - (Self::eth_recover(ðereum_signature, &data, &[][..]), None) + match signature { + MultiAddressSignature::EVM(ethereum_signature) => + (Self::eth_recover(ðereum_signature, &data, &[][..]), None), + MultiAddressSignature::Native(sr25519_signature) => + (Self::sr25519_recover(&sr25519_signature, &data, &[][..]), None), + } }, // // The weight of this logic is included in the `claim_attest` dispatchable. // - Call::claim_attest { dest: account, ethereum_signature, statement } => { + Call::claim_attest { dest: account, signature, statement } => { let data = account.using_encoded(to_ascii_hex); - ( - Self::eth_recover(ðereum_signature, &data, &statement), - Some(statement.as_slice()), - ) + match signature { + MultiAddressSignature::EVM(ethereum_signature) => ( + Self::eth_recover(ðereum_signature, &data, &statement[..]), + Some(statement.as_slice()), + ), + MultiAddressSignature::Native(sr25519_signature) => ( + Self::sr25519_recover(&sr25519_signature, &data, &statement[..]), + Some(statement.as_slice()), + ), + } }, _ => return Err(InvalidTransaction::Call.into()), }; @@ -595,7 +487,7 @@ pub mod pallet { ensure!(>::contains_key(&signer), e); let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); - match Signing::::get(signer) { + match Signing::::get(signer.clone()) { None => ensure!(maybe_statement.is_none(), e), Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e), } @@ -642,41 +534,51 @@ impl Pallet { // Attempts to recover the Ethereum address from a message signature signed by using // the Ethereum RPC's `personal_sign` and `eth_sign`. - fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option { + fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option { let msg = keccak_256(&Self::ethereum_signable_message(what, extra)); let mut res = EthereumAddress::default(); res.0 .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]); - Some(res) + Some(MultiAddress::EVM(res)) + } + + // Attempts to recover the Substrate address from a message signature signed by using + // the Substrate RPC's `sign`. + fn sr25519_recover(_s: &Sr25519Signature, what: &[u8], extra: &[u8]) -> Option { + let _msg = keccak_256(&Self::ethereum_signable_message(what, extra)); + let res = AccountId32::new([0; 32]); + Some(MultiAddress::Native(res)) } fn process_claim( - signer: EthereumAddress, - dest: Option, + signer: MultiAddress, + dest: Option, ) -> sp_runtime::DispatchResult { println!("process_claim: signer: {:?}, dest: {:?}", signer, dest); let balance_due = >::get(&signer).ok_or(Error::::SignerHasNoClaim)?; let new_total = Self::total().checked_sub(&balance_due).ok_or(Error::::PotUnderflow)?; - let dest = match dest { + let recipient = match dest { Some(d) => d, - None => T::AddressMapping::into_account_id(H160::from(signer)), + None => signer.clone(), }; + let recipient = Self::convert_multi_address_to_account_id(recipient)?; + let vesting = Vesting::::get(&signer); - if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() { + if vesting.is_some() && T::VestingSchedule::vesting_balance(&recipient).is_some() { return Err(Error::::VestedBalanceExists.into()) } // We first need to deposit the balance to ensure that the account exists. - CurrencyOf::::deposit_creating(&dest, balance_due); + CurrencyOf::::deposit_creating(&recipient, balance_due); // Check if this claim should have a vesting schedule. if let Some(vs) = vesting { // This can only fail if the account already has a vesting schedule, // but this is checked above. - T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2) + T::VestingSchedule::add_vesting_schedule(&recipient, vs.0, vs.1, vs.2) .expect("No other vesting schedule exists, as checked above; qed"); } @@ -686,94 +588,39 @@ impl Pallet { Signing::::remove(&signer); // Let's deposit an event to let the outside world know this happened. - Self::deposit_event(Event::::Claimed { - who: dest, - ethereum_address: signer, - amount: balance_due, - }); - - Ok(()) - } -} + Self::deposit_event(Event::::Claimed { recipient, source: signer, amount: balance_due }); -/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are -/// otherwise free to place on chain. -#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] -#[scale_info(skip_type_params(T))] -pub struct PrevalidateAttests(sp_std::marker::PhantomData) -where - ::RuntimeCall: IsSubType>; - -impl Debug for PrevalidateAttests -where - ::RuntimeCall: IsSubType>, -{ - #[cfg(feature = "std")] - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "PrevalidateAttests") - } - - #[cfg(not(feature = "std"))] - fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { Ok(()) } -} -impl PrevalidateAttests -where - ::RuntimeCall: IsSubType>, -{ - /// Create new `SignedExtension` to check runtime version. - pub fn new() -> Self { - Self(sp_std::marker::PhantomData) - } -} + fn get_signer_multi_address( + _dest: Option, + signature: MultiAddressSignature, + data: Vec, + statement: Vec, + ) -> Result> { + let signer = match signature { + MultiAddressSignature::EVM(ethereum_signature) => + Self::eth_recover(ðereum_signature, &data, &statement[..]) + .ok_or(Error::::InvalidEthereumSignature)?, + MultiAddressSignature::Native(sr25519_signature) => + Self::sr25519_recover(&sr25519_signature, &data, &statement[..]) + .ok_or(Error::::InvalidNativeSignature)?, + }; -impl SignedExtension for PrevalidateAttests -where - ::RuntimeCall: IsSubType>, -{ - type AccountId = T::AccountId; - type Call = ::RuntimeCall; - type AdditionalSigned = (); - type Pre = (); - const IDENTIFIER: &'static str = "PrevalidateAttests"; - - fn additional_signed(&self) -> Result { - Ok(()) + Ok(signer) } - fn pre_dispatch( - self, - who: &Self::AccountId, - call: &Self::Call, - info: &DispatchInfoOf, - len: usize, - ) -> Result { - Ok(self.validate(who, call, info, len).map(|_| ())?) - } + fn convert_multi_address_to_account_id(dest: MultiAddress) -> Result> { + let account = match dest { + MultiAddress::EVM(a) => T::AddressMapping::into_account_id(H160::from(a)), + MultiAddress::Native(a) => match Decode::decode(&mut a.encode().as_slice()) { + Ok(a) => a, + Err(_) => return Err(Error::::InvalidNativeAccount), + }, + }; - // - // The weight of this logic is included in the `attest` dispatchable. - // - fn validate( - &self, - who: &Self::AccountId, - call: &Self::Call, - _info: &DispatchInfoOf, - _len: usize, - ) -> TransactionValidity { - if let Some(local_call) = call.is_sub_type() { - if let Call::attest { statement: attested_statement } = local_call { - let signer = Preclaims::::get(who) - .ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?; - if let Some(s) = Signing::::get(signer) { - let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); - ensure!(&attested_statement[..] == s.to_text(), e); - } - } - } - Ok(ValidTransaction::default()) + Ok(account) } } @@ -784,16 +631,16 @@ mod secp_utils { pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey { libsecp256k1::PublicKey::from_secret_key(secret) } - pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress { + pub fn eth(secret: &libsecp256k1::SecretKey) -> MultiAddress { let mut res = EthereumAddress::default(); res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]); - res + MultiAddress::EVM(res) } pub fn sig( secret: &libsecp256k1::SecretKey, what: &[u8], extra: &[u8], - ) -> EcdsaSignature { + ) -> MultiAddressSignature { let msg = keccak_256(&>::ethereum_signable_message( &to_ascii_hex(what)[..], extra, @@ -802,6 +649,6 @@ mod secp_utils { let mut r = [0u8; 65]; r[0..64].copy_from_slice(&sig.serialize()[..]); r[64] = recovery_id.serialize(); - EcdsaSignature(r) + MultiAddressSignature::EVM(EcdsaSignature(r)) } } diff --git a/pallets/claims/src/mock.rs b/pallets/claims/src/mock.rs index 733cd049b..27c25816d 100644 --- a/pallets/claims/src/mock.rs +++ b/pallets/claims/src/mock.rs @@ -97,7 +97,7 @@ parameter_types! { pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:"; } ord_parameter_types! { - pub const Six: AccountId32 = get_account_id(6); + pub const Six: AccountId32 = get_multi_address_account_id(6).to_account_id_32(); } impl Config for Test { @@ -136,8 +136,8 @@ pub fn frank() -> libsecp256k1::SecretKey { libsecp256k1::SecretKey::parse(&keccak_256(b"Frank")).unwrap() } -pub fn get_account_id(id: u8) -> AccountId32 { - AccountId32::new([id; 32]) +pub fn get_multi_address_account_id(id: u8) -> MultiAddress { + MultiAddress::Native(AccountId32::new([id; 32])) } // This function basically just builds a genesis storage key/value store according to @@ -150,10 +150,10 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .unwrap(); pallet_airdrop_claims::GenesisConfig:: { claims: vec![ - (eth(&alice()), 100, None, None), - (eth(&dave()), 200, None, Some(StatementKind::Regular)), - (eth(&eve()), 300, Some(get_account_id(42)), Some(StatementKind::Safe)), - (eth(&frank()), 400, Some(get_account_id(43)), None), + (eth(&alice()), 100, None), + (eth(&dave()), 200, Some(StatementKind::Regular)), + (eth(&eve()), 300, Some(StatementKind::Safe)), + (eth(&frank()), 400, None), ], vesting: vec![(eth(&alice()), (50, 10, 1))], expiry: None, diff --git a/pallets/claims/src/tests.rs b/pallets/claims/src/tests.rs index 095ee6d96..f4410068b 100644 --- a/pallets/claims/src/tests.rs +++ b/pallets/claims/src/tests.rs @@ -29,11 +29,26 @@ fn basic_setup_works() { assert_eq!(ClaimsPallet::claims(ð(&dave())), Some(200)); assert_eq!(ClaimsPallet::claims(ð(&eve())), Some(300)); assert_eq!(ClaimsPallet::claims(ð(&frank())), Some(400)); - assert_eq!(ClaimsPallet::claims(&EthereumAddress::default()), None); + assert_eq!(ClaimsPallet::claims(&MultiAddress::EVM(EthereumAddress::default())), None); assert_eq!(ClaimsPallet::vesting(ð(&alice())), Some((50, 10, 1))); }); } +#[test] +fn eth_signature_works() { + new_test_ext().execute_with(|| { + let data = Some(get_multi_address_account_id(42)).encode(); + let s = sig::(&alice(), &data, &[][..]); + let sig = match s { + MultiAddressSignature::EVM(s) => s, + _ => panic!("should be evm signature"), + }; + + assert_eq!(ClaimsPallet::eth_recover(&sig, &to_ascii_hex(&data), &[][..]), Some(eth(&alice()))); + assert!(ClaimsPallet::eth_recover(&sig, &Some(get_multi_address_account_id(43)).encode(), &[][..]) != Some(eth(&alice()))); + }); +} + #[test] fn serde_works() { let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]); @@ -46,14 +61,14 @@ fn serde_works() { #[test] fn claiming_works() { new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); assert_ok!(ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&alice(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) )); - assert_eq!(Balances::free_balance(&get_account_id(42)), 100); - assert_eq!(VestingPallet::vesting_balance(&get_account_id(42)), Some(50)); + assert_eq!(Balances::free_balance(&get_multi_address_account_id(42).to_account_id_32()), 100); + assert_eq!(VestingPallet::vesting_balance(&get_multi_address_account_id(42).to_account_id_32()), Some(50)); assert_eq!(ClaimsPallet::total(), total_claims() - 100); }); } @@ -61,37 +76,35 @@ fn claiming_works() { #[test] fn basic_claim_moving_works() { new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); assert_noop!( ClaimsPallet::move_claim( - RuntimeOrigin::signed(get_account_id(1)), + RuntimeOrigin::signed(get_multi_address_account_id(1).to_account_id_32()), eth(&alice()), eth(&bob()), - None ), BadOrigin ); assert_ok!(ClaimsPallet::move_claim( - RuntimeOrigin::signed(get_account_id(6)), + RuntimeOrigin::signed(get_multi_address_account_id(6).to_account_id_32()), eth(&alice()), eth(&bob()), - None )); assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&alice(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) ), Error::::SignerHasNoClaim ); assert_ok!(ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&bob(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&bob(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) )); - assert_eq!(Balances::free_balance(&get_account_id(42)), 100); - assert_eq!(VestingPallet::vesting_balance(&get_account_id(42)), Some(50)); + assert_eq!(Balances::free_balance(&get_multi_address_account_id(42).to_account_id_32()), 100); + assert_eq!(VestingPallet::vesting_balance(&get_multi_address_account_id(42).to_account_id_32()), Some(50)); assert_eq!(ClaimsPallet::total(), total_claims() - 100); }); } @@ -100,40 +113,22 @@ fn basic_claim_moving_works() { fn claim_attest_moving_works() { new_test_ext().execute_with(|| { assert_ok!(ClaimsPallet::move_claim( - RuntimeOrigin::signed(get_account_id(6)), + RuntimeOrigin::signed(get_multi_address_account_id(6).to_account_id_32()), eth(&dave()), eth(&bob()), - None )); let s = sig::( &bob(), - &Some(get_account_id(42)).encode(), + &Some(get_multi_address_account_id(42)).encode(), StatementKind::Regular.to_text(), ); assert_ok!(ClaimsPallet::claim_attest( RuntimeOrigin::none(), - Some(get_account_id(42)), + Some(get_multi_address_account_id(42)), s, StatementKind::Regular.to_text().to_vec() )); - assert_eq!(Balances::free_balance(&get_account_id(42)), 200); - }); -} - -#[test] -fn attest_moving_works() { - new_test_ext().execute_with(|| { - assert_ok!(ClaimsPallet::move_claim( - RuntimeOrigin::signed(get_account_id(6)), - eth(&eve()), - eth(&bob()), - Some(get_account_id(42)) - )); - assert_ok!(ClaimsPallet::attest( - RuntimeOrigin::signed(get_account_id(42)), - StatementKind::Safe.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&get_account_id(42)), 300); + assert_eq!(Balances::free_balance(&get_multi_address_account_id(42).to_account_id_32()), 200); }); } @@ -142,29 +137,29 @@ fn claiming_does_not_bypass_signing() { new_test_ext().execute_with(|| { assert_ok!(ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&alice(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) )); assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&dave(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&dave(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) ), Error::::InvalidStatement, ); assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&eve(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&eve(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) ), Error::::InvalidStatement, ); assert_ok!(ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&frank(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&frank(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) )); }); } @@ -172,12 +167,16 @@ fn claiming_does_not_bypass_signing() { #[test] fn attest_claiming_works() { new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); - let s = - sig::(&dave(), &Some(get_account_id(42)).encode(), StatementKind::Safe.to_text()); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); + let data = Some(get_multi_address_account_id(42)).encode(); + let s = sig::( + &dave(), + &data, + StatementKind::Safe.to_text(), + ); let r = ClaimsPallet::claim_attest( RuntimeOrigin::none(), - Some(get_account_id(42)), + Some(get_multi_address_account_id(42)), s.clone(), StatementKind::Safe.to_text().to_vec(), ); @@ -185,7 +184,7 @@ fn attest_claiming_works() { let r = ClaimsPallet::claim_attest( RuntimeOrigin::none(), - Some(get_account_id(42)), + Some(get_multi_address_account_id(42)), s, StatementKind::Regular.to_text().to_vec(), ); @@ -195,26 +194,26 @@ fn attest_claiming_works() { let s = sig::( &dave(), - &Some(get_account_id(42)).encode(), + &Some(get_multi_address_account_id(42)).encode(), StatementKind::Regular.to_text(), ); assert_ok!(ClaimsPallet::claim_attest( RuntimeOrigin::none(), - Some(get_account_id(42)), + Some(get_multi_address_account_id(42)), s, StatementKind::Regular.to_text().to_vec() )); - assert_eq!(Balances::free_balance(&get_account_id(42)), 200); + assert_eq!(Balances::free_balance(&get_multi_address_account_id(42).to_account_id_32()), 200); assert_eq!(ClaimsPallet::total(), total_claims() - 200); let s = sig::( &dave(), - &Some(get_account_id(42)).encode(), + &Some(get_multi_address_account_id(42)).encode(), StatementKind::Regular.to_text(), ); let r = ClaimsPallet::claim_attest( RuntimeOrigin::none(), - Some(get_account_id(42)), + Some(get_multi_address_account_id(42)), s, StatementKind::Regular.to_text().to_vec(), ); @@ -222,93 +221,16 @@ fn attest_claiming_works() { }); } -#[test] -fn attesting_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); - assert_noop!( - ClaimsPallet::attest( - RuntimeOrigin::signed(get_account_id(69)), - StatementKind::Safe.to_text().to_vec() - ), - Error::::SenderHasNoClaim - ); - assert_noop!( - ClaimsPallet::attest( - RuntimeOrigin::signed(get_account_id(42)), - StatementKind::Regular.to_text().to_vec() - ), - Error::::InvalidStatement - ); - assert_ok!(ClaimsPallet::attest( - RuntimeOrigin::signed(get_account_id(42)), - StatementKind::Safe.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&get_account_id(42)), 300); - assert_eq!(ClaimsPallet::total(), total_claims() - 300); - }); -} - -#[test] -fn claim_cannot_clobber_preclaim() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); - // Alice's claim is 100 - assert_ok!(ClaimsPallet::claim( - RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(42)).encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&get_account_id(42)), 100); - // Eve's claim is 300 through Account 42 - assert_ok!(ClaimsPallet::attest( - RuntimeOrigin::signed(get_account_id(42)), - StatementKind::Safe.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&get_account_id(42)), 100 + 300); - assert_eq!(ClaimsPallet::total(), total_claims() - 400); - }); -} - -#[test] -fn valid_attest_transactions_are_free() { - new_test_ext().execute_with(|| { - let p = PrevalidateAttests::::new(); - let c = RuntimeCall::ClaimsPallet(ClaimsCall::attest { - statement: StatementKind::Safe.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - assert_eq!(di.pays_fee, Pays::No); - let r = p.validate(&get_account_id(42), &c, &di, 20); - assert_eq!(r, TransactionValidity::Ok(ValidTransaction::default())); - }); -} - -#[test] -fn invalid_attest_transactions_are_recognized() { - new_test_ext().execute_with(|| { - let p = PrevalidateAttests::::new(); - let c = RuntimeCall::ClaimsPallet(ClaimsCall::attest { - statement: StatementKind::Regular.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - let r = p.validate(&get_account_id(42), &c, &di, 20); - assert!(r.is_err()); - let c = RuntimeCall::ClaimsPallet(ClaimsCall::attest { - statement: StatementKind::Safe.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - let r = p.validate(&get_account_id(69), &c, &di, 20); - assert!(r.is_err()); - }); -} - #[test] fn cannot_bypass_attest_claiming() { new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); - let s = sig::(&dave(), &Some(get_account_id(42)).encode(), &[]); - let r = ClaimsPallet::claim(RuntimeOrigin::none(), Some(get_account_id(42)), s.clone()); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); + let s = sig::(&dave(), &Some(get_multi_address_account_id(42)).encode(), &[]); + let r = ClaimsPallet::claim( + RuntimeOrigin::none(), + Some(get_multi_address_account_id(42)), + s.clone(), + ); assert_noop!(r, Error::::InvalidStatement); }); } @@ -318,7 +240,7 @@ fn add_claim_works() { new_test_ext().execute_with(|| { assert_noop!( ClaimsPallet::mint_claim( - RuntimeOrigin::signed(get_account_id(42)), + RuntimeOrigin::signed(get_multi_address_account_id(42).to_account_id_32()), eth(&bob()), 200, None, @@ -326,12 +248,12 @@ fn add_claim_works() { ), sp_runtime::traits::BadOrigin, ); - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(69)), - sig::(&bob(), &Some(get_account_id(69)).encode(), &[][..]) + Some(get_multi_address_account_id(69)), + sig::(&bob(), &Some(get_multi_address_account_id(69)).encode(), &[][..]) ), Error::::SignerHasNoClaim, ); @@ -339,11 +261,11 @@ fn add_claim_works() { assert_eq!(ClaimsPallet::total(), total_claims() + 200); assert_ok!(ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(69)), - sig::(&bob(), &Some(get_account_id(69)).encode(), &[][..]) + Some(get_multi_address_account_id(69)), + sig::(&bob(), &Some(get_multi_address_account_id(69)).encode(), &[][..]) )); - assert_eq!(Balances::free_balance(get_account_id(69)), 200); - assert_eq!(VestingPallet::vesting_balance(&get_account_id(69)), None); + assert_eq!(Balances::free_balance(get_multi_address_account_id(69).to_account_id_32()), 200); + assert_eq!(VestingPallet::vesting_balance(&get_multi_address_account_id(69).to_account_id_32()), None); assert_eq!(ClaimsPallet::total(), total_claims()); }); } @@ -353,7 +275,7 @@ fn add_claim_with_vesting_works() { new_test_ext().execute_with(|| { assert_noop!( ClaimsPallet::mint_claim( - RuntimeOrigin::signed(get_account_id(42)), + RuntimeOrigin::signed(get_multi_address_account_id(42).to_account_id_32()), eth(&bob()), 200, Some((50, 10, 1)), @@ -361,12 +283,12 @@ fn add_claim_with_vesting_works() { ), sp_runtime::traits::BadOrigin, ); - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(69)), - sig::(&bob(), &Some(get_account_id(69)).encode(), &[][..]) + Some(get_multi_address_account_id(69)), + sig::(&bob(), &Some(get_multi_address_account_id(69)).encode(), &[][..]) ), Error::::SignerHasNoClaim, ); @@ -379,17 +301,17 @@ fn add_claim_with_vesting_works() { )); assert_ok!(ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(69)), - sig::(&bob(), &Some(get_account_id(69)).encode(), &[][..]) + Some(get_multi_address_account_id(69)), + sig::(&bob(), &Some(get_multi_address_account_id(69)).encode(), &[][..]) )); - assert_eq!(Balances::free_balance(get_account_id(69)), 200); - assert_eq!(VestingPallet::vesting_balance(&get_account_id(69)), Some(50)); + assert_eq!(Balances::free_balance(get_multi_address_account_id(69).to_account_id_32()), 200); + assert_eq!(VestingPallet::vesting_balance(&get_multi_address_account_id(69).to_account_id_32()), Some(50)); // Make sure we can not transfer the vested balance. assert_err!( >::transfer( - &get_account_id(69), - &get_account_id(80), + &get_multi_address_account_id(69).to_account_id_32(), + &get_multi_address_account_id(80).to_account_id_32(), 180, ExistenceRequirement::AllowDeath ), @@ -403,7 +325,7 @@ fn add_claim_with_statement_works() { new_test_ext().execute_with(|| { assert_noop!( ClaimsPallet::mint_claim( - RuntimeOrigin::signed(get_account_id(42)), + RuntimeOrigin::signed(get_multi_address_account_id(42).to_account_id_32()), eth(&bob()), 200, None, @@ -411,17 +333,17 @@ fn add_claim_with_statement_works() { ), sp_runtime::traits::BadOrigin, ); - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); let signature = sig::( &bob(), - &Some(get_account_id(69)).encode(), + &Some(get_multi_address_account_id(69)).encode(), StatementKind::Regular.to_text(), ); assert_noop!( ClaimsPallet::claim_attest( RuntimeOrigin::none(), - Some(get_account_id(69)), + Some(get_multi_address_account_id(69)), signature.clone(), StatementKind::Regular.to_text().to_vec() ), @@ -437,7 +359,7 @@ fn add_claim_with_statement_works() { assert_noop!( ClaimsPallet::claim_attest( RuntimeOrigin::none(), - Some(get_account_id(69)), + Some(get_multi_address_account_id(69)), signature.clone(), vec![], ), @@ -445,23 +367,23 @@ fn add_claim_with_statement_works() { ); assert_ok!(ClaimsPallet::claim_attest( RuntimeOrigin::none(), - Some(get_account_id(69)), + Some(get_multi_address_account_id(69)), signature.clone(), StatementKind::Regular.to_text().to_vec() )); - assert_eq!(Balances::free_balance(get_account_id(69)), 200); + assert_eq!(Balances::free_balance(get_multi_address_account_id(69).to_account_id_32()), 200); }); } #[test] fn origin_signed_claiming_fail() { new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); assert_err!( ClaimsPallet::claim( - RuntimeOrigin::signed(get_account_id(42)), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(42)).encode(), &[][..]) + RuntimeOrigin::signed(get_multi_address_account_id(42).to_account_id_32()), + Some(get_multi_address_account_id(42)), + sig::(&alice(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) ), sp_runtime::traits::BadOrigin, ); @@ -471,17 +393,17 @@ fn origin_signed_claiming_fail() { #[test] fn double_claiming_doesnt_work() { new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); assert_ok!(ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&alice(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) )); assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&alice(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) ), Error::::SignerHasNoClaim ); @@ -491,11 +413,11 @@ fn double_claiming_doesnt_work() { #[test] fn claiming_while_vested_doesnt_work() { new_test_ext().execute_with(|| { - CurrencyOf::::make_free_balance_be(&get_account_id(69), total_claims()); - assert_eq!(Balances::free_balance(get_account_id(69)), total_claims()); + CurrencyOf::::make_free_balance_be(&get_multi_address_account_id(69).to_account_id_32(), total_claims()); + assert_eq!(Balances::free_balance(get_multi_address_account_id(69).to_account_id_32()), total_claims()); // A user is already vested assert_ok!(::VestingSchedule::add_vesting_schedule( - &get_account_id(69), + &get_multi_address_account_id(69).to_account_id_32(), total_claims(), 100, 10 @@ -514,8 +436,8 @@ fn claiming_while_vested_doesnt_work() { assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(69)), - sig::(&bob(), &Some(get_account_id(69)).encode(), &[][..]) + Some(get_multi_address_account_id(69)), + sig::(&bob(), &Some(get_multi_address_account_id(69)).encode(), &[][..]) ), Error::::VestedBalanceExists, ); @@ -525,12 +447,12 @@ fn claiming_while_vested_doesnt_work() { #[test] fn non_sender_sig_doesnt_work() { new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(69)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&alice(), &Some(get_multi_address_account_id(69)).encode(), &[][..]) ), Error::::SignerHasNoClaim ); @@ -540,12 +462,12 @@ fn non_sender_sig_doesnt_work() { #[test] fn non_claimant_doesnt_work() { new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(get_account_id(42)), 0); + assert_eq!(Balances::free_balance(get_multi_address_account_id(42).to_account_id_32()), 0); assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&bob(), &Some(get_account_id(69)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&bob(), &Some(get_multi_address_account_id(69)).encode(), &[][..]) ), Error::::SignerHasNoClaim ); @@ -560,7 +482,7 @@ fn real_eth_sig_works() { let sig = EcdsaSignature(sig); let who = 42u64.using_encoded(to_ascii_hex); let signer = ClaimsPallet::eth_recover(&sig, &who, &[][..]).unwrap(); - assert_eq!(signer.0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]); + assert_eq!(signer.to_ethereum_address().unwrap().0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]); }); } @@ -574,10 +496,10 @@ fn validate_unsigned_works() { >::validate_unsigned( source, &ClaimsCall::claim { - dest: Some(get_account_id(1)), - ethereum_signature: sig::( + dest: Some(get_multi_address_account_id(1)), + signature: sig::( &alice(), - &Some(get_account_id(1)).encode(), + &Some(get_multi_address_account_id(1)).encode(), &[][..] ) } @@ -594,8 +516,8 @@ fn validate_unsigned_works() { >::validate_unsigned( source, &ClaimsCall::claim { - dest: Some(get_account_id(0)), - ethereum_signature: EcdsaSignature([0; 65]) + dest: Some(get_multi_address_account_id(0)), + signature: MultiAddressSignature::EVM(EcdsaSignature([0; 65])) } ), InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), @@ -604,10 +526,10 @@ fn validate_unsigned_works() { >::validate_unsigned( source, &ClaimsCall::claim { - dest: Some(get_account_id(1)), - ethereum_signature: sig::( + dest: Some(get_multi_address_account_id(1)), + signature: sig::( &bob(), - &Some(get_account_id(1)).encode(), + &Some(get_multi_address_account_id(1)).encode(), &[][..] ) } @@ -616,12 +538,12 @@ fn validate_unsigned_works() { ); let s = sig::( &dave(), - &Some(get_account_id(1)).encode(), + &Some(get_multi_address_account_id(1)).encode(), StatementKind::Regular.to_text(), ); let call = ClaimsCall::claim_attest { - dest: Some(get_account_id(1)), - ethereum_signature: s, + dest: Some(get_multi_address_account_id(1)), + signature: s, statement: StatementKind::Regular.to_text().to_vec(), }; assert_eq!( @@ -638,8 +560,8 @@ fn validate_unsigned_works() { >::validate_unsigned( source, &ClaimsCall::claim_attest { - dest: Some(get_account_id(1)), - ethereum_signature: EcdsaSignature([0; 65]), + dest: Some(get_multi_address_account_id(1)), + signature: MultiAddressSignature::EVM(EcdsaSignature([0; 65])), statement: StatementKind::Regular.to_text().to_vec() } ), @@ -648,12 +570,12 @@ fn validate_unsigned_works() { let s = sig::( &bob(), - &Some(get_account_id(1)).encode(), + &Some(get_multi_address_account_id(1)).encode(), StatementKind::Regular.to_text(), ); let call = ClaimsCall::claim_attest { - dest: Some(get_account_id(1)), - ethereum_signature: s, + dest: Some(get_multi_address_account_id(1)), + signature: s, statement: StatementKind::Regular.to_text().to_vec(), }; assert_eq!( @@ -661,11 +583,14 @@ fn validate_unsigned_works() { InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), ); - let s = - sig::(&dave(), &Some(get_account_id(1)).encode(), StatementKind::Safe.to_text()); + let s = sig::( + &dave(), + &Some(get_multi_address_account_id(1)).encode(), + StatementKind::Safe.to_text(), + ); let call = ClaimsCall::claim_attest { - dest: Some(get_account_id(1)), - ethereum_signature: s, + dest: Some(get_multi_address_account_id(1)), + signature: s, statement: StatementKind::Regular.to_text().to_vec(), }; assert_eq!( @@ -673,11 +598,14 @@ fn validate_unsigned_works() { InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), ); - let s = - sig::(&dave(), &Some(get_account_id(1)).encode(), StatementKind::Safe.to_text()); + let s = sig::( + &dave(), + &Some(get_multi_address_account_id(1)).encode(), + StatementKind::Safe.to_text(), + ); let call = ClaimsCall::claim_attest { - dest: Some(get_account_id(1)), - ethereum_signature: s, + dest: Some(get_multi_address_account_id(1)), + signature: s, statement: StatementKind::Safe.to_text().to_vec(), }; assert_eq!( @@ -694,8 +622,8 @@ fn test_unclaimed_returned_to_destination() { let claim_of_alice = 100; assert_ok!(ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&alice(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&alice(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) )); assert_eq!(Total::::get(), original_total_claims - claim_of_alice); @@ -703,7 +631,7 @@ fn test_unclaimed_returned_to_destination() { assert_ok!(ClaimsPallet::force_set_expiry_config( RuntimeOrigin::root(), 5, - get_account_id(100) + get_multi_address_account_id(100) )); // run to after expiry block @@ -711,7 +639,7 @@ fn test_unclaimed_returned_to_destination() { assert_eq!(Total::::get(), 0); // the dest account should receive the remaining pot balance assert_eq!( - Balances::free_balance(get_account_id(100)), + Balances::free_balance(get_multi_address_account_id(100).to_account_id_32()), original_total_claims - claim_of_alice ); @@ -720,8 +648,8 @@ fn test_unclaimed_returned_to_destination() { assert_noop!( ClaimsPallet::claim( RuntimeOrigin::none(), - Some(get_account_id(42)), - sig::(&frank(), &Some(get_account_id(42)).encode(), &[][..]) + Some(get_multi_address_account_id(42)), + sig::(&frank(), &Some(get_multi_address_account_id(42)).encode(), &[][..]) ), Error::::PotUnderflow ); diff --git a/pallets/claims/src/utils/ethereum_address.rs b/pallets/claims/src/utils/ethereum_address.rs new file mode 100644 index 000000000..60f814b35 --- /dev/null +++ b/pallets/claims/src/utils/ethereum_address.rs @@ -0,0 +1,61 @@ +use super::*; + +/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account). +/// +/// This gets serialized to the 0x-prefixed hex representation. +#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +pub struct EthereumAddress(pub [u8; 20]); + +impl Serialize for EthereumAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]); + serializer.serialize_str(&format!("0x{}", hex)) + } +} + +impl<'de> Deserialize<'de> for EthereumAddress { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let base_string = String::deserialize(deserializer)?; + let offset = if base_string.starts_with("0x") { 2 } else { 0 }; + let s = &base_string[offset..]; + if s.len() != 40 { + Err(serde::de::Error::custom( + "Bad length of Ethereum address (should be 42 including '0x')", + ))?; + } + let raw: Vec = rustc_hex::FromHex::from_hex(s) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; + let mut r = Self::default(); + r.0.copy_from_slice(&raw); + Ok(r) + } +} + +impl From for H160 { + fn from(a: EthereumAddress) -> Self { + let mut r = Self::default(); + r.0.copy_from_slice(&a.0); + r + } +} + +#[derive(Clone, Copy, Eq, Encode, Decode, TypeInfo)] +pub struct EcdsaSignature(pub [u8; 65]); + +impl PartialEq for EcdsaSignature { + fn eq(&self, other: &Self) -> bool { + &self.0[..] == &other.0[..] + } +} + +impl sp_std::fmt::Debug for EcdsaSignature { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + write!(f, "EcdsaSignature({:?})", &self.0[..]) + } +} diff --git a/pallets/claims/src/utils/mod.rs b/pallets/claims/src/utils/mod.rs new file mode 100644 index 000000000..637db603e --- /dev/null +++ b/pallets/claims/src/utils/mod.rs @@ -0,0 +1,61 @@ +use pallet_evm::{AddressMapping, HashedAddressMapping}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::{ + prelude::{format, string::String}, + TypeInfo, +}; +#[cfg(feature = "std")] +use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; +use sp_core::{H160}; +use sp_runtime::{traits::BlakeTwo256, AccountId32, RuntimeDebug}; +use sp_std::prelude::*; + +pub mod ethereum_address; + +use ethereum_address::{EcdsaSignature, EthereumAddress}; + +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, Serialize, Deserialize)] +pub enum MultiAddress { + /// Claimer is Ethereum address + EVM(EthereumAddress), + /// Claimer is Substrate address + Native(AccountId32), +} + +impl MultiAddress { + pub fn to_account_id_32(&self) -> AccountId32 { + match self { + MultiAddress::EVM(ethereum_address) => + HashedAddressMapping::::into_account_id(H160::from(ethereum_address.0)), + MultiAddress::Native(substrate_address) => substrate_address.clone(), + } + } + + pub fn to_ethereum_address(&self) -> Option { + match self { + MultiAddress::EVM(ethereum_address) => Some(ethereum_address.clone()), + MultiAddress::Native(_) => None, + } + } +} + +#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub enum MultiAddressSignature { + EVM(EcdsaSignature), + Native(Sr25519Signature), +} + +#[derive(Clone, Copy, Eq, Encode, Decode, TypeInfo)] +pub struct Sr25519Signature(pub [u8; 65]); + +impl PartialEq for Sr25519Signature { + fn eq(&self, other: &Self) -> bool { + &self.0[..] == &other.0[..] + } +} + +impl sp_std::fmt::Debug for Sr25519Signature { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + write!(f, "Sr25519Signature({:?})", &self.0[..]) + } +}