diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs index 19fbbe32d8..e32cbeff7a 100644 --- a/polkadot/runtime/common/src/claims.rs +++ b/polkadot/runtime/common/src/claims.rs @@ -14,44 +14,29 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! Module to process claims from Ethereum addresses. +//! Pallet to process claims from Ethereum addresses. use sp_std::{prelude::*, fmt::Debug}; use sp_io::{hashing::keccak_256, crypto::secp256k1_ecdsa_recover}; -use frame_support::{ - decl_event, decl_storage, decl_module, decl_error, ensure, - traits::{Currency, Get, VestingSchedule, EnsureOrigin, IsSubType}, - weights::{Weight, Pays, DispatchClass}, - pallet_prelude::DispatchResultWithPostInfo, -}; -use frame_system::{ensure_signed, ensure_root, ensure_none}; +use frame_support::{ensure, traits::{Currency, Get, VestingSchedule, IsSubType}, weights::Weight}; use parity_scale_codec::{Encode, Decode}; #[cfg(feature = "std")] use serde::{self, Serialize, Deserialize, Serializer, Deserializer}; #[cfg(feature = "std")] use sp_runtime::traits::Zero; use sp_runtime::{ - traits::{CheckedSub, SignedExtension, DispatchInfoOf}, RuntimeDebug, DispatchResult, + traits::{CheckedSub, SignedExtension, DispatchInfoOf}, RuntimeDebug, transaction_validity::{ TransactionLongevity, TransactionValidity, ValidTransaction, InvalidTransaction, TransactionSource, TransactionValidityError, }, }; use primitives::v1::ValidityError; +pub use pallet::*; type CurrencyOf = <::VestingSchedule as VestingSchedule<::AccountId>>::Currency; type BalanceOf = as Currency<::AccountId>>::Balance; -/// Configuration trait. -pub trait Config: frame_system::Config { - /// The overarching event type. - type Event: From> + Into<::Event>; - type VestingSchedule: VestingSchedule; - type Prefix: Get<&'static [u8]>; - type MoveClaimOrigin: EnsureOrigin; - type WeightInfo: WeightInfo; -} - pub trait WeightInfo { fn claim() -> Weight; fn mint_claim() -> Weight; @@ -147,18 +132,38 @@ impl sp_std::fmt::Debug for EcdsaSignature { } } -decl_event!( - pub enum Event where - Balance = BalanceOf, - AccountId = ::AccountId - { - /// Someone claimed some DOTs. [who, ethereum_address, amount] - Claimed(AccountId, EthereumAddress, Balance), - } -); +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use super::*; -decl_error! { - pub enum Error for Module { + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// Configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + type VestingSchedule: VestingSchedule; + #[pallet::constant] + type Prefix: Get<&'static [u8]>; + type MoveClaimOrigin: EnsureOrigin; + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", BalanceOf = "Balance")] + pub enum Event { + /// Someone claimed some DOTs. [who, ethereum_address, amount] + Claimed(T::AccountId, EthereumAddress, BalanceOf), + } + + #[pallet::error] + pub enum Error { /// Invalid Ethereum signature. InvalidEthereumSignature, /// Ethereum address has no claim. @@ -173,56 +178,84 @@ decl_error! { /// The account already has a vested balance. VestedBalanceExists, } -} -decl_storage! { - // A macro for the Storage trait, and its implementation, for this module. - // This allows for type-safe usage of the Substrate storage database, so you can - // keep things around between blocks. - trait Store for Module as Claims { - Claims get(fn claims) build(|config: &GenesisConfig| { - config.claims.iter().map(|(a, b, _, _)| (a.clone(), b.clone())).collect::>() - }): map hasher(identity) EthereumAddress => Option>; - Total get(fn total) build(|config: &GenesisConfig| { - config.claims.iter().fold(Zero::zero(), |acc: BalanceOf, &(_, b, _, _)| acc + b) - }): BalanceOf; - /// Vesting schedule for a claim. - /// First balance is the total amount that should be held for vesting. - /// Second balance is how much should be unlocked per block. - /// The block number is when the vesting should start. - Vesting get(fn vesting) config(): - map hasher(identity) EthereumAddress - => Option<(BalanceOf, BalanceOf, T::BlockNumber)>; + #[pallet::storage] + #[pallet::getter(fn claims)] + pub(super) type Claims = StorageMap<_, Identity, EthereumAddress, BalanceOf>; - /// The statement kind that must be signed, if any. - Signing build(|config: &GenesisConfig| { - config.claims.iter() + #[pallet::storage] + #[pallet::getter(fn total)] + pub(super) type Total = StorageValue<_, BalanceOf, ValueQuery>; + + /// Vesting schedule for a claim. + /// First balance is the total amount that should be held for vesting. + /// Second balance is how much should be unlocked per block. + /// The block number is when the vesting should start. + #[pallet::storage] + #[pallet::getter(fn vesting)] + pub(super) type Vesting = StorageMap< + _, + Identity, EthereumAddress, + (BalanceOf, BalanceOf, T::BlockNumber), + >; + + /// 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>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub claims: Vec<(EthereumAddress, BalanceOf, Option, Option)>, + pub vesting: Vec<(EthereumAddress, (BalanceOf, BalanceOf, T::BlockNumber))>, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + claims: Default::default(), + vesting: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + // build `Claims` + self.claims.iter().map(|(a, b, _, _)| (a.clone(), b.clone())).for_each(|(a, b)| { + Claims::::insert(a, b); + }); + // build `Total` + Total::::put( + self.claims.iter().fold(Zero::zero(), |acc: BalanceOf, &(_, b, _, _)| acc + b), + ); + // build `Vesting` + self.vesting.iter().for_each(|(k, v)| { Vesting::::insert(k, v); }); + // build `Signing` + self.claims.iter() .filter_map(|(a, _, _, s)| Some((a.clone(), s.clone()?))) - .collect::>() - }): map hasher(identity) EthereumAddress => Option; - - /// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to. - Preclaims build(|config: &GenesisConfig| { - config.claims.iter() + .for_each(|(a, s)| { + Signing::::insert(a, s); + }); + // build `Preclaims` + self.claims.iter() .filter_map(|(a, _, i, _)| Some((i.clone()?, a.clone()))) - .collect::>() - }): map hasher(identity) T::AccountId => Option; + .for_each(|(i, a)| { + Preclaims::::insert(i, a); + }); + } } - add_extra_genesis { - config(claims): Vec<(EthereumAddress, BalanceOf, Option, Option)>; - } -} -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - type Error = Error; - - /// The Prefix that is used in signed Ethereum messages for this network - const Prefix: &[u8] = T::Prefix::get(); - - /// Deposit one of this module's events by using the default implementation. - fn deposit_event() = default; + #[pallet::hooks] + impl Hooks> for Pallet {} + #[pallet::call] + impl Pallet { /// Make a claim to collect your DOTs. /// /// The dispatch origin for this call must be _None_. @@ -247,16 +280,21 @@ decl_module! { /// /// Total Complexity: O(1) /// - #[weight = T::WeightInfo::claim()] - fn claim(origin, dest: T::AccountId, ethereum_signature: EcdsaSignature) { + #[pallet::weight(T::WeightInfo::claim())] + pub(super) fn claim( + origin: OriginFor, + dest: T::AccountId, + ethereum_signature: EcdsaSignature + ) -> DispatchResult { ensure_none(origin)?; let data = dest.using_encoded(to_ascii_hex); let signer = Self::eth_recover(ðereum_signature, &data, &[][..]) .ok_or(Error::::InvalidEthereumSignature)?; - ensure!(Signing::get(&signer).is_none(), Error::::InvalidStatement); + ensure!(Signing::::get(&signer).is_none(), Error::::InvalidStatement); Self::process_claim(signer, dest)?; + Ok(()) } /// Mint a new claim to collect DOTs. @@ -274,13 +312,14 @@ decl_module! { /// /// Total Complexity: O(1) /// - #[weight = T::WeightInfo::mint_claim()] - fn mint_claim(origin, + #[pallet::weight(T::WeightInfo::mint_claim())] + pub(super) fn mint_claim( + origin: OriginFor, who: EthereumAddress, value: BalanceOf, vesting_schedule: Option<(BalanceOf, BalanceOf, T::BlockNumber)>, statement: Option, - ) { + ) -> DispatchResult { ensure_root(origin)?; >::mutate(|t| *t += value); @@ -289,8 +328,9 @@ decl_module! { >::insert(who, vs); } if let Some(s) = statement { - Signing::insert(who, s); + Signing::::insert(who, s); } + Ok(()) } /// Make a claim to collect your DOTs by signing a statement. @@ -319,21 +359,23 @@ decl_module! { /// /// Total Complexity: O(1) /// - #[weight = T::WeightInfo::claim_attest()] - fn claim_attest(origin, + #[pallet::weight(T::WeightInfo::claim_attest())] + pub(super) fn claim_attest( + origin: OriginFor, dest: T::AccountId, ethereum_signature: EcdsaSignature, statement: Vec, - ) { + ) -> DispatchResult { ensure_none(origin)?; let data = dest.using_encoded(to_ascii_hex); let signer = Self::eth_recover(ðereum_signature, &data, &statement) .ok_or(Error::::InvalidEthereumSignature)?; - if let Some(s) = Signing::get(signer) { + if let Some(s) = Signing::::get(signer) { ensure!(s.to_text() == &statement[..], Error::::InvalidStatement); } Self::process_claim(signer, dest)?; + Ok(()) } /// Attest to a statement, needed to finalize the claims process. @@ -353,23 +395,25 @@ decl_module! { /// /// Total Complexity: O(1) /// - #[weight = ( + #[pallet::weight(( T::WeightInfo::attest(), DispatchClass::Normal, Pays::No - )] - fn attest(origin, statement: Vec) { + ))] + pub(super) 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) { + if let Some(s) = Signing::::get(signer) { ensure!(s.to_text() == &statement[..], Error::::InvalidStatement); } Self::process_claim(signer, who.clone())?; Preclaims::::remove(&who); + Ok(()) } - #[weight = T::WeightInfo::move_claim()] - fn move_claim(origin, + #[pallet::weight(T::WeightInfo::move_claim())] + pub(super) fn move_claim( + origin: OriginFor, old: EthereumAddress, new: EthereumAddress, maybe_preclaim: Option, @@ -378,7 +422,7 @@ decl_module! { 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)); + 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) } )); @@ -398,7 +442,7 @@ fn to_ascii_hex(data: &[u8]) -> Vec { r } -impl Module { +impl Pallet { // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign. fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec { let prefix = T::Prefix::get(); @@ -425,7 +469,7 @@ impl Module { Some(res) } - fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> DispatchResult { + fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult { let balance_due = >::get(&signer) .ok_or(Error::::SignerHasNoClaim)?; @@ -450,16 +494,16 @@ impl Module { >::put(new_total); >::remove(&signer); >::remove(&signer); - Signing::remove(&signer); + Signing::::remove(&signer); // Let's deposit an event to let the outside world know this happened. - Self::deposit_event(RawEvent::Claimed(dest, signer, balance_due)); + Self::deposit_event(Event::::Claimed(dest, signer, balance_due)); Ok(()) } } -impl sp_runtime::traits::ValidateUnsigned for Module { +impl sp_runtime::traits::ValidateUnsigned for Pallet { type Call = Call; fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { @@ -490,7 +534,7 @@ impl sp_runtime::traits::ValidateUnsigned for Module { ensure!(>::contains_key(&signer), e); let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); - match Signing::get(signer) { + match Signing::::get(signer) { None => ensure!(maybe_statement.is_none(), e), Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e), } @@ -561,7 +605,7 @@ impl SignedExtension for PrevalidateAttests where if let Call::attest(attested_statement) = local_call { let signer = Preclaims::::get(who) .ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?; - if let Some(s) = Signing::get(signer) { + if let Some(s) = Signing::::get(signer) { let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); ensure!(&attested_statement[..] == s.to_text(), e); } @@ -585,7 +629,7 @@ mod secp_utils { res } pub fn sig(secret: &secp256k1::SecretKey, what: &[u8], extra: &[u8]) -> EcdsaSignature { - let msg = keccak_256(&>::ethereum_signable_message(&to_ascii_hex(what)[..], extra)); + let msg = keccak_256(&>::ethereum_signable_message(&to_ascii_hex(what)[..], extra)); let (sig, recovery_id) = secp256k1::sign(&secp256k1::Message::parse(&msg), secret); let mut r = [0u8; 65]; r[0..64].copy_from_slice(&sig.serialize()[..]); @@ -608,7 +652,7 @@ mod tests { use sp_runtime::{traits::{BlakeTwo256, IdentityLookup, Identity}, testing::Header}; use frame_support::{ assert_ok, assert_err, assert_noop, parameter_types, - ord_parameter_types, weights::{Pays, GetDispatchInfo}, traits::ExistenceRequirement, + ord_parameter_types, weights::{Pays, GetDispatchInfo}, traits::{ExistenceRequirement, GenesisBuild}, dispatch::DispatchError::BadOrigin, }; use pallet_balances; @@ -1070,7 +1114,7 @@ mod tests { new_test_ext().execute_with(|| { assert_eq!( - >::validate_unsigned(source, &ClaimsCall::claim(1, sig::(&alice(), &1u64.encode(), &[][..]))), + >::validate_unsigned(source, &ClaimsCall::claim(1, sig::(&alice(), &1u64.encode(), &[][..]))), Ok(ValidTransaction { priority: 100, requires: vec![], @@ -1080,17 +1124,17 @@ mod tests { }) ); assert_eq!( - >::validate_unsigned(source, &ClaimsCall::claim(0, EcdsaSignature([0; 65]))), + >::validate_unsigned(source, &ClaimsCall::claim(0, EcdsaSignature([0; 65]))), InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), ); assert_eq!( - >::validate_unsigned(source, &ClaimsCall::claim(1, sig::(&bob(), &1u64.encode(), &[][..]))), + >::validate_unsigned(source, &ClaimsCall::claim(1, sig::(&bob(), &1u64.encode(), &[][..]))), InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), ); let s = sig::(&dave(), &1u64.encode(), StatementKind::Regular.to_text()); let call = ClaimsCall::claim_attest(1, s, StatementKind::Regular.to_text().to_vec()); assert_eq!( - >::validate_unsigned(source, &call), + >::validate_unsigned(source, &call), Ok(ValidTransaction { priority: 100, requires: vec![], @@ -1100,7 +1144,7 @@ mod tests { }) ); assert_eq!( - >::validate_unsigned( + >::validate_unsigned( source, &ClaimsCall::claim_attest(1, EcdsaSignature([0; 65]), StatementKind::Regular.to_text().to_vec()) @@ -1111,21 +1155,21 @@ mod tests { let s = sig::(&bob(), &1u64.encode(), StatementKind::Regular.to_text()); let call = ClaimsCall::claim_attest(1, s, StatementKind::Regular.to_text().to_vec()); assert_eq!( - >::validate_unsigned(source, &call), + >::validate_unsigned(source, &call), InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), ); let s = sig::(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); let call = ClaimsCall::claim_attest(1, s, StatementKind::Regular.to_text().to_vec()); assert_eq!( - >::validate_unsigned(source, &call), + >::validate_unsigned(source, &call), InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), ); let s = sig::(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); let call = ClaimsCall::claim_attest(1, s, StatementKind::Saft.to_text().to_vec()); assert_eq!( - >::validate_unsigned(source, &call), + >::validate_unsigned(source, &call), InvalidTransaction::Custom(ValidityError::InvalidStatement.into()).into(), ); }); @@ -1151,7 +1195,7 @@ mod benchmarking { let secret_key = secp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); let eth_address = eth(&secret_key); let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - super::Module::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, None)?; + super::Pallet::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, None)?; Ok(()) } @@ -1159,7 +1203,7 @@ mod benchmarking { let secret_key = secp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); let eth_address = eth(&secret_key); let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - super::Module::::mint_claim( + super::Pallet::::mint_claim( RawOrigin::Root.into(), eth_address, VALUE.into(), @@ -1184,13 +1228,13 @@ mod benchmarking { let account: T::AccountId = account("user", c, SEED); let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); let signature = sig::(&secret_key, &account.encode(), &[][..]); - super::Module::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, None)?; + super::Pallet::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, None)?; assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); let source = sp_runtime::transaction_validity::TransactionSource::External; let call = Call::::claim(account.clone(), signature.clone()); }: { - super::Module::::validate_unsigned(source, &call)?; - super::Module::::claim(RawOrigin::None.into(), account, signature)?; + super::Pallet::::validate_unsigned(source, &call)?; + super::Pallet::::claim(RawOrigin::None.into(), account, signature)?; } verify { assert_eq!(Claims::::get(eth_address), None); @@ -1230,13 +1274,13 @@ mod benchmarking { let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); let statement = StatementKind::Regular; let signature = sig::(&secret_key, &account.encode(), statement.to_text()); - super::Module::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, Some(statement))?; + super::Pallet::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, Some(statement))?; assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); let call = Call::::claim_attest(account.clone(), signature.clone(), StatementKind::Regular.to_text().to_vec()); let source = sp_runtime::transaction_validity::TransactionSource::External; }: { - super::Module::::validate_unsigned(source, &call)?; - super::Module::::claim_attest(RawOrigin::None.into(), account, signature, statement.to_text().to_vec())?; + super::Pallet::::validate_unsigned(source, &call)?; + super::Pallet::::claim_attest(RawOrigin::None.into(), account, signature, statement.to_text().to_vec())?; } verify { assert_eq!(Claims::::get(eth_address), None); @@ -1258,7 +1302,7 @@ mod benchmarking { let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); let statement = StatementKind::Regular; let signature = sig::(&secret_key, &account.encode(), statement.to_text()); - super::Module::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, Some(statement))?; + super::Pallet::::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting, Some(statement))?; Preclaims::::insert(&account, eth_address); assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); @@ -1267,7 +1311,7 @@ mod benchmarking { let validate = |who: &T::AccountId, call: &super::Call| -> DispatchResult { if let Call::attest(attested_statement) = call { let signer = Preclaims::::get(who).ok_or("signer has no claim")?; - if let Some(s) = Signing::get(signer) { + if let Some(s) = Signing::::get(signer) { ensure!(&attested_statement[..] == s.to_text(), "invalid statement"); } } @@ -1275,7 +1319,7 @@ mod benchmarking { }; }: { validate(&account, &call)?; - super::Module::::attest(RawOrigin::Signed(account).into(), statement.to_text().to_vec())?; + super::Pallet::::attest(RawOrigin::Signed(account).into(), statement.to_text().to_vec())?; } verify { assert_eq!(Claims::::get(eth_address), None); @@ -1330,7 +1374,7 @@ mod benchmarking { let extra = StatementKind::default().to_text(); }: { for _ in 0 .. i { - assert!(super::Module::::eth_recover(&signature, &data, extra).is_some()); + assert!(super::Pallet::::eth_recover(&signature, &data, extra).is_some()); } } }