From e85cd8ced8b0c5de38ad66fc3aeb1b8e3da24bf2 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Tue, 26 Jul 2022 17:54:35 +0400 Subject: [PATCH 01/41] Add initial pallet-token-claims --- Cargo.lock | 4 ++++ crates/pallet-token-claims/Cargo.toml | 11 +++++++++++ crates/pallet-token-claims/src/lib.rs | 3 +++ 3 files changed, 18 insertions(+) create mode 100644 crates/pallet-token-claims/Cargo.toml create mode 100644 crates/pallet-token-claims/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a8732adf0..be63ba585 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5647,6 +5647,10 @@ dependencies = [ "sp-timestamp", ] +[[package]] +name = "pallet-token-claims" +version = "0.1.0" + [[package]] name = "pallet-transaction-payment" version = "4.0.0-dev" diff --git a/crates/pallet-token-claims/Cargo.toml b/crates/pallet-token-claims/Cargo.toml new file mode 100644 index 000000000..b3308e1c5 --- /dev/null +++ b/crates/pallet-token-claims/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pallet-token-claims" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] + +[features] +default = ["std"] +std = [] diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs new file mode 100644 index 000000000..778ca6eda --- /dev/null +++ b/crates/pallet-token-claims/src/lib.rs @@ -0,0 +1,3 @@ +//! Token claims. + +#![cfg_attr(not(feature = "std"), no_std)] From 7d8ba61f9ba9f94f6644d226e30536168389696c Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Tue, 26 Jul 2022 18:50:15 +0400 Subject: [PATCH 02/41] Add base pallet structure --- Cargo.lock | 6 ++++++ crates/pallet-token-claims/Cargo.toml | 10 +++++++++- crates/pallet-token-claims/src/lib.rs | 25 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index be63ba585..0bbda67f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5650,6 +5650,12 @@ dependencies = [ [[package]] name = "pallet-token-claims" version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", +] [[package]] name = "pallet-transaction-payment" diff --git a/crates/pallet-token-claims/Cargo.toml b/crates/pallet-token-claims/Cargo.toml index b3308e1c5..4f062a08a 100644 --- a/crates/pallet-token-claims/Cargo.toml +++ b/crates/pallet-token-claims/Cargo.toml @@ -5,7 +5,15 @@ edition = "2021" publish = false [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } [features] default = ["std"] -std = [] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", +] diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 778ca6eda..004503f4d 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -1,3 +1,28 @@ //! Token claims. #![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::StorageVersion; + +/// The current storage version. +const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + +// We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to +// fix them at their end. +#[allow(clippy::missing_docs_in_private_items)] +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} +} +pub use pallet::*; From 544234269636ac76cc48970163d81ba98af00f66 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Tue, 26 Jul 2022 20:30:37 +0400 Subject: [PATCH 03/41] Add events --- crates/pallet-token-claims/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 004503f4d..3de9ebefd 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -23,6 +23,13 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config {} + pub trait Config: frame_system::Config { + /// Overarching event type. + type Event: From> + IsType<::Event>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event {} } pub use pallet::*; From a5d294ef853e4a054107741c0e986dd7d36343d0 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Tue, 26 Jul 2022 20:38:34 +0400 Subject: [PATCH 04/41] Add weights --- crates/pallet-token-claims/src/lib.rs | 6 ++++++ crates/pallet-token-claims/src/weights.rs | 8 ++++++++ 2 files changed, 14 insertions(+) create mode 100644 crates/pallet-token-claims/src/weights.rs diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 3de9ebefd..8a8b07001 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -4,6 +4,8 @@ use frame_support::traits::StorageVersion; +mod weights; + /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -16,6 +18,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; use super::*; + use crate::weights::WeightInfo; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -26,6 +29,9 @@ pub mod pallet { pub trait Config: frame_system::Config { /// Overarching event type. type Event: From> + IsType<::Event>; + + /// The weight informtation provider type. + type WeightInfo: WeightInfo; } #[pallet::event] diff --git a/crates/pallet-token-claims/src/weights.rs b/crates/pallet-token-claims/src/weights.rs new file mode 100644 index 000000000..5c860988a --- /dev/null +++ b/crates/pallet-token-claims/src/weights.rs @@ -0,0 +1,8 @@ +//! The weights. + +use frame_support::dispatch::Weight; + +/// The weight information trait, to be implemented in from the benches. +pub trait WeightInfo {} + +impl WeightInfo for () {} From b2eac6dab98727f334782fef1cc896e545328fb1 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Tue, 26 Jul 2022 22:06:44 +0400 Subject: [PATCH 05/41] Add call impl --- crates/pallet-token-claims/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 8a8b07001..3d59201f5 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -37,5 +37,8 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event {} + + #[pallet::call] + impl Pallet {} } pub use pallet::*; From bb4141872c9717434c181525cff93aff20bef3da Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Tue, 26 Jul 2022 23:40:14 +0400 Subject: [PATCH 06/41] Add EthereumAddress type --- Cargo.lock | 3 + crates/pallet-token-claims/Cargo.toml | 7 ++ crates/pallet-token-claims/src/lib.rs | 1 + crates/pallet-token-claims/src/types.rs | 94 +++++++++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 crates/pallet-token-claims/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 0bbda67f2..bb223debe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5654,7 +5654,10 @@ dependencies = [ "frame-support", "frame-system", "parity-scale-codec", + "rustc-hex", "scale-info", + "serde", + "serde_json", ] [[package]] diff --git a/crates/pallet-token-claims/Cargo.toml b/crates/pallet-token-claims/Cargo.toml index 4f062a08a..8a4ec908c 100644 --- a/crates/pallet-token-claims/Cargo.toml +++ b/crates/pallet-token-claims/Cargo.toml @@ -8,7 +8,12 @@ publish = false codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +rustc-hex = { version = "2.1.0", optional = true } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1", optional = true } + +[dev-dependencies] +serde_json = "1" [features] default = ["std"] @@ -16,4 +21,6 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", + "rustc-hex", + "serde", ] diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 3d59201f5..1da84605c 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -4,6 +4,7 @@ use frame_support::traits::StorageVersion; +mod types; mod weights; /// The current storage version. diff --git a/crates/pallet-token-claims/src/types.rs b/crates/pallet-token-claims/src/types.rs new file mode 100644 index 000000000..bbe630ff2 --- /dev/null +++ b/crates/pallet-token-claims/src/types.rs @@ -0,0 +1,94 @@ +//! Custom types we use. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + serde::{Deserializer, Serializer}, + Deserialize, RuntimeDebug, Serialize, +}; +use scale_info::TypeInfo; + +/// 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, MaxEncodedLen, +)] +pub struct EthereumAddress([u8; 20]); + +#[cfg(feature = "std")] +impl Serialize for EthereumAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let hex: String = "0x" + .chars() + .chain(rustc_hex::ToHexIter::new(self.0.iter())) + .collect(); + serializer.serialize_str(&hex) + } +} + +#[cfg(feature = "std")] +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 { + return Err(serde::de::Error::custom( + "bad length of Ethereum address (should be 42 including '0x')", + )); + } + let mut iter = rustc_hex::FromHexIter::new(s); + + let mut to_fill = [0u8; 20]; + for slot in to_fill.iter_mut() { + // We check the length above, so this must work. + let result = iter.next().unwrap(); + + let ch = result.map_err(|err| match err { + rustc_hex::FromHexError::InvalidHexCharacter(ch, idx) => { + serde::de::Error::custom(&format_args!( + "invalid character '{}' at position {}, expected 0-9 or a-z or A-Z", + ch, idx + )) + } + // We check the length above, so this will never happen. + rustc_hex::FromHexError::InvalidHexLength => unreachable!(), + })?; + *slot = ch; + } + Ok(EthereumAddress(to_fill)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_ok() { + assert_eq!( + &serde_json::to_string(&EthereumAddress([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 + ])) + .unwrap(), + "\"0x000102030405060708090a0b0c0d0e0f10111213\"", + ); + } + + #[test] + fn deserialize_ok() { + assert_eq!( + serde_json::from_str::( + "\"0x000102030405060708090a0b0c0d0e0f10111213\"" + ) + .unwrap(), + EthereumAddress([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + ); + } +} From 99f660de8aeca098273c9f5835dca79f6b3345bb Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Wed, 27 Jul 2022 00:06:53 +0400 Subject: [PATCH 07/41] Add claims storage --- crates/pallet-token-claims/src/lib.rs | 29 +++++++++++++++++++++++-- crates/pallet-token-claims/src/types.rs | 12 ++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 1da84605c..1c2921a26 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -2,7 +2,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::StorageVersion; +use frame_support::traits::{Currency, StorageVersion}; mod types; mod weights; @@ -10,6 +10,11 @@ mod weights; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); +/// The currency from a given config. +type CurrencyOf = ::Currency; +/// The balance from a given config. +type BalanceOf = as Currency<::AccountId>>::Balance; + // We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to // fix them at their end. #[allow(clippy::missing_docs_in_private_items)] @@ -19,7 +24,10 @@ pub mod pallet { use frame_system::pallet_prelude::*; use super::*; - use crate::weights::WeightInfo; + use crate::{ + types::{ClaimInfo, EthereumAddress}, + weights::WeightInfo, + }; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -31,10 +39,27 @@ pub mod pallet { /// Overarching event type. type Event: From> + IsType<::Event>; + /// Currency to claim. + type Currency: Currency<::AccountId>; + + /// Vesting schedule configuration type. + type VestingSchedule: Member + Parameter; + /// The weight informtation provider type. type WeightInfo: WeightInfo; } + /// The public key of the robonode. + #[pallet::storage] + #[pallet::getter(fn claims)] + pub type Claims = StorageMap< + _, + Twox64Concat, + EthereumAddress, + ClaimInfo, ::VestingSchedule>, + OptionQuery, + >; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event {} diff --git a/crates/pallet-token-claims/src/types.rs b/crates/pallet-token-claims/src/types.rs index bbe630ff2..58c9f650c 100644 --- a/crates/pallet-token-claims/src/types.rs +++ b/crates/pallet-token-claims/src/types.rs @@ -15,6 +15,18 @@ use scale_info::TypeInfo; )] pub struct EthereumAddress([u8; 20]); +/// The claim information. +#[derive( + Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ClaimInfo { + /// The amount to claim. + pub balance: Balance, + /// The vesting configuration for the given claim. + pub vesting: Option, +} + #[cfg(feature = "std")] impl Serialize for EthereumAddress { fn serialize(&self, serializer: S) -> Result From b949698b4046f81d6483bab3aab79173e6a6b915 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Wed, 27 Jul 2022 14:34:41 +0400 Subject: [PATCH 08/41] Move ethereum types to primitives-ethereum crate --- Cargo.lock | 15 ++- crates/pallet-token-claims/Cargo.toml | 5 +- crates/pallet-token-claims/src/lib.rs | 6 +- crates/pallet-token-claims/src/types.rs | 91 +----------------- crates/primitives-ethereum/Cargo.toml | 26 +++++ .../src/ecdsa_signature.rs | 15 +++ .../src/ethereum_address.rs | 94 +++++++++++++++++++ crates/primitives-ethereum/src/lib.rs | 9 ++ 8 files changed, 164 insertions(+), 97 deletions(-) create mode 100644 crates/primitives-ethereum/Cargo.toml create mode 100644 crates/primitives-ethereum/src/ecdsa_signature.rs create mode 100644 crates/primitives-ethereum/src/ethereum_address.rs create mode 100644 crates/primitives-ethereum/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index bb223debe..0cd00f2d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5654,7 +5654,7 @@ dependencies = [ "frame-support", "frame-system", "parity-scale-codec", - "rustc-hex", + "primitives-ethereum", "scale-info", "serde", "serde_json", @@ -6180,6 +6180,19 @@ dependencies = [ "sp-std", ] +[[package]] +name = "primitives-ethereum" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "rustc-hex", + "scale-info", + "serde", + "serde_json", +] + [[package]] name = "primitives-frontier" version = "0.1.0" diff --git a/crates/pallet-token-claims/Cargo.toml b/crates/pallet-token-claims/Cargo.toml index 8a4ec908c..00ea30f74 100644 --- a/crates/pallet-token-claims/Cargo.toml +++ b/crates/pallet-token-claims/Cargo.toml @@ -5,10 +5,11 @@ edition = "2021" publish = false [dependencies] +primitives-ethereum = { version = "0.1", path = "../primitives-ethereum", default-features = false } + codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } -rustc-hex = { version = "2.1.0", optional = true } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1", optional = true } @@ -21,6 +22,6 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", - "rustc-hex", + "primitives-ethereum/std", "serde", ] diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 1c2921a26..10081d470 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -22,12 +22,10 @@ type BalanceOf = as Currency<::Acco pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + use primitives_ethereum::EthereumAddress; use super::*; - use crate::{ - types::{ClaimInfo, EthereumAddress}, - weights::WeightInfo, - }; + use crate::{types::ClaimInfo, weights::WeightInfo}; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] diff --git a/crates/pallet-token-claims/src/types.rs b/crates/pallet-token-claims/src/types.rs index 58c9f650c..6a4c18ce2 100644 --- a/crates/pallet-token-claims/src/types.rs +++ b/crates/pallet-token-claims/src/types.rs @@ -1,20 +1,9 @@ //! Custom types we use. use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - serde::{Deserializer, Serializer}, - Deserialize, RuntimeDebug, Serialize, -}; +use frame_support::{Deserialize, RuntimeDebug, Serialize}; use scale_info::TypeInfo; -/// 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, MaxEncodedLen, -)] -pub struct EthereumAddress([u8; 20]); - /// The claim information. #[derive( Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, @@ -26,81 +15,3 @@ pub struct ClaimInfo { /// The vesting configuration for the given claim. pub vesting: Option, } - -#[cfg(feature = "std")] -impl Serialize for EthereumAddress { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let hex: String = "0x" - .chars() - .chain(rustc_hex::ToHexIter::new(self.0.iter())) - .collect(); - serializer.serialize_str(&hex) - } -} - -#[cfg(feature = "std")] -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 { - return Err(serde::de::Error::custom( - "bad length of Ethereum address (should be 42 including '0x')", - )); - } - let mut iter = rustc_hex::FromHexIter::new(s); - - let mut to_fill = [0u8; 20]; - for slot in to_fill.iter_mut() { - // We check the length above, so this must work. - let result = iter.next().unwrap(); - - let ch = result.map_err(|err| match err { - rustc_hex::FromHexError::InvalidHexCharacter(ch, idx) => { - serde::de::Error::custom(&format_args!( - "invalid character '{}' at position {}, expected 0-9 or a-z or A-Z", - ch, idx - )) - } - // We check the length above, so this will never happen. - rustc_hex::FromHexError::InvalidHexLength => unreachable!(), - })?; - *slot = ch; - } - Ok(EthereumAddress(to_fill)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize_ok() { - assert_eq!( - &serde_json::to_string(&EthereumAddress([ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 - ])) - .unwrap(), - "\"0x000102030405060708090a0b0c0d0e0f10111213\"", - ); - } - - #[test] - fn deserialize_ok() { - assert_eq!( - serde_json::from_str::( - "\"0x000102030405060708090a0b0c0d0e0f10111213\"" - ) - .unwrap(), - EthereumAddress([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) - ); - } -} diff --git a/crates/primitives-ethereum/Cargo.toml b/crates/primitives-ethereum/Cargo.toml new file mode 100644 index 000000000..b4c71d3cb --- /dev/null +++ b/crates/primitives-ethereum/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "primitives-ethereum" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +rustc-hex = { version = "2.1.0", optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1", optional = true } + +[dev-dependencies] +serde_json = "1" + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "rustc-hex", + "serde", +] diff --git a/crates/primitives-ethereum/src/ecdsa_signature.rs b/crates/primitives-ethereum/src/ecdsa_signature.rs new file mode 100644 index 000000000..27f8a3693 --- /dev/null +++ b/crates/primitives-ethereum/src/ecdsa_signature.rs @@ -0,0 +1,15 @@ +//! ECDSA Signature. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::RuntimeDebug; +use scale_info::TypeInfo; + +/// A ECDSA signature, used by Ethereum. +#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct EcdsaSignature(pub [u8; 65]); + +impl Default for EcdsaSignature { + fn default() -> Self { + Self([0; 65]) + } +} diff --git a/crates/primitives-ethereum/src/ethereum_address.rs b/crates/primitives-ethereum/src/ethereum_address.rs new file mode 100644 index 000000000..6c09c0f27 --- /dev/null +++ b/crates/primitives-ethereum/src/ethereum_address.rs @@ -0,0 +1,94 @@ +//! Ethereum address. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + serde::{Deserializer, Serializer}, + Deserialize, RuntimeDebug, Serialize, +}; +use scale_info::TypeInfo; + +/// 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, MaxEncodedLen, +)] +pub struct EthereumAddress(pub [u8; 20]); + +#[cfg(feature = "std")] +impl Serialize for EthereumAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let hex: String = "0x" + .chars() + .chain(rustc_hex::ToHexIter::new(self.0.iter())) + .collect(); + serializer.serialize_str(&hex) + } +} + +#[cfg(feature = "std")] +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 { + return Err(serde::de::Error::custom( + "bad length of Ethereum address (should be 42 including '0x')", + )); + } + let mut iter = rustc_hex::FromHexIter::new(s); + + let mut to_fill = [0u8; 20]; + for slot in to_fill.iter_mut() { + // We check the length above, so this must work. + let result = iter.next().unwrap(); + + let ch = result.map_err(|err| match err { + rustc_hex::FromHexError::InvalidHexCharacter(ch, idx) => { + serde::de::Error::custom(&format_args!( + "invalid character '{}' at position {}, expected 0-9 or a-z or A-Z", + ch, idx + )) + } + // We check the length above, so this will never happen. + rustc_hex::FromHexError::InvalidHexLength => unreachable!(), + })?; + *slot = ch; + } + Ok(EthereumAddress(to_fill)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_ok() { + assert_eq!( + &serde_json::to_string(&EthereumAddress([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 + ])) + .unwrap(), + "\"0x000102030405060708090a0b0c0d0e0f10111213\"", + ); + } + + #[test] + fn deserialize_ok() { + assert_eq!( + serde_json::from_str::( + "\"0x000102030405060708090a0b0c0d0e0f10111213\"" + ) + .unwrap(), + EthereumAddress([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + ); + } +} diff --git a/crates/primitives-ethereum/src/lib.rs b/crates/primitives-ethereum/src/lib.rs new file mode 100644 index 000000000..e8548ea6b --- /dev/null +++ b/crates/primitives-ethereum/src/lib.rs @@ -0,0 +1,9 @@ +//! Common ethereum related priomitives. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod ecdsa_signature; +mod ethereum_address; + +pub use ecdsa_signature::*; +pub use ethereum_address::*; From 93b593f5eaea244f29a2944765675c8346978db3 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Wed, 27 Jul 2022 16:11:01 +0400 Subject: [PATCH 09/41] Implement the initial claiming logic --- crates/pallet-token-claims/src/lib.rs | 107 +++++++++++++++++++--- crates/pallet-token-claims/src/traits.rs | 58 ++++++++++++ crates/pallet-token-claims/src/types.rs | 9 ++ crates/pallet-token-claims/src/weights.rs | 11 ++- 4 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 crates/pallet-token-claims/src/traits.rs diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 10081d470..b63fdbba2 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -4,6 +4,9 @@ use frame_support::traits::{Currency, StorageVersion}; +pub use self::pallet::*; + +pub mod traits; mod types; mod weights; @@ -14,6 +17,8 @@ const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); type CurrencyOf = ::Currency; /// The balance from a given config. type BalanceOf = as Currency<::AccountId>>::Balance; +/// The claim info from a given config. +type ClaimInfoOf = types::ClaimInfo, ::VestingSchedule>; // We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to // fix them at their end. @@ -22,10 +27,14 @@ type BalanceOf = as Currency<::Acco pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use primitives_ethereum::EthereumAddress; + use primitives_ethereum::{EcdsaSignature, EthereumAddress}; use super::*; - use crate::{types::ClaimInfo, weights::WeightInfo}; + use crate::{ + traits::{PreconstructedMessageVerifier, VestingInterface}, + types::{ClaimInfo, EthereumSignatureMessageParams}, + weights::WeightInfo, + }; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -41,7 +50,19 @@ pub mod pallet { type Currency: Currency<::AccountId>; /// Vesting schedule configuration type. - type VestingSchedule: Member + Parameter; + type VestingSchedule: Member + Parameter + MaxEncodedLen; + + /// Interface into the vesting implementation. + type VestingInterface: VestingInterface< + AccountId = Self::AccountId, + Balance = BalanceOf, + Schedule = Self::VestingSchedule, + >; + + /// The ethereum signature verifier for the claim requests. + type EthereumSignatureVerifier: PreconstructedMessageVerifier< + MessageParams = EthereumSignatureMessageParams, + >; /// The weight informtation provider type. type WeightInfo: WeightInfo; @@ -50,19 +71,79 @@ pub mod pallet { /// The public key of the robonode. #[pallet::storage] #[pallet::getter(fn claims)] - pub type Claims = StorageMap< - _, - Twox64Concat, - EthereumAddress, - ClaimInfo, ::VestingSchedule>, - OptionQuery, - >; + pub type Claims = StorageMap<_, Twox64Concat, EthereumAddress, ClaimInfoOf, OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event {} + pub enum Event { + /// Tokens were claimed. + TokensClaimed { + /// Who claimed the tokens. + who: T::AccountId, + /// The ethereum address used for token claiming. + ethereum_address: EthereumAddress, + /// The balance that was claimed. + balance: BalanceOf, + /// The vesting schedule. + vesting: Option, + }, + } + + #[pallet::error] + pub enum Error { + /// The signature was invalid. + InvalidSignature, + /// No claim was found. + NoClaim, + } #[pallet::call] - impl Pallet {} + impl Pallet { + /// Claim the tokens. + #[pallet::weight(T::WeightInfo::claim())] + pub fn claim( + origin: OriginFor, + ethereum_address: EthereumAddress, + ethereum_signature: EcdsaSignature, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let message_params = EthereumSignatureMessageParams { + account_id: who.clone(), + ethereum_address, + }; + + if ::EthereumSignatureVerifier::verify( + message_params, + ðereum_address, + ethereum_signature, + ) { + return Err(Error::::InvalidSignature.into()); + } + + Self::process_claim(who, ethereum_address) + } + } + + impl Pallet { + fn process_claim(who: T::AccountId, ethereum_address: EthereumAddress) -> DispatchResult { + let ClaimInfo { balance, vesting } = + >::take(ethereum_address).ok_or(>::NoClaim)?; + + T::Currency::deposit_creating(&who, balance); + + if let Some(ref vesting) = vesting { + T::VestingInterface::lock_under_vesting(&who, balance, vesting.clone())?; + } + + Self::deposit_event(Event::TokensClaimed { + who, + ethereum_address, + balance, + vesting, + }); + + Ok(()) + } + } } -pub use pallet::*; diff --git a/crates/pallet-token-claims/src/traits.rs b/crates/pallet-token-claims/src/traits.rs new file mode 100644 index 000000000..5a74c8d8e --- /dev/null +++ b/crates/pallet-token-claims/src/traits.rs @@ -0,0 +1,58 @@ +//! Traits we use and expose. + +use frame_support::dispatch::DispatchResult; +use primitives_ethereum::{EcdsaSignature, EthereumAddress}; + +/// The verifier for the Ethereum signature. +/// +/// The idea is we don't pass in the message we use for the verification, but instead we pass in +/// the message parameters. +/// +/// This abstraction built with EIP-712 in mind, but can also be implemented with any generic +/// ECDSA signature. +pub trait PreconstructedMessageVerifier { + /// The type describing the parameters used to construct a message. + type MessageParams; + + /// Generate a message and verify the provided `signature` against the said message. + /// Extract the [`EthereumAddress`] from the signature and return it. + /// + /// The caller should check that the extracted address matches what is expected, as successfull + /// recovery does not necessarily guarantee the correctness of the signature - that can only + /// be achieved with checking the recovered address against the expected one. + fn recover_signer( + message_params: Self::MessageParams, + signature: EcdsaSignature, + ) -> Option; + + /// Calls [`Self::recover_signer`] and then checks that the `signer` matches the recovered address. + fn verify( + message_params: Self::MessageParams, + signer: &EthereumAddress, + signature: EcdsaSignature, + ) -> bool { + let recovered = match Self::recover_signer(message_params, signature) { + Some(recovered) => recovered, + None => return false, + }; + &recovered == signer + } +} + +/// The interface to the vesting implementation. +pub trait VestingInterface { + /// The Account ID to apply vesting to. + type AccountId; + /// The type of balance to lock under the vesting. + type Balance; + /// The vesting schedule configuration. + type Schedule; + + /// Lock the specified amount of balance (`balance_to_lock`) on the given account (`account`) + /// with the provided vesting schedule configuration (`schedule`). + fn lock_under_vesting( + account: &Self::AccountId, + balance_to_lock: Self::Balance, + schedule: Self::Schedule, + ) -> DispatchResult; +} diff --git a/crates/pallet-token-claims/src/types.rs b/crates/pallet-token-claims/src/types.rs index 6a4c18ce2..6ec40b36e 100644 --- a/crates/pallet-token-claims/src/types.rs +++ b/crates/pallet-token-claims/src/types.rs @@ -2,6 +2,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{Deserialize, RuntimeDebug, Serialize}; +use primitives_ethereum::EthereumAddress; use scale_info::TypeInfo; /// The claim information. @@ -15,3 +16,11 @@ pub struct ClaimInfo { /// The vesting configuration for the given claim. pub vesting: Option, } + +/// The collection of parameters used for construcing a message that had to be signed. +pub struct EthereumSignatureMessageParams { + /// The account ID of whoever is requesting the claim. + pub account_id: AccountId, + /// The ethereum address the claim is authorized for. + pub ethereum_address: EthereumAddress, +} diff --git a/crates/pallet-token-claims/src/weights.rs b/crates/pallet-token-claims/src/weights.rs index 5c860988a..761c8366a 100644 --- a/crates/pallet-token-claims/src/weights.rs +++ b/crates/pallet-token-claims/src/weights.rs @@ -3,6 +3,13 @@ use frame_support::dispatch::Weight; /// The weight information trait, to be implemented in from the benches. -pub trait WeightInfo {} +pub trait WeightInfo { + /// Weight for `claim` call. + fn claim() -> Weight; +} -impl WeightInfo for () {} +impl WeightInfo for () { + fn claim() -> Weight { + 0 + } +} From 6414cb0dc5ac1d8e4cfe60ade064d21a2ad461e9 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Wed, 27 Jul 2022 18:49:00 +0400 Subject: [PATCH 10/41] Add genesis --- crates/pallet-token-claims/src/lib.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index b63fdbba2..30ffd3c16 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -50,7 +50,7 @@ pub mod pallet { type Currency: Currency<::AccountId>; /// Vesting schedule configuration type. - type VestingSchedule: Member + Parameter + MaxEncodedLen; + type VestingSchedule: Member + Parameter + MaxEncodedLen + MaybeSerializeDeserialize; /// Interface into the vesting implementation. type VestingInterface: VestingInterface< @@ -73,6 +73,29 @@ pub mod pallet { #[pallet::getter(fn claims)] pub type Claims = StorageMap<_, Twox64Concat, EthereumAddress, ClaimInfoOf, OptionQuery>; + #[pallet::genesis_config] + pub struct GenesisConfig { + claims: Vec<(EthereumAddress, ClaimInfoOf)>, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + claims: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + for (eth_address, info) in self.claims.iter() { + Claims::::insert(eth_address, info.clone()); + } + } + } + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { From 3f02d8df6a7cecbac569c8e1d58b0672570e65c6 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Wed, 27 Jul 2022 20:04:51 +0400 Subject: [PATCH 11/41] Implement basic tests and benches scaffold --- Cargo.lock | 4 + crates/pallet-token-claims/Cargo.toml | 10 ++ .../pallet-token-claims/src/benchmarking.rs | 65 ++++++++ crates/pallet-token-claims/src/lib.rs | 10 +- crates/pallet-token-claims/src/mock.rs | 147 ++++++++++++++++++ crates/pallet-token-claims/src/tests.rs | 15 ++ 6 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 crates/pallet-token-claims/src/benchmarking.rs create mode 100644 crates/pallet-token-claims/src/mock.rs create mode 100644 crates/pallet-token-claims/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 0cd00f2d3..0ad3cf76a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5651,13 +5651,17 @@ dependencies = [ name = "pallet-token-claims" version = "0.1.0" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", + "pallet-balances", "parity-scale-codec", "primitives-ethereum", "scale-info", "serde", "serde_json", + "sp-core", + "sp-runtime", ] [[package]] diff --git a/crates/pallet-token-claims/Cargo.toml b/crates/pallet-token-claims/Cargo.toml index 00ea30f74..5d8b88e39 100644 --- a/crates/pallet-token-claims/Cargo.toml +++ b/crates/pallet-token-claims/Cargo.toml @@ -8,18 +8,28 @@ publish = false primitives-ethereum = { version = "0.1", path = "../primitives-ethereum", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +frame-benchmarking = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master", optional = true } frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1", optional = true } [dev-dependencies] +pallet-balances = { git = "https://github.com/humanode-network/substrate", branch = "master" } serde_json = "1" +sp-core = { git = "https://github.com/humanode-network/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/humanode-network/substrate", branch = "master" } [features] default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] std = [ "codec/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "primitives-ethereum/std", diff --git a/crates/pallet-token-claims/src/benchmarking.rs b/crates/pallet-token-claims/src/benchmarking.rs new file mode 100644 index 000000000..3813aad2c --- /dev/null +++ b/crates/pallet-token-claims/src/benchmarking.rs @@ -0,0 +1,65 @@ +//! The benchmarks for the pallet. + +use frame_benchmarking::benchmarks; +use frame_system::RawOrigin; +use primitives_ethereum::{EcdsaSignature, EthereumAddress}; + +use crate::*; + +/// The benchmark interface into the environment. +pub trait Interface { + type Config: super::Config; + + fn create_ethereum_address() -> EthereumAddress; + fn create_claim_info() -> super::ClaimInfoOf; + fn create_ecdsa_signature() -> EcdsaSignature; + fn create_account_id() -> ::AccountId; +} + +benchmarks! { + where_clause { + where + T: Interface + } + + claim { + let ethereum_address = ::create_ethereum_address(); + let claim_info = ::create_claim_info(); + let signature = ::create_ecdsa_signature(); + let account_id = ::create_account_id(); + >::insert(ethereum_address, claim_info); + }: _(RawOrigin::Signed(account_id), ethereum_address, signature) + verify { + assert_eq!(Claims::::get(ethereum_address), None); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Test, + ); +} + +#[cfg(test)] +impl Interface for crate::mock::Test { + type Config = Self; + + fn create_ethereum_address() -> EthereumAddress { + EthereumAddress::default() + } + + fn create_claim_info() -> crate::ClaimInfoOf { + crate::types::ClaimInfo { + balance: 0, + vesting: None, + } + } + + fn create_ecdsa_signature() -> EcdsaSignature { + EcdsaSignature::default() + } + + fn create_account_id() -> ::AccountId { + 0 + } +} diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 30ffd3c16..ab555d226 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -10,6 +10,13 @@ pub mod traits; mod types; mod weights; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -75,7 +82,8 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig { - claims: Vec<(EthereumAddress, ClaimInfoOf)>, + /// The claims to initialize at genesis. + pub claims: Vec<(EthereumAddress, ClaimInfoOf)>, } #[cfg(feature = "std")] diff --git a/crates/pallet-token-claims/src/mock.rs b/crates/pallet-token-claims/src/mock.rs new file mode 100644 index 000000000..596089d5c --- /dev/null +++ b/crates/pallet-token-claims/src/mock.rs @@ -0,0 +1,147 @@ +//! The mock for the pallet. + +use frame_support::{ + ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +use crate::{self as pallet_token_claims}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + TokenClaims: pallet_token_claims::{Pallet, Call, Storage, Config, Event}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +parameter_types! { + pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:"; +} +ord_parameter_types! { + pub const Six: u64 = 6; +} + +impl pallet_token_claims::Config for Test { + type Event = Event; + type Currency = Balances; + type VestingSchedule = MockVestingSchedule; + type VestingInterface = MockVestingInterface; + type EthereumSignatureVerifier = MockEthereumSignatureVerifier; + type WeightInfo = (); +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> frame_support::sp_io::TestExternalities { + let genesis_config = GenesisConfig { + system: Default::default(), + balances: Default::default(), + token_claims: Default::default(), + }; + use sp_runtime::BuildStorage; + let storage = genesis_config.build_storage().unwrap(); + storage.into() +} + +/// Test mocks +mod mocks { + use codec::{Decode, Encode, MaxEncodedLen}; + use frame_support::{Deserialize, Serialize}; + use scale_info::TypeInfo; + + use super::*; + use crate::{ + traits::{self, PreconstructedMessageVerifier}, + types::EthereumSignatureMessageParams, + }; + + #[derive( + Debug, Clone, Decode, Encode, MaxEncodedLen, TypeInfo, PartialEq, Eq, Serialize, Deserialize, + )] + pub struct MockVestingSchedule; + + #[derive(Debug)] + pub struct MockVestingInterface; + + impl traits::VestingInterface for MockVestingInterface { + type AccountId = ::AccountId; + type Balance = crate::BalanceOf; + type Schedule = MockVestingSchedule; + + fn lock_under_vesting( + _account: &Self::AccountId, + _balance_to_lock: Self::Balance, + _schedule: Self::Schedule, + ) -> frame_support::dispatch::DispatchResult { + Ok(()) + } + } + + #[derive(Debug)] + pub struct MockEthereumSignatureVerifier; + + impl PreconstructedMessageVerifier for MockEthereumSignatureVerifier { + type MessageParams = + EthereumSignatureMessageParams<::AccountId>; + + fn recover_signer( + _message_params: Self::MessageParams, + _signature: primitives_ethereum::EcdsaSignature, + ) -> Option { + None + } + } +} +use self::mocks::*; diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs new file mode 100644 index 000000000..e72b1b558 --- /dev/null +++ b/crates/pallet-token-claims/src/tests.rs @@ -0,0 +1,15 @@ +//! The tests for the pallet. + +use primitives_ethereum::EthereumAddress; + +use crate::{ + mock::{new_test_ext, Test}, + *, +}; + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(Claims::::get(&EthereumAddress::default()), None); + }); +} From 11e83963b79e352234389c4b4f093b0f692b5aa4 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Thu, 28 Jul 2022 21:28:01 +0400 Subject: [PATCH 12/41] Fix a bug at claiming --- crates/pallet-token-claims/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index ab555d226..184de63b1 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -144,7 +144,7 @@ pub mod pallet { ethereum_address, }; - if ::EthereumSignatureVerifier::verify( + if !::EthereumSignatureVerifier::verify( message_params, ðereum_address, ethereum_signature, From f7fc390e098724376a2d255b7e56b720177fc44a Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Thu, 28 Jul 2022 21:57:08 +0400 Subject: [PATCH 13/41] Implement some tests --- Cargo.lock | 81 ++++++++- crates/pallet-token-claims/Cargo.toml | 1 + .../pallet-token-claims/src/benchmarking.rs | 17 ++ crates/pallet-token-claims/src/mock.rs | 94 +++++------ crates/pallet-token-claims/src/mock/utils.rs | 47 ++++++ crates/pallet-token-claims/src/tests.rs | 157 +++++++++++++++++- crates/pallet-token-claims/src/types.rs | 1 + 7 files changed, 330 insertions(+), 68 deletions(-) create mode 100644 crates/pallet-token-claims/src/mock/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 0ad3cf76a..957caad12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1395,6 +1395,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.8.1" @@ -1481,6 +1487,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "downcast-rs" version = "1.2.0" @@ -2141,6 +2153,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -4715,11 +4736,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab571328afa78ae322493cacca3efac6a0f2e0a67305b4df31fd439ef129ac0" dependencies = [ "cfg-if", - "downcast", + "downcast 0.10.0", "fragile", "lazy_static", - "mockall_derive", - "predicates", + "mockall_derive 0.10.2", + "predicates 1.0.8", + "predicates-tree", +] + +[[package]] +name = "mockall" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2be9a9090bc1cac2930688fa9478092a64c6a92ddc6ae0692d46b37d9cab709" +dependencies = [ + "cfg-if", + "downcast 0.11.0", + "fragile", + "lazy_static", + "mockall_derive 0.11.2", + "predicates 2.1.1", "predicates-tree", ] @@ -4735,6 +4771,18 @@ dependencies = [ "syn 1.0.95", ] +[[package]] +name = "mockall_derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d702a0530a0141cf4ed147cf5ec7be6f2c187d4e37fcbefc39cf34116bfe8f" +dependencies = [ + "cfg-if", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + [[package]] name = "more-asserts" version = "0.2.2" @@ -5339,7 +5387,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "mockall", + "mockall 0.10.2", "parity-scale-codec", "scale-info", "serde", @@ -5357,7 +5405,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "mockall", + "mockall 0.10.2", "parity-scale-codec", "scale-info", "serde", @@ -5654,6 +5702,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "mockall 0.11.2", "pallet-balances", "parity-scale-codec", "primitives-ethereum", @@ -6064,7 +6113,7 @@ dependencies = [ "frame-support", "frame-system", "getrandom 0.2.6", - "mockall", + "mockall 0.10.2", "num_enum", "pallet-bioauth", "pallet-evm", @@ -6087,7 +6136,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "mockall", + "mockall 0.10.2", "pallet-evm-accounts-mapping", "parity-scale-codec", "primitive-types", @@ -6138,7 +6187,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" dependencies = [ "difference", - "float-cmp", + "float-cmp 0.8.0", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "float-cmp 0.9.0", + "itertools 0.10.3", "normalize-line-endings", "predicates-core", "regex", @@ -6880,7 +6943,7 @@ dependencies = [ "async-trait", "facetec-api-client", "hex", - "mockall", + "mockall 0.10.2", "parity-scale-codec", "primitives-auth-ticket", "primitives-liveness-data", diff --git a/crates/pallet-token-claims/Cargo.toml b/crates/pallet-token-claims/Cargo.toml index 5d8b88e39..eaf5d15e0 100644 --- a/crates/pallet-token-claims/Cargo.toml +++ b/crates/pallet-token-claims/Cargo.toml @@ -15,6 +15,7 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" serde = { version = "1", optional = true } [dev-dependencies] +mockall = "0.11" pallet-balances = { git = "https://github.com/humanode-network/substrate", branch = "master" } serde_json = "1" sp-core = { git = "https://github.com/humanode-network/substrate", branch = "master" } diff --git a/crates/pallet-token-claims/src/benchmarking.rs b/crates/pallet-token-claims/src/benchmarking.rs index 3813aad2c..6b80e6406 100644 --- a/crates/pallet-token-claims/src/benchmarking.rs +++ b/crates/pallet-token-claims/src/benchmarking.rs @@ -28,9 +28,26 @@ benchmarks! { let signature = ::create_ecdsa_signature(); let account_id = ::create_account_id(); >::insert(ethereum_address, claim_info); + + #[cfg(test)] + let test_data = { + use crate::mock; + + let recover_signer_ctx = mock::MockEthereumSignatureVerifier::recover_signer_context(); + recover_signer_ctx.expect().returning(move |_, _| Some((ðereum_address).clone())); + (recover_signer_ctx,) + }; + + }: _(RawOrigin::Signed(account_id), ethereum_address, signature) verify { assert_eq!(Claims::::get(ethereum_address), None); + + #[cfg(test)] + { + let (recover_signer_ctx,) = test_data; + recover_signer_ctx.checkpoint(); + } } impl_benchmark_test_suite!( diff --git a/crates/pallet-token-claims/src/mock.rs b/crates/pallet-token-claims/src/mock.rs index 596089d5c..ef81f23fb 100644 --- a/crates/pallet-token-claims/src/mock.rs +++ b/crates/pallet-token-claims/src/mock.rs @@ -1,17 +1,22 @@ //! The mock for the pallet. use frame_support::{ - ord_parameter_types, parameter_types, + ord_parameter_types, parameter_types, sp_io, traits::{ConstU32, ConstU64}, }; +use primitives_ethereum::{EcdsaSignature, EthereumAddress}; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; use crate::{self as pallet_token_claims}; +mod utils; +pub use self::utils::*; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -82,66 +87,41 @@ impl pallet_token_claims::Config for Test { type WeightInfo = (); } -// This function basically just builds a genesis storage key/value store according to -// our desired mockup. -pub fn new_test_ext() -> frame_support::sp_io::TestExternalities { +/// Utility function for creating dummy ethereum accounts. +pub fn eth(num: u8) -> EthereumAddress { + let mut addr = [0; 20]; + addr[19] = num; + EthereumAddress(addr) +} + +pub fn sig(num: u8) -> EcdsaSignature { + let mut signature = [0; 65]; + signature[64] = num; + EcdsaSignature(signature) +} + +pub fn new_test_ext() -> sp_io::TestExternalities { let genesis_config = GenesisConfig { system: Default::default(), balances: Default::default(), - token_claims: Default::default(), + token_claims: TokenClaimsConfig { + claims: [(eth(1), 10, None), (eth(2), 20, Some(MockVestingSchedule))] + .into_iter() + .map(|(eth_address, balance, vesting)| { + ( + eth_address, + pallet_token_claims::types::ClaimInfo { balance, vesting }, + ) + }) + .collect(), + }, }; - use sp_runtime::BuildStorage; - let storage = genesis_config.build_storage().unwrap(); - storage.into() + new_test_ext_with(genesis_config) } -/// Test mocks -mod mocks { - use codec::{Decode, Encode, MaxEncodedLen}; - use frame_support::{Deserialize, Serialize}; - use scale_info::TypeInfo; - - use super::*; - use crate::{ - traits::{self, PreconstructedMessageVerifier}, - types::EthereumSignatureMessageParams, - }; - - #[derive( - Debug, Clone, Decode, Encode, MaxEncodedLen, TypeInfo, PartialEq, Eq, Serialize, Deserialize, - )] - pub struct MockVestingSchedule; - - #[derive(Debug)] - pub struct MockVestingInterface; - - impl traits::VestingInterface for MockVestingInterface { - type AccountId = ::AccountId; - type Balance = crate::BalanceOf; - type Schedule = MockVestingSchedule; - - fn lock_under_vesting( - _account: &Self::AccountId, - _balance_to_lock: Self::Balance, - _schedule: Self::Schedule, - ) -> frame_support::dispatch::DispatchResult { - Ok(()) - } - } - - #[derive(Debug)] - pub struct MockEthereumSignatureVerifier; - - impl PreconstructedMessageVerifier for MockEthereumSignatureVerifier { - type MessageParams = - EthereumSignatureMessageParams<::AccountId>; - - fn recover_signer( - _message_params: Self::MessageParams, - _signature: primitives_ethereum::EcdsaSignature, - ) -> Option { - None - } - } +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext_with(genesis_config: GenesisConfig) -> sp_io::TestExternalities { + let storage = genesis_config.build_storage().unwrap(); + storage.into() } -use self::mocks::*; diff --git a/crates/pallet-token-claims/src/mock/utils.rs b/crates/pallet-token-claims/src/mock/utils.rs new file mode 100644 index 000000000..225d97ac1 --- /dev/null +++ b/crates/pallet-token-claims/src/mock/utils.rs @@ -0,0 +1,47 @@ +//! Mock utils. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{Deserialize, Serialize}; +use mockall::mock; +use primitives_ethereum::EcdsaSignature; +use scale_info::TypeInfo; + +use super::*; +use crate::{traits, types::EthereumSignatureMessageParams}; + +type AccountId = ::AccountId; + +#[derive( + Debug, Clone, Decode, Encode, MaxEncodedLen, TypeInfo, PartialEq, Eq, Serialize, Deserialize, +)] +pub struct MockVestingSchedule; + +mock! { + #[derive(Debug)] + pub VestingInterface {} + impl traits::VestingInterface for VestingInterface { + type AccountId = AccountId; + type Balance = crate::BalanceOf; + type Schedule = MockVestingSchedule; + + fn lock_under_vesting( + account: &::AccountId, + balance_to_lock: ::Balance, + schedule: ::Schedule, + ) -> frame_support::dispatch::DispatchResult; + } +} + +mock! { + #[derive(Debug)] + pub EthereumSignatureVerifier {} + + impl traits::PreconstructedMessageVerifier for EthereumSignatureVerifier { + type MessageParams = EthereumSignatureMessageParams; + + fn recover_signer( + message_params: ::MessageParams, + signature: EcdsaSignature, + ) -> Option; + } +} diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index e72b1b558..2ac64256a 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -1,15 +1,168 @@ //! The tests for the pallet. +use frame_support::{assert_err, assert_ok}; +use mockall::predicate; use primitives_ethereum::EthereumAddress; use crate::{ - mock::{new_test_ext, Test}, + mock::{ + eth, new_test_ext, sig, Balances, MockEthereumSignatureVerifier, MockVestingInterface, + MockVestingSchedule, Origin, Test, TokenClaims, + }, + types::{ClaimInfo, EthereumSignatureMessageParams}, *, }; #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - assert_eq!(Claims::::get(&EthereumAddress::default()), None); + assert_eq!(>::get(&EthereumAddress::default()), None); + assert_eq!( + >::get(ð(1)), + Some(ClaimInfo { + balance: 10, + vesting: None + }) + ); + assert_eq!( + >::get(ð(2)), + Some(ClaimInfo { + balance: 20, + vesting: Some(MockVestingSchedule) + }) + ); + }); +} + +/// This test verifies that claiming works in the happy path (when there is no vesting). +#[test] +fn claiming_works_no_vesting() { + new_test_ext().execute_with(|| { + assert!(>::contains_key(ð(1))); + assert_eq!(Balances::free_balance(42), 0); + + let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); + recover_signer_ctx + .expect() + .once() + .returning(|_, _| Some(eth(1))); + let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); + lock_under_vesting_ctx.expect().never(); + + assert_ok!(TokenClaims::claim(Origin::signed(42), eth(1), sig(1),)); + + assert!(!>::contains_key(ð(1))); + assert_eq!(Balances::free_balance(42), 10); + }); +} + +/// This test verifies that claiming works in the happy path with vesting. +#[test] +fn claiming_works_with_vesting() { + new_test_ext().execute_with(|| { + // Check test preconditions. + assert!(>::contains_key(ð(1))); + assert_eq!(Balances::free_balance(42), 0); + + // Set mock expectations. + let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); + let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); + recover_signer_ctx + .expect() + .once() + .with( + predicate::eq(EthereumSignatureMessageParams { + account_id: 42, + ethereum_address: eth(2), + }), + predicate::eq(sig(1)), + ) + .return_const(Some(eth(2))); + lock_under_vesting_ctx + .expect() + .once() + .with(predicate::eq(42), predicate::eq(20), predicate::always()) + .return_const(Ok(())); + + // Invoke the function under test. + assert_ok!(TokenClaims::claim(Origin::signed(42), eth(2), sig(1))); + + // Assert state changes. + assert!(!>::contains_key(ð(2))); + assert_eq!(Balances::free_balance(42), 20); + }); +} + +/// This test verifies that claiming does not go through when the ethereum address recovery from +/// the ethereum signature fails. +#[test] +fn claim_eth_signature_recovery_failure() { + new_test_ext().execute_with(|| { + // Check test preconditions. + assert!(>::contains_key(ð(2))); + assert_eq!(Balances::free_balance(42), 0); + + // Set mock expectations. + let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); + let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); + recover_signer_ctx + .expect() + .once() + .with( + predicate::eq(EthereumSignatureMessageParams { + account_id: 42, + ethereum_address: eth(1), + }), + predicate::eq(sig(1)), + ) + .return_const(None); + lock_under_vesting_ctx.expect().never(); + + // Invoke the function under test. + assert_err!( + TokenClaims::claim(Origin::signed(42), eth(1), sig(1)), + >::InvalidSignature + ); + + // Assert state changes. + assert!(>::contains_key(ð(1))); + assert_eq!(Balances::free_balance(42), 0); + }); +} + +/// This test verifies that claiming does not go through when the ethereum address recovery from +/// the ethereum signature recoves an address that does not match the expected one. +#[test] +fn claim_eth_signature_recovery_invalid() { + new_test_ext().execute_with(|| { + // Check test preconditions. + assert!(>::contains_key(ð(2))); + assert_eq!(Balances::free_balance(42), 0); + + // Set mock expectations. + let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); + let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); + recover_signer_ctx + .expect() + .once() + .with( + predicate::eq(EthereumSignatureMessageParams { + account_id: 42, + ethereum_address: eth(1), + }), + predicate::eq(sig(1)), + ) + .return_const(Some(eth(2))); + lock_under_vesting_ctx.expect().never(); + + // Invoke the function under test. + assert_err!( + TokenClaims::claim(Origin::signed(42), eth(1), sig(1)), + >::InvalidSignature + ); + + // Assert state changes. + assert!(>::contains_key(ð(1))); + assert_eq!(Balances::free_balance(42), 0); }); } diff --git a/crates/pallet-token-claims/src/types.rs b/crates/pallet-token-claims/src/types.rs index 6ec40b36e..4280e0be0 100644 --- a/crates/pallet-token-claims/src/types.rs +++ b/crates/pallet-token-claims/src/types.rs @@ -18,6 +18,7 @@ pub struct ClaimInfo { } /// The collection of parameters used for construcing a message that had to be signed. +#[derive(PartialEq, Eq, RuntimeDebug)] pub struct EthereumSignatureMessageParams { /// The account ID of whoever is requesting the claim. pub account_id: AccountId, From f7960d4462ccad79736469d9bd5dce9401a3744b Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Thu, 28 Jul 2022 22:29:16 +0400 Subject: [PATCH 14/41] Proper guarding of the mock objects --- Cargo.lock | 1 + crates/pallet-token-claims/Cargo.toml | 1 + crates/pallet-token-claims/src/benchmarking.rs | 10 ++++++++-- crates/pallet-token-claims/src/mock/utils.rs | 11 +++++++++++ crates/pallet-token-claims/src/tests.rs | 14 ++++++++++++-- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 957caad12..f17a048d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5703,6 +5703,7 @@ dependencies = [ "frame-support", "frame-system", "mockall 0.11.2", + "once_cell", "pallet-balances", "parity-scale-codec", "primitives-ethereum", diff --git a/crates/pallet-token-claims/Cargo.toml b/crates/pallet-token-claims/Cargo.toml index eaf5d15e0..4b09c8fc1 100644 --- a/crates/pallet-token-claims/Cargo.toml +++ b/crates/pallet-token-claims/Cargo.toml @@ -16,6 +16,7 @@ serde = { version = "1", optional = true } [dev-dependencies] mockall = "0.11" +once_cell = "1" pallet-balances = { git = "https://github.com/humanode-network/substrate", branch = "master" } serde_json = "1" sp-core = { git = "https://github.com/humanode-network/substrate", branch = "master" } diff --git a/crates/pallet-token-claims/src/benchmarking.rs b/crates/pallet-token-claims/src/benchmarking.rs index 6b80e6406..8824b0284 100644 --- a/crates/pallet-token-claims/src/benchmarking.rs +++ b/crates/pallet-token-claims/src/benchmarking.rs @@ -33,9 +33,12 @@ benchmarks! { let test_data = { use crate::mock; + let mock_runtime_guard = mock::runtime_lock(); + let recover_signer_ctx = mock::MockEthereumSignatureVerifier::recover_signer_context(); recover_signer_ctx.expect().returning(move |_, _| Some((ðereum_address).clone())); - (recover_signer_ctx,) + + (mock_runtime_guard, recover_signer_ctx) }; @@ -45,8 +48,11 @@ benchmarks! { #[cfg(test)] { - let (recover_signer_ctx,) = test_data; + let (mock_runtime_guard, recover_signer_ctx,) = test_data; + recover_signer_ctx.checkpoint(); + + drop(mock_runtime_guard); } } diff --git a/crates/pallet-token-claims/src/mock/utils.rs b/crates/pallet-token-claims/src/mock/utils.rs index 225d97ac1..bd9159fc8 100644 --- a/crates/pallet-token-claims/src/mock/utils.rs +++ b/crates/pallet-token-claims/src/mock/utils.rs @@ -45,3 +45,14 @@ mock! { ) -> Option; } } + +pub fn runtime_lock() -> std::sync::MutexGuard<'static, ()> { + static MOCK_RUNTIME_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + // Ignore the poisoning for the tests that panic. + // We only care about concurrency here, not about the poisoning. + match MOCK_RUNTIME_MUTEX.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + } +} diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 2ac64256a..cfe5159e5 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -6,8 +6,8 @@ use primitives_ethereum::EthereumAddress; use crate::{ mock::{ - eth, new_test_ext, sig, Balances, MockEthereumSignatureVerifier, MockVestingInterface, - MockVestingSchedule, Origin, Test, TokenClaims, + self, eth, new_test_ext, sig, Balances, MockEthereumSignatureVerifier, + MockVestingInterface, MockVestingSchedule, Origin, Test, TokenClaims, }, types::{ClaimInfo, EthereumSignatureMessageParams}, *, @@ -16,6 +16,8 @@ use crate::{ #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { + let _guard = mock::runtime_lock(); + assert_eq!(>::get(&EthereumAddress::default()), None); assert_eq!( >::get(ð(1)), @@ -38,6 +40,8 @@ fn basic_setup_works() { #[test] fn claiming_works_no_vesting() { new_test_ext().execute_with(|| { + let _guard = mock::runtime_lock(); + assert!(>::contains_key(ð(1))); assert_eq!(Balances::free_balance(42), 0); @@ -60,6 +64,8 @@ fn claiming_works_no_vesting() { #[test] fn claiming_works_with_vesting() { new_test_ext().execute_with(|| { + let _guard = mock::runtime_lock(); + // Check test preconditions. assert!(>::contains_key(ð(1))); assert_eq!(Balances::free_balance(42), 0); @@ -98,6 +104,8 @@ fn claiming_works_with_vesting() { #[test] fn claim_eth_signature_recovery_failure() { new_test_ext().execute_with(|| { + let _guard = mock::runtime_lock(); + // Check test preconditions. assert!(>::contains_key(ð(2))); assert_eq!(Balances::free_balance(42), 0); @@ -135,6 +143,8 @@ fn claim_eth_signature_recovery_failure() { #[test] fn claim_eth_signature_recovery_invalid() { new_test_ext().execute_with(|| { + let _guard = mock::runtime_lock(); + // Check test preconditions. assert!(>::contains_key(ð(2))); assert_eq!(Balances::free_balance(42), 0); From 2c517e8e30f59deeff3b66619cb0f1e8abea3675 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 29 Jul 2022 15:07:54 +0400 Subject: [PATCH 15/41] Cleaner test interface --- crates/pallet-token-claims/src/mock/utils.rs | 18 +++++++++++++++ crates/pallet-token-claims/src/tests.rs | 24 ++++++-------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/crates/pallet-token-claims/src/mock/utils.rs b/crates/pallet-token-claims/src/mock/utils.rs index bd9159fc8..a317f01ec 100644 --- a/crates/pallet-token-claims/src/mock/utils.rs +++ b/crates/pallet-token-claims/src/mock/utils.rs @@ -56,3 +56,21 @@ pub fn runtime_lock() -> std::sync::MutexGuard<'static, ()> { Err(poisoned) => poisoned.into_inner(), } } + +pub trait TestExternalitiesExt { + fn execute_with_ext(&mut self, execute: E) -> R + where + E: for<'e> FnOnce(&'e ()) -> R; +} + +impl TestExternalitiesExt for frame_support::sp_io::TestExternalities { + fn execute_with_ext(&mut self, execute: E) -> R + where + E: for<'e> FnOnce(&'e ()) -> R, + { + let guard = runtime_lock(); + let result = self.execute_with(|| execute(&guard)); + drop(guard); + result + } +} diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index cfe5159e5..f1318ca11 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -6,8 +6,8 @@ use primitives_ethereum::EthereumAddress; use crate::{ mock::{ - self, eth, new_test_ext, sig, Balances, MockEthereumSignatureVerifier, - MockVestingInterface, MockVestingSchedule, Origin, Test, TokenClaims, + eth, new_test_ext, sig, Balances, MockEthereumSignatureVerifier, MockVestingInterface, + MockVestingSchedule, Origin, Test, TestExternalitiesExt, TokenClaims, }, types::{ClaimInfo, EthereumSignatureMessageParams}, *, @@ -15,9 +15,7 @@ use crate::{ #[test] fn basic_setup_works() { - new_test_ext().execute_with(|| { - let _guard = mock::runtime_lock(); - + new_test_ext().execute_with_ext(|_| { assert_eq!(>::get(&EthereumAddress::default()), None); assert_eq!( >::get(ð(1)), @@ -39,9 +37,7 @@ fn basic_setup_works() { /// This test verifies that claiming works in the happy path (when there is no vesting). #[test] fn claiming_works_no_vesting() { - new_test_ext().execute_with(|| { - let _guard = mock::runtime_lock(); - + new_test_ext().execute_with_ext(|_| { assert!(>::contains_key(ð(1))); assert_eq!(Balances::free_balance(42), 0); @@ -63,9 +59,7 @@ fn claiming_works_no_vesting() { /// This test verifies that claiming works in the happy path with vesting. #[test] fn claiming_works_with_vesting() { - new_test_ext().execute_with(|| { - let _guard = mock::runtime_lock(); - + new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert!(>::contains_key(ð(1))); assert_eq!(Balances::free_balance(42), 0); @@ -103,9 +97,7 @@ fn claiming_works_with_vesting() { /// the ethereum signature fails. #[test] fn claim_eth_signature_recovery_failure() { - new_test_ext().execute_with(|| { - let _guard = mock::runtime_lock(); - + new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert!(>::contains_key(ð(2))); assert_eq!(Balances::free_balance(42), 0); @@ -142,9 +134,7 @@ fn claim_eth_signature_recovery_failure() { /// the ethereum signature recoves an address that does not match the expected one. #[test] fn claim_eth_signature_recovery_invalid() { - new_test_ext().execute_with(|| { - let _guard = mock::runtime_lock(); - + new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert!(>::contains_key(ð(2))); assert_eq!(Balances::free_balance(42), 0); From 719ec33bf690192026937b558de040a84bb36137 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 29 Jul 2022 15:08:43 +0400 Subject: [PATCH 16/41] Checkpoint mock contexts before the tests finish --- crates/pallet-token-claims/src/tests.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index f1318ca11..b9225835d 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -53,6 +53,9 @@ fn claiming_works_no_vesting() { assert!(!>::contains_key(ð(1))); assert_eq!(Balances::free_balance(42), 10); + + recover_signer_ctx.checkpoint(); + lock_under_vesting_ctx.checkpoint(); }); } @@ -90,6 +93,10 @@ fn claiming_works_with_vesting() { // Assert state changes. assert!(!>::contains_key(ð(2))); assert_eq!(Balances::free_balance(42), 20); + + // Assert mock invocations. + recover_signer_ctx.checkpoint(); + lock_under_vesting_ctx.checkpoint(); }); } @@ -127,6 +134,10 @@ fn claim_eth_signature_recovery_failure() { // Assert state changes. assert!(>::contains_key(ð(1))); assert_eq!(Balances::free_balance(42), 0); + + // Assert mock invocations. + recover_signer_ctx.checkpoint(); + lock_under_vesting_ctx.checkpoint(); }); } @@ -164,5 +175,9 @@ fn claim_eth_signature_recovery_invalid() { // Assert state changes. assert!(>::contains_key(ð(1))); assert_eq!(Balances::free_balance(42), 0); + + // Assert mock invocations. + recover_signer_ctx.checkpoint(); + lock_under_vesting_ctx.checkpoint(); }); } From 48484ef94e18a4d1812a989c08511424832d326c Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 29 Jul 2022 15:09:14 +0400 Subject: [PATCH 17/41] Assert noop instead of just error --- crates/pallet-token-claims/src/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index b9225835d..0de663152 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -1,6 +1,6 @@ //! The tests for the pallet. -use frame_support::{assert_err, assert_ok}; +use frame_support::{assert_noop, assert_ok}; use mockall::predicate; use primitives_ethereum::EthereumAddress; @@ -126,7 +126,7 @@ fn claim_eth_signature_recovery_failure() { lock_under_vesting_ctx.expect().never(); // Invoke the function under test. - assert_err!( + assert_noop!( TokenClaims::claim(Origin::signed(42), eth(1), sig(1)), >::InvalidSignature ); @@ -167,7 +167,7 @@ fn claim_eth_signature_recovery_invalid() { lock_under_vesting_ctx.expect().never(); // Invoke the function under test. - assert_err!( + assert_noop!( TokenClaims::claim(Origin::signed(42), eth(1), sig(1)), >::InvalidSignature ); From 24530b78fbb0dd3d218417056925e00c81b5485f Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 29 Jul 2022 15:09:46 +0400 Subject: [PATCH 18/41] Test for vesting interface failure --- crates/pallet-token-claims/src/tests.rs | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 0de663152..30fb1ed92 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -3,6 +3,7 @@ use frame_support::{assert_noop, assert_ok}; use mockall::predicate; use primitives_ethereum::EthereumAddress; +use sp_runtime::DispatchError; use crate::{ mock::{ @@ -181,3 +182,48 @@ fn claim_eth_signature_recovery_invalid() { lock_under_vesting_ctx.checkpoint(); }); } + +/// This test verifies that claiming does end up in a consistent state if the vesting interface call +/// returns an error. +#[test] +fn claim_lock_under_vesting_failure() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert!(>::contains_key(ð(2))); + assert_eq!(Balances::free_balance(42), 0); + + // Set mock expectations. + let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); + let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); + recover_signer_ctx + .expect() + .once() + .with( + predicate::eq(EthereumSignatureMessageParams { + account_id: 42, + ethereum_address: eth(2), + }), + predicate::eq(sig(1)), + ) + .return_const(Some(eth(2))); + lock_under_vesting_ctx + .expect() + .once() + .with(predicate::eq(42), predicate::eq(20), predicate::always()) + .return_const(Err(DispatchError::Other("vesting interface failed"))); + + // Invoke the function under test. + assert_noop!( + TokenClaims::claim(Origin::signed(42), eth(2), sig(1)), + DispatchError::Other("vesting interface failed"), + ); + + // Assert state changes. + assert!(>::contains_key(ð(2))); + assert_eq!(Balances::free_balance(42), 0); + + // Assert mock invocations. + recover_signer_ctx.checkpoint(); + lock_under_vesting_ctx.checkpoint(); + }); +} From 5e877db55eba012f0bffa742b7ced9491adaf19c Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 29 Jul 2022 15:10:23 +0400 Subject: [PATCH 19/41] Execute the whole process_claim in a new storage layer --- crates/pallet-token-claims/src/lib.rs | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 184de63b1..5ab2f3fdf 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -32,7 +32,7 @@ type ClaimInfoOf = types::ClaimInfo, ::VestingSched #[allow(clippy::missing_docs_in_private_items)] #[frame_support::pallet] pub mod pallet { - use frame_support::pallet_prelude::*; + use frame_support::{pallet_prelude::*, storage::with_storage_layer}; use frame_system::pallet_prelude::*; use primitives_ethereum::{EcdsaSignature, EthereumAddress}; @@ -158,23 +158,25 @@ pub mod pallet { impl Pallet { fn process_claim(who: T::AccountId, ethereum_address: EthereumAddress) -> DispatchResult { - let ClaimInfo { balance, vesting } = - >::take(ethereum_address).ok_or(>::NoClaim)?; + with_storage_layer(move || { + let ClaimInfo { balance, vesting } = + >::take(ethereum_address).ok_or(>::NoClaim)?; - T::Currency::deposit_creating(&who, balance); + T::Currency::deposit_creating(&who, balance); - if let Some(ref vesting) = vesting { - T::VestingInterface::lock_under_vesting(&who, balance, vesting.clone())?; - } + if let Some(ref vesting) = vesting { + T::VestingInterface::lock_under_vesting(&who, balance, vesting.clone())?; + } - Self::deposit_event(Event::TokensClaimed { - who, - ethereum_address, - balance, - vesting, - }); + Self::deposit_event(Event::TokensClaimed { + who, + ethereum_address, + balance, + vesting, + }); - Ok(()) + Ok(()) + }) } } } From f4f7112701085502cf3f9aa7b01b31da4b88a471 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 29 Jul 2022 17:40:31 +0400 Subject: [PATCH 20/41] Proper benchmarking interface and implementation --- .../pallet-token-claims/src/benchmarking.rs | 75 +++++++++++++------ 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/crates/pallet-token-claims/src/benchmarking.rs b/crates/pallet-token-claims/src/benchmarking.rs index 8824b0284..2b32fa739 100644 --- a/crates/pallet-token-claims/src/benchmarking.rs +++ b/crates/pallet-token-claims/src/benchmarking.rs @@ -6,27 +6,46 @@ use primitives_ethereum::{EcdsaSignature, EthereumAddress}; use crate::*; +/// The possible claim info variants that we'd need to conduct the benchmarks. +pub enum ClaimInfoVariant { + /// Claim without vesting and any balance. + WithoutVesting, + /// Claim with vesting and any balance. + WithVesting, +} + /// The benchmark interface into the environment. -pub trait Interface { - type Config: super::Config; +pub trait Interface: super::Config { + /// Obtain an Account ID. + fn create_account_id() -> ::AccountId; + /// Obtain an ethereum address. fn create_ethereum_address() -> EthereumAddress; - fn create_claim_info() -> super::ClaimInfoOf; - fn create_ecdsa_signature() -> EcdsaSignature; - fn create_account_id() -> ::AccountId; + + /// Obtain an ECDSA signature that would fit the provided Account ID and the Ethereum address + /// under the associated runtime. + fn create_ecdsa_signature( + account_id: &::AccountId, + ethereum_address: &EthereumAddress, + ) -> EcdsaSignature; + + /// Obtain a claim info variant. + fn create_claim_info(claim_info_variant: ClaimInfoVariant) -> super::ClaimInfoOf; } benchmarks! { where_clause { where - T: Interface + T: Interface } claim { - let ethereum_address = ::create_ethereum_address(); - let claim_info = ::create_claim_info(); - let signature = ::create_ecdsa_signature(); let account_id = ::create_account_id(); + let ethereum_address = ::create_ethereum_address(); + let ethereum_signature = ::create_ecdsa_signature(&account_id, ðereum_address); + + // We bench with the worst case scenario - with vested claim. + let claim_info = ::create_claim_info(ClaimInfoVariant::WithVesting); >::insert(ethereum_address, claim_info); #[cfg(test)] @@ -36,21 +55,25 @@ benchmarks! { let mock_runtime_guard = mock::runtime_lock(); let recover_signer_ctx = mock::MockEthereumSignatureVerifier::recover_signer_context(); - recover_signer_ctx.expect().returning(move |_, _| Some((ðereum_address).clone())); + recover_signer_ctx.expect().return_const(Some(ethereum_address)); + + let lock_under_vesting_ctx = mock::MockVestingInterface::lock_under_vesting_context(); + lock_under_vesting_ctx.expect().return_const(Ok(())); - (mock_runtime_guard, recover_signer_ctx) + (mock_runtime_guard, recover_signer_ctx, lock_under_vesting_ctx) }; - }: _(RawOrigin::Signed(account_id), ethereum_address, signature) + }: _(RawOrigin::Signed(account_id), ethereum_address, ethereum_signature) verify { assert_eq!(Claims::::get(ethereum_address), None); #[cfg(test)] { - let (mock_runtime_guard, recover_signer_ctx,) = test_data; + let (mock_runtime_guard, recover_signer_ctx, lock_under_vesting_ctx) = test_data; recover_signer_ctx.checkpoint(); + lock_under_vesting_ctx.checkpoint(); drop(mock_runtime_guard); } @@ -65,24 +88,28 @@ benchmarks! { #[cfg(test)] impl Interface for crate::mock::Test { - type Config = Self; + fn create_account_id() -> ::AccountId { + 42 + } fn create_ethereum_address() -> EthereumAddress { EthereumAddress::default() } - fn create_claim_info() -> crate::ClaimInfoOf { - crate::types::ClaimInfo { - balance: 0, - vesting: None, - } - } - - fn create_ecdsa_signature() -> EcdsaSignature { + fn create_ecdsa_signature( + _account_id: &::AccountId, + _ethereum_address: &EthereumAddress, + ) -> EcdsaSignature { EcdsaSignature::default() } - fn create_account_id() -> ::AccountId { - 0 + fn create_claim_info(claim_info_variant: ClaimInfoVariant) -> crate::ClaimInfoOf { + crate::types::ClaimInfo { + balance: 100, + vesting: match claim_info_variant { + ClaimInfoVariant::WithoutVesting => None, + ClaimInfoVariant::WithVesting => Some(crate::mock::MockVestingSchedule), + }, + } } } From 2b6daccf5f8af726ebf1cc9f739522af0ee3a2bb Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 29 Jul 2022 19:01:01 +0400 Subject: [PATCH 21/41] Drop unused data from the test runtime --- crates/pallet-token-claims/src/mock.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/pallet-token-claims/src/mock.rs b/crates/pallet-token-claims/src/mock.rs index ef81f23fb..1fd68359b 100644 --- a/crates/pallet-token-claims/src/mock.rs +++ b/crates/pallet-token-claims/src/mock.rs @@ -1,7 +1,7 @@ //! The mock for the pallet. use frame_support::{ - ord_parameter_types, parameter_types, sp_io, + sp_io, traits::{ConstU32, ConstU64}, }; use primitives_ethereum::{EcdsaSignature, EthereumAddress}; @@ -71,12 +71,6 @@ impl pallet_balances::Config for Test { type WeightInfo = (); } -parameter_types! { - pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:"; -} -ord_parameter_types! { - pub const Six: u64 = 6; -} impl pallet_token_claims::Config for Test { type Event = Event; From 30de7b6c8d3a6d7cddca81e27bf9263d6d269e3b Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 29 Jul 2022 19:33:13 +0400 Subject: [PATCH 22/41] Use pot account and ensure proper balances everywhere --- Cargo.lock | 1 + crates/pallet-token-claims/Cargo.toml | 2 + .../pallet-token-claims/src/benchmarking.rs | 53 ++++++++----------- crates/pallet-token-claims/src/lib.rs | 31 ++++++++++- crates/pallet-token-claims/src/mock.rs | 27 +++++++++- 5 files changed, 78 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f17a048d0..c6ee13301 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5705,6 +5705,7 @@ dependencies = [ "mockall 0.11.2", "once_cell", "pallet-balances", + "pallet-pot", "parity-scale-codec", "primitives-ethereum", "scale-info", diff --git a/crates/pallet-token-claims/Cargo.toml b/crates/pallet-token-claims/Cargo.toml index 4b09c8fc1..a41cfb8f1 100644 --- a/crates/pallet-token-claims/Cargo.toml +++ b/crates/pallet-token-claims/Cargo.toml @@ -15,6 +15,8 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" serde = { version = "1", optional = true } [dev-dependencies] +pallet-pot = { version = "0.1", path = "../pallet-pot" } + mockall = "0.11" once_cell = "1" pallet-balances = { git = "https://github.com/humanode-network/substrate", branch = "master" } diff --git a/crates/pallet-token-claims/src/benchmarking.rs b/crates/pallet-token-claims/src/benchmarking.rs index 2b32fa739..49fbdc818 100644 --- a/crates/pallet-token-claims/src/benchmarking.rs +++ b/crates/pallet-token-claims/src/benchmarking.rs @@ -6,21 +6,18 @@ use primitives_ethereum::{EcdsaSignature, EthereumAddress}; use crate::*; -/// The possible claim info variants that we'd need to conduct the benchmarks. -pub enum ClaimInfoVariant { - /// Claim without vesting and any balance. - WithoutVesting, - /// Claim with vesting and any balance. - WithVesting, -} - /// The benchmark interface into the environment. pub trait Interface: super::Config { /// Obtain an Account ID. - fn create_account_id() -> ::AccountId; + /// + /// This is an account to claim the funds to. + fn account_id_to_claim_to() -> ::AccountId; /// Obtain an ethereum address. - fn create_ethereum_address() -> EthereumAddress; + /// + /// This is an ethereum account that is supposed to have a valid calim associated with it + /// in the runtime genesis. + fn ethereum_address() -> EthereumAddress; /// Obtain an ECDSA signature that would fit the provided Account ID and the Ethereum address /// under the associated runtime. @@ -28,9 +25,6 @@ pub trait Interface: super::Config { account_id: &::AccountId, ethereum_address: &EthereumAddress, ) -> EcdsaSignature; - - /// Obtain a claim info variant. - fn create_claim_info(claim_info_variant: ClaimInfoVariant) -> super::ClaimInfoOf; } benchmarks! { @@ -40,13 +34,14 @@ benchmarks! { } claim { - let account_id = ::create_account_id(); - let ethereum_address = ::create_ethereum_address(); + let account_id = ::account_id_to_claim_to(); + let ethereum_address = ::ethereum_address(); let ethereum_signature = ::create_ecdsa_signature(&account_id, ðereum_address); - // We bench with the worst case scenario - with vested claim. - let claim_info = ::create_claim_info(ClaimInfoVariant::WithVesting); - >::insert(ethereum_address, claim_info); + // We assume the genesis has the corresponding claim; crash the bench if it doesn't. + let claim_info = Claims::::get(ethereum_address).unwrap(); + + let account_balance_before = >::total_balance(&account_id); #[cfg(test)] let test_data = { @@ -63,11 +58,15 @@ benchmarks! { (mock_runtime_guard, recover_signer_ctx, lock_under_vesting_ctx) }; + let origin = RawOrigin::Signed(account_id.clone()); - }: _(RawOrigin::Signed(account_id), ethereum_address, ethereum_signature) + }: _(origin, ethereum_address, ethereum_signature) verify { assert_eq!(Claims::::get(ethereum_address), None); + let account_balance_after = >::total_balance(&account_id); + assert_eq!(account_balance_after - account_balance_before, claim_info.balance); + #[cfg(test)] { let (mock_runtime_guard, recover_signer_ctx, lock_under_vesting_ctx) = test_data; @@ -88,12 +87,12 @@ benchmarks! { #[cfg(test)] impl Interface for crate::mock::Test { - fn create_account_id() -> ::AccountId { + fn account_id_to_claim_to() -> ::AccountId { 42 } - fn create_ethereum_address() -> EthereumAddress { - EthereumAddress::default() + fn ethereum_address() -> EthereumAddress { + mock::eth(1) } fn create_ecdsa_signature( @@ -102,14 +101,4 @@ impl Interface for crate::mock::Test { ) -> EcdsaSignature { EcdsaSignature::default() } - - fn create_claim_info(claim_info_variant: ClaimInfoVariant) -> crate::ClaimInfoOf { - crate::types::ClaimInfo { - balance: 100, - vesting: match claim_info_variant { - ClaimInfoVariant::WithoutVesting => None, - ClaimInfoVariant::WithVesting => Some(crate::mock::MockVestingSchedule), - }, - } - } } diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 5ab2f3fdf..0276b08c5 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -32,7 +32,12 @@ type ClaimInfoOf = types::ClaimInfo, ::VestingSched #[allow(clippy::missing_docs_in_private_items)] #[frame_support::pallet] pub mod pallet { - use frame_support::{pallet_prelude::*, storage::with_storage_layer}; + use frame_support::{ + pallet_prelude::*, + sp_runtime::traits::{CheckedAdd, Zero}, + storage::with_storage_layer, + traits::{ExistenceRequirement, WithdrawReasons}, + }; use frame_system::pallet_prelude::*; use primitives_ethereum::{EcdsaSignature, EthereumAddress}; @@ -56,6 +61,10 @@ pub mod pallet { /// Currency to claim. type Currency: Currency<::AccountId>; + /// The ID for the pot account to use. + #[pallet::constant] + type PotAccountId: Get<::AccountId>; + /// Vesting schedule configuration type. type VestingSchedule: Member + Parameter + MaxEncodedLen + MaybeSerializeDeserialize; @@ -98,9 +107,21 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { + let mut total_claimable_balance: BalanceOf = Zero::zero(); + for (eth_address, info) in self.claims.iter() { Claims::::insert(eth_address, info.clone()); + total_claimable_balance = + total_claimable_balance.checked_add(&info.balance).unwrap(); } + + // Ensure that our pot account has exatly the right balance. + let expected_pot_balance = >::minimum_balance() + total_claimable_balance; + let pot_account_id = T::PotAccountId::get(); + assert_eq!( + >::free_balance(&pot_account_id), + expected_pot_balance, + ); } } @@ -162,7 +183,13 @@ pub mod pallet { let ClaimInfo { balance, vesting } = >::take(ethereum_address).ok_or(>::NoClaim)?; - T::Currency::deposit_creating(&who, balance); + let funds = T::Currency::withdraw( + &T::PotAccountId::get(), + balance, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive, + )?; + T::Currency::resolve_creating(&who, funds); if let Some(ref vesting) = vesting { T::VestingInterface::lock_under_vesting(&who, balance, vesting.clone())?; diff --git a/crates/pallet-token-claims/src/mock.rs b/crates/pallet-token-claims/src/mock.rs index 1fd68359b..763be2a37 100644 --- a/crates/pallet-token-claims/src/mock.rs +++ b/crates/pallet-token-claims/src/mock.rs @@ -1,8 +1,9 @@ //! The mock for the pallet. use frame_support::{ - sp_io, + parameter_types, sp_io, traits::{ConstU32, ConstU64}, + PalletId, }; use primitives_ethereum::{EcdsaSignature, EthereumAddress}; use sp_core::H256; @@ -28,6 +29,7 @@ frame_support::construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Pot: pallet_pot::{Pallet, Config, Event}, TokenClaims: pallet_token_claims::{Pallet, Call, Storage, Config, Event}, } ); @@ -71,10 +73,24 @@ impl pallet_balances::Config for Test { type WeightInfo = (); } +parameter_types! { + pub const PotPalletId: PalletId = PalletId(*b"tokenclm"); +} + +impl pallet_pot::Config for Test { + type Event = Event; + type Currency = Balances; + type PalletId = PotPalletId; +} + +parameter_types! { + pub PotAccountId: u64 = Pot::account_id(); +} impl pallet_token_claims::Config for Test { type Event = Event; type Currency = Balances; + type PotAccountId = PotAccountId; type VestingSchedule = MockVestingSchedule; type VestingInterface = MockVestingInterface; type EthereumSignatureVerifier = MockEthereumSignatureVerifier; @@ -97,7 +113,14 @@ pub fn sig(num: u8) -> EcdsaSignature { pub fn new_test_ext() -> sp_io::TestExternalities { let genesis_config = GenesisConfig { system: Default::default(), - balances: Default::default(), + balances: BalancesConfig { + balances: vec![( + Pot::account_id(), + 30 /* tokens sum */ + + 1, /* existential deposit */ + )], + }, + pot: Default::default(), token_claims: TokenClaimsConfig { claims: [(eth(1), 10, None), (eth(2), 20, Some(MockVestingSchedule))] .into_iter() From 3217d10e4ded63ee5755fd534072afb5c1b5ed1d Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 17:20:40 +0400 Subject: [PATCH 23/41] Fix the comment for Claims storage accessor --- crates/pallet-token-claims/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 0276b08c5..2344b4440 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -84,7 +84,7 @@ pub mod pallet { type WeightInfo: WeightInfo; } - /// The public key of the robonode. + /// The claims that are available in the system. #[pallet::storage] #[pallet::getter(fn claims)] pub type Claims = StorageMap<_, Twox64Concat, EthereumAddress, ClaimInfoOf, OptionQuery>; From 12568faad0f0082281043327f8446a2f88bf4824 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 17:33:49 +0400 Subject: [PATCH 24/41] Compute and maintain the total claimable balance --- crates/pallet-token-claims/src/lib.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 2344b4440..241704223 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -33,8 +33,8 @@ type ClaimInfoOf = types::ClaimInfo, ::VestingSched #[frame_support::pallet] pub mod pallet { use frame_support::{ - pallet_prelude::*, - sp_runtime::traits::{CheckedAdd, Zero}, + pallet_prelude::{ValueQuery, *}, + sp_runtime::traits::{CheckedAdd, Saturating, Zero}, storage::with_storage_layer, traits::{ExistenceRequirement, WithdrawReasons}, }; @@ -89,6 +89,11 @@ pub mod pallet { #[pallet::getter(fn claims)] pub type Claims = StorageMap<_, Twox64Concat, EthereumAddress, ClaimInfoOf, OptionQuery>; + /// The total amount of claimable balance in the pot. + #[pallet::storage] + #[pallet::getter(fn total_claimable)] + pub type TotalClaimable = StorageValue<_, BalanceOf, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { /// The claims to initialize at genesis. @@ -122,6 +127,9 @@ pub mod pallet { >::free_balance(&pot_account_id), expected_pot_balance, ); + + // Initialize the total claimable balance. + >::update_total_claimable_balance(); } } @@ -183,18 +191,20 @@ pub mod pallet { let ClaimInfo { balance, vesting } = >::take(ethereum_address).ok_or(>::NoClaim)?; - let funds = T::Currency::withdraw( + let funds = >::withdraw( &T::PotAccountId::get(), balance, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive, )?; - T::Currency::resolve_creating(&who, funds); + >::resolve_creating(&who, funds); if let Some(ref vesting) = vesting { T::VestingInterface::lock_under_vesting(&who, balance, vesting.clone())?; } + Self::update_total_claimable_balance(); + Self::deposit_event(Event::TokensClaimed { who, ethereum_address, @@ -205,5 +215,12 @@ pub mod pallet { Ok(()) }) } + + fn update_total_claimable_balance() { + >::set( + >::free_balance(&T::PotAccountId::get()) + .saturating_sub(>::minimum_balance()), + ); + } } } From 40cdb5aa05e4da0a37b98c822071919dc98185d9 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 18:06:56 +0400 Subject: [PATCH 25/41] Update the tests with more explicit and easy to use eth addr parameters --- .../pallet-token-claims/src/benchmarking.rs | 2 +- crates/pallet-token-claims/src/mock.rs | 44 +++++++++---- crates/pallet-token-claims/src/tests.rs | 64 +++++++++++-------- 3 files changed, 71 insertions(+), 39 deletions(-) diff --git a/crates/pallet-token-claims/src/benchmarking.rs b/crates/pallet-token-claims/src/benchmarking.rs index 49fbdc818..763c80157 100644 --- a/crates/pallet-token-claims/src/benchmarking.rs +++ b/crates/pallet-token-claims/src/benchmarking.rs @@ -92,7 +92,7 @@ impl Interface for crate::mock::Test { } fn ethereum_address() -> EthereumAddress { - mock::eth(1) + mock::eth(mock::EthAddr::NoVesting) } fn create_ecdsa_signature( diff --git a/crates/pallet-token-claims/src/mock.rs b/crates/pallet-token-claims/src/mock.rs index 763be2a37..b259a94ec 100644 --- a/crates/pallet-token-claims/src/mock.rs +++ b/crates/pallet-token-claims/src/mock.rs @@ -97,13 +97,32 @@ impl pallet_token_claims::Config for Test { type WeightInfo = (); } +pub enum EthAddr { + NoVesting, + WithVesting, + Unknown, + Other(u8), +} + +impl From for u8 { + fn from(eth_addr: EthAddr) -> Self { + match eth_addr { + EthAddr::NoVesting => 1, + EthAddr::WithVesting => 2, + EthAddr::Unknown => 3, + EthAddr::Other(val) => val, + } + } +} + /// Utility function for creating dummy ethereum accounts. -pub fn eth(num: u8) -> EthereumAddress { +pub fn eth(val: EthAddr) -> EthereumAddress { let mut addr = [0; 20]; - addr[19] = num; + addr[19] = val.into(); EthereumAddress(addr) } +/// Utility function for creating dummy ecdsa signatures. pub fn sig(num: u8) -> EcdsaSignature { let mut signature = [0; 65]; signature[64] = num; @@ -122,15 +141,18 @@ pub fn new_test_ext() -> sp_io::TestExternalities { }, pot: Default::default(), token_claims: TokenClaimsConfig { - claims: [(eth(1), 10, None), (eth(2), 20, Some(MockVestingSchedule))] - .into_iter() - .map(|(eth_address, balance, vesting)| { - ( - eth_address, - pallet_token_claims::types::ClaimInfo { balance, vesting }, - ) - }) - .collect(), + claims: [ + (eth(EthAddr::NoVesting), 10, None), + (eth(EthAddr::WithVesting), 20, Some(MockVestingSchedule)), + ] + .into_iter() + .map(|(eth_address, balance, vesting)| { + ( + eth_address, + pallet_token_claims::types::ClaimInfo { balance, vesting }, + ) + }) + .collect(), }, }; new_test_ext_with(genesis_config) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 30fb1ed92..fdcf158f8 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -7,8 +7,8 @@ use sp_runtime::DispatchError; use crate::{ mock::{ - eth, new_test_ext, sig, Balances, MockEthereumSignatureVerifier, MockVestingInterface, - MockVestingSchedule, Origin, Test, TestExternalitiesExt, TokenClaims, + eth, new_test_ext, sig, Balances, EthAddr, MockEthereumSignatureVerifier, + MockVestingInterface, MockVestingSchedule, Origin, Test, TestExternalitiesExt, TokenClaims, }, types::{ClaimInfo, EthereumSignatureMessageParams}, *, @@ -19,14 +19,14 @@ fn basic_setup_works() { new_test_ext().execute_with_ext(|_| { assert_eq!(>::get(&EthereumAddress::default()), None); assert_eq!( - >::get(ð(1)), + >::get(ð(EthAddr::NoVesting)), Some(ClaimInfo { balance: 10, vesting: None }) ); assert_eq!( - >::get(ð(2)), + >::get(ð(EthAddr::WithVesting)), Some(ClaimInfo { balance: 20, vesting: Some(MockVestingSchedule) @@ -39,20 +39,24 @@ fn basic_setup_works() { #[test] fn claiming_works_no_vesting() { new_test_ext().execute_with_ext(|_| { - assert!(>::contains_key(ð(1))); + assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); recover_signer_ctx .expect() .once() - .returning(|_, _| Some(eth(1))); + .returning(|_, _| Some(eth(EthAddr::NoVesting))); let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); lock_under_vesting_ctx.expect().never(); - assert_ok!(TokenClaims::claim(Origin::signed(42), eth(1), sig(1),)); + assert_ok!(TokenClaims::claim( + Origin::signed(42), + eth(EthAddr::NoVesting), + sig(1), + )); - assert!(!>::contains_key(ð(1))); + assert!(!>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 10); recover_signer_ctx.checkpoint(); @@ -65,7 +69,7 @@ fn claiming_works_no_vesting() { fn claiming_works_with_vesting() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. - assert!(>::contains_key(ð(1))); + assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); // Set mock expectations. @@ -77,11 +81,11 @@ fn claiming_works_with_vesting() { .with( predicate::eq(EthereumSignatureMessageParams { account_id: 42, - ethereum_address: eth(2), + ethereum_address: eth(EthAddr::WithVesting), }), predicate::eq(sig(1)), ) - .return_const(Some(eth(2))); + .return_const(Some(eth(EthAddr::WithVesting))); lock_under_vesting_ctx .expect() .once() @@ -89,10 +93,14 @@ fn claiming_works_with_vesting() { .return_const(Ok(())); // Invoke the function under test. - assert_ok!(TokenClaims::claim(Origin::signed(42), eth(2), sig(1))); + assert_ok!(TokenClaims::claim( + Origin::signed(42), + eth(EthAddr::WithVesting), + sig(1) + )); // Assert state changes. - assert!(!>::contains_key(ð(2))); + assert!(!>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 20); // Assert mock invocations. @@ -107,7 +115,7 @@ fn claiming_works_with_vesting() { fn claim_eth_signature_recovery_failure() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. - assert!(>::contains_key(ð(2))); + assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); // Set mock expectations. @@ -119,7 +127,7 @@ fn claim_eth_signature_recovery_failure() { .with( predicate::eq(EthereumSignatureMessageParams { account_id: 42, - ethereum_address: eth(1), + ethereum_address: eth(EthAddr::NoVesting), }), predicate::eq(sig(1)), ) @@ -128,12 +136,12 @@ fn claim_eth_signature_recovery_failure() { // Invoke the function under test. assert_noop!( - TokenClaims::claim(Origin::signed(42), eth(1), sig(1)), + TokenClaims::claim(Origin::signed(42), eth(EthAddr::NoVesting), sig(1)), >::InvalidSignature ); // Assert state changes. - assert!(>::contains_key(ð(1))); + assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); // Assert mock invocations. @@ -148,7 +156,8 @@ fn claim_eth_signature_recovery_failure() { fn claim_eth_signature_recovery_invalid() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. - assert!(>::contains_key(ð(2))); + assert!(>::contains_key(ð(EthAddr::NoVesting))); + assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); // Set mock expectations. @@ -160,21 +169,22 @@ fn claim_eth_signature_recovery_invalid() { .with( predicate::eq(EthereumSignatureMessageParams { account_id: 42, - ethereum_address: eth(1), + ethereum_address: eth(EthAddr::NoVesting), }), predicate::eq(sig(1)), ) - .return_const(Some(eth(2))); + .return_const(Some(eth(EthAddr::Unknown))); lock_under_vesting_ctx.expect().never(); // Invoke the function under test. assert_noop!( - TokenClaims::claim(Origin::signed(42), eth(1), sig(1)), + TokenClaims::claim(Origin::signed(42), eth(EthAddr::NoVesting), sig(1)), >::InvalidSignature ); // Assert state changes. - assert!(>::contains_key(ð(1))); + assert!(>::contains_key(ð(EthAddr::NoVesting))); + assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); // Assert mock invocations. @@ -189,7 +199,7 @@ fn claim_eth_signature_recovery_invalid() { fn claim_lock_under_vesting_failure() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. - assert!(>::contains_key(ð(2))); + assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); // Set mock expectations. @@ -201,11 +211,11 @@ fn claim_lock_under_vesting_failure() { .with( predicate::eq(EthereumSignatureMessageParams { account_id: 42, - ethereum_address: eth(2), + ethereum_address: eth(EthAddr::WithVesting), }), predicate::eq(sig(1)), ) - .return_const(Some(eth(2))); + .return_const(Some(eth(EthAddr::WithVesting))); lock_under_vesting_ctx .expect() .once() @@ -214,12 +224,12 @@ fn claim_lock_under_vesting_failure() { // Invoke the function under test. assert_noop!( - TokenClaims::claim(Origin::signed(42), eth(2), sig(1)), + TokenClaims::claim(Origin::signed(42), eth(EthAddr::WithVesting), sig(1)), DispatchError::Other("vesting interface failed"), ); // Assert state changes. - assert!(>::contains_key(ð(2))); + assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); // Assert mock invocations. From be7ff9b8023cc3c52eb1958437c5245e69aeb2b2 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 18:14:23 +0400 Subject: [PATCH 26/41] Check the pot balance in tests --- crates/pallet-token-claims/src/tests.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index fdcf158f8..3be8c28df 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -17,6 +17,7 @@ use crate::{ #[test] fn basic_setup_works() { new_test_ext().execute_with_ext(|_| { + // Check the claims. assert_eq!(>::get(&EthereumAddress::default()), None); assert_eq!( >::get(ð(EthAddr::NoVesting)), @@ -32,6 +33,12 @@ fn basic_setup_works() { vesting: Some(MockVestingSchedule) }) ); + + // Check the pot balance. + assert_eq!( + >::free_balance(::PotAccountId::get()), + 30 + >::minimum_balance() + ); }); } From 214b537d29f563771223736e85740f09a75ff0ca Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 18:17:47 +0400 Subject: [PATCH 27/41] Add a test for non-existing claims --- crates/pallet-token-claims/src/tests.rs | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 3be8c28df..fb456b0c9 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -244,3 +244,43 @@ fn claim_lock_under_vesting_failure() { lock_under_vesting_ctx.checkpoint(); }); } + +/// This test verifies that when there is no claim, the claim call fails. +#[test] +fn claim_non_existing() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert!(!>::contains_key(ð(EthAddr::Unknown))); + assert_eq!(Balances::free_balance(42), 0); + + // Set mock expectations. + let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); + let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); + recover_signer_ctx + .expect() + .once() + .with( + predicate::eq(EthereumSignatureMessageParams { + account_id: 42, + ethereum_address: eth(EthAddr::Unknown), + }), + predicate::eq(sig(1)), + ) + .return_const(Some(eth(EthAddr::Unknown))); + lock_under_vesting_ctx.expect().never(); + + // Invoke the function under test. + assert_noop!( + TokenClaims::claim(Origin::signed(42), eth(EthAddr::Unknown), sig(1)), + >::NoClaim, + ); + + // Assert state changes. + assert!(!>::contains_key(ð(EthAddr::Unknown))); + assert_eq!(Balances::free_balance(42), 0); + + // Assert mock invocations. + recover_signer_ctx.checkpoint(); + lock_under_vesting_ctx.checkpoint(); + }); +} From a62b410a89217356e34d8472a3833505bc918070 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 18:38:57 +0400 Subject: [PATCH 28/41] Proper genesis tests --- crates/pallet-token-claims/src/lib.rs | 11 ++++-- crates/pallet-token-claims/src/tests.rs | 51 ++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 241704223..53de6461f 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -123,10 +123,13 @@ pub mod pallet { // Ensure that our pot account has exatly the right balance. let expected_pot_balance = >::minimum_balance() + total_claimable_balance; let pot_account_id = T::PotAccountId::get(); - assert_eq!( - >::free_balance(&pot_account_id), - expected_pot_balance, - ); + let actual_pot_balance = >::free_balance(&pot_account_id); + if actual_pot_balance != expected_pot_balance { + panic!( + "invalid balance in the token claims pot account: got {:?}, expected {:?}", + actual_pot_balance, expected_pot_balance + ); + } // Initialize the total claimable balance. >::update_total_claimable_balance(); diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index fb456b0c9..0be9aad0e 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -7,8 +7,9 @@ use sp_runtime::DispatchError; use crate::{ mock::{ - eth, new_test_ext, sig, Balances, EthAddr, MockEthereumSignatureVerifier, - MockVestingInterface, MockVestingSchedule, Origin, Test, TestExternalitiesExt, TokenClaims, + eth, new_test_ext, new_test_ext_with, sig, Balances, EthAddr, + MockEthereumSignatureVerifier, MockVestingInterface, MockVestingSchedule, Origin, Test, + TestExternalitiesExt, TokenClaims, }, types::{ClaimInfo, EthereumSignatureMessageParams}, *, @@ -284,3 +285,49 @@ fn claim_non_existing() { lock_under_vesting_ctx.checkpoint(); }); } + +/// This test verifies that empty claims in genesis are handled correctly. +#[test] +fn genesis_empty() { + new_test_ext_with(mock::GenesisConfig { + balances: mock::BalancesConfig { + balances: vec![( + mock::Pot::account_id(), + 1, /* existential deposit only */ + )], + }, + ..Default::default() + }) + .execute_with_ext(|_| { + // Check the pot balance. + assert_eq!( + >::free_balance(::PotAccountId::get()), + >::minimum_balance() + ); + }); +} + +/// This test verifies that the genesis builder correctly ensures the pot balance. +#[test] +#[should_panic = "invalid balance in the token claims pot account: got 124, expected 457"] +fn genesis_ensure_balance_is_checked() { + new_test_ext_with(mock::GenesisConfig { + balances: mock::BalancesConfig { + balances: vec![( + mock::Pot::account_id(), + 1 /* existential deposit */ + + 123, /* total claimable amount that doesn't match the sum of claims */ + )], + }, + token_claims: mock::TokenClaimsConfig { + claims: vec![( + EthereumAddress([0; 20]), + ClaimInfo { + balance: 456, + vesting: None, + }, + )], + }, + ..Default::default() + }); +} From 2eb79134552dcb91463df19639b848c7c9dac0a2 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 18:43:31 +0400 Subject: [PATCH 29/41] Correct the claiming_works_no_vesting test --- crates/pallet-token-claims/src/tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 0be9aad0e..60d2cdd1f 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -47,26 +47,38 @@ fn basic_setup_works() { #[test] fn claiming_works_no_vesting() { new_test_ext().execute_with_ext(|_| { + // Check test preconditions. assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); + // Set mock expectations. let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); recover_signer_ctx .expect() .once() + .with( + predicate::eq(EthereumSignatureMessageParams { + account_id: 42, + ethereum_address: eth(EthAddr::NoVesting), + }), + predicate::eq(sig(1)), + ) .returning(|_, _| Some(eth(EthAddr::NoVesting))); let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); lock_under_vesting_ctx.expect().never(); + // Invoke the function under test. assert_ok!(TokenClaims::claim( Origin::signed(42), eth(EthAddr::NoVesting), sig(1), )); + // Assert state changes. assert!(!>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 10); + // Assert mock invocations. recover_signer_ctx.checkpoint(); lock_under_vesting_ctx.checkpoint(); }); From 897af4f4942df8e07617528721be6723735019a4 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 18:54:04 +0400 Subject: [PATCH 30/41] Check the total issuance and the pot account balance deduction --- crates/pallet-token-claims/src/tests.rs | 57 ++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 60d2cdd1f..e3a332c1c 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -15,6 +15,14 @@ use crate::{ *, }; +fn pot_account_balance() -> BalanceOf { + >::free_balance(&::PotAccountId::get()) +} + +fn currency_total_issuance() -> BalanceOf { + >::total_issuance() +} + #[test] fn basic_setup_works() { new_test_ext().execute_with_ext(|_| { @@ -37,7 +45,7 @@ fn basic_setup_works() { // Check the pot balance. assert_eq!( - >::free_balance(::PotAccountId::get()), + pot_account_balance(), 30 + >::minimum_balance() ); }); @@ -50,6 +58,8 @@ fn claiming_works_no_vesting() { // Check test preconditions. assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); + let pot_account_balance_before = pot_account_balance(); + let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); @@ -77,6 +87,11 @@ fn claiming_works_no_vesting() { // Assert state changes. assert!(!>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 10); + assert_eq!(pot_account_balance_before - pot_account_balance(), 10); + assert_eq!( + currency_total_issuance_before - currency_total_issuance(), + 0 + ); // Assert mock invocations. recover_signer_ctx.checkpoint(); @@ -91,6 +106,8 @@ fn claiming_works_with_vesting() { // Check test preconditions. assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); + let pot_account_balance_before = pot_account_balance(); + let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); @@ -122,6 +139,11 @@ fn claiming_works_with_vesting() { // Assert state changes. assert!(!>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 20); + assert_eq!(pot_account_balance_before - pot_account_balance(), 20); + assert_eq!( + currency_total_issuance_before - currency_total_issuance(), + 0 + ); // Assert mock invocations. recover_signer_ctx.checkpoint(); @@ -137,6 +159,8 @@ fn claim_eth_signature_recovery_failure() { // Check test preconditions. assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); + let pot_account_balance_before = pot_account_balance(); + let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); @@ -163,6 +187,11 @@ fn claim_eth_signature_recovery_failure() { // Assert state changes. assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); + assert_eq!(pot_account_balance_before - pot_account_balance(), 0); + assert_eq!( + currency_total_issuance_before - currency_total_issuance(), + 0 + ); // Assert mock invocations. recover_signer_ctx.checkpoint(); @@ -179,6 +208,8 @@ fn claim_eth_signature_recovery_invalid() { assert!(>::contains_key(ð(EthAddr::NoVesting))); assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); + let pot_account_balance_before = pot_account_balance(); + let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); @@ -206,6 +237,11 @@ fn claim_eth_signature_recovery_invalid() { assert!(>::contains_key(ð(EthAddr::NoVesting))); assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); + assert_eq!(pot_account_balance_before - pot_account_balance(), 0); + assert_eq!( + currency_total_issuance_before - currency_total_issuance(), + 0 + ); // Assert mock invocations. recover_signer_ctx.checkpoint(); @@ -221,6 +257,8 @@ fn claim_lock_under_vesting_failure() { // Check test preconditions. assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); + let pot_account_balance_before = pot_account_balance(); + let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); @@ -251,6 +289,11 @@ fn claim_lock_under_vesting_failure() { // Assert state changes. assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); + assert_eq!(pot_account_balance_before - pot_account_balance(), 0); + assert_eq!( + currency_total_issuance_before - currency_total_issuance(), + 0 + ); // Assert mock invocations. recover_signer_ctx.checkpoint(); @@ -265,6 +308,8 @@ fn claim_non_existing() { // Check test preconditions. assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); + let pot_account_balance_before = pot_account_balance(); + let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); @@ -291,6 +336,11 @@ fn claim_non_existing() { // Assert state changes. assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); + assert_eq!(pot_account_balance_before - pot_account_balance(), 0); + assert_eq!( + currency_total_issuance_before - currency_total_issuance(), + 0 + ); // Assert mock invocations. recover_signer_ctx.checkpoint(); @@ -312,10 +362,7 @@ fn genesis_empty() { }) .execute_with_ext(|_| { // Check the pot balance. - assert_eq!( - >::free_balance(::PotAccountId::get()), - >::minimum_balance() - ); + assert_eq!(pot_account_balance(), >::minimum_balance()); }); } From 4ea4fc6af1be6b58a68af57b3c79574b26132333 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 19:24:22 +0400 Subject: [PATCH 31/41] Ensure we can sequentially consume all of the claims --- crates/pallet-token-claims/src/tests.rs | 74 +++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index e3a332c1c..cad145f37 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -390,3 +390,77 @@ fn genesis_ensure_balance_is_checked() { ..Default::default() }); } + +/// This test verifies that we can consume all of the claims seqentially and get to the empty +/// claimable balance in the pot but without killing the pot account. +#[test] +fn claiming_sequential() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(Balances::free_balance(42), 0); + let pot_account_balance_before = pot_account_balance(); + let currency_total_issuance_before = currency_total_issuance(); + + // Prepare the keys to iterate over all the claims. + let claims: Vec<_> = >::iter().collect(); + + // Iterate over all the claims conuming them. + for (claim_eth_address, claim_info) in &claims { + // Set mock expectations. + let recover_signer_ctx = MockEthereumSignatureVerifier::recover_signer_context(); + recover_signer_ctx + .expect() + .once() + .with( + predicate::eq(EthereumSignatureMessageParams { + account_id: 42, + ethereum_address: *claim_eth_address, + }), + predicate::eq(sig(1)), + ) + .return_const(Some(*claim_eth_address)); + let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); + + match claim_info.vesting { + Some(ref vesting) => lock_under_vesting_ctx + .expect() + .once() + .with( + predicate::eq(42), + predicate::eq(claim_info.balance), + predicate::eq(vesting.clone()), + ) + .return_const(Ok(())), + None => lock_under_vesting_ctx.expect().never(), + }; + + assert_ok!(TokenClaims::claim( + Origin::signed(42), + *claim_eth_address, + sig(1), + )); + + // Assert state changes for this local iteration. + assert!(!>::contains_key(claim_eth_address)); + assert_eq!( + currency_total_issuance_before - currency_total_issuance(), + 0 + ); + + // Assert mock invocations. + recover_signer_ctx.checkpoint(); + lock_under_vesting_ctx.checkpoint(); + } + + // Assert overall state changes. + assert_eq!( + Balances::free_balance(42), + pot_account_balance_before - >::minimum_balance() + ); + assert_eq!(pot_account_balance(), >::minimum_balance()); + assert_eq!( + currency_total_issuance_before - currency_total_issuance(), + 0 + ); + }); +} From 1b27916032430d958c7126924e5b76754bf8673c Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 19:25:40 +0400 Subject: [PATCH 32/41] Avoid using `returning` needlessly --- crates/pallet-token-claims/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index cad145f37..3ddfb4097 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -73,7 +73,7 @@ fn claiming_works_no_vesting() { }), predicate::eq(sig(1)), ) - .returning(|_, _| Some(eth(EthAddr::NoVesting))); + .return_const(Some(eth(EthAddr::NoVesting))); let lock_under_vesting_ctx = MockVestingInterface::lock_under_vesting_context(); lock_under_vesting_ctx.expect().never(); From b18284743d6bdc4361eda4917c56dca60342f631 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 19:31:54 +0400 Subject: [PATCH 33/41] Ensure total claimable balance is updated correctly --- crates/pallet-token-claims/src/tests.rs | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 3ddfb4097..4b8fa214f 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -19,6 +19,10 @@ fn pot_account_balance() -> BalanceOf { >::free_balance(&::PotAccountId::get()) } +fn total_claimable_balance() -> BalanceOf { + >::get() +} + fn currency_total_issuance() -> BalanceOf { >::total_issuance() } @@ -48,6 +52,9 @@ fn basic_setup_works() { pot_account_balance(), 30 + >::minimum_balance() ); + + // Check the total claimable balance value. + assert_eq!(total_claimable_balance(), 30); }); } @@ -59,6 +66,7 @@ fn claiming_works_no_vesting() { assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); let pot_account_balance_before = pot_account_balance(); + let total_claimable_balance_before = total_claimable_balance(); let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. @@ -88,6 +96,10 @@ fn claiming_works_no_vesting() { assert!(!>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 10); assert_eq!(pot_account_balance_before - pot_account_balance(), 10); + assert_eq!( + total_claimable_balance_before - total_claimable_balance(), + 10 + ); assert_eq!( currency_total_issuance_before - currency_total_issuance(), 0 @@ -107,6 +119,7 @@ fn claiming_works_with_vesting() { assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); let pot_account_balance_before = pot_account_balance(); + let total_claimable_balance_before = total_claimable_balance(); let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. @@ -140,6 +153,10 @@ fn claiming_works_with_vesting() { assert!(!>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 20); assert_eq!(pot_account_balance_before - pot_account_balance(), 20); + assert_eq!( + total_claimable_balance_before - total_claimable_balance(), + 20 + ); assert_eq!( currency_total_issuance_before - currency_total_issuance(), 0 @@ -160,6 +177,7 @@ fn claim_eth_signature_recovery_failure() { assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); let pot_account_balance_before = pot_account_balance(); + let total_claimable_balance_before = total_claimable_balance(); let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. @@ -188,6 +206,10 @@ fn claim_eth_signature_recovery_failure() { assert!(>::contains_key(ð(EthAddr::NoVesting))); assert_eq!(Balances::free_balance(42), 0); assert_eq!(pot_account_balance_before - pot_account_balance(), 0); + assert_eq!( + total_claimable_balance_before - total_claimable_balance(), + 0 + ); assert_eq!( currency_total_issuance_before - currency_total_issuance(), 0 @@ -209,6 +231,7 @@ fn claim_eth_signature_recovery_invalid() { assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); let pot_account_balance_before = pot_account_balance(); + let total_claimable_balance_before = total_claimable_balance(); let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. @@ -238,6 +261,10 @@ fn claim_eth_signature_recovery_invalid() { assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); assert_eq!(pot_account_balance_before - pot_account_balance(), 0); + assert_eq!( + total_claimable_balance_before - total_claimable_balance(), + 0 + ); assert_eq!( currency_total_issuance_before - currency_total_issuance(), 0 @@ -258,6 +285,7 @@ fn claim_lock_under_vesting_failure() { assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); let pot_account_balance_before = pot_account_balance(); + let total_claimable_balance_before = total_claimable_balance(); let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. @@ -290,6 +318,10 @@ fn claim_lock_under_vesting_failure() { assert!(>::contains_key(ð(EthAddr::WithVesting))); assert_eq!(Balances::free_balance(42), 0); assert_eq!(pot_account_balance_before - pot_account_balance(), 0); + assert_eq!( + total_claimable_balance_before - total_claimable_balance(), + 0 + ); assert_eq!( currency_total_issuance_before - currency_total_issuance(), 0 @@ -309,6 +341,7 @@ fn claim_non_existing() { assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); let pot_account_balance_before = pot_account_balance(); + let total_claimable_balance_before = total_claimable_balance(); let currency_total_issuance_before = currency_total_issuance(); // Set mock expectations. @@ -337,6 +370,10 @@ fn claim_non_existing() { assert!(!>::contains_key(ð(EthAddr::Unknown))); assert_eq!(Balances::free_balance(42), 0); assert_eq!(pot_account_balance_before - pot_account_balance(), 0); + assert_eq!( + total_claimable_balance_before - total_claimable_balance(), + 0 + ); assert_eq!( currency_total_issuance_before - currency_total_issuance(), 0 @@ -458,6 +495,7 @@ fn claiming_sequential() { pot_account_balance_before - >::minimum_balance() ); assert_eq!(pot_account_balance(), >::minimum_balance()); + assert_eq!(total_claimable_balance(), 0); assert_eq!( currency_total_issuance_before - currency_total_issuance(), 0 From 9dd01e2dbd78d555ba629986a2c4f06088e60302 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 19:36:43 +0400 Subject: [PATCH 34/41] Check total issuance at benches --- crates/pallet-token-claims/src/benchmarking.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/pallet-token-claims/src/benchmarking.rs b/crates/pallet-token-claims/src/benchmarking.rs index 763c80157..870a32df1 100644 --- a/crates/pallet-token-claims/src/benchmarking.rs +++ b/crates/pallet-token-claims/src/benchmarking.rs @@ -42,6 +42,7 @@ benchmarks! { let claim_info = Claims::::get(ethereum_address).unwrap(); let account_balance_before = >::total_balance(&account_id); + let currency_total_issuance_before = >::total_issuance(); #[cfg(test)] let test_data = { @@ -66,6 +67,10 @@ benchmarks! { let account_balance_after = >::total_balance(&account_id); assert_eq!(account_balance_after - account_balance_before, claim_info.balance); + assert_eq!( + currency_total_issuance_before, + >::total_issuance(), + ); #[cfg(test)] { From edea26527482178d4d865d202db196aeb94db4c1 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 19:42:52 +0400 Subject: [PATCH 35/41] Correct the mock expectations at benches --- crates/pallet-token-claims/src/benchmarking.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/pallet-token-claims/src/benchmarking.rs b/crates/pallet-token-claims/src/benchmarking.rs index 870a32df1..32c1371bc 100644 --- a/crates/pallet-token-claims/src/benchmarking.rs +++ b/crates/pallet-token-claims/src/benchmarking.rs @@ -51,10 +51,10 @@ benchmarks! { let mock_runtime_guard = mock::runtime_lock(); let recover_signer_ctx = mock::MockEthereumSignatureVerifier::recover_signer_context(); - recover_signer_ctx.expect().return_const(Some(ethereum_address)); + recover_signer_ctx.expect().times(1..).return_const(Some(ethereum_address)); let lock_under_vesting_ctx = mock::MockVestingInterface::lock_under_vesting_context(); - lock_under_vesting_ctx.expect().return_const(Ok(())); + lock_under_vesting_ctx.expect().times(1..).return_const(Ok(())); (mock_runtime_guard, recover_signer_ctx, lock_under_vesting_ctx) }; @@ -97,7 +97,7 @@ impl Interface for crate::mock::Test { } fn ethereum_address() -> EthereumAddress { - mock::eth(mock::EthAddr::NoVesting) + mock::eth(mock::EthAddr::WithVesting) } fn create_ecdsa_signature( From eb9098e865eca03ab24af6a79ec25747f5d66a25 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 1 Aug 2022 19:57:57 +0400 Subject: [PATCH 36/41] Add the total claimable balance assertion --- crates/pallet-token-claims/src/lib.rs | 17 ++++++++ crates/pallet-token-claims/src/mock.rs | 1 + crates/pallet-token-claims/src/tests.rs | 56 ++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 53de6461f..a294722b5 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -98,6 +98,11 @@ pub mod pallet { pub struct GenesisConfig { /// The claims to initialize at genesis. pub claims: Vec<(EthereumAddress, ClaimInfoOf)>, + /// The total claimable balance. + /// + /// If provided, must be equal to the sum of all claims balances. + /// This is useful for double-checking the expected sum during the genesis construction. + pub total_claimable: Option>, } #[cfg(feature = "std")] @@ -105,6 +110,7 @@ pub mod pallet { fn default() -> Self { GenesisConfig { claims: Default::default(), + total_claimable: None, } } } @@ -133,6 +139,17 @@ pub mod pallet { // Initialize the total claimable balance. >::update_total_claimable_balance(); + + // Check that the total claimable balance we computed matched the one declared in the + // genesis configuration. + if let Some(expected_total_claimable_balance) = self.total_claimable { + if expected_total_claimable_balance != total_claimable_balance { + panic!( + "computed total claimable balance ({:?}) is different from the one specified at the genesis config ({:?})", + total_claimable_balance, expected_total_claimable_balance + ); + } + } } } diff --git a/crates/pallet-token-claims/src/mock.rs b/crates/pallet-token-claims/src/mock.rs index b259a94ec..e2156c6c3 100644 --- a/crates/pallet-token-claims/src/mock.rs +++ b/crates/pallet-token-claims/src/mock.rs @@ -153,6 +153,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ) }) .collect(), + total_claimable: Some(30), }, }; new_test_ext_with(genesis_config) diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 4b8fa214f..c71ae3137 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -406,7 +406,7 @@ fn genesis_empty() { /// This test verifies that the genesis builder correctly ensures the pot balance. #[test] #[should_panic = "invalid balance in the token claims pot account: got 124, expected 457"] -fn genesis_ensure_balance_is_checked() { +fn genesis_ensure_pot_balance_is_checked() { new_test_ext_with(mock::GenesisConfig { balances: mock::BalancesConfig { balances: vec![( @@ -423,6 +423,60 @@ fn genesis_ensure_balance_is_checked() { vesting: None, }, )], + total_claimable: Some(456), + }, + ..Default::default() + }); +} + +/// This test verifies that the genesis builder asserted the equality of the configured and computed +/// total claimable balances. +#[test] +#[should_panic = "computed total claimable balance (123) is different from the one specified at the genesis config (456)"] +fn genesis_ensure_total_claimable_balance_is_asserted() { + new_test_ext_with(mock::GenesisConfig { + balances: mock::BalancesConfig { + balances: vec![( + mock::Pot::account_id(), + 1 /* existential deposit */ + + 123, /* total claimable amount */ + )], + }, + token_claims: mock::TokenClaimsConfig { + claims: vec![( + EthereumAddress([0; 20]), + ClaimInfo { + balance: 123, /* the only contribution to the total claimable balance */ + vesting: None, + }, + )], + total_claimable: Some(456), /* the configured total claimable balance that doesn't matched the computed value */ + }, + ..Default::default() + }); +} + +/// This test verifies that the genesis builder works when no assertion of the total claimable +/// balance is set. +#[test] +fn genesis_no_total_claimable_balance_assertion_works() { + new_test_ext_with(mock::GenesisConfig { + balances: mock::BalancesConfig { + balances: vec![( + mock::Pot::account_id(), + 1 /* existential deposit */ + + 123, /* total claimable amount */ + )], + }, + token_claims: mock::TokenClaimsConfig { + claims: vec![( + EthereumAddress([0; 20]), + ClaimInfo { + balance: 123, + vesting: None, + }, + )], + total_claimable: None, /* don't assert */ }, ..Default::default() }); From 7b78ec606a69072b0410695c8a1a163e437f0808 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Tue, 2 Aug 2022 03:01:36 +0400 Subject: [PATCH 37/41] Correct no_std use --- crates/pallet-token-claims/src/lib.rs | 4 +++- crates/pallet-token-claims/src/types.rs | 4 +++- crates/primitives-ethereum/src/ethereum_address.rs | 7 +++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index a294722b5..df339ef5c 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -32,9 +32,11 @@ type ClaimInfoOf = types::ClaimInfo, ::VestingSched #[allow(clippy::missing_docs_in_private_items)] #[frame_support::pallet] pub mod pallet { + #[cfg(feature = "std")] + use frame_support::sp_runtime::traits::{CheckedAdd, Zero}; use frame_support::{ pallet_prelude::{ValueQuery, *}, - sp_runtime::traits::{CheckedAdd, Saturating, Zero}, + sp_runtime::traits::Saturating, storage::with_storage_layer, traits::{ExistenceRequirement, WithdrawReasons}, }; diff --git a/crates/pallet-token-claims/src/types.rs b/crates/pallet-token-claims/src/types.rs index 4280e0be0..286f0c98a 100644 --- a/crates/pallet-token-claims/src/types.rs +++ b/crates/pallet-token-claims/src/types.rs @@ -1,7 +1,9 @@ //! Custom types we use. use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{Deserialize, RuntimeDebug, Serialize}; +use frame_support::RuntimeDebug; +#[cfg(feature = "std")] +use frame_support::{Deserialize, Serialize}; use primitives_ethereum::EthereumAddress; use scale_info::TypeInfo; diff --git a/crates/primitives-ethereum/src/ethereum_address.rs b/crates/primitives-ethereum/src/ethereum_address.rs index 6c09c0f27..47412f70d 100644 --- a/crates/primitives-ethereum/src/ethereum_address.rs +++ b/crates/primitives-ethereum/src/ethereum_address.rs @@ -1,10 +1,9 @@ //! Ethereum address. use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - serde::{Deserializer, Serializer}, - Deserialize, RuntimeDebug, Serialize, -}; +#[cfg(feature = "std")] +use frame_support::serde::{self, Deserialize, Deserializer, Serialize, Serializer}; +use frame_support::RuntimeDebug; use scale_info::TypeInfo; /// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account). From 2600ba865079fb03f6934c3e6d3ac1b91c9de85f Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Tue, 2 Aug 2022 04:28:15 +0400 Subject: [PATCH 38/41] Export types and traits --- crates/pallet-token-claims/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index df339ef5c..ceb541d21 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -7,8 +7,8 @@ use frame_support::traits::{Currency, StorageVersion}; pub use self::pallet::*; pub mod traits; -mod types; -mod weights; +pub mod types; +pub mod weights; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; From 26e7762af1581cbd14d0ac77e52ad2eab9f5ef78 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Wed, 3 Aug 2022 01:54:34 +0400 Subject: [PATCH 39/41] Fix typo findings from code review Co-authored-by: Dmitry Lavrenov <39522748+dmitrylavrenov@users.noreply.github.com> --- crates/pallet-token-claims/src/traits.rs | 2 +- crates/pallet-token-claims/src/types.rs | 2 +- crates/primitives-ethereum/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/pallet-token-claims/src/traits.rs b/crates/pallet-token-claims/src/traits.rs index 5a74c8d8e..cc6e6a96c 100644 --- a/crates/pallet-token-claims/src/traits.rs +++ b/crates/pallet-token-claims/src/traits.rs @@ -8,7 +8,7 @@ use primitives_ethereum::{EcdsaSignature, EthereumAddress}; /// The idea is we don't pass in the message we use for the verification, but instead we pass in /// the message parameters. /// -/// This abstraction built with EIP-712 in mind, but can also be implemented with any generic +/// This abstraction is built with EIP-712 in mind, but can also be implemented with any generic /// ECDSA signature. pub trait PreconstructedMessageVerifier { /// The type describing the parameters used to construct a message. diff --git a/crates/pallet-token-claims/src/types.rs b/crates/pallet-token-claims/src/types.rs index 286f0c98a..21da771ee 100644 --- a/crates/pallet-token-claims/src/types.rs +++ b/crates/pallet-token-claims/src/types.rs @@ -19,7 +19,7 @@ pub struct ClaimInfo { pub vesting: Option, } -/// The collection of parameters used for construcing a message that had to be signed. +/// The collection of parameters used for constructing a message that had to be signed. #[derive(PartialEq, Eq, RuntimeDebug)] pub struct EthereumSignatureMessageParams { /// The account ID of whoever is requesting the claim. diff --git a/crates/primitives-ethereum/src/lib.rs b/crates/primitives-ethereum/src/lib.rs index e8548ea6b..532729c82 100644 --- a/crates/primitives-ethereum/src/lib.rs +++ b/crates/primitives-ethereum/src/lib.rs @@ -1,4 +1,4 @@ -//! Common ethereum related priomitives. +//! Common ethereum related primitives. #![cfg_attr(not(feature = "std"), no_std)] From f7bcabc9195a4f0033c9f86f241bce335637e865 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Wed, 3 Aug 2022 22:07:37 +0400 Subject: [PATCH 40/41] Fix another typo Co-authored-by: Dmitry Lavrenov <39522748+dmitrylavrenov@users.noreply.github.com> --- crates/pallet-token-claims/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index ceb541d21..025228901 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -128,7 +128,7 @@ pub mod pallet { total_claimable_balance.checked_add(&info.balance).unwrap(); } - // Ensure that our pot account has exatly the right balance. + // Ensure that our pot account has exactly the right balance. let expected_pot_balance = >::minimum_balance() + total_claimable_balance; let pot_account_id = T::PotAccountId::get(); let actual_pot_balance = >::free_balance(&pot_account_id); From aa3764a97023e7c57423f6d5fed80bcc4b0be045 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Thu, 4 Aug 2022 00:54:03 +0400 Subject: [PATCH 41/41] Move the update_total_claimable_balance to the bottom --- crates/pallet-token-claims/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index 025228901..a594a2460 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -139,9 +139,6 @@ pub mod pallet { ); } - // Initialize the total claimable balance. - >::update_total_claimable_balance(); - // Check that the total claimable balance we computed matched the one declared in the // genesis configuration. if let Some(expected_total_claimable_balance) = self.total_claimable { @@ -152,6 +149,9 @@ pub mod pallet { ); } } + + // Initialize the total claimable balance. + >::update_total_claimable_balance(); } }