From d597717ab45787181388e0cfaac32addc34700fa Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov <39522748+dmitrylavrenov@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:22:04 +0300 Subject: [PATCH 1/3] Move `pallet-evm-balances` and `pallet-evm-system` from frontier, preserving history (#1420) * Sovereign accounts and EVM system layer (#94) (#99) * Sovereign accounts and EVM system layer (#94) * Sovereign accounts and EVM system layer (#86) * Frame evm system (#62) * Add initial impl of evm-system * Check account existence * Improve creation account logic * Add new line * Add default implementations * Add mock * Use DispatchResult instead of custom enums * Basic create account tests * Add simple tests with remove account and nonce update * Remove default implementations for OnNewAccount and OnKilledAccount * Add mock objects for OnNewAccount and OnKilledAccount * Use mock logic in tests * Some tests improvements * Add docs to tests * Check events in tests * Add default implementation for OnNewAccount and OnKilledAccount for empty tuple (#63) * Implement StoredMap for EvmSystem (#64) * Add try-runtime feature into `pallet-evm-system` (#67) Add try-runtime feature at pallet-evm-system * Use `sp_std` library to add FromStr trait for tests at `pallet-evm-system` (#68) Use sp_std library to add FromStr trait for tests at pallet-evm-system * Rename FullAccount into Account at `pallet-evm-system` (#69) Rename FullAccount into Account at pallet-evm-system * Fix `try_mutate_exists` implementation and add tests to check it (#70) * Fix try_mutate_exists logic * Add tests * Fix AccountData type at mock * Remove redundant mock expectations * Add comments for new tests * More explicitly handle (none,false) case * Rename some_data back to maybe_account_data * Add data changes for try_mutate_exists_fails_without_changes test * Add try_mutate_exists_account_not_created test * Add assert_noop to check state chages * Return success for try_mutate_exists_account_not_created test * Use workspace deps * Remove license * Implement missed AccountProvider for EvmSystem * Remove deprecated trait Store * Add Apache-2.0 license * Remove deprecated storage getter * Remove redundant brackets * Sovereign EVM balances layer (#100) * Sovereign EVM balances layer (#95) * Sovereign EVM balances layer (#87) * Frame EVM balances (#65) * Add initital frame balances structure * Add account data balances logic * Define main types * Add imbalances logic * Add DustCleaner * Implement balances related operations * Implement currencies for the pallet * Implement Inspect for the pallet * Make account_data mod private * Leave only free balance data for account * Support try-runtime features * Apply formatting * Fix comment * Add mock with evm, evm-system, evm-balances configs * Add basic setup test * Add fee deduction test * Add issuance_after_tip test * Add refunds_should_work test * Add refunds_and_priority_should_work test * Fix clippy in tests * Fix basec setup works test with evm balances checks * Remove redundant set block in tests * Add call_should_fail_with_priority_greater_than_max_fee test * Add call_should_succeed_with_priority_equal_to_max_fee test * Use EvmSystem as AccountProvider in tests * Add account_should_be_reaped test * Add deposit_into_existing test * Add balance_transfer_works test * Add slashing_balance_works test * Add withdraw_balance_works test * Add transferring_too_high_value_should_not_panic test * Rename test to transfer_works * Add basic tests for currency * Add burn and issue related tests * Add deposit_creating_works test * Add currency_make_free_balance_be test * Rename evm logic related tests * Fix comment * Rename slashing related test * Rename test with make free balance * Rename test with transferring too high value * Assert evm system account existence for currency_deposit_creating_works test * Add EvmSystem events check * Remove license * Use workspace dep * Fix mock * Remove deprecated trait Store * Remove deprecated storage getter * Add Apache-2.0 license * Fix tests * Apply required changes for fungible inspect trait * Support `Mutate`, `Unbalanced`, `Balanced` fungible related traits for `pallet-evm-balances` (#102) * Move currencies implementation into separate mod * [substrate=apply] Deprecate Currency; introduce holds and freezing into fungible traits #12951 * Apply new balances logic to currency trait implementation * Use DustRemoval over Credit instead of NegativeImbalance * Implement Balanced, Unbalanced, Mutate fungible traits for pallet * Revert using negative imbalance * Apply formatter * Fix withdraw_consequence method * Move currency related tests to separate mod * Add transfer_fails_funds_unavailable test to currency tests * Add transfer_works_full_balance test to currency tests * Fix slashing conditions * Add slash_works_full_balance test to currency tests * Add deposit_into_existing related fails tests * Add withdraw_works_full_balance test to currency tests * Add withdraw fails related tests to currency tests * Add basic fungible tests * Add reducable balance test to fungible tests * Add can deposit related tests * Fix can_withdraw logic * Add can_withdraw related tests * Add write_balance_works test * Add set_total_issuance_works test * Add decrease_balance related tests * Add increase_balance related tests * Add deactivate_reactivate_works test * Add mint_into related tests * Add burn_from related tests * Add shelve related tests * Add restored related tests * Add transfer related tests * Add balanced related tests * Undo formatting * Simplify reducible logic implementation * Simplify can_deposit implementation * Simplify can_withdraw implementation * Properly use semantics of total and free balances * Improve reducable_balance_works test * Fix test race conditions * Apply formatter for `pallet-evm-system` and `pallet-evm-balances` (#112) (#114) Apply formatter for pallet-evm-system and pallet-evm-balances * Use `execute_with_ext` in `pallet-evm-system` and `pallet-evm-balances` tests (#113) (#115) Use execute_with_ext in pallet-evm-system and pallet-evm-balances tests * Fix removing account logic at `pallet-evm-system` (#111) (#116) * Add assert_total_issuance_invariant helper * Add evm_system_removing_account_non_zero_balance test * Fix removing account logic * Undo redundant changes * Redesign fix usage * Undo try_mutate_exists_account_removed changes * Rename Exists account removal status into Remains * Improve docs * Apply renaming at tests * Use assert_storage_noop * Remove rusttoolchain * Add missing `on_created_account` logic usage for `inc_account_nonce` at `pallet-evm-system` (#147) * Add on_created_account for new account at inc_account_nonce * Add the corresponding event check at test * Properly handle `on_created_account` related to `inc_account_nonce` logic (#151) * Properly handle new account creation related to inc_account_nonce * Move outside mutate * Do the check inside mutate * Use mutate_exists * Use get_or_insert_default at mutate_exists Co-authored-by: MOZGIII * Remove redundant mut --------- Co-authored-by: MOZGIII * Properly manage contract accounts creation and removal logic (#140) * Introduce has_code to AccountInfo at pallet-evm-system * Improve AccountProvider related naming * Rework tests logic * Use had_code in tuple * Apply fmt * Set has_code to true in case account exists * Some renaming * Explicitly set has_code to true at create_contract_account_fails test * Introduce evm managed terminologies * Apply fmt * Improve comments in tests * Add create_evm_managed_account_works_already_exists test * Add more data checks in tests * Use assert_storage_noop * Improve docs for create_evm_managed_account call Co-authored-by: MOZGIII * Improve docs for remove_evm_managed_account call Co-authored-by: MOZGIII * Improve some docs * Properly unmark that account is not managed by EVM * Unify tests naming semantic * Apply fmt * Add migrations logic (#148) * Add STORAGE_VERSION usage * Add migrations structure * Implement OnRuntimeUpgrade for MigrationV0ToV1 * Some improvements * Implement logic for pre_upgrade and post_upgrade * Use reference in EvmProvider * Fix vec usage at try-runtime * Properly use translate * Use u64 for counting at try-runtime * Properly use old Account storage alias * Ensure storafe data is updated correctly * Some renaming * Renaming after another iteration of investigations * Return prev implementation * Dissallow removing account as pub method * Minor refactoring * Don't use managed_by_evm field * Remove storage version usage * Migrations renaming * Remove unused logic at migrations and leave todo for future implementations * Working migrations with todo * Properly use nonce related operations * Apply introduced logic in tests * Apply fmt * Tests refactoring * Minor improvements * Remove unused error * Use create_account at account provider * More info to comment * Remove migrations from current code changes * Use on_account_self_destruct naming --------- Co-authored-by: MOZGIII * Move to crates --------- Co-authored-by: MOZGIII --- crates/evm-balances/Cargo.toml | 50 + crates/evm-balances/src/account_data.rs | 66 ++ crates/evm-balances/src/imbalances.rs | 172 ++++ crates/evm-balances/src/impl_currency.rs | 300 ++++++ crates/evm-balances/src/impl_fungible.rs | 209 +++++ crates/evm-balances/src/lib.rs | 308 +++++++ crates/evm-balances/src/mock.rs | 229 +++++ crates/evm-balances/src/tests/currency.rs | 579 ++++++++++++ crates/evm-balances/src/tests/fungible.rs | 1018 +++++++++++++++++++++ crates/evm-balances/src/tests/mod.rs | 277 ++++++ crates/evm-system/Cargo.toml | 46 + crates/evm-system/src/lib.rs | 256 ++++++ crates/evm-system/src/mock.rs | 140 +++ crates/evm-system/src/tests.rs | 347 +++++++ 14 files changed, 3997 insertions(+) create mode 100644 crates/evm-balances/Cargo.toml create mode 100644 crates/evm-balances/src/account_data.rs create mode 100644 crates/evm-balances/src/imbalances.rs create mode 100644 crates/evm-balances/src/impl_currency.rs create mode 100644 crates/evm-balances/src/impl_fungible.rs create mode 100644 crates/evm-balances/src/lib.rs create mode 100644 crates/evm-balances/src/mock.rs create mode 100644 crates/evm-balances/src/tests/currency.rs create mode 100644 crates/evm-balances/src/tests/fungible.rs create mode 100644 crates/evm-balances/src/tests/mod.rs create mode 100644 crates/evm-system/Cargo.toml create mode 100644 crates/evm-system/src/lib.rs create mode 100644 crates/evm-system/src/mock.rs create mode 100644 crates/evm-system/src/tests.rs diff --git a/crates/evm-balances/Cargo.toml b/crates/evm-balances/Cargo.toml new file mode 100644 index 000000000..a0b49cb61 --- /dev/null +++ b/crates/evm-balances/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pallet-evm-balances" +version = "1.0.0-dev" +license = "Apache-2.0" +description = "FRAME EVM BALANCES pallet." +edition = { workspace = true } +repository = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { workspace = true, default-features = false } +scale-codec = { package = "parity-scale-codec", workspace = true } +scale-info = { workspace = true } +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +fp-evm = { workspace = true } +pallet-evm = { workspace = true } +pallet-evm-system = { workspace = true } +pallet-timestamp = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } + +[features] +default = ["std"] +std = [ + "log/std", + "scale-codec/std", + "scale-info/std", + # Substrate + "frame-support/std", + "frame-system/std", + "pallet-timestamp/std", + "sp-runtime/std", + "sp-std/std", + # Frontier + "fp-evm/std", + "pallet-evm/std", + "pallet-evm-system/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/crates/evm-balances/src/account_data.rs b/crates/evm-balances/src/account_data.rs new file mode 100644 index 000000000..4bd3fffdd --- /dev/null +++ b/crates/evm-balances/src/account_data.rs @@ -0,0 +1,66 @@ +//! Account balances logic. + +use frame_support::traits::WithdrawReasons; + +use super::*; + +/// All balance information for an account. +#[derive( + Encode, + Decode, + Clone, + PartialEq, + Eq, + Default, + RuntimeDebug, + MaxEncodedLen, + TypeInfo +)] +pub struct AccountData { + /// Non-reserved part of the balance. There may still be restrictions on this, but it is the + /// total pool what may in principle be transferred, reserved and used for tipping. + /// + /// This is the only balance that matters in terms of most operations on tokens. It + /// alone is used to determine the balance when in the contract execution environment. + pub free: Balance, +} + +impl AccountData { + /// The total balance in this account. + pub(crate) fn total(&self) -> Balance { + self.free + } +} + +/// Simplified reasons for withdrawing balance. +#[derive( + Encode, + Decode, + Clone, + Copy, + PartialEq, + Eq, + RuntimeDebug, + MaxEncodedLen, + TypeInfo +)] +pub enum Reasons { + /// Paying system transaction fees. + Fee = 0, + /// Any reason other than paying system transaction fees. + Misc = 1, + /// Any reason at all. + All = 2, +} + +impl From for Reasons { + fn from(r: WithdrawReasons) -> Reasons { + if r == WithdrawReasons::TRANSACTION_PAYMENT { + Reasons::Fee + } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { + Reasons::All + } else { + Reasons::Misc + } + } +} diff --git a/crates/evm-balances/src/imbalances.rs b/crates/evm-balances/src/imbalances.rs new file mode 100644 index 000000000..1a5c619ab --- /dev/null +++ b/crates/evm-balances/src/imbalances.rs @@ -0,0 +1,172 @@ +//! Imbalances implementation. + +use frame_support::traits::{SameOrOther, TryDrop}; +use sp_std::{cmp::Ordering, mem}; + +use super::*; + +/// Opaque, move-only struct with private fields that serves as a token denoting that +/// funds have been created without any equal and opposite accounting. +#[must_use] +#[derive(RuntimeDebug, PartialEq, Eq)] +pub struct PositiveImbalance, I: 'static = ()>(T::Balance); + +impl, I: 'static> PositiveImbalance { + /// Create a new positive imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + PositiveImbalance(amount) + } +} + +/// Opaque, move-only struct with private fields that serves as a token denoting that +/// funds have been destroyed without any equal and opposite accounting. +#[must_use] +#[derive(RuntimeDebug, PartialEq, Eq)] +pub struct NegativeImbalance, I: 'static = ()>(T::Balance); + +impl, I: 'static> NegativeImbalance { + /// Create a new negative imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + NegativeImbalance(amount) + } +} + +impl, I: 'static> TryDrop for PositiveImbalance { + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } +} + +impl, I: 'static> Default for PositiveImbalance { + fn default() -> Self { + Self::zero() + } +} + +impl, I: 'static> Imbalance for PositiveImbalance { + type Opposite = NegativeImbalance; + + fn zero() -> Self { + Self(Zero::zero()) + } + + fn drop_zero(self) -> result::Result<(), Self> { + if self.0.is_zero() { + Ok(()) + } else { + Err(self) + } + } + + fn split(self, amount: T::Balance) -> (Self, Self) { + let first = self.0.min(amount); + let second = self.0 - first; + + mem::forget(self); + (Self(first), Self(second)) + } + + fn merge(mut self, other: Self) -> Self { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + + self + } + + fn subsume(&mut self, other: Self) { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + } + + fn offset(self, other: Self::Opposite) -> SameOrOther { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + match a.cmp(&b) { + Ordering::Greater => SameOrOther::Same(Self(a - b)), + Ordering::Less => SameOrOther::Other(NegativeImbalance::new(b - a)), + Ordering::Equal => SameOrOther::None, + } + } + + fn peek(&self) -> T::Balance { + self.0 + } +} + +impl, I: 'static> TryDrop for NegativeImbalance { + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } +} + +impl, I: 'static> Default for NegativeImbalance { + fn default() -> Self { + Self::zero() + } +} + +impl, I: 'static> Imbalance for NegativeImbalance { + type Opposite = PositiveImbalance; + + fn zero() -> Self { + Self(Zero::zero()) + } + + fn drop_zero(self) -> result::Result<(), Self> { + if self.0.is_zero() { + Ok(()) + } else { + Err(self) + } + } + + fn split(self, amount: T::Balance) -> (Self, Self) { + let first = self.0.min(amount); + let second = self.0 - first; + + mem::forget(self); + (Self(first), Self(second)) + } + + fn merge(mut self, other: Self) -> Self { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + + self + } + + fn subsume(&mut self, other: Self) { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + } + + fn offset(self, other: Self::Opposite) -> SameOrOther { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + match a.cmp(&b) { + Ordering::Greater => SameOrOther::Same(Self(a - b)), + Ordering::Less => SameOrOther::Other(PositiveImbalance::new(b - a)), + Ordering::Equal => SameOrOther::None, + } + } + + fn peek(&self) -> T::Balance { + self.0 + } +} + +impl, I: 'static> Drop for PositiveImbalance { + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + TotalIssuance::::mutate(|v| *v = v.saturating_add(self.0)); + } +} + +impl, I: 'static> Drop for NegativeImbalance { + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + TotalIssuance::::mutate(|v| *v = v.saturating_sub(self.0)); + } +} diff --git a/crates/evm-balances/src/impl_currency.rs b/crates/evm-balances/src/impl_currency.rs new file mode 100644 index 000000000..9bb70f92e --- /dev/null +++ b/crates/evm-balances/src/impl_currency.rs @@ -0,0 +1,300 @@ +//! Currency trait implementation. + +use frame_support::traits::Currency; + +use super::*; + +impl, I: 'static> Currency<>::AccountId> for Pallet +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + type Balance = T::Balance; + type PositiveImbalance = PositiveImbalance; + type NegativeImbalance = NegativeImbalance; + + fn total_balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).total() + } + + fn can_slash(who: &>::AccountId, value: Self::Balance) -> bool { + if value.is_zero() { + return true; + } + Self::free_balance(who) >= value + } + + fn total_issuance() -> Self::Balance { + TotalIssuance::::get() + } + + fn active_issuance() -> Self::Balance { + >::active_issuance() + } + + fn deactivate(amount: Self::Balance) { + >::deactivate(amount); + } + + fn reactivate(amount: Self::Balance) { + >::reactivate(amount); + } + + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + + // Burn funds from the total issuance, returning a positive imbalance for the amount burned. + // Is a no-op if amount to be burned is zero. + fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { + if amount.is_zero() { + return PositiveImbalance::zero(); + } + >::mutate(|issued| { + *issued = issued.checked_sub(&amount).unwrap_or_else(|| { + amount = *issued; + Zero::zero() + }); + }); + PositiveImbalance::new(amount) + } + + // Create new funds into the total issuance, returning a negative imbalance + // for the amount issued. + // Is a no-op if amount to be issued it zero. + fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { + if amount.is_zero() { + return NegativeImbalance::zero(); + } + >::mutate(|issued| { + *issued = issued.checked_add(&amount).unwrap_or_else(|| { + amount = Self::Balance::max_value() - *issued; + Self::Balance::max_value() + }) + }); + NegativeImbalance::new(amount) + } + + fn free_balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).free + } + + // We don't have any existing withdrawal restrictions like locked and reserved balance. + // Is a no-op if amount to be withdrawn is zero. + fn ensure_can_withdraw( + _who: &>::AccountId, + _amount: T::Balance, + _reasons: WithdrawReasons, + _new_balance: T::Balance, + ) -> DispatchResult { + Ok(()) + } + + // Transfer some free balance from `transactor` to `dest`, respecting existence requirements. + // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`. + fn transfer( + transactor: &>::AccountId, + dest: &>::AccountId, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult { + if value.is_zero() || transactor == dest { + return Ok(()); + } + let keep_alive = match existence_requirement { + ExistenceRequirement::KeepAlive => Preservation::Preserve, + ExistenceRequirement::AllowDeath => Preservation::Expendable, + }; + >::transfer(transactor, dest, value, keep_alive)?; + Ok(()) + } + + /// Slash a target account `who`, returning the negative imbalance created and any left over + /// amount that could not be slashed. + /// + /// Is a no-op if `value` to be slashed is zero or the account does not exist. + /// + /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn + /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid + /// having to draw from reserved funds, however we err on the side of punishment if things are + /// inconsistent or `can_slash` wasn't used appropriately. + fn slash( + who: &>::AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()); + } + + if Self::total_balance(who).is_zero() { + return (NegativeImbalance::zero(), value); + } + + let result = match Self::try_mutate_account_handling_dust( + who, + |account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { + // Best value is the most amount we can slash following liveness rules. + let actual = value.min(account.free); + account.free.saturating_reduce(actual); + let remaining = value.saturating_sub(actual); + Ok((NegativeImbalance::new(actual), remaining)) + }, + ) { + Ok((imbalance, remaining)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(remaining), + }); + (imbalance, remaining) + } + Err(_) => (Self::NegativeImbalance::zero(), value), + }; + result + } + + /// Deposit some `value` into the free balance of an existing target account `who`. + /// + /// Is a no-op if the `value` to be deposited is zero. + fn deposit_into_existing( + who: &>::AccountId, + value: Self::Balance, + ) -> Result { + if value.is_zero() { + return Ok(PositiveImbalance::zero()); + } + + Self::try_mutate_account_handling_dust( + who, + |account, is_new| -> Result { + ensure!(!is_new, Error::::DeadAccount); + account.free = account + .free + .checked_add(&value) + .ok_or(ArithmeticError::Overflow)?; + Self::deposit_event(Event::Deposit { + who: who.clone(), + amount: value, + }); + Ok(PositiveImbalance::new(value)) + }, + ) + } + + /// Deposit some `value` into the free balance of `who`, possibly creating a new account. + /// + /// This function is a no-op if: + /// - the `value` to be deposited is zero; or + /// - the `value` to be deposited is less than the required ED and the account does not yet + /// exist; or + /// - the deposit would necessitate the account to exist and there are no provider references; + /// or + /// - `value` is so large it would cause the balance of `who` to overflow. + fn deposit_creating( + who: &>::AccountId, + value: Self::Balance, + ) -> Self::PositiveImbalance { + if value.is_zero() { + return Self::PositiveImbalance::zero(); + } + + Self::try_mutate_account_handling_dust( + who, + |account, is_new| -> Result { + let ed = T::ExistentialDeposit::get(); + ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); + + // defensive only: overflow should never happen, however in case it does, then this + // operation is a no-op. + account.free = match account.free.checked_add(&value) { + Some(x) => x, + None => return Ok(Self::PositiveImbalance::zero()), + }; + + Self::deposit_event(Event::Deposit { + who: who.clone(), + amount: value, + }); + Ok(PositiveImbalance::new(value)) + }, + ) + .unwrap_or_else(|_| Self::PositiveImbalance::zero()) + } + + /// Withdraw some free balance from an account, respecting existence requirements. + /// + /// Is a no-op if value to be withdrawn is zero. + fn withdraw( + who: &>::AccountId, + value: Self::Balance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> result::Result { + if value.is_zero() { + return Ok(NegativeImbalance::zero()); + } + + Self::try_mutate_account_handling_dust( + who, + |account, _| -> Result { + let new_free_account = account + .free + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + + // bail if we need to keep the account alive and this would kill it. + let ed = T::ExistentialDeposit::get(); + let would_be_dead = new_free_account < ed; + let would_kill = would_be_dead && account.free >= ed; + ensure!( + liveness == ExistenceRequirement::AllowDeath || !would_kill, + Error::::Expendability + ); + + Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; + + account.free = new_free_account; + + Self::deposit_event(Event::Withdraw { + who: who.clone(), + amount: value, + }); + Ok(NegativeImbalance::new(value)) + }, + ) + } + + /// Force the new free balance of a target account `who` to some new value `balance`. + fn make_free_balance_be( + who: &>::AccountId, + value: Self::Balance, + ) -> SignedImbalance { + Self::try_mutate_account_handling_dust( + who, + |account, + is_new| + -> Result, DispatchError> { + let ed = T::ExistentialDeposit::get(); + // If we're attempting to set an existing account to less than ED, then + // bypass the entire operation. It's a no-op if you follow it through, but + // since this is an instance where we might account for a negative imbalance + // (in the dust cleaner of set_account) before we account for its actual + // equal and opposite cause (returned as an Imbalance), then in the + // instance that there's no other accounts on the system at all, we might + // underflow the issuance and our arithmetic will be off. + ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); + + let imbalance = if account.free <= value { + SignedImbalance::Positive(PositiveImbalance::new(value - account.free)) + } else { + SignedImbalance::Negative(NegativeImbalance::new(account.free - value)) + }; + account.free = value; + Self::deposit_event(Event::BalanceSet { + who: who.clone(), + free: account.free, + }); + Ok(imbalance) + }, + ) + .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) + } +} diff --git a/crates/evm-balances/src/impl_fungible.rs b/crates/evm-balances/src/impl_fungible.rs new file mode 100644 index 000000000..5c86426e3 --- /dev/null +++ b/crates/evm-balances/src/impl_fungible.rs @@ -0,0 +1,209 @@ +//! Fungible related traits implementation. + +use super::*; + +impl, I: 'static> fungible::Inspect<>::AccountId> for Pallet { + type Balance = T::Balance; + + fn total_issuance() -> Self::Balance { + TotalIssuance::::get() + } + + fn active_issuance() -> Self::Balance { + TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) + } + + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + + fn total_balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).total() + } + + fn balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).free + } + + fn reducible_balance( + who: &>::AccountId, + preservation: Preservation, + _force: Fortitude, + ) -> Self::Balance { + let a = Self::account(who); + let untouchable = match preservation { + Preservation::Expendable => Zero::zero(), + _ => T::ExistentialDeposit::get(), + }; + a.free.saturating_sub(untouchable) + } + + fn can_deposit( + who: &>::AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + if amount.is_zero() { + return DepositConsequence::Success; + } + + if provenance == Provenance::Minted + && TotalIssuance::::get().checked_add(&amount).is_none() + { + return DepositConsequence::Overflow; + } + + let account = Self::account(who); + match account.free.checked_add(&amount) { + None => return DepositConsequence::Overflow, + Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum, + Some(x) => x, + }; + + // NOTE: We assume that we are a provider, so don't need to do any checks in the + // case of account creation. + + DepositConsequence::Success + } + + fn can_withdraw( + who: &>::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + if amount.is_zero() { + return WithdrawConsequence::Success; + } + + if TotalIssuance::::get().checked_sub(&amount).is_none() { + return WithdrawConsequence::Underflow; + } + + let account = Self::account(who); + let new_free_balance = match account.free.checked_sub(&amount) { + Some(x) => x, + None => return WithdrawConsequence::BalanceLow, + }; + + // Provider restriction - total account balance cannot be reduced to zero if it cannot + // sustain the loss of a provider reference. + // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes, + // then this will need to adapt accordingly. + let ed = T::ExistentialDeposit::get(); + if new_free_balance < ed { + return WithdrawConsequence::ReducedToZero(new_free_balance); + } + + WithdrawConsequence::Success + } +} + +impl, I: 'static> fungible::Unbalanced<>::AccountId> for Pallet { + fn handle_dust(dust: fungible::Dust<>::AccountId, Self>) { + T::DustRemoval::on_unbalanced(NegativeImbalance::new(dust.0)); + } + + fn write_balance( + who: &>::AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + let max_reduction = >::reducible_balance( + who, + Preservation::Expendable, + Fortitude::Force, + ); + let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult { + // Make sure the reduction (if there is one) is no more than the maximum allowed. + let reduction = account.free.saturating_sub(amount); + ensure!( + reduction <= max_reduction, + Error::::InsufficientBalance + ); + + account.free = amount; + Ok(()) + })?; + result?; + Ok(maybe_dust) + } + + fn set_total_issuance(amount: Self::Balance) { + TotalIssuance::::mutate(|t| *t = amount); + } + + fn deactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_accrue(amount)); + } + + fn reactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); + } +} + +impl, I: 'static> fungible::Mutate<>::AccountId> for Pallet { + fn done_mint_into(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Minted { + who: who.clone(), + amount, + }); + } + + fn done_burn_from(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Burned { + who: who.clone(), + amount, + }); + } + + fn done_shelve(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Suspended { + who: who.clone(), + amount, + }); + } + + fn done_restore(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Restored { + who: who.clone(), + amount, + }); + } + + fn done_transfer( + source: &>::AccountId, + dest: &>::AccountId, + amount: Self::Balance, + ) { + Self::deposit_event(Event::Transfer { + from: source.clone(), + to: dest.clone(), + amount, + }); + } +} + +impl, I: 'static> fungible::Balanced<>::AccountId> for Pallet { + type OnDropCredit = fungible::DecreaseIssuance<>::AccountId, Self>; + type OnDropDebt = fungible::IncreaseIssuance<>::AccountId, Self>; + + fn done_deposit(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Deposit { + who: who.clone(), + amount, + }); + } + + fn done_withdraw(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Withdraw { + who: who.clone(), + amount, + }); + } + + fn done_issue(amount: Self::Balance) { + Self::deposit_event(Event::Issued { amount }); + } + + fn done_rescind(amount: Self::Balance) { + Self::deposit_event(Event::Rescinded { amount }); + } +} diff --git a/crates/evm-balances/src/lib.rs b/crates/evm-balances/src/lib.rs new file mode 100644 index 000000000..5cdee7374 --- /dev/null +++ b/crates/evm-balances/src/lib.rs @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! # EVM Balances Pallet. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + ensure, + traits::{ + fungible, + tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence}, + ExistenceRequirement, Get, Imbalance, OnUnbalanced, SignedImbalance, StorageVersion, + StoredMap, WithdrawReasons, + }, +}; +use scale_codec::{Codec, Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Zero}, + ArithmeticError, DispatchError, DispatchResult, RuntimeDebug, Saturating, +}; +use sp_std::{fmt::Debug, result}; + +mod impl_currency; +mod impl_fungible; + +mod account_data; +pub use account_data::{AccountData, Reasons}; + +mod imbalances; +pub use imbalances::{NegativeImbalance, PositiveImbalance}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub use pallet::*; + +/// The current storage version. +const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use sp_runtime::{ + traits::{AtLeast32BitUnsigned, MaybeDisplay}, + FixedPointOperand, + }; + use sp_std::fmt::Debug; + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// The user account identifier type. + type AccountId: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + MaybeDisplay + + Ord + + MaxEncodedLen; + + /// The balance of an account. + type Balance: Parameter + + Member + + AtLeast32BitUnsigned + + Codec + + Default + + Copy + + MaybeSerializeDeserialize + + Debug + + MaxEncodedLen + + TypeInfo + + FixedPointOperand; + + /// The minimum amount required to keep an account open. + #[pallet::constant] + type ExistentialDeposit: Get; + + /// The means of storing the balances of an account. + type AccountStore: StoredMap<>::AccountId, AccountData>; + + /// Handler for the unbalanced reduction when removing a dust account. + type DustRemoval: OnUnbalanced>; + } + + /// The total units issued. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; + + /// The total units of outstanding deactivated balance. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type InactiveIssuance, I: 'static = ()> = + StorageValue<_, T::Balance, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// An account was created with some free balance. + Endowed { + account: >::AccountId, + free_balance: T::Balance, + }, + /// An account was removed whose balance was non-zero but below ExistentialDeposit, + /// resulting in an outright loss. + DustLost { + account: >::AccountId, + amount: T::Balance, + }, + /// Transfer succeeded. + Transfer { + from: >::AccountId, + to: >::AccountId, + amount: T::Balance, + }, + /// A balance was set by root. + BalanceSet { + who: >::AccountId, + free: T::Balance, + }, + /// Some amount was deposited (e.g. for transaction fees). + Deposit { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was withdrawn from the account (e.g. for transaction fees). + Withdraw { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was removed from the account (e.g. for misbehavior). + Slashed { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was minted into an account. + Minted { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was burned from an account. + Burned { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was suspended from an account (it can be restored later). + Suspended { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was restored into an account. + Restored { + who: >::AccountId, + amount: T::Balance, + }, + /// Total issuance was increased by `amount`, creating a credit to be balanced. + Issued { amount: T::Balance }, + /// Total issuance was decreased by `amount`, creating a debt to be balanced. + Rescinded { amount: T::Balance }, + } + + #[pallet::error] + pub enum Error { + /// Account liquidity restrictions prevent withdrawal. + LiquidityRestrictions, + /// Balance too low to send value. + InsufficientBalance, + /// Value too low to create account due to existential deposit. + ExistentialDeposit, + /// Transfer/payment would kill account. + Expendability, + /// Beneficiary account must pre-exist. + DeadAccount, + } +} + +impl, I: 'static> Pallet { + /// Get the free balance of an account. + pub fn free_balance( + who: impl sp_std::borrow::Borrow<>::AccountId>, + ) -> T::Balance { + Self::account(who.borrow()).free + } + + /// Get all data information for an account. + fn account(who: &>::AccountId) -> AccountData { + T::AccountStore::get(who) + } + + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. + /// + /// It returns the result from the closure. Any dust is handled through the low-level + /// `fungible::Unbalanced` trap-door for legacy dust management. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + pub(crate) fn try_mutate_account_handling_dust>( + who: &>::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> Result { + let (r, maybe_dust) = Self::try_mutate_account(who, f)?; + if let Some(dust) = maybe_dust { + >::handle_raw_dust(dust); + } + Ok(r) + } + + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. + /// + /// It returns both the result from the closure, and an optional amount of dust + /// which should be handled once it is known that all nested mutates that could affect + /// storage items what the dust handler touches have completed. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + pub(crate) fn mutate_account( + who: &>::AccountId, + f: impl FnOnce(&mut AccountData) -> R, + ) -> Result<(R, Option), DispatchError> { + Self::try_mutate_account(who, |a, _| -> Result { Ok(f(a)) }) + } + + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the + /// result of `f` is an `Err`. + /// + /// It returns both the result from the closure, and an optional amount of dust + /// which should be handled once it is known that all nested mutates that could affect + /// storage items what the dust handler touches have completed. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + fn try_mutate_account>( + who: &>::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> Result<(R, Option), E> { + let result = T::AccountStore::try_mutate_exists(who, |maybe_account| { + let is_new = maybe_account.is_none(); + let mut account = maybe_account.take().unwrap_or_default(); + f(&mut account, is_new).map(move |result| { + let maybe_endowed = if is_new { Some(account.free) } else { None }; + + // Handle any steps needed after mutating an account. + // + // This includes DustRemoval unbalancing, in the case than the `new` account's total + // balance is non-zero but below ED. + // + // Updates `maybe_account` to `Some` iff the account has sufficient balance. + // Evaluates `maybe_dust`, which is `Some` containing the dust to be dropped, iff + // some dust should be dropped. + // + // We should never be dropping if reserved is non-zero. Reserved being non-zero + // should imply that we have a consumer ref, so this is economically safe. + let maybe_dust = if account.free < T::ExistentialDeposit::get() { + if account.free.is_zero() { + None + } else { + Some(account.free) + } + } else { + assert!(account.free.is_zero() || account.free >= T::ExistentialDeposit::get()); + *maybe_account = Some(account); + None + }; + + (maybe_endowed, maybe_dust, result) + }) + }); + result.map(|(maybe_endowed, maybe_dust, result)| { + if let Some(endowed) = maybe_endowed { + Self::deposit_event(Event::Endowed { + account: who.clone(), + free_balance: endowed, + }); + } + if let Some(amount) = maybe_dust { + Pallet::::deposit_event(Event::DustLost { + account: who.clone(), + amount, + }); + } + (result, maybe_dust) + }) + } +} diff --git a/crates/evm-balances/src/mock.rs b/crates/evm-balances/src/mock.rs new file mode 100644 index 000000000..ed44d8b16 --- /dev/null +++ b/crates/evm-balances/src/mock.rs @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test mock for unit tests. + +use std::collections::BTreeMap; + +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, FindAuthor}, + weights::Weight, +}; +use pallet_evm::{EnsureAddressNever, FixedGasWeightMapping, IdentityAddressMapping}; +use sp_core::{H160, H256, U256}; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, ConsensusEngineId, +}; +use sp_std::{boxed::Box, prelude::*, str::FromStr}; + +use crate::{self as pallet_evm_balances, *}; + +pub(crate) const INIT_BALANCE: u64 = 10_000_000_000_000_000; + +pub(crate) fn alice() -> H160 { + H160::from_str("1000000000000000000000000000000000000000").unwrap() +} + +pub(crate) fn bob() -> H160 { + H160::from_str("2000000000000000000000000000000000000000").unwrap() +} + +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, + Timestamp: pallet_timestamp, + EvmSystem: pallet_evm_system, + EvmBalances: pallet_evm_balances, + EVM: pallet_evm, + } +} + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = generic::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_evm_system::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AccountId = H160; + type Index = u64; + type AccountData = AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} + +impl pallet_evm_balances::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AccountId = H160; + type Balance = u64; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = EvmSystem; + type DustRemoval = (); +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +pub struct FixedGasPrice; + +impl pallet_evm::FeeCalculator for FixedGasPrice { + fn min_gas_price() -> (U256, Weight) { + // Return some meaningful gas price and weight + (1_000_000_000u128.into(), Weight::from_parts(7u64, 0)) + } +} + +pub struct FindAuthorTruncated; + +impl FindAuthor for FindAuthorTruncated { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(H160::from_str("1234500000000000000000000000000000000000").unwrap()) + } +} + +const BLOCK_GAS_LIMIT: u64 = 150_000_000; +const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; + +parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); + pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); +} + +impl pallet_evm::Config for Test { + type AccountProvider = EvmSystem; + type FeeCalculator = FixedGasPrice; + type GasWeightMapping = FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type CallOrigin = + EnsureAddressNever<::AccountId>; + type WithdrawOrigin = + EnsureAddressNever<::AccountId>; + type AddressMapping = IdentityAddressMapping; + type Currency = EvmBalances; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type ChainId = (); + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = FindAuthorTruncated; + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type Timestamp = Timestamp; + type WeightInfo = (); +} + +/// Build test externalities from the custom genesis. +/// Using this call requires manual assertions on the genesis init logic. +pub fn new_test_ext() -> sp_io::TestExternalities { + // Build genesis. + let config = GenesisConfig { + evm: EVMConfig { + accounts: { + let mut map = BTreeMap::new(); + let init_genesis_account = fp_evm::GenesisAccount { + balance: INIT_BALANCE.into(), + code: Default::default(), + nonce: Default::default(), + storage: Default::default(), + }; + map.insert(alice(), init_genesis_account.clone()); + map.insert(bob(), init_genesis_account); + map + }, + }, + ..Default::default() + }; + let storage = config.build_storage().unwrap(); + + // Make test externalities from the storage. + storage.into() +} + +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(), + } +} + +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/evm-balances/src/tests/currency.rs b/crates/evm-balances/src/tests/currency.rs new file mode 100644 index 000000000..c0b499b21 --- /dev/null +++ b/crates/evm-balances/src/tests/currency.rs @@ -0,0 +1,579 @@ +//! Tests regarding the functionality of the `Currency` trait set implementations. + +use frame_support::{assert_noop, assert_ok, traits::Currency}; +use sp_core::H160; +use sp_runtime::TokenError; +use sp_std::str::FromStr; + +use crate::{mock::*, tests::assert_total_issuance_invariant, *}; + +#[test] +fn total_issuance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the total issuance value. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + }); +} + +#[test] +fn total_balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the total balance value. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + }); +} + +#[test] +fn free_balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the free balance value. + assert_eq!(EvmBalances::free_balance(&alice()), INIT_BALANCE); + }); +} + +#[test] +fn can_slash_works() { + new_test_ext().execute_with_ext(|_| { + // Check possible slashing if slashing value is less than current balance. + assert!(EvmBalances::can_slash(&alice(), 100)); + + // Check possible slashing if slashing value is equal to current balance. + assert!(EvmBalances::can_slash(&alice(), INIT_BALANCE)); + + // Check slashing restriction if slashing value that is greater than current balance. + assert!(!EvmBalances::can_slash(&alice(), INIT_BALANCE + 1)); + }); +} + +#[test] +fn active_issuance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the active issuance value. + assert_eq!(EvmBalances::active_issuance(), 2 * INIT_BALANCE); + }); +} + +#[test] +fn deactivate_reactivate_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(>::get(), 0); + + // Deactivate some balance. + EvmBalances::deactivate(100); + // Assert state changes. + assert_eq!(>::get(), 100); + + // Reactivate some balance. + EvmBalances::reactivate(40); + // Assert state changes. + assert_eq!(>::get(), 60); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn minimum_balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the minimum balance value. + assert_eq!(EvmBalances::minimum_balance(), 1); + }); +} + +#[test] +fn burn_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Burn some balance. + let imbalance = EvmBalances::burn(100); + + // Assert state changes. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE - 100); + drop(imbalance); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn issue_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Issue some balance. + let imbalance = EvmBalances::issue(100); + + // Assert state changes. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE + 100); + drop(imbalance); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn transfer_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = 100; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + ExistenceRequirement::KeepAlive + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - transfered_amount + ); + assert_eq!( + EvmBalances::total_balance(&bob()), + INIT_BALANCE + transfered_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Transfer { + from: alice(), + to: bob(), + amount: transfered_amount, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn transfer_works_full_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + ExistenceRequirement::AllowDeath + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - transfered_amount + ); + assert_eq!( + EvmBalances::total_balance(&bob()), + INIT_BALANCE + transfered_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Transfer { + from: alice(), + to: bob(), + amount: transfered_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn transfer_fails_funds_unavailable() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE + 1; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + ExistenceRequirement::KeepAlive + ), + TokenError::FundsUnavailable + ); + }); +} + +#[test] +fn transfer_fails_not_expendable() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + ExistenceRequirement::KeepAlive + ), + TokenError::NotExpendable + ); + }); +} + +#[test] +fn slash_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let slashed_amount = 1000; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert!(EvmBalances::slash(&alice(), slashed_amount).1.is_zero()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - slashed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Slashed { + who: alice(), + amount: slashed_amount, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn slash_works_full_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let slashed_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert!(EvmBalances::slash(&alice(), slashed_amount).1.is_zero()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - slashed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Slashed { + who: alice(), + amount: slashed_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn deposit_into_existing_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let deposited_amount = 10; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let imbalance = EvmBalances::deposit_into_existing(&alice(), deposited_amount).unwrap(); + drop(imbalance); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + deposited_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Deposit { + who: alice(), + amount: deposited_amount, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn deposit_into_existing_fails_overflow() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let deposited_amount = u64::MAX; + + // Invoke the function under test. + assert_noop!( + EvmBalances::deposit_into_existing(&alice(), deposited_amount), + ArithmeticError::Overflow + ); + }); +} + +#[test] +fn deposit_into_existing_fails_dead_account() { + new_test_ext().execute_with_ext(|_| { + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&charlie), 0); + + let deposited_amount = 10; + + // Invoke the function under test. + assert_noop!( + EvmBalances::deposit_into_existing(&charlie, deposited_amount), + Error::::DeadAccount + ); + }); +} + +#[test] +fn deposit_creating_works() { + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let deposited_amount = 10; + assert!(!EvmSystem::account_exists(&charlie)); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let _ = EvmBalances::deposit_creating(&charlie, deposited_amount); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&charlie), deposited_amount); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Deposit { + who: charlie, + amount: deposited_amount, + })); + assert!(EvmSystem::account_exists(&charlie)); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::NewAccount { account: charlie }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn withdraw_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = 1000; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let imbalance = EvmBalances::withdraw( + &alice(), + withdrawed_amount, + WithdrawReasons::FEE, + ExistenceRequirement::KeepAlive, + ) + .unwrap(); + drop(imbalance); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - withdrawed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { + who: alice(), + amount: withdrawed_amount, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn withdraw_works_full_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let imbalance = EvmBalances::withdraw( + &alice(), + withdrawed_amount, + WithdrawReasons::FEE, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + drop(imbalance); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - withdrawed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { + who: alice(), + amount: withdrawed_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn withdraw_fails_insufficient_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_noop!( + EvmBalances::withdraw( + &alice(), + withdrawed_amount, + WithdrawReasons::TRANSFER, + ExistenceRequirement::AllowDeath + ), + Error::::InsufficientBalance + ); + }); +} + +#[test] +fn withdraw_fails_expendability() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = INIT_BALANCE; + + // Invoke the function under test. + assert_noop!( + EvmBalances::withdraw( + &alice(), + withdrawed_amount, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive + ), + Error::::Expendability + ); + }); +} + +#[test] +fn make_free_balance_be_works() { + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let made_free_balance = 100; + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&charlie), 0); + + // Invoke the function under test. + let _ = EvmBalances::make_free_balance_be(&charlie, made_free_balance); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&charlie), made_free_balance); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn evm_system_account_should_be_reaped() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert!(EvmSystem::account_exists(&bob())); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &bob(), + &alice(), + INIT_BALANCE, + ExistenceRequirement::AllowDeath + )); + + // Assert state changes. + assert_eq!(EvmBalances::free_balance(&bob()), 0); + assert!(!EvmSystem::account_exists(&bob())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: bob() }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn transferring_too_high_value_should_not_panic() { + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let eve = H160::from_str("1000000000000000000000000000000000000004").unwrap(); + EvmBalances::make_free_balance_be(&charlie, u64::MAX); + EvmBalances::make_free_balance_be(&eve, 1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer(&charlie, &eve, u64::MAX, ExistenceRequirement::AllowDeath), + ArithmeticError::Overflow, + ); + }); +} diff --git a/crates/evm-balances/src/tests/fungible.rs b/crates/evm-balances/src/tests/fungible.rs new file mode 100644 index 000000000..fa587a885 --- /dev/null +++ b/crates/evm-balances/src/tests/fungible.rs @@ -0,0 +1,1018 @@ +//! Tests regarding the functionality of the fungible trait set implementations. + +use frame_support::{ + assert_noop, assert_ok, + traits::{ + fungible::{Balanced, Inspect, Mutate, Unbalanced}, + tokens::Precision, + }, +}; +use sp_core::H160; +use sp_runtime::TokenError; +use sp_std::str::FromStr; + +use crate::{mock::*, tests::assert_total_issuance_invariant, *}; + +#[test] +fn total_issuance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the total issuance value. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + }); +} + +#[test] +fn active_issuance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the active issuance value. + assert_eq!(EvmBalances::active_issuance(), 2 * INIT_BALANCE); + }); +} + +#[test] +fn minimum_balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the minimum balance value. + assert_eq!(EvmBalances::minimum_balance(), 1); + }); +} + +#[test] +fn total_balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the total balance value. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + }); +} + +#[test] +fn balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the balance value. + assert_eq!(EvmBalances::balance(&alice()), INIT_BALANCE); + }); +} + +#[test] +fn reducable_balance_works() { + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Expendable, Fortitude::Polite), + INIT_BALANCE + ); + + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Expendable, Fortitude::Force), + INIT_BALANCE + ); + + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Preserve, Fortitude::Polite), + INIT_BALANCE - 1 + ); + + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Preserve, Fortitude::Force), + INIT_BALANCE - 1 + ); + + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Protect, Fortitude::Polite), + INIT_BALANCE - 1 + ); + + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Protect, Fortitude::Force), + INIT_BALANCE - 1 + ); + }); +} + +#[test] +fn can_deposit_works_success() { + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_deposit(&alice(), 10, Provenance::Minted), + DepositConsequence::Success + ); + }); +} + +#[test] +fn can_deposit_works_overflow() { + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_deposit(&alice(), u64::MAX, Provenance::Minted), + DepositConsequence::Overflow + ); + }); +} + +#[test] +fn can_withdraw_works_success() { + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_withdraw(&alice(), 10), + WithdrawConsequence::Success + ); + }); +} + +#[test] +fn can_withdraw_works_underflow() { + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_withdraw(&alice(), u64::MAX), + WithdrawConsequence::Underflow + ); + }); +} + +#[test] +fn can_withdraw_works_balance_low() { + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_withdraw(&alice(), INIT_BALANCE + 1), + WithdrawConsequence::BalanceLow + ); + }); +} + +#[test] +fn can_withdraw_works_reduced_to_zero() { + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_withdraw(&alice(), INIT_BALANCE), + WithdrawConsequence::ReducedToZero(0) + ); + }); +} + +#[test] +fn write_balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let write_balance = 10; + + // Invoke the function under test. + assert_eq!( + EvmBalances::write_balance(&alice(), write_balance), + Ok(None) + ); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), write_balance); + }); +} + +#[test] +fn set_total_issuance_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + let set_total_issuance_balance = 100; + + // Invoke the function under test. + EvmBalances::set_total_issuance(set_total_issuance_balance); + + // Assert state changes. + assert_eq!(EvmBalances::total_issuance(), set_total_issuance_balance); + }); +} + +#[test] +fn decrease_balance_works_exact_expendable() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let decreased_balance = 100; + + // Invoke the function under test. + assert_ok!(EvmBalances::decrease_balance( + &alice(), + decreased_balance, + Precision::Exact, + Preservation::Expendable, + Fortitude::Polite + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - decreased_balance + ); + }); +} + +#[test] +fn decrease_balance_works_best_effort_preserve() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let decreased_balance = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_ok!(EvmBalances::decrease_balance( + &alice(), + decreased_balance, + Precision::BestEffort, + Preservation::Preserve, + Fortitude::Polite + )); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 1); + }); +} + +#[test] +fn decrease_balance_works_full_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let decreased_balance = INIT_BALANCE; + + // Invoke the function under test. + assert_ok!(EvmBalances::decrease_balance( + &alice(), + decreased_balance, + Precision::Exact, + Preservation::Expendable, + Fortitude::Polite + )); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 0); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + }); +} + +#[test] +fn decrease_balance_fails_funds_unavailable() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let decreased_balance = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_noop!( + EvmBalances::decrease_balance( + &alice(), + decreased_balance, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite + ), + TokenError::FundsUnavailable + ); + }); +} + +#[test] +fn increase_balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let increased_balance = 100; + + // Invoke the function under test. + assert_ok!(EvmBalances::increase_balance( + &alice(), + increased_balance, + Precision::Exact, + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + increased_balance + ); + }); +} + +#[test] +fn increase_balance_works_best_effort() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let increased_balance = u64::MAX; + + // Invoke the function under test. + assert_ok!(EvmBalances::increase_balance( + &alice(), + increased_balance, + Precision::BestEffort, + )); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), u64::MAX); + }); +} + +#[test] +fn increase_balance_fails_overflow() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let increased_balance = u64::MAX; + + // Invoke the function under test. + assert_noop!( + EvmBalances::increase_balance(&alice(), increased_balance, Precision::Exact), + ArithmeticError::Overflow + ); + }); +} + +#[test] +fn deactivate_reactivate_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(>::get(), 0); + + // Deactivate some balance. + EvmBalances::deactivate(100); + // Assert state changes. + assert_eq!(>::get(), 100); + + // Reactivate some balance. + EvmBalances::reactivate(40); + // Assert state changes. + assert_eq!(>::get(), 60); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn mint_into_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let minted_balance = 10; + + // Invoke the function under test. + assert_ok!(EvmBalances::mint_into(&alice(), minted_balance)); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + minted_balance + ); + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE + minted_balance + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Minted { + who: alice(), + amount: minted_balance, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn mint_into_fails_overflow() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let minted_balance = u64::MAX; + + // Invoke the function under test. + assert_noop!( + EvmBalances::mint_into(&alice(), minted_balance), + ArithmeticError::Overflow + ); + }); +} + +#[test] +fn burn_from_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let burned_balance = 10; + + // Invoke the function under test. + assert_ok!(EvmBalances::burn_from( + &alice(), + burned_balance, + Precision::Exact, + Fortitude::Polite + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - burned_balance + ); + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE - burned_balance + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { + who: alice(), + amount: burned_balance, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn burn_from_works_best_effort() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let burned_balance = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_ok!(EvmBalances::burn_from( + &alice(), + burned_balance, + Precision::BestEffort, + Fortitude::Polite + )); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 0); + assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { + who: alice(), + amount: INIT_BALANCE, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn burn_from_works_exact_full_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let burned_balance = INIT_BALANCE; + + // Invoke the function under test. + assert_ok!(EvmBalances::burn_from( + &alice(), + burned_balance, + Precision::Exact, + Fortitude::Polite + )); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 0); + assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { + who: alice(), + amount: INIT_BALANCE, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn burn_from_fails_funds_unavailable() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let burned_balance = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_noop!( + EvmBalances::burn_from( + &alice(), + burned_balance, + Precision::Exact, + Fortitude::Polite + ), + TokenError::FundsUnavailable + ); + }); +} + +#[test] +fn shelve_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let shelved_balance = 10; + + // Invoke the function under test. + assert_ok!(EvmBalances::shelve(&alice(), shelved_balance)); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - shelved_balance + ); + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE - shelved_balance + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Suspended { + who: alice(), + amount: shelved_balance, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn shelve_works_exact_full_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let shelved_balance = INIT_BALANCE; + + // Invoke the function under test. + assert_ok!(EvmBalances::shelve(&alice(), shelved_balance)); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 0); + assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Suspended { + who: alice(), + amount: INIT_BALANCE, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn shelve_fails_funds_unavailable() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let shelved_balance = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_noop!( + EvmBalances::shelve(&alice(), shelved_balance), + TokenError::FundsUnavailable + ); + }); +} + +#[test] +fn restore_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let restored_balance = 10; + + // Invoke the function under test. + assert_ok!(EvmBalances::restore(&alice(), restored_balance)); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + restored_balance + ); + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE + restored_balance + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Restored { + who: alice(), + amount: restored_balance, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn restore_fails_overflow() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let restored_balance = u64::MAX; + + // Invoke the function under test. + assert_noop!( + EvmBalances::restore(&alice(), restored_balance), + ArithmeticError::Overflow + ); + }); +} + +#[test] +fn transfer_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = 100; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + Preservation::Preserve + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - transfered_amount + ); + assert_eq!( + EvmBalances::total_balance(&bob()), + INIT_BALANCE + transfered_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Transfer { + from: alice(), + to: bob(), + amount: transfered_amount, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn transfer_works_full_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + Preservation::Expendable + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - transfered_amount + ); + assert_eq!( + EvmBalances::total_balance(&bob()), + INIT_BALANCE + transfered_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Transfer { + from: alice(), + to: bob(), + amount: transfered_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn transfer_fails_funds_unavailable() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE + 1; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer(&alice(), &bob(), transfered_amount, Preservation::Preserve), + TokenError::FundsUnavailable + ); + }); +} + +#[test] +fn transfer_fails_not_expendable() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer(&alice(), &bob(), transfered_amount, Preservation::Preserve), + TokenError::NotExpendable + ); + }); +} + +#[test] +fn transfer_fails_underflow() { + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let eve = H160::from_str("1000000000000000000000000000000000000004").unwrap(); + EvmBalances::set_balance(&charlie, u64::MAX); + EvmBalances::set_balance(&eve, 1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer(&charlie, &eve, u64::MAX, Preservation::Expendable), + // Withdraw consequence is checked first by reducing total issuance. + ArithmeticError::Underflow, + ); + }); +} + +#[test] +fn rescind_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let rescinded_balance = 100; + + // Burn some balance. + let imbalance = EvmBalances::rescind(rescinded_balance); + + // Assert state changes. + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE - rescinded_balance + ); + drop(imbalance); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Rescinded { + amount: rescinded_balance, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn issue_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let issued_balance = 100; + + // Burn some balance. + let imbalance = EvmBalances::issue(issued_balance); + + // Assert state changes. + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE + issued_balance + ); + drop(imbalance); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Issued { + amount: issued_balance, + })); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn deposit_flow_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let deposited_amount = 10; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let debt = EvmBalances::deposit(&alice(), deposited_amount, Precision::Exact).unwrap(); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + deposited_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Deposit { + who: alice(), + amount: deposited_amount, + })); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + let _ = EvmBalances::settle(&bob(), debt, Preservation::Expendable); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn deposit_works_new_account() { + new_test_ext().execute_with_ext(|_| { + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&charlie), 0); + + let deposited_amount = 10; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let debt = EvmBalances::deposit(&charlie, deposited_amount, Precision::Exact).unwrap(); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&charlie), deposited_amount); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Deposit { + who: charlie, + amount: deposited_amount, + })); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + let _ = EvmBalances::settle(&bob(), debt, Preservation::Expendable); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + assert!(EvmSystem::account_exists(&charlie)); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::NewAccount { account: charlie }, + )); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn withdraw_works() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = 1000; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let credit = EvmBalances::withdraw( + &alice(), + withdrawed_amount, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + ) + .unwrap(); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - withdrawed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Withdraw { + who: alice(), + amount: withdrawed_amount, + })); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + let _ = EvmBalances::resolve(&bob(), credit); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn withdraw_works_full_balance() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let credit = EvmBalances::withdraw( + &alice(), + withdrawed_amount, + Precision::Exact, + Preservation::Expendable, + Fortitude::Polite, + ) + .unwrap(); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - withdrawed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { + who: alice(), + amount: withdrawed_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + let _ = EvmBalances::resolve(&bob(), credit); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + assert_total_issuance_invariant(); + }); +} diff --git a/crates/evm-balances/src/tests/mod.rs b/crates/evm-balances/src/tests/mod.rs new file mode 100644 index 000000000..dc0fcd955 --- /dev/null +++ b/crates/evm-balances/src/tests/mod.rs @@ -0,0 +1,277 @@ +//! Unit tests. + +use frame_support::{assert_ok, traits::Currency, weights::Weight}; +use pallet_evm::{FeeCalculator, FixedGasWeightMapping, GasWeightMapping, Runner}; +use sp_core::{H160, U256}; +use sp_runtime::traits::UniqueSaturatedInto; +use sp_std::str::FromStr; + +use crate::{mock::*, *}; + +mod currency; +mod fungible; + +fn assert_total_issuance_invariant() { + let iterated_total_issuance: u64 = >::iter_values() + .map(|account_data| account_data.data.total()) + .sum(); + + let total_issuance = EvmBalances::total_issuance(); + + assert_eq!(iterated_total_issuance, total_issuance); +} + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with_ext(|_| { + // Check the accounts. + assert_eq!( + ::account(&alice()), + account_data::AccountData { free: INIT_BALANCE } + ); + assert_eq!( + ::account(&bob()), + account_data::AccountData { free: INIT_BALANCE } + ); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn evm_system_removing_account_non_zero_balance() { + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let contract = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + EVM::create_account(contract, vec![1, 2, 3]); + + // Transfer some balance to contract address. + assert_ok!(EvmBalances::transfer( + &alice(), + &contract, + 1000, + ExistenceRequirement::KeepAlive + )); + + assert_eq!(EvmBalances::free_balance(&contract), 1000); + + // Invoke the function under test. + EVM::remove_account(&contract); + + // Assert state changes. + assert_eq!(EvmBalances::free_balance(&contract), 1000); + assert!(EvmSystem::account_exists(&contract)); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn evm_fee_deduction() { + new_test_ext().execute_with_ext(|_| { + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + + // Seed account + let _ = ::Currency::deposit_creating(&charlie, 100); + assert_eq!(EvmBalances::free_balance(&charlie), 100); + + // Deduct fees as 10 units + let imbalance = + <::OnChargeTransaction as pallet_evm::OnChargeEVMTransaction>::withdraw_fee( + &charlie, + U256::from(10), + ) + .unwrap(); + assert_eq!(EvmBalances::free_balance(&charlie), 90); + + // Refund fees as 5 units + <::OnChargeTransaction as pallet_evm::OnChargeEVMTransaction>::correct_and_deposit_fee(&charlie, U256::from(5), U256::from(5), imbalance); + assert_eq!(EvmBalances::free_balance(&charlie), 95); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn evm_issuance_after_tip() { + new_test_ext().execute_with_ext(|_| { + let before_tip = ::Currency::total_issuance(); + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + assert_ok!(::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(U256::from(2_000_000_000)), + None, + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + )); + + // Only base fee is burned + let base_fee: u64 = ::FeeCalculator::min_gas_price() + .0 + .unique_saturated_into(); + + let after_tip = ::Currency::total_issuance(); + + assert_eq!(after_tip, (before_tip - (base_fee * 21_000))); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn evm_refunds_should_work() { + new_test_ext().execute_with_ext(|_| { + let before_call = EVM::account_basic(&alice()).0.balance; + // Gas price is not part of the actual fee calculations anymore, only the base fee. + // + // Because we first deduct max_fee_per_gas * gas_limit (2_000_000_000 * 1000000) we need + // to ensure that the difference (max fee VS base fee) is refunded. + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + let _ = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(U256::from(2_000_000_000)), + None, + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + ); + + let (base_fee, _) = ::FeeCalculator::min_gas_price(); + let total_cost = (U256::from(21_000) * base_fee) + U256::from(1); + let after_call = EVM::account_basic(&alice()).0.balance; + assert_eq!(after_call, before_call - total_cost); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn evm_refunds_and_priority_should_work() { + new_test_ext().execute_with_ext(|_| { + let before_call = EVM::account_basic(&alice()).0.balance; + // We deliberately set a base fee + max tip > max fee. + // The effective priority tip will be 1GWEI instead 1.5GWEI: + // (max_fee_per_gas - base_fee).min(max_priority_fee) + // (2 - 1).min(1.5) + let tip = U256::from(1_500_000_000); + let max_fee_per_gas = U256::from(2_000_000_000); + let used_gas = U256::from(21_000); + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + let _ = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(max_fee_per_gas), + Some(tip), + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + ); + + let (base_fee, _) = ::FeeCalculator::min_gas_price(); + let actual_tip = (max_fee_per_gas - base_fee).min(tip) * used_gas; + let total_cost = (used_gas * base_fee) + U256::from(actual_tip) + U256::from(1); + let after_call = EVM::account_basic(&alice()).0.balance; + // The tip is deducted but never refunded to the caller. + assert_eq!(after_call, before_call - total_cost); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn evm_call_should_fail_with_priority_greater_than_max_fee() { + new_test_ext().execute_with_ext(|_| { + // Max priority greater than max fee should fail. + let tip: u128 = 1_100_000_000; + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + let result = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(U256::from(1_000_000_000)), + Some(U256::from(tip)), + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + ); + assert!(result.is_err()); + // Some used weight is returned as part of the error. + assert_eq!(result.unwrap_err().weight, Weight::from_parts(7, 0)); + + assert_total_issuance_invariant(); + }); +} + +#[test] +fn evm_call_should_succeed_with_priority_equal_to_max_fee() { + new_test_ext().execute_with_ext(|_| { + let tip: u128 = 1_000_000_000; + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + // Mimics the input for pre-eip-1559 transaction types where `gas_price` + // is used for both `max_fee_per_gas` and `max_priority_fee_per_gas`. + let result = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(U256::from(1_000_000_000)), + Some(U256::from(tip)), + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + ); + assert!(result.is_ok()); + + assert_total_issuance_invariant(); + }); +} diff --git a/crates/evm-system/Cargo.toml b/crates/evm-system/Cargo.toml new file mode 100644 index 000000000..1a114304f --- /dev/null +++ b/crates/evm-system/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "pallet-evm-system" +version = "1.0.0-dev" +license = "Apache-2.0" +description = "FRAME EVM SYSTEM pallet." +edition = { workspace = true } +repository = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { workspace = true, default-features = false } +scale-codec = { package = "parity-scale-codec", workspace = true } +scale-info = { workspace = true } +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +# Frontier +fp-evm = { workspace = true } + +[dev-dependencies] +mockall = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } + +[features] +default = ["std"] +std = [ + "log/std", + "scale-codec/std", + "scale-info/std", + # Substrate + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + # Frontier + "fp-evm/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/crates/evm-system/src/lib.rs b/crates/evm-system/src/lib.rs new file mode 100644 index 000000000..78cb897c9 --- /dev/null +++ b/crates/evm-system/src/lib.rs @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! # EVM System Pallet. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::StoredMap; +use scale_codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{One, Zero}, + DispatchError, RuntimeDebug, +}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub use pallet::*; + +/// Account information. +#[derive( + Clone, + Eq, + PartialEq, + Default, + RuntimeDebug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen +)] +pub struct AccountInfo { + /// The number of transactions this account has sent. + pub nonce: Index, + /// The additional data that belongs to this account. Used to store the balance(s) in a lot of + /// chains. + pub data: AccountData, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use sp_runtime::traits::{AtLeast32Bit, MaybeDisplay}; + use sp_std::fmt::Debug; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The user account identifier type. + type AccountId: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + MaybeDisplay + + Ord + + MaxEncodedLen; + + /// Account index (aka nonce) type. This stores the number of previous transactions + /// associated with a sender account. + type Index: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + Default + + MaybeDisplay + + AtLeast32Bit + + Copy + + MaxEncodedLen; + + /// Data to be associated with an account (other than nonce/transaction counter, which this + /// pallet does regardless). + type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; + + /// Handler for when a new account has just been created. + type OnNewAccount: OnNewAccount<::AccountId>; + + /// A function that is invoked when an account has been determined to be dead. + /// + /// All resources should be cleaned up associated with the given account. + type OnKilledAccount: OnKilledAccount<::AccountId>; + } + + /// The full account information for a particular account ID. + #[pallet::storage] + pub type Account = StorageMap< + _, + Blake2_128Concat, + ::AccountId, + AccountInfo<::Index, ::AccountData>, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A new account was created. + NewAccount { account: ::AccountId }, + /// An account was reaped. + KilledAccount { account: ::AccountId }, + } +} + +/// The outcome of the account creation operation. +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum AccountCreationOutcome { + /// Account was created. + Created, + /// Account already exists in the system, so action was taken. + AlreadyExists, +} + +impl Pallet { + /// Check the account existence. + pub fn account_exists(who: &::AccountId) -> bool { + Account::::contains_key(who) + } + + /// An account is being created. + fn on_created_account(who: ::AccountId) { + ::OnNewAccount::on_new_account(&who); + Self::deposit_event(Event::NewAccount { account: who }); + } + + /// Do anything that needs to be done after an account has been killed. + fn on_killed_account(who: ::AccountId) { + ::OnKilledAccount::on_killed_account(&who); + Self::deposit_event(Event::KilledAccount { account: who }); + } + + /// Retrieve the account transaction counter from storage. + pub fn account_nonce(who: &::AccountId) -> ::Index { + Account::::get(who).nonce + } + + /// Increment a particular account's nonce by 1. + pub fn inc_account_nonce(who: &::AccountId) { + let is_new_account = Account::::mutate_exists(who, |maybe_account| { + let is_new_account = maybe_account.is_none(); + + let account = maybe_account.get_or_insert_default(); + account.nonce += ::Index::one(); + + is_new_account + }); + + // Meaning that account is being created. + if is_new_account { + Self::on_created_account(who.clone()); + } + } + + /// Create an account. + pub fn create_account(who: &::AccountId) -> AccountCreationOutcome { + if Self::account_exists(who) { + return AccountCreationOutcome::AlreadyExists; + } + + Account::::insert(who.clone(), AccountInfo::<_, _>::default()); + Self::on_created_account(who.clone()); + AccountCreationOutcome::Created + } +} + +impl StoredMap<::AccountId, ::AccountData> for Pallet { + fn get(k: &::AccountId) -> ::AccountData { + Account::::get(k).data + } + + fn try_mutate_exists>( + k: &::AccountId, + f: impl FnOnce(&mut Option<::AccountData>) -> Result, + ) -> Result { + let (mut maybe_account_data, nonce, was_providing) = if Self::account_exists(k) { + let account_info = Account::::get(k); + (Some(account_info.data), account_info.nonce, true) + } else { + (None, ::Index::zero(), false) + }; + + let result = f(&mut maybe_account_data)?; + + match (maybe_account_data, was_providing) { + (Some(data), false) => { + Account::::mutate(k, |a| a.data = data); + Self::on_created_account(k.clone()); + } + (Some(data), true) => { + Account::::mutate(k, |a| a.data = data); + } + (None, true) => { + if nonce != ::Index::zero() { + Account::::mutate(k, |a| a.data = Default::default()); + } else { + Account::::remove(k); + Self::on_killed_account(k.clone()); + } + } + (None, false) => { + // Do nothing. + } + } + + Ok(result) + } +} + +impl fp_evm::AccountProvider for Pallet { + type AccountId = ::AccountId; + type Index = ::Index; + + fn create_account(who: &Self::AccountId) { + let _ = Self::create_account(who); + } + + fn on_account_self_destruct(_who: &Self::AccountId) { + // Do nothing as account state shouldn't be modified. + } + + fn account_nonce(who: &Self::AccountId) -> Self::Index { + Self::account_nonce(who) + } + + fn inc_account_nonce(who: &Self::AccountId) { + Self::inc_account_nonce(who); + } +} + +/// Interface to handle account creation. +pub trait OnNewAccount { + /// A new account `who` has been registered. + fn on_new_account(who: &AccountId); +} + +impl OnNewAccount for () { + fn on_new_account(_who: &AccountId) {} +} + +/// Interface to handle account killing. +pub trait OnKilledAccount { + /// The account with the given id was reaped. + fn on_killed_account(who: &AccountId); +} + +impl OnKilledAccount for () { + fn on_killed_account(_who: &AccountId) {} +} diff --git a/crates/evm-system/src/mock.rs b/crates/evm-system/src/mock.rs new file mode 100644 index 000000000..7688a55ef --- /dev/null +++ b/crates/evm-system/src/mock.rs @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test mock for unit tests. + +use frame_support::traits::{ConstU32, ConstU64}; +use mockall::mock; +use sp_core::{H160, H256}; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +use sp_std::{boxed::Box, prelude::*}; + +use crate::{self as pallet_evm_system, *}; + +mock! { + #[derive(Debug)] + pub DummyOnNewAccount {} + + impl OnNewAccount for DummyOnNewAccount { + pub fn on_new_account(who: &H160); + } +} + +mock! { + #[derive(Debug)] + pub DummyOnKilledAccount {} + + impl OnKilledAccount for DummyOnKilledAccount { + pub fn on_killed_account(who: &H160); + } +} + +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, + EvmSystem: pallet_evm_system, + } +} + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = H160; + type Lookup = IdentityLookup; + type Header = generic::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_evm_system::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AccountId = H160; + type Index = u64; + type AccountData = u64; + type OnNewAccount = MockDummyOnNewAccount; + type OnKilledAccount = MockDummyOnKilledAccount; +} + +/// Build test externalities from the custom genesis. +/// Using this call requires manual assertions on the genesis init logic. +pub fn new_test_ext() -> sp_io::TestExternalities { + // Build genesis. + let config = GenesisConfig { + ..Default::default() + }; + let storage = config.build_storage().unwrap(); + + // Make test externalities from the storage. + storage.into() +} + +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(), + } +} + +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/evm-system/src/tests.rs b/crates/evm-system/src/tests.rs new file mode 100644 index 000000000..a07609536 --- /dev/null +++ b/crates/evm-system/src/tests.rs @@ -0,0 +1,347 @@ +//! Unit tests. + +use sp_std::str::FromStr; + +use frame_support::{assert_noop, assert_storage_noop}; +use mockall::predicate; +use sp_core::H160; + +use crate::{mock::*, *}; + +/// This test verifies that creating an account works as expected +/// in case a new account should be created. +#[test] +fn create_account_created() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Check test preconditions. + assert!(!EvmSystem::account_exists(&account_id)); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); + on_new_account_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(()); + + // Invoke the function under test. + assert_eq!( + EvmSystem::create_account(&account_id), + AccountCreationOutcome::Created + ); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!( + >::get(&account_id), + AccountInfo::<_, _>::default() + ); + System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { + account: account_id, + })); + + // Assert mock invocations. + on_new_account_ctx.checkpoint(); + }); +} + +/// This test verifies that creating an account works as expected +/// when the account already exists. +#[test] +fn create_account_already_exists() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + >::insert(account_id.clone(), AccountInfo::<_, _>::default()); + + // Invoke the function under test. + assert_storage_noop!(assert_eq!( + EvmSystem::create_account(&account_id), + AccountCreationOutcome::AlreadyExists + )); + }); +} + +/// This test verifies that incrementing account nonce works in the happy path +/// in case a new account should be created. +#[test] +fn inc_account_nonce_account_created() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Check test preconditions. + assert!(!EvmSystem::account_exists(&account_id)); + + let nonce_before = EvmSystem::account_nonce(&account_id); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); + on_new_account_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(()); + + // Invoke the function under test. + EvmSystem::inc_account_nonce(&account_id); + + // Assert state changes. + assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 1); + assert!(EvmSystem::account_exists(&account_id)); + System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { + account: account_id, + })); + + // Invoke the function under test again to check that the account is not being created now. + EvmSystem::inc_account_nonce(&account_id); + // Assert state changes. + assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 2); + assert!(EvmSystem::account_exists(&account_id)); + + // Assert mock invocations. + on_new_account_ctx.checkpoint(); + }); +} + +/// This test verifies that incrementing account nonce works in the happy path +/// in case an account already exists. +#[test] +fn inc_account_nonce_account_exists() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + >::insert(account_id.clone(), AccountInfo::<_, _>::default()); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + let nonce_before = EvmSystem::account_nonce(&account_id); + + // Invoke the function under test. + EvmSystem::inc_account_nonce(&account_id); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 1); + }); +} + +/// This test verifies that try_mutate_exists works as expected in case data wasn't providing +/// and returned data is `Some`. As a result, a new account has been created. +#[test] +fn try_mutate_exists_account_created() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Check test preconditions. + assert!(!EvmSystem::account_exists(&account_id)); + + // Set mock expectations. + let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); + on_new_account_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(()); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { + *maybe_data = Some(1); + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!( + >::get(&account_id), + AccountInfo { + data: 1, + ..Default::default() + } + ); + System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { + account: account_id, + })); + + // Assert mock invocations. + on_new_account_ctx.checkpoint(); + }); +} + +/// This test verifies that try_mutate_exists works as expected in case data was providing +/// and returned data is `Some`. As a result, the account has been updated. +#[test] +fn try_mutate_exists_account_updated() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + let nonce = 1; + let data = 1; + >::insert(account_id.clone(), AccountInfo { nonce, data }); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { + if let Some(ref mut data) = maybe_data { + *data += 1; + } + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!( + >::get(&account_id), + AccountInfo { + nonce, + data: data + 1, + } + ); + }); +} + +/// This test verifies that try_mutate_exists works as expected in case data was providing +/// and returned data is `None`, account has zero nonce. As a result, the account has been removed. +#[test] +fn try_mutate_exists_account_removed() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + >::insert(account_id.clone(), AccountInfo::<_, _>::default()); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + // Set mock expectations. + let on_killed_account_ctx = MockDummyOnKilledAccount::on_killed_account_context(); + on_killed_account_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(()); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { + *maybe_data = None; + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(!EvmSystem::account_exists(&account_id)); + System::assert_has_event(RuntimeEvent::EvmSystem(Event::KilledAccount { + account: account_id, + })); + + // Assert mock invocations. + on_killed_account_ctx.checkpoint(); + }); +} + +/// This test verifies that try_mutate_exists works as expected in case data was providing +/// and returned data is `None`, account has non zero nonce. As a result, the account has been retained. +#[test] +fn try_mutate_exists_account_retained() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + let nonce = 10; + let data = 100; + + let account_info = AccountInfo { nonce, data }; + >::insert(account_id.clone(), account_info); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { + *maybe_data = None; + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!( + >::get(&account_id), + AccountInfo { + nonce, + ..Default::default() + } + ); + }); +} + +/// This test verifies that try_mutate_exists works as expected in case data wasn't providing +/// and returned data is `None`. As a result, the account hasn't been created. +#[test] +fn try_mutate_exists_account_not_created() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Check test preconditions. + assert!(!EvmSystem::account_exists(&account_id)); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_storage_noop!(>::try_mutate_exists( + account_id, + |maybe_data| -> Result<(), ()> { + *maybe_data = None; + Ok(()) + } + ) + .unwrap()); + }); +} + +/// This test verifies that try_mutate_exists works as expected in case getting error +/// during data mutation. +#[test] +fn try_mutate_exists_without_changes() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + >::insert(account_id.clone(), AccountInfo::<_, _>::default()); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + // Invoke the function under test. + assert_noop!( + >::try_mutate_exists(account_id, |maybe_data| -> Result<(), ()> { + *maybe_data = None; + Err(()) + }), + () + ); + }); +} From 918930e6af420d5c160fa7829d7b5b4f389f97b3 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 7 Feb 2025 13:23:35 +0300 Subject: [PATCH 2/3] Rename pallets names --- crates/{evm-balances => pallet-evm-balances}/Cargo.toml | 0 crates/{evm-balances => pallet-evm-balances}/src/account_data.rs | 0 crates/{evm-balances => pallet-evm-balances}/src/imbalances.rs | 0 crates/{evm-balances => pallet-evm-balances}/src/impl_currency.rs | 0 crates/{evm-balances => pallet-evm-balances}/src/impl_fungible.rs | 0 crates/{evm-balances => pallet-evm-balances}/src/lib.rs | 0 crates/{evm-balances => pallet-evm-balances}/src/mock.rs | 0 .../{evm-balances => pallet-evm-balances}/src/tests/currency.rs | 0 .../{evm-balances => pallet-evm-balances}/src/tests/fungible.rs | 0 crates/{evm-balances => pallet-evm-balances}/src/tests/mod.rs | 0 crates/{evm-system => pallet-evm-system}/Cargo.toml | 0 crates/{evm-system => pallet-evm-system}/src/lib.rs | 0 crates/{evm-system => pallet-evm-system}/src/mock.rs | 0 crates/{evm-system => pallet-evm-system}/src/tests.rs | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename crates/{evm-balances => pallet-evm-balances}/Cargo.toml (100%) rename crates/{evm-balances => pallet-evm-balances}/src/account_data.rs (100%) rename crates/{evm-balances => pallet-evm-balances}/src/imbalances.rs (100%) rename crates/{evm-balances => pallet-evm-balances}/src/impl_currency.rs (100%) rename crates/{evm-balances => pallet-evm-balances}/src/impl_fungible.rs (100%) rename crates/{evm-balances => pallet-evm-balances}/src/lib.rs (100%) rename crates/{evm-balances => pallet-evm-balances}/src/mock.rs (100%) rename crates/{evm-balances => pallet-evm-balances}/src/tests/currency.rs (100%) rename crates/{evm-balances => pallet-evm-balances}/src/tests/fungible.rs (100%) rename crates/{evm-balances => pallet-evm-balances}/src/tests/mod.rs (100%) rename crates/{evm-system => pallet-evm-system}/Cargo.toml (100%) rename crates/{evm-system => pallet-evm-system}/src/lib.rs (100%) rename crates/{evm-system => pallet-evm-system}/src/mock.rs (100%) rename crates/{evm-system => pallet-evm-system}/src/tests.rs (100%) diff --git a/crates/evm-balances/Cargo.toml b/crates/pallet-evm-balances/Cargo.toml similarity index 100% rename from crates/evm-balances/Cargo.toml rename to crates/pallet-evm-balances/Cargo.toml diff --git a/crates/evm-balances/src/account_data.rs b/crates/pallet-evm-balances/src/account_data.rs similarity index 100% rename from crates/evm-balances/src/account_data.rs rename to crates/pallet-evm-balances/src/account_data.rs diff --git a/crates/evm-balances/src/imbalances.rs b/crates/pallet-evm-balances/src/imbalances.rs similarity index 100% rename from crates/evm-balances/src/imbalances.rs rename to crates/pallet-evm-balances/src/imbalances.rs diff --git a/crates/evm-balances/src/impl_currency.rs b/crates/pallet-evm-balances/src/impl_currency.rs similarity index 100% rename from crates/evm-balances/src/impl_currency.rs rename to crates/pallet-evm-balances/src/impl_currency.rs diff --git a/crates/evm-balances/src/impl_fungible.rs b/crates/pallet-evm-balances/src/impl_fungible.rs similarity index 100% rename from crates/evm-balances/src/impl_fungible.rs rename to crates/pallet-evm-balances/src/impl_fungible.rs diff --git a/crates/evm-balances/src/lib.rs b/crates/pallet-evm-balances/src/lib.rs similarity index 100% rename from crates/evm-balances/src/lib.rs rename to crates/pallet-evm-balances/src/lib.rs diff --git a/crates/evm-balances/src/mock.rs b/crates/pallet-evm-balances/src/mock.rs similarity index 100% rename from crates/evm-balances/src/mock.rs rename to crates/pallet-evm-balances/src/mock.rs diff --git a/crates/evm-balances/src/tests/currency.rs b/crates/pallet-evm-balances/src/tests/currency.rs similarity index 100% rename from crates/evm-balances/src/tests/currency.rs rename to crates/pallet-evm-balances/src/tests/currency.rs diff --git a/crates/evm-balances/src/tests/fungible.rs b/crates/pallet-evm-balances/src/tests/fungible.rs similarity index 100% rename from crates/evm-balances/src/tests/fungible.rs rename to crates/pallet-evm-balances/src/tests/fungible.rs diff --git a/crates/evm-balances/src/tests/mod.rs b/crates/pallet-evm-balances/src/tests/mod.rs similarity index 100% rename from crates/evm-balances/src/tests/mod.rs rename to crates/pallet-evm-balances/src/tests/mod.rs diff --git a/crates/evm-system/Cargo.toml b/crates/pallet-evm-system/Cargo.toml similarity index 100% rename from crates/evm-system/Cargo.toml rename to crates/pallet-evm-system/Cargo.toml diff --git a/crates/evm-system/src/lib.rs b/crates/pallet-evm-system/src/lib.rs similarity index 100% rename from crates/evm-system/src/lib.rs rename to crates/pallet-evm-system/src/lib.rs diff --git a/crates/evm-system/src/mock.rs b/crates/pallet-evm-system/src/mock.rs similarity index 100% rename from crates/evm-system/src/mock.rs rename to crates/pallet-evm-system/src/mock.rs diff --git a/crates/evm-system/src/tests.rs b/crates/pallet-evm-system/src/tests.rs similarity index 100% rename from crates/evm-system/src/tests.rs rename to crates/pallet-evm-system/src/tests.rs From dba0d19da494f95bc18b531c08d69f9893f79635 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 7 Feb 2025 17:45:49 +0300 Subject: [PATCH 3/3] Apply fmt --- .../pallet-evm-balances/src/account_data.rs | 74 +- crates/pallet-evm-balances/src/imbalances.rs | 248 +-- .../pallet-evm-balances/src/impl_currency.rs | 524 +++--- .../pallet-evm-balances/src/impl_fungible.rs | 388 ++--- crates/pallet-evm-balances/src/lib.rs | 503 +++--- crates/pallet-evm-balances/src/mock.rs | 274 ++-- .../pallet-evm-balances/src/tests/currency.rs | 904 +++++----- .../pallet-evm-balances/src/tests/fungible.rs | 1460 ++++++++--------- crates/pallet-evm-balances/src/tests/mod.rs | 414 ++--- crates/pallet-evm-system/src/lib.rs | 387 +++-- crates/pallet-evm-system/src/mock.rs | 158 +- crates/pallet-evm-system/src/tests.rs | 551 ++++--- 12 files changed, 2927 insertions(+), 2958 deletions(-) diff --git a/crates/pallet-evm-balances/src/account_data.rs b/crates/pallet-evm-balances/src/account_data.rs index 4bd3fffdd..958427333 100644 --- a/crates/pallet-evm-balances/src/account_data.rs +++ b/crates/pallet-evm-balances/src/account_data.rs @@ -5,62 +5,42 @@ use frame_support::traits::WithdrawReasons; use super::*; /// All balance information for an account. -#[derive( - Encode, - Decode, - Clone, - PartialEq, - Eq, - Default, - RuntimeDebug, - MaxEncodedLen, - TypeInfo -)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct AccountData { - /// Non-reserved part of the balance. There may still be restrictions on this, but it is the - /// total pool what may in principle be transferred, reserved and used for tipping. - /// - /// This is the only balance that matters in terms of most operations on tokens. It - /// alone is used to determine the balance when in the contract execution environment. - pub free: Balance, + /// Non-reserved part of the balance. There may still be restrictions on this, but it is the + /// total pool what may in principle be transferred, reserved and used for tipping. + /// + /// This is the only balance that matters in terms of most operations on tokens. It + /// alone is used to determine the balance when in the contract execution environment. + pub free: Balance, } impl AccountData { - /// The total balance in this account. - pub(crate) fn total(&self) -> Balance { - self.free - } + /// The total balance in this account. + pub(crate) fn total(&self) -> Balance { + self.free + } } /// Simplified reasons for withdrawing balance. -#[derive( - Encode, - Decode, - Clone, - Copy, - PartialEq, - Eq, - RuntimeDebug, - MaxEncodedLen, - TypeInfo -)] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub enum Reasons { - /// Paying system transaction fees. - Fee = 0, - /// Any reason other than paying system transaction fees. - Misc = 1, - /// Any reason at all. - All = 2, + /// Paying system transaction fees. + Fee = 0, + /// Any reason other than paying system transaction fees. + Misc = 1, + /// Any reason at all. + All = 2, } impl From for Reasons { - fn from(r: WithdrawReasons) -> Reasons { - if r == WithdrawReasons::TRANSACTION_PAYMENT { - Reasons::Fee - } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { - Reasons::All - } else { - Reasons::Misc - } - } + fn from(r: WithdrawReasons) -> Reasons { + if r == WithdrawReasons::TRANSACTION_PAYMENT { + Reasons::Fee + } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { + Reasons::All + } else { + Reasons::Misc + } + } } diff --git a/crates/pallet-evm-balances/src/imbalances.rs b/crates/pallet-evm-balances/src/imbalances.rs index 1a5c619ab..77529d439 100644 --- a/crates/pallet-evm-balances/src/imbalances.rs +++ b/crates/pallet-evm-balances/src/imbalances.rs @@ -12,10 +12,10 @@ use super::*; pub struct PositiveImbalance, I: 'static = ()>(T::Balance); impl, I: 'static> PositiveImbalance { - /// Create a new positive imbalance from a balance. - pub fn new(amount: T::Balance) -> Self { - PositiveImbalance(amount) - } + /// Create a new positive imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + PositiveImbalance(amount) + } } /// Opaque, move-only struct with private fields that serves as a token denoting that @@ -25,148 +25,148 @@ impl, I: 'static> PositiveImbalance { pub struct NegativeImbalance, I: 'static = ()>(T::Balance); impl, I: 'static> NegativeImbalance { - /// Create a new negative imbalance from a balance. - pub fn new(amount: T::Balance) -> Self { - NegativeImbalance(amount) - } + /// Create a new negative imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + NegativeImbalance(amount) + } } impl, I: 'static> TryDrop for PositiveImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } } impl, I: 'static> Default for PositiveImbalance { - fn default() -> Self { - Self::zero() - } + fn default() -> Self { + Self::zero() + } } impl, I: 'static> Imbalance for PositiveImbalance { - type Opposite = NegativeImbalance; - - fn zero() -> Self { - Self(Zero::zero()) - } - - fn drop_zero(self) -> result::Result<(), Self> { - if self.0.is_zero() { - Ok(()) - } else { - Err(self) - } - } - - fn split(self, amount: T::Balance) -> (Self, Self) { - let first = self.0.min(amount); - let second = self.0 - first; - - mem::forget(self); - (Self(first), Self(second)) - } - - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - - self - } - - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - } - - fn offset(self, other: Self::Opposite) -> SameOrOther { - let (a, b) = (self.0, other.0); - mem::forget((self, other)); - - match a.cmp(&b) { - Ordering::Greater => SameOrOther::Same(Self(a - b)), - Ordering::Less => SameOrOther::Other(NegativeImbalance::new(b - a)), - Ordering::Equal => SameOrOther::None, - } - } - - fn peek(&self) -> T::Balance { - self.0 - } + type Opposite = NegativeImbalance; + + fn zero() -> Self { + Self(Zero::zero()) + } + + fn drop_zero(self) -> result::Result<(), Self> { + if self.0.is_zero() { + Ok(()) + } else { + Err(self) + } + } + + fn split(self, amount: T::Balance) -> (Self, Self) { + let first = self.0.min(amount); + let second = self.0 - first; + + mem::forget(self); + (Self(first), Self(second)) + } + + fn merge(mut self, other: Self) -> Self { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + + self + } + + fn subsume(&mut self, other: Self) { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + } + + fn offset(self, other: Self::Opposite) -> SameOrOther { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + match a.cmp(&b) { + Ordering::Greater => SameOrOther::Same(Self(a - b)), + Ordering::Less => SameOrOther::Other(NegativeImbalance::new(b - a)), + Ordering::Equal => SameOrOther::None, + } + } + + fn peek(&self) -> T::Balance { + self.0 + } } impl, I: 'static> TryDrop for NegativeImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } } impl, I: 'static> Default for NegativeImbalance { - fn default() -> Self { - Self::zero() - } + fn default() -> Self { + Self::zero() + } } impl, I: 'static> Imbalance for NegativeImbalance { - type Opposite = PositiveImbalance; - - fn zero() -> Self { - Self(Zero::zero()) - } - - fn drop_zero(self) -> result::Result<(), Self> { - if self.0.is_zero() { - Ok(()) - } else { - Err(self) - } - } - - fn split(self, amount: T::Balance) -> (Self, Self) { - let first = self.0.min(amount); - let second = self.0 - first; - - mem::forget(self); - (Self(first), Self(second)) - } - - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - - self - } - - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - } - - fn offset(self, other: Self::Opposite) -> SameOrOther { - let (a, b) = (self.0, other.0); - mem::forget((self, other)); - - match a.cmp(&b) { - Ordering::Greater => SameOrOther::Same(Self(a - b)), - Ordering::Less => SameOrOther::Other(PositiveImbalance::new(b - a)), - Ordering::Equal => SameOrOther::None, - } - } - - fn peek(&self) -> T::Balance { - self.0 - } + type Opposite = PositiveImbalance; + + fn zero() -> Self { + Self(Zero::zero()) + } + + fn drop_zero(self) -> result::Result<(), Self> { + if self.0.is_zero() { + Ok(()) + } else { + Err(self) + } + } + + fn split(self, amount: T::Balance) -> (Self, Self) { + let first = self.0.min(amount); + let second = self.0 - first; + + mem::forget(self); + (Self(first), Self(second)) + } + + fn merge(mut self, other: Self) -> Self { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + + self + } + + fn subsume(&mut self, other: Self) { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + } + + fn offset(self, other: Self::Opposite) -> SameOrOther { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + match a.cmp(&b) { + Ordering::Greater => SameOrOther::Same(Self(a - b)), + Ordering::Less => SameOrOther::Other(PositiveImbalance::new(b - a)), + Ordering::Equal => SameOrOther::None, + } + } + + fn peek(&self) -> T::Balance { + self.0 + } } impl, I: 'static> Drop for PositiveImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - TotalIssuance::::mutate(|v| *v = v.saturating_add(self.0)); - } + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + TotalIssuance::::mutate(|v| *v = v.saturating_add(self.0)); + } } impl, I: 'static> Drop for NegativeImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - TotalIssuance::::mutate(|v| *v = v.saturating_sub(self.0)); - } + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + TotalIssuance::::mutate(|v| *v = v.saturating_sub(self.0)); + } } diff --git a/crates/pallet-evm-balances/src/impl_currency.rs b/crates/pallet-evm-balances/src/impl_currency.rs index 9bb70f92e..1fd978a94 100644 --- a/crates/pallet-evm-balances/src/impl_currency.rs +++ b/crates/pallet-evm-balances/src/impl_currency.rs @@ -6,268 +6,268 @@ use super::*; impl, I: 'static> Currency<>::AccountId> for Pallet where - T::Balance: MaybeSerializeDeserialize + Debug, + T::Balance: MaybeSerializeDeserialize + Debug, { - type Balance = T::Balance; - type PositiveImbalance = PositiveImbalance; - type NegativeImbalance = NegativeImbalance; - - fn total_balance(who: &>::AccountId) -> Self::Balance { - Self::account(who).total() - } - - fn can_slash(who: &>::AccountId, value: Self::Balance) -> bool { - if value.is_zero() { - return true; - } - Self::free_balance(who) >= value - } - - fn total_issuance() -> Self::Balance { - TotalIssuance::::get() - } - - fn active_issuance() -> Self::Balance { - >::active_issuance() - } - - fn deactivate(amount: Self::Balance) { - >::deactivate(amount); - } - - fn reactivate(amount: Self::Balance) { - >::reactivate(amount); - } - - fn minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - - // Burn funds from the total issuance, returning a positive imbalance for the amount burned. - // Is a no-op if amount to be burned is zero. - fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { - if amount.is_zero() { - return PositiveImbalance::zero(); - } - >::mutate(|issued| { - *issued = issued.checked_sub(&amount).unwrap_or_else(|| { - amount = *issued; - Zero::zero() - }); - }); - PositiveImbalance::new(amount) - } - - // Create new funds into the total issuance, returning a negative imbalance - // for the amount issued. - // Is a no-op if amount to be issued it zero. - fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { - if amount.is_zero() { - return NegativeImbalance::zero(); - } - >::mutate(|issued| { - *issued = issued.checked_add(&amount).unwrap_or_else(|| { - amount = Self::Balance::max_value() - *issued; - Self::Balance::max_value() - }) - }); - NegativeImbalance::new(amount) - } - - fn free_balance(who: &>::AccountId) -> Self::Balance { - Self::account(who).free - } - - // We don't have any existing withdrawal restrictions like locked and reserved balance. - // Is a no-op if amount to be withdrawn is zero. - fn ensure_can_withdraw( - _who: &>::AccountId, - _amount: T::Balance, - _reasons: WithdrawReasons, - _new_balance: T::Balance, - ) -> DispatchResult { - Ok(()) - } - - // Transfer some free balance from `transactor` to `dest`, respecting existence requirements. - // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`. - fn transfer( - transactor: &>::AccountId, - dest: &>::AccountId, - value: Self::Balance, - existence_requirement: ExistenceRequirement, - ) -> DispatchResult { - if value.is_zero() || transactor == dest { - return Ok(()); - } - let keep_alive = match existence_requirement { - ExistenceRequirement::KeepAlive => Preservation::Preserve, - ExistenceRequirement::AllowDeath => Preservation::Expendable, - }; - >::transfer(transactor, dest, value, keep_alive)?; - Ok(()) - } - - /// Slash a target account `who`, returning the negative imbalance created and any left over - /// amount that could not be slashed. - /// - /// Is a no-op if `value` to be slashed is zero or the account does not exist. - /// - /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn - /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid - /// having to draw from reserved funds, however we err on the side of punishment if things are - /// inconsistent or `can_slash` wasn't used appropriately. - fn slash( - who: &>::AccountId, - value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()); - } - - if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value); - } - - let result = match Self::try_mutate_account_handling_dust( - who, - |account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { - // Best value is the most amount we can slash following liveness rules. - let actual = value.min(account.free); - account.free.saturating_reduce(actual); - let remaining = value.saturating_sub(actual); - Ok((NegativeImbalance::new(actual), remaining)) - }, - ) { - Ok((imbalance, remaining)) => { - Self::deposit_event(Event::Slashed { - who: who.clone(), - amount: value.saturating_sub(remaining), - }); - (imbalance, remaining) - } - Err(_) => (Self::NegativeImbalance::zero(), value), - }; - result - } - - /// Deposit some `value` into the free balance of an existing target account `who`. - /// - /// Is a no-op if the `value` to be deposited is zero. - fn deposit_into_existing( - who: &>::AccountId, - value: Self::Balance, - ) -> Result { - if value.is_zero() { - return Ok(PositiveImbalance::zero()); - } - - Self::try_mutate_account_handling_dust( - who, - |account, is_new| -> Result { - ensure!(!is_new, Error::::DeadAccount); - account.free = account - .free - .checked_add(&value) - .ok_or(ArithmeticError::Overflow)?; - Self::deposit_event(Event::Deposit { - who: who.clone(), - amount: value, - }); - Ok(PositiveImbalance::new(value)) - }, - ) - } - - /// Deposit some `value` into the free balance of `who`, possibly creating a new account. - /// - /// This function is a no-op if: - /// - the `value` to be deposited is zero; or - /// - the `value` to be deposited is less than the required ED and the account does not yet - /// exist; or - /// - the deposit would necessitate the account to exist and there are no provider references; - /// or - /// - `value` is so large it would cause the balance of `who` to overflow. - fn deposit_creating( - who: &>::AccountId, - value: Self::Balance, - ) -> Self::PositiveImbalance { - if value.is_zero() { - return Self::PositiveImbalance::zero(); - } - - Self::try_mutate_account_handling_dust( - who, - |account, is_new| -> Result { - let ed = T::ExistentialDeposit::get(); - ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); - - // defensive only: overflow should never happen, however in case it does, then this - // operation is a no-op. - account.free = match account.free.checked_add(&value) { - Some(x) => x, - None => return Ok(Self::PositiveImbalance::zero()), - }; - - Self::deposit_event(Event::Deposit { - who: who.clone(), - amount: value, - }); - Ok(PositiveImbalance::new(value)) - }, - ) - .unwrap_or_else(|_| Self::PositiveImbalance::zero()) - } - - /// Withdraw some free balance from an account, respecting existence requirements. - /// - /// Is a no-op if value to be withdrawn is zero. - fn withdraw( - who: &>::AccountId, - value: Self::Balance, - reasons: WithdrawReasons, - liveness: ExistenceRequirement, - ) -> result::Result { - if value.is_zero() { - return Ok(NegativeImbalance::zero()); - } - - Self::try_mutate_account_handling_dust( - who, - |account, _| -> Result { - let new_free_account = account - .free - .checked_sub(&value) - .ok_or(Error::::InsufficientBalance)?; - - // bail if we need to keep the account alive and this would kill it. - let ed = T::ExistentialDeposit::get(); - let would_be_dead = new_free_account < ed; - let would_kill = would_be_dead && account.free >= ed; - ensure!( - liveness == ExistenceRequirement::AllowDeath || !would_kill, - Error::::Expendability - ); - - Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; - - account.free = new_free_account; - - Self::deposit_event(Event::Withdraw { - who: who.clone(), - amount: value, - }); - Ok(NegativeImbalance::new(value)) - }, - ) - } - - /// Force the new free balance of a target account `who` to some new value `balance`. - fn make_free_balance_be( - who: &>::AccountId, - value: Self::Balance, - ) -> SignedImbalance { - Self::try_mutate_account_handling_dust( + type Balance = T::Balance; + type PositiveImbalance = PositiveImbalance; + type NegativeImbalance = NegativeImbalance; + + fn total_balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).total() + } + + fn can_slash(who: &>::AccountId, value: Self::Balance) -> bool { + if value.is_zero() { + return true; + } + Self::free_balance(who) >= value + } + + fn total_issuance() -> Self::Balance { + TotalIssuance::::get() + } + + fn active_issuance() -> Self::Balance { + >::active_issuance() + } + + fn deactivate(amount: Self::Balance) { + >::deactivate(amount); + } + + fn reactivate(amount: Self::Balance) { + >::reactivate(amount); + } + + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + + // Burn funds from the total issuance, returning a positive imbalance for the amount burned. + // Is a no-op if amount to be burned is zero. + fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { + if amount.is_zero() { + return PositiveImbalance::zero(); + } + >::mutate(|issued| { + *issued = issued.checked_sub(&amount).unwrap_or_else(|| { + amount = *issued; + Zero::zero() + }); + }); + PositiveImbalance::new(amount) + } + + // Create new funds into the total issuance, returning a negative imbalance + // for the amount issued. + // Is a no-op if amount to be issued it zero. + fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { + if amount.is_zero() { + return NegativeImbalance::zero(); + } + >::mutate(|issued| { + *issued = issued.checked_add(&amount).unwrap_or_else(|| { + amount = Self::Balance::max_value() - *issued; + Self::Balance::max_value() + }) + }); + NegativeImbalance::new(amount) + } + + fn free_balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).free + } + + // We don't have any existing withdrawal restrictions like locked and reserved balance. + // Is a no-op if amount to be withdrawn is zero. + fn ensure_can_withdraw( + _who: &>::AccountId, + _amount: T::Balance, + _reasons: WithdrawReasons, + _new_balance: T::Balance, + ) -> DispatchResult { + Ok(()) + } + + // Transfer some free balance from `transactor` to `dest`, respecting existence requirements. + // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`. + fn transfer( + transactor: &>::AccountId, + dest: &>::AccountId, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult { + if value.is_zero() || transactor == dest { + return Ok(()); + } + let keep_alive = match existence_requirement { + ExistenceRequirement::KeepAlive => Preservation::Preserve, + ExistenceRequirement::AllowDeath => Preservation::Expendable, + }; + >::transfer(transactor, dest, value, keep_alive)?; + Ok(()) + } + + /// Slash a target account `who`, returning the negative imbalance created and any left over + /// amount that could not be slashed. + /// + /// Is a no-op if `value` to be slashed is zero or the account does not exist. + /// + /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn + /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid + /// having to draw from reserved funds, however we err on the side of punishment if things are + /// inconsistent or `can_slash` wasn't used appropriately. + fn slash( + who: &>::AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()); + } + + if Self::total_balance(who).is_zero() { + return (NegativeImbalance::zero(), value); + } + + let result = match Self::try_mutate_account_handling_dust( + who, + |account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { + // Best value is the most amount we can slash following liveness rules. + let actual = value.min(account.free); + account.free.saturating_reduce(actual); + let remaining = value.saturating_sub(actual); + Ok((NegativeImbalance::new(actual), remaining)) + }, + ) { + Ok((imbalance, remaining)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(remaining), + }); + (imbalance, remaining) + } + Err(_) => (Self::NegativeImbalance::zero(), value), + }; + result + } + + /// Deposit some `value` into the free balance of an existing target account `who`. + /// + /// Is a no-op if the `value` to be deposited is zero. + fn deposit_into_existing( + who: &>::AccountId, + value: Self::Balance, + ) -> Result { + if value.is_zero() { + return Ok(PositiveImbalance::zero()); + } + + Self::try_mutate_account_handling_dust( + who, + |account, is_new| -> Result { + ensure!(!is_new, Error::::DeadAccount); + account.free = account + .free + .checked_add(&value) + .ok_or(ArithmeticError::Overflow)?; + Self::deposit_event(Event::Deposit { + who: who.clone(), + amount: value, + }); + Ok(PositiveImbalance::new(value)) + }, + ) + } + + /// Deposit some `value` into the free balance of `who`, possibly creating a new account. + /// + /// This function is a no-op if: + /// - the `value` to be deposited is zero; or + /// - the `value` to be deposited is less than the required ED and the account does not yet + /// exist; or + /// - the deposit would necessitate the account to exist and there are no provider references; + /// or + /// - `value` is so large it would cause the balance of `who` to overflow. + fn deposit_creating( + who: &>::AccountId, + value: Self::Balance, + ) -> Self::PositiveImbalance { + if value.is_zero() { + return Self::PositiveImbalance::zero(); + } + + Self::try_mutate_account_handling_dust( + who, + |account, is_new| -> Result { + let ed = T::ExistentialDeposit::get(); + ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); + + // defensive only: overflow should never happen, however in case it does, then this + // operation is a no-op. + account.free = match account.free.checked_add(&value) { + Some(x) => x, + None => return Ok(Self::PositiveImbalance::zero()), + }; + + Self::deposit_event(Event::Deposit { + who: who.clone(), + amount: value, + }); + Ok(PositiveImbalance::new(value)) + }, + ) + .unwrap_or_else(|_| Self::PositiveImbalance::zero()) + } + + /// Withdraw some free balance from an account, respecting existence requirements. + /// + /// Is a no-op if value to be withdrawn is zero. + fn withdraw( + who: &>::AccountId, + value: Self::Balance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> result::Result { + if value.is_zero() { + return Ok(NegativeImbalance::zero()); + } + + Self::try_mutate_account_handling_dust( + who, + |account, _| -> Result { + let new_free_account = account + .free + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + + // bail if we need to keep the account alive and this would kill it. + let ed = T::ExistentialDeposit::get(); + let would_be_dead = new_free_account < ed; + let would_kill = would_be_dead && account.free >= ed; + ensure!( + liveness == ExistenceRequirement::AllowDeath || !would_kill, + Error::::Expendability + ); + + Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; + + account.free = new_free_account; + + Self::deposit_event(Event::Withdraw { + who: who.clone(), + amount: value, + }); + Ok(NegativeImbalance::new(value)) + }, + ) + } + + /// Force the new free balance of a target account `who` to some new value `balance`. + fn make_free_balance_be( + who: &>::AccountId, + value: Self::Balance, + ) -> SignedImbalance { + Self::try_mutate_account_handling_dust( who, |account, is_new| @@ -296,5 +296,5 @@ where }, ) .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) - } + } } diff --git a/crates/pallet-evm-balances/src/impl_fungible.rs b/crates/pallet-evm-balances/src/impl_fungible.rs index 5c86426e3..d9a080f02 100644 --- a/crates/pallet-evm-balances/src/impl_fungible.rs +++ b/crates/pallet-evm-balances/src/impl_fungible.rs @@ -3,207 +3,207 @@ use super::*; impl, I: 'static> fungible::Inspect<>::AccountId> for Pallet { - type Balance = T::Balance; - - fn total_issuance() -> Self::Balance { - TotalIssuance::::get() - } - - fn active_issuance() -> Self::Balance { - TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) - } - - fn minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - - fn total_balance(who: &>::AccountId) -> Self::Balance { - Self::account(who).total() - } - - fn balance(who: &>::AccountId) -> Self::Balance { - Self::account(who).free - } - - fn reducible_balance( - who: &>::AccountId, - preservation: Preservation, - _force: Fortitude, - ) -> Self::Balance { - let a = Self::account(who); - let untouchable = match preservation { - Preservation::Expendable => Zero::zero(), - _ => T::ExistentialDeposit::get(), - }; - a.free.saturating_sub(untouchable) - } - - fn can_deposit( - who: &>::AccountId, - amount: Self::Balance, - provenance: Provenance, - ) -> DepositConsequence { - if amount.is_zero() { - return DepositConsequence::Success; - } - - if provenance == Provenance::Minted - && TotalIssuance::::get().checked_add(&amount).is_none() - { - return DepositConsequence::Overflow; - } - - let account = Self::account(who); - match account.free.checked_add(&amount) { - None => return DepositConsequence::Overflow, - Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum, - Some(x) => x, - }; - - // NOTE: We assume that we are a provider, so don't need to do any checks in the - // case of account creation. - - DepositConsequence::Success - } - - fn can_withdraw( - who: &>::AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - if amount.is_zero() { - return WithdrawConsequence::Success; - } - - if TotalIssuance::::get().checked_sub(&amount).is_none() { - return WithdrawConsequence::Underflow; - } - - let account = Self::account(who); - let new_free_balance = match account.free.checked_sub(&amount) { - Some(x) => x, - None => return WithdrawConsequence::BalanceLow, - }; - - // Provider restriction - total account balance cannot be reduced to zero if it cannot - // sustain the loss of a provider reference. - // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes, - // then this will need to adapt accordingly. - let ed = T::ExistentialDeposit::get(); - if new_free_balance < ed { - return WithdrawConsequence::ReducedToZero(new_free_balance); - } - - WithdrawConsequence::Success - } + type Balance = T::Balance; + + fn total_issuance() -> Self::Balance { + TotalIssuance::::get() + } + + fn active_issuance() -> Self::Balance { + TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) + } + + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + + fn total_balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).total() + } + + fn balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).free + } + + fn reducible_balance( + who: &>::AccountId, + preservation: Preservation, + _force: Fortitude, + ) -> Self::Balance { + let a = Self::account(who); + let untouchable = match preservation { + Preservation::Expendable => Zero::zero(), + _ => T::ExistentialDeposit::get(), + }; + a.free.saturating_sub(untouchable) + } + + fn can_deposit( + who: &>::AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + if amount.is_zero() { + return DepositConsequence::Success; + } + + if provenance == Provenance::Minted + && TotalIssuance::::get().checked_add(&amount).is_none() + { + return DepositConsequence::Overflow; + } + + let account = Self::account(who); + match account.free.checked_add(&amount) { + None => return DepositConsequence::Overflow, + Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum, + Some(x) => x, + }; + + // NOTE: We assume that we are a provider, so don't need to do any checks in the + // case of account creation. + + DepositConsequence::Success + } + + fn can_withdraw( + who: &>::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + if amount.is_zero() { + return WithdrawConsequence::Success; + } + + if TotalIssuance::::get().checked_sub(&amount).is_none() { + return WithdrawConsequence::Underflow; + } + + let account = Self::account(who); + let new_free_balance = match account.free.checked_sub(&amount) { + Some(x) => x, + None => return WithdrawConsequence::BalanceLow, + }; + + // Provider restriction - total account balance cannot be reduced to zero if it cannot + // sustain the loss of a provider reference. + // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes, + // then this will need to adapt accordingly. + let ed = T::ExistentialDeposit::get(); + if new_free_balance < ed { + return WithdrawConsequence::ReducedToZero(new_free_balance); + } + + WithdrawConsequence::Success + } } impl, I: 'static> fungible::Unbalanced<>::AccountId> for Pallet { - fn handle_dust(dust: fungible::Dust<>::AccountId, Self>) { - T::DustRemoval::on_unbalanced(NegativeImbalance::new(dust.0)); - } - - fn write_balance( - who: &>::AccountId, - amount: Self::Balance, - ) -> Result, DispatchError> { - let max_reduction = >::reducible_balance( - who, - Preservation::Expendable, - Fortitude::Force, - ); - let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult { - // Make sure the reduction (if there is one) is no more than the maximum allowed. - let reduction = account.free.saturating_sub(amount); - ensure!( - reduction <= max_reduction, - Error::::InsufficientBalance - ); - - account.free = amount; - Ok(()) - })?; - result?; - Ok(maybe_dust) - } - - fn set_total_issuance(amount: Self::Balance) { - TotalIssuance::::mutate(|t| *t = amount); - } - - fn deactivate(amount: Self::Balance) { - InactiveIssuance::::mutate(|b| b.saturating_accrue(amount)); - } - - fn reactivate(amount: Self::Balance) { - InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); - } + fn handle_dust(dust: fungible::Dust<>::AccountId, Self>) { + T::DustRemoval::on_unbalanced(NegativeImbalance::new(dust.0)); + } + + fn write_balance( + who: &>::AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + let max_reduction = >::reducible_balance( + who, + Preservation::Expendable, + Fortitude::Force, + ); + let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult { + // Make sure the reduction (if there is one) is no more than the maximum allowed. + let reduction = account.free.saturating_sub(amount); + ensure!( + reduction <= max_reduction, + Error::::InsufficientBalance + ); + + account.free = amount; + Ok(()) + })?; + result?; + Ok(maybe_dust) + } + + fn set_total_issuance(amount: Self::Balance) { + TotalIssuance::::mutate(|t| *t = amount); + } + + fn deactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_accrue(amount)); + } + + fn reactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); + } } impl, I: 'static> fungible::Mutate<>::AccountId> for Pallet { - fn done_mint_into(who: &>::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::Minted { - who: who.clone(), - amount, - }); - } - - fn done_burn_from(who: &>::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::Burned { - who: who.clone(), - amount, - }); - } - - fn done_shelve(who: &>::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::Suspended { - who: who.clone(), - amount, - }); - } - - fn done_restore(who: &>::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::Restored { - who: who.clone(), - amount, - }); - } - - fn done_transfer( - source: &>::AccountId, - dest: &>::AccountId, - amount: Self::Balance, - ) { - Self::deposit_event(Event::Transfer { - from: source.clone(), - to: dest.clone(), - amount, - }); - } + fn done_mint_into(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Minted { + who: who.clone(), + amount, + }); + } + + fn done_burn_from(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Burned { + who: who.clone(), + amount, + }); + } + + fn done_shelve(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Suspended { + who: who.clone(), + amount, + }); + } + + fn done_restore(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Restored { + who: who.clone(), + amount, + }); + } + + fn done_transfer( + source: &>::AccountId, + dest: &>::AccountId, + amount: Self::Balance, + ) { + Self::deposit_event(Event::Transfer { + from: source.clone(), + to: dest.clone(), + amount, + }); + } } impl, I: 'static> fungible::Balanced<>::AccountId> for Pallet { - type OnDropCredit = fungible::DecreaseIssuance<>::AccountId, Self>; - type OnDropDebt = fungible::IncreaseIssuance<>::AccountId, Self>; - - fn done_deposit(who: &>::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::Deposit { - who: who.clone(), - amount, - }); - } - - fn done_withdraw(who: &>::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::Withdraw { - who: who.clone(), - amount, - }); - } - - fn done_issue(amount: Self::Balance) { - Self::deposit_event(Event::Issued { amount }); - } - - fn done_rescind(amount: Self::Balance) { - Self::deposit_event(Event::Rescinded { amount }); - } + type OnDropCredit = fungible::DecreaseIssuance<>::AccountId, Self>; + type OnDropDebt = fungible::IncreaseIssuance<>::AccountId, Self>; + + fn done_deposit(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Deposit { + who: who.clone(), + amount, + }); + } + + fn done_withdraw(who: &>::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::Withdraw { + who: who.clone(), + amount, + }); + } + + fn done_issue(amount: Self::Balance) { + Self::deposit_event(Event::Issued { amount }); + } + + fn done_rescind(amount: Self::Balance) { + Self::deposit_event(Event::Rescinded { amount }); + } } diff --git a/crates/pallet-evm-balances/src/lib.rs b/crates/pallet-evm-balances/src/lib.rs index 5cdee7374..58b8f8c02 100644 --- a/crates/pallet-evm-balances/src/lib.rs +++ b/crates/pallet-evm-balances/src/lib.rs @@ -6,19 +6,19 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ - ensure, - traits::{ - fungible, - tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence}, - ExistenceRequirement, Get, Imbalance, OnUnbalanced, SignedImbalance, StorageVersion, - StoredMap, WithdrawReasons, - }, + ensure, + traits::{ + fungible, + tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence}, + ExistenceRequirement, Get, Imbalance, OnUnbalanced, SignedImbalance, StorageVersion, + StoredMap, WithdrawReasons, + }, }; use scale_codec::{Codec, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Zero}, - ArithmeticError, DispatchError, DispatchResult, RuntimeDebug, Saturating, + traits::{Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Zero}, + ArithmeticError, DispatchError, DispatchResult, RuntimeDebug, Saturating, }; use sp_std::{fmt::Debug, result}; @@ -43,266 +43,267 @@ const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[frame_support::pallet] pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use sp_runtime::{ - traits::{AtLeast32BitUnsigned, MaybeDisplay}, - FixedPointOperand, - }; - use sp_std::fmt::Debug; + use frame_support::pallet_prelude::*; + use sp_runtime::{ + traits::{AtLeast32BitUnsigned, MaybeDisplay}, + FixedPointOperand, + }; + use sp_std::fmt::Debug; - #[pallet::pallet] - #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(PhantomData<(T, I)>); + use super::*; - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From> - + IsType<::RuntimeEvent>; + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData<(T, I)>); - /// The user account identifier type. - type AccountId: Parameter - + Member - + MaybeSerializeDeserialize - + Debug - + MaybeDisplay - + Ord - + MaxEncodedLen; + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; - /// The balance of an account. - type Balance: Parameter - + Member - + AtLeast32BitUnsigned - + Codec - + Default - + Copy - + MaybeSerializeDeserialize - + Debug - + MaxEncodedLen - + TypeInfo - + FixedPointOperand; + /// The user account identifier type. + type AccountId: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + MaybeDisplay + + Ord + + MaxEncodedLen; - /// The minimum amount required to keep an account open. - #[pallet::constant] - type ExistentialDeposit: Get; + /// The balance of an account. + type Balance: Parameter + + Member + + AtLeast32BitUnsigned + + Codec + + Default + + Copy + + MaybeSerializeDeserialize + + Debug + + MaxEncodedLen + + TypeInfo + + FixedPointOperand; - /// The means of storing the balances of an account. - type AccountStore: StoredMap<>::AccountId, AccountData>; + /// The minimum amount required to keep an account open. + #[pallet::constant] + type ExistentialDeposit: Get; - /// Handler for the unbalanced reduction when removing a dust account. - type DustRemoval: OnUnbalanced>; - } + /// The means of storing the balances of an account. + type AccountStore: StoredMap<>::AccountId, AccountData>; - /// The total units issued. - #[pallet::storage] - #[pallet::whitelist_storage] - pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; + /// Handler for the unbalanced reduction when removing a dust account. + type DustRemoval: OnUnbalanced>; + } - /// The total units of outstanding deactivated balance. - #[pallet::storage] - #[pallet::whitelist_storage] - pub type InactiveIssuance, I: 'static = ()> = - StorageValue<_, T::Balance, ValueQuery>; + /// The total units issued. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event, I: 'static = ()> { - /// An account was created with some free balance. - Endowed { - account: >::AccountId, - free_balance: T::Balance, - }, - /// An account was removed whose balance was non-zero but below ExistentialDeposit, - /// resulting in an outright loss. - DustLost { - account: >::AccountId, - amount: T::Balance, - }, - /// Transfer succeeded. - Transfer { - from: >::AccountId, - to: >::AccountId, - amount: T::Balance, - }, - /// A balance was set by root. - BalanceSet { - who: >::AccountId, - free: T::Balance, - }, - /// Some amount was deposited (e.g. for transaction fees). - Deposit { - who: >::AccountId, - amount: T::Balance, - }, - /// Some amount was withdrawn from the account (e.g. for transaction fees). - Withdraw { - who: >::AccountId, - amount: T::Balance, - }, - /// Some amount was removed from the account (e.g. for misbehavior). - Slashed { - who: >::AccountId, - amount: T::Balance, - }, - /// Some amount was minted into an account. - Minted { - who: >::AccountId, - amount: T::Balance, - }, - /// Some amount was burned from an account. - Burned { - who: >::AccountId, - amount: T::Balance, - }, - /// Some amount was suspended from an account (it can be restored later). - Suspended { - who: >::AccountId, - amount: T::Balance, - }, - /// Some amount was restored into an account. - Restored { - who: >::AccountId, - amount: T::Balance, - }, - /// Total issuance was increased by `amount`, creating a credit to be balanced. - Issued { amount: T::Balance }, - /// Total issuance was decreased by `amount`, creating a debt to be balanced. - Rescinded { amount: T::Balance }, - } + /// The total units of outstanding deactivated balance. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type InactiveIssuance, I: 'static = ()> = + StorageValue<_, T::Balance, ValueQuery>; - #[pallet::error] - pub enum Error { - /// Account liquidity restrictions prevent withdrawal. - LiquidityRestrictions, - /// Balance too low to send value. - InsufficientBalance, - /// Value too low to create account due to existential deposit. - ExistentialDeposit, - /// Transfer/payment would kill account. - Expendability, - /// Beneficiary account must pre-exist. - DeadAccount, - } + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// An account was created with some free balance. + Endowed { + account: >::AccountId, + free_balance: T::Balance, + }, + /// An account was removed whose balance was non-zero but below ExistentialDeposit, + /// resulting in an outright loss. + DustLost { + account: >::AccountId, + amount: T::Balance, + }, + /// Transfer succeeded. + Transfer { + from: >::AccountId, + to: >::AccountId, + amount: T::Balance, + }, + /// A balance was set by root. + BalanceSet { + who: >::AccountId, + free: T::Balance, + }, + /// Some amount was deposited (e.g. for transaction fees). + Deposit { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was withdrawn from the account (e.g. for transaction fees). + Withdraw { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was removed from the account (e.g. for misbehavior). + Slashed { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was minted into an account. + Minted { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was burned from an account. + Burned { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was suspended from an account (it can be restored later). + Suspended { + who: >::AccountId, + amount: T::Balance, + }, + /// Some amount was restored into an account. + Restored { + who: >::AccountId, + amount: T::Balance, + }, + /// Total issuance was increased by `amount`, creating a credit to be balanced. + Issued { amount: T::Balance }, + /// Total issuance was decreased by `amount`, creating a debt to be balanced. + Rescinded { amount: T::Balance }, + } + + #[pallet::error] + pub enum Error { + /// Account liquidity restrictions prevent withdrawal. + LiquidityRestrictions, + /// Balance too low to send value. + InsufficientBalance, + /// Value too low to create account due to existential deposit. + ExistentialDeposit, + /// Transfer/payment would kill account. + Expendability, + /// Beneficiary account must pre-exist. + DeadAccount, + } } impl, I: 'static> Pallet { - /// Get the free balance of an account. - pub fn free_balance( - who: impl sp_std::borrow::Borrow<>::AccountId>, - ) -> T::Balance { - Self::account(who.borrow()).free - } + /// Get the free balance of an account. + pub fn free_balance( + who: impl sp_std::borrow::Borrow<>::AccountId>, + ) -> T::Balance { + Self::account(who.borrow()).free + } - /// Get all data information for an account. - fn account(who: &>::AccountId) -> AccountData { - T::AccountStore::get(who) - } + /// Get all data information for an account. + fn account(who: &>::AccountId) -> AccountData { + T::AccountStore::get(who) + } - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. - /// - /// It returns the result from the closure. Any dust is handled through the low-level - /// `fungible::Unbalanced` trap-door for legacy dust management. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - pub(crate) fn try_mutate_account_handling_dust>( - who: &>::AccountId, - f: impl FnOnce(&mut AccountData, bool) -> Result, - ) -> Result { - let (r, maybe_dust) = Self::try_mutate_account(who, f)?; - if let Some(dust) = maybe_dust { - >::handle_raw_dust(dust); - } - Ok(r) - } + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. + /// + /// It returns the result from the closure. Any dust is handled through the low-level + /// `fungible::Unbalanced` trap-door for legacy dust management. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + pub(crate) fn try_mutate_account_handling_dust>( + who: &>::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> Result { + let (r, maybe_dust) = Self::try_mutate_account(who, f)?; + if let Some(dust) = maybe_dust { + >::handle_raw_dust(dust); + } + Ok(r) + } - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. - /// - /// It returns both the result from the closure, and an optional amount of dust - /// which should be handled once it is known that all nested mutates that could affect - /// storage items what the dust handler touches have completed. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - pub(crate) fn mutate_account( - who: &>::AccountId, - f: impl FnOnce(&mut AccountData) -> R, - ) -> Result<(R, Option), DispatchError> { - Self::try_mutate_account(who, |a, _| -> Result { Ok(f(a)) }) - } + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. + /// + /// It returns both the result from the closure, and an optional amount of dust + /// which should be handled once it is known that all nested mutates that could affect + /// storage items what the dust handler touches have completed. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + pub(crate) fn mutate_account( + who: &>::AccountId, + f: impl FnOnce(&mut AccountData) -> R, + ) -> Result<(R, Option), DispatchError> { + Self::try_mutate_account(who, |a, _| -> Result { Ok(f(a)) }) + } - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the - /// result of `f` is an `Err`. - /// - /// It returns both the result from the closure, and an optional amount of dust - /// which should be handled once it is known that all nested mutates that could affect - /// storage items what the dust handler touches have completed. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - fn try_mutate_account>( - who: &>::AccountId, - f: impl FnOnce(&mut AccountData, bool) -> Result, - ) -> Result<(R, Option), E> { - let result = T::AccountStore::try_mutate_exists(who, |maybe_account| { - let is_new = maybe_account.is_none(); - let mut account = maybe_account.take().unwrap_or_default(); - f(&mut account, is_new).map(move |result| { - let maybe_endowed = if is_new { Some(account.free) } else { None }; + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the + /// result of `f` is an `Err`. + /// + /// It returns both the result from the closure, and an optional amount of dust + /// which should be handled once it is known that all nested mutates that could affect + /// storage items what the dust handler touches have completed. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + fn try_mutate_account>( + who: &>::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> Result<(R, Option), E> { + let result = T::AccountStore::try_mutate_exists(who, |maybe_account| { + let is_new = maybe_account.is_none(); + let mut account = maybe_account.take().unwrap_or_default(); + f(&mut account, is_new).map(move |result| { + let maybe_endowed = if is_new { Some(account.free) } else { None }; - // Handle any steps needed after mutating an account. - // - // This includes DustRemoval unbalancing, in the case than the `new` account's total - // balance is non-zero but below ED. - // - // Updates `maybe_account` to `Some` iff the account has sufficient balance. - // Evaluates `maybe_dust`, which is `Some` containing the dust to be dropped, iff - // some dust should be dropped. - // - // We should never be dropping if reserved is non-zero. Reserved being non-zero - // should imply that we have a consumer ref, so this is economically safe. - let maybe_dust = if account.free < T::ExistentialDeposit::get() { - if account.free.is_zero() { - None - } else { - Some(account.free) - } - } else { - assert!(account.free.is_zero() || account.free >= T::ExistentialDeposit::get()); - *maybe_account = Some(account); - None - }; + // Handle any steps needed after mutating an account. + // + // This includes DustRemoval unbalancing, in the case than the `new` account's total + // balance is non-zero but below ED. + // + // Updates `maybe_account` to `Some` iff the account has sufficient balance. + // Evaluates `maybe_dust`, which is `Some` containing the dust to be dropped, iff + // some dust should be dropped. + // + // We should never be dropping if reserved is non-zero. Reserved being non-zero + // should imply that we have a consumer ref, so this is economically safe. + let maybe_dust = if account.free < T::ExistentialDeposit::get() { + if account.free.is_zero() { + None + } else { + Some(account.free) + } + } else { + assert!(account.free.is_zero() || account.free >= T::ExistentialDeposit::get()); + *maybe_account = Some(account); + None + }; - (maybe_endowed, maybe_dust, result) - }) - }); - result.map(|(maybe_endowed, maybe_dust, result)| { - if let Some(endowed) = maybe_endowed { - Self::deposit_event(Event::Endowed { - account: who.clone(), - free_balance: endowed, - }); - } - if let Some(amount) = maybe_dust { - Pallet::::deposit_event(Event::DustLost { - account: who.clone(), - amount, - }); - } - (result, maybe_dust) - }) - } + (maybe_endowed, maybe_dust, result) + }) + }); + result.map(|(maybe_endowed, maybe_dust, result)| { + if let Some(endowed) = maybe_endowed { + Self::deposit_event(Event::Endowed { + account: who.clone(), + free_balance: endowed, + }); + } + if let Some(amount) = maybe_dust { + Pallet::::deposit_event(Event::DustLost { + account: who.clone(), + amount, + }); + } + (result, maybe_dust) + }) + } } diff --git a/crates/pallet-evm-balances/src/mock.rs b/crates/pallet-evm-balances/src/mock.rs index ed44d8b16..fc808139e 100644 --- a/crates/pallet-evm-balances/src/mock.rs +++ b/crates/pallet-evm-balances/src/mock.rs @@ -20,16 +20,16 @@ use std::collections::BTreeMap; use frame_support::{ - parameter_types, - traits::{ConstU32, ConstU64, FindAuthor}, - weights::Weight, + parameter_types, + traits::{ConstU32, ConstU64, FindAuthor}, + weights::Weight, }; use pallet_evm::{EnsureAddressNever, FixedGasWeightMapping, IdentityAddressMapping}; use sp_core::{H160, H256, U256}; use sp_runtime::{ - generic, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, ConsensusEngineId, + generic, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, ConsensusEngineId, }; use sp_std::{boxed::Box, prelude::*, str::FromStr}; @@ -38,192 +38,192 @@ use crate::{self as pallet_evm_balances, *}; pub(crate) const INIT_BALANCE: u64 = 10_000_000_000_000_000; pub(crate) fn alice() -> H160 { - H160::from_str("1000000000000000000000000000000000000000").unwrap() + H160::from_str("1000000000000000000000000000000000000000").unwrap() } pub(crate) fn bob() -> H160 { - H160::from_str("2000000000000000000000000000000000000000").unwrap() + H160::from_str("2000000000000000000000000000000000000000").unwrap() } 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, - Timestamp: pallet_timestamp, - EvmSystem: pallet_evm_system, - EvmBalances: pallet_evm_balances, - EVM: pallet_evm, - } + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Timestamp: pallet_timestamp, + EvmSystem: pallet_evm_system, + EvmBalances: pallet_evm_balances, + EVM: pallet_evm, + } } impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = generic::Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = generic::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet_evm_system::Config for Test { - type RuntimeEvent = RuntimeEvent; - type AccountId = H160; - type Index = u64; - type AccountData = AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); + type RuntimeEvent = RuntimeEvent; + type AccountId = H160; + type Index = u64; + type AccountData = AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); } impl pallet_evm_balances::Config for Test { - type RuntimeEvent = RuntimeEvent; - type AccountId = H160; - type Balance = u64; - type ExistentialDeposit = ConstU64<1>; - type AccountStore = EvmSystem; - type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type AccountId = H160; + type Balance = u64; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = EvmSystem; + type DustRemoval = (); } parameter_types! { - pub const MinimumPeriod: u64 = 1000; + pub const MinimumPeriod: u64 = 1000; } impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); } pub struct FixedGasPrice; impl pallet_evm::FeeCalculator for FixedGasPrice { - fn min_gas_price() -> (U256, Weight) { - // Return some meaningful gas price and weight - (1_000_000_000u128.into(), Weight::from_parts(7u64, 0)) - } + fn min_gas_price() -> (U256, Weight) { + // Return some meaningful gas price and weight + (1_000_000_000u128.into(), Weight::from_parts(7u64, 0)) + } } pub struct FindAuthorTruncated; impl FindAuthor for FindAuthorTruncated { - fn find_author<'a, I>(_digests: I) -> Option - where - I: 'a + IntoIterator, - { - Some(H160::from_str("1234500000000000000000000000000000000000").unwrap()) - } + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(H160::from_str("1234500000000000000000000000000000000000").unwrap()) + } } const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; parameter_types! { - pub BlockGasLimit: U256 = U256::max_value(); - pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); + pub BlockGasLimit: U256 = U256::max_value(); + pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); + pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); } impl pallet_evm::Config for Test { - type AccountProvider = EvmSystem; - type FeeCalculator = FixedGasPrice; - type GasWeightMapping = FixedGasWeightMapping; - type WeightPerGas = WeightPerGas; - type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; - type CallOrigin = - EnsureAddressNever<::AccountId>; - type WithdrawOrigin = - EnsureAddressNever<::AccountId>; - type AddressMapping = IdentityAddressMapping; - type Currency = EvmBalances; - type RuntimeEvent = RuntimeEvent; - type PrecompilesType = (); - type PrecompilesValue = (); - type ChainId = (); - type BlockGasLimit = BlockGasLimit; - type Runner = pallet_evm::runner::stack::Runner; - type OnChargeTransaction = (); - type OnCreate = (); - type FindAuthor = FindAuthorTruncated; - type GasLimitPovSizeRatio = GasLimitPovSizeRatio; - type Timestamp = Timestamp; - type WeightInfo = (); + type AccountProvider = EvmSystem; + type FeeCalculator = FixedGasPrice; + type GasWeightMapping = FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type CallOrigin = + EnsureAddressNever<::AccountId>; + type WithdrawOrigin = + EnsureAddressNever<::AccountId>; + type AddressMapping = IdentityAddressMapping; + type Currency = EvmBalances; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type ChainId = (); + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = FindAuthorTruncated; + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type Timestamp = Timestamp; + type WeightInfo = (); } /// Build test externalities from the custom genesis. /// Using this call requires manual assertions on the genesis init logic. pub fn new_test_ext() -> sp_io::TestExternalities { - // Build genesis. - let config = GenesisConfig { - evm: EVMConfig { - accounts: { - let mut map = BTreeMap::new(); - let init_genesis_account = fp_evm::GenesisAccount { - balance: INIT_BALANCE.into(), - code: Default::default(), - nonce: Default::default(), - storage: Default::default(), - }; - map.insert(alice(), init_genesis_account.clone()); - map.insert(bob(), init_genesis_account); - map - }, - }, - ..Default::default() - }; - let storage = config.build_storage().unwrap(); - - // Make test externalities from the storage. - storage.into() + // Build genesis. + let config = GenesisConfig { + evm: EVMConfig { + accounts: { + let mut map = BTreeMap::new(); + let init_genesis_account = fp_evm::GenesisAccount { + balance: INIT_BALANCE.into(), + code: Default::default(), + nonce: Default::default(), + storage: Default::default(), + }; + map.insert(alice(), init_genesis_account.clone()); + map.insert(bob(), init_genesis_account); + map + }, + }, + ..Default::default() + }; + let storage = config.build_storage().unwrap(); + + // Make test externalities from the storage. + storage.into() } pub fn runtime_lock() -> std::sync::MutexGuard<'static, ()> { - static MOCK_RUNTIME_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(()); + 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(), - } + // 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(), + } } pub trait TestExternalitiesExt { - fn execute_with_ext(&mut self, execute: E) -> R - where - E: for<'e> FnOnce(&'e ()) -> R; + 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 - } + 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-evm-balances/src/tests/currency.rs b/crates/pallet-evm-balances/src/tests/currency.rs index c0b499b21..a9496d927 100644 --- a/crates/pallet-evm-balances/src/tests/currency.rs +++ b/crates/pallet-evm-balances/src/tests/currency.rs @@ -9,571 +9,571 @@ use crate::{mock::*, tests::assert_total_issuance_invariant, *}; #[test] fn total_issuance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the total issuance value. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - }); + new_test_ext().execute_with_ext(|_| { + // Check the total issuance value. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + }); } #[test] fn total_balance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the total balance value. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - }); + new_test_ext().execute_with_ext(|_| { + // Check the total balance value. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + }); } #[test] fn free_balance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the free balance value. - assert_eq!(EvmBalances::free_balance(&alice()), INIT_BALANCE); - }); + new_test_ext().execute_with_ext(|_| { + // Check the free balance value. + assert_eq!(EvmBalances::free_balance(&alice()), INIT_BALANCE); + }); } #[test] fn can_slash_works() { - new_test_ext().execute_with_ext(|_| { - // Check possible slashing if slashing value is less than current balance. - assert!(EvmBalances::can_slash(&alice(), 100)); + new_test_ext().execute_with_ext(|_| { + // Check possible slashing if slashing value is less than current balance. + assert!(EvmBalances::can_slash(&alice(), 100)); - // Check possible slashing if slashing value is equal to current balance. - assert!(EvmBalances::can_slash(&alice(), INIT_BALANCE)); + // Check possible slashing if slashing value is equal to current balance. + assert!(EvmBalances::can_slash(&alice(), INIT_BALANCE)); - // Check slashing restriction if slashing value that is greater than current balance. - assert!(!EvmBalances::can_slash(&alice(), INIT_BALANCE + 1)); - }); + // Check slashing restriction if slashing value that is greater than current balance. + assert!(!EvmBalances::can_slash(&alice(), INIT_BALANCE + 1)); + }); } #[test] fn active_issuance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the active issuance value. - assert_eq!(EvmBalances::active_issuance(), 2 * INIT_BALANCE); - }); + new_test_ext().execute_with_ext(|_| { + // Check the active issuance value. + assert_eq!(EvmBalances::active_issuance(), 2 * INIT_BALANCE); + }); } #[test] fn deactivate_reactivate_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(>::get(), 0); - - // Deactivate some balance. - EvmBalances::deactivate(100); - // Assert state changes. - assert_eq!(>::get(), 100); - - // Reactivate some balance. - EvmBalances::reactivate(40); - // Assert state changes. - assert_eq!(>::get(), 60); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(>::get(), 0); + + // Deactivate some balance. + EvmBalances::deactivate(100); + // Assert state changes. + assert_eq!(>::get(), 100); + + // Reactivate some balance. + EvmBalances::reactivate(40); + // Assert state changes. + assert_eq!(>::get(), 60); + + assert_total_issuance_invariant(); + }); } #[test] fn minimum_balance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the minimum balance value. - assert_eq!(EvmBalances::minimum_balance(), 1); - }); + new_test_ext().execute_with_ext(|_| { + // Check the minimum balance value. + assert_eq!(EvmBalances::minimum_balance(), 1); + }); } #[test] fn burn_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Burn some balance. - let imbalance = EvmBalances::burn(100); + // Burn some balance. + let imbalance = EvmBalances::burn(100); - // Assert state changes. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE - 100); - drop(imbalance); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + // Assert state changes. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE - 100); + drop(imbalance); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn issue_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Issue some balance. - let imbalance = EvmBalances::issue(100); + // Issue some balance. + let imbalance = EvmBalances::issue(100); - // Assert state changes. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE + 100); - drop(imbalance); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + // Assert state changes. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE + 100); + drop(imbalance); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn transfer_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let transfered_amount = 100; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_ok!(EvmBalances::transfer( - &alice(), - &bob(), - transfered_amount, - ExistenceRequirement::KeepAlive - )); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - transfered_amount - ); - assert_eq!( - EvmBalances::total_balance(&bob()), - INIT_BALANCE + transfered_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Transfer { - from: alice(), - to: bob(), - amount: transfered_amount, - })); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = 100; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + ExistenceRequirement::KeepAlive + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - transfered_amount + ); + assert_eq!( + EvmBalances::total_balance(&bob()), + INIT_BALANCE + transfered_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Transfer { + from: alice(), + to: bob(), + amount: transfered_amount, + })); + + assert_total_issuance_invariant(); + }); } #[test] fn transfer_works_full_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let transfered_amount = INIT_BALANCE; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_ok!(EvmBalances::transfer( - &alice(), - &bob(), - transfered_amount, - ExistenceRequirement::AllowDeath - )); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - transfered_amount - ); - assert_eq!( - EvmBalances::total_balance(&bob()), - INIT_BALANCE + transfered_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Transfer { - from: alice(), - to: bob(), - amount: transfered_amount, - })); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + ExistenceRequirement::AllowDeath + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - transfered_amount + ); + assert_eq!( + EvmBalances::total_balance(&bob()), + INIT_BALANCE + transfered_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Transfer { + from: alice(), + to: bob(), + amount: transfered_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); } #[test] fn transfer_fails_funds_unavailable() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let transfered_amount = INIT_BALANCE + 1; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_noop!( - EvmBalances::transfer( - &alice(), - &bob(), - transfered_amount, - ExistenceRequirement::KeepAlive - ), - TokenError::FundsUnavailable - ); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE + 1; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + ExistenceRequirement::KeepAlive + ), + TokenError::FundsUnavailable + ); + }); } #[test] fn transfer_fails_not_expendable() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let transfered_amount = INIT_BALANCE; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_noop!( - EvmBalances::transfer( - &alice(), - &bob(), - transfered_amount, - ExistenceRequirement::KeepAlive - ), - TokenError::NotExpendable - ); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + ExistenceRequirement::KeepAlive + ), + TokenError::NotExpendable + ); + }); } #[test] fn slash_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let slashed_amount = 1000; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert!(EvmBalances::slash(&alice(), slashed_amount).1.is_zero()); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - slashed_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Slashed { - who: alice(), - amount: slashed_amount, - })); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let slashed_amount = 1000; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert!(EvmBalances::slash(&alice(), slashed_amount).1.is_zero()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - slashed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Slashed { + who: alice(), + amount: slashed_amount, + })); + + assert_total_issuance_invariant(); + }); } #[test] fn slash_works_full_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let slashed_amount = INIT_BALANCE; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert!(EvmBalances::slash(&alice(), slashed_amount).1.is_zero()); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - slashed_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Slashed { - who: alice(), - amount: slashed_amount, - })); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let slashed_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert!(EvmBalances::slash(&alice(), slashed_amount).1.is_zero()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - slashed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Slashed { + who: alice(), + amount: slashed_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); } #[test] fn deposit_into_existing_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let deposited_amount = 10; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - let imbalance = EvmBalances::deposit_into_existing(&alice(), deposited_amount).unwrap(); - drop(imbalance); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE + deposited_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Deposit { - who: alice(), - amount: deposited_amount, - })); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let deposited_amount = 10; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let imbalance = EvmBalances::deposit_into_existing(&alice(), deposited_amount).unwrap(); + drop(imbalance); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + deposited_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Deposit { + who: alice(), + amount: deposited_amount, + })); + + assert_total_issuance_invariant(); + }); } #[test] fn deposit_into_existing_fails_overflow() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let deposited_amount = u64::MAX; - - // Invoke the function under test. - assert_noop!( - EvmBalances::deposit_into_existing(&alice(), deposited_amount), - ArithmeticError::Overflow - ); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let deposited_amount = u64::MAX; + + // Invoke the function under test. + assert_noop!( + EvmBalances::deposit_into_existing(&alice(), deposited_amount), + ArithmeticError::Overflow + ); + }); } #[test] fn deposit_into_existing_fails_dead_account() { - new_test_ext().execute_with_ext(|_| { - let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + new_test_ext().execute_with_ext(|_| { + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&charlie), 0); + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&charlie), 0); - let deposited_amount = 10; + let deposited_amount = 10; - // Invoke the function under test. - assert_noop!( - EvmBalances::deposit_into_existing(&charlie, deposited_amount), - Error::::DeadAccount - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::deposit_into_existing(&charlie, deposited_amount), + Error::::DeadAccount + ); + }); } #[test] fn deposit_creating_works() { - new_test_ext().execute_with_ext(|_| { - // Prepare test preconditions. - let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); - let deposited_amount = 10; - assert!(!EvmSystem::account_exists(&charlie)); - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - let _ = EvmBalances::deposit_creating(&charlie, deposited_amount); - - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&charlie), deposited_amount); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Deposit { - who: charlie, - amount: deposited_amount, - })); - assert!(EvmSystem::account_exists(&charlie)); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::NewAccount { account: charlie }, - )); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let deposited_amount = 10; + assert!(!EvmSystem::account_exists(&charlie)); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let _ = EvmBalances::deposit_creating(&charlie, deposited_amount); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&charlie), deposited_amount); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Deposit { + who: charlie, + amount: deposited_amount, + })); + assert!(EvmSystem::account_exists(&charlie)); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::NewAccount { account: charlie }, + )); + + assert_total_issuance_invariant(); + }); } #[test] fn withdraw_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let withdrawed_amount = 1000; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - let imbalance = EvmBalances::withdraw( - &alice(), - withdrawed_amount, - WithdrawReasons::FEE, - ExistenceRequirement::KeepAlive, - ) - .unwrap(); - drop(imbalance); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - withdrawed_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { - who: alice(), - amount: withdrawed_amount, - })); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = 1000; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let imbalance = EvmBalances::withdraw( + &alice(), + withdrawed_amount, + WithdrawReasons::FEE, + ExistenceRequirement::KeepAlive, + ) + .unwrap(); + drop(imbalance); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - withdrawed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { + who: alice(), + amount: withdrawed_amount, + })); + + assert_total_issuance_invariant(); + }); } #[test] fn withdraw_works_full_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let withdrawed_amount = INIT_BALANCE; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - let imbalance = EvmBalances::withdraw( - &alice(), - withdrawed_amount, - WithdrawReasons::FEE, - ExistenceRequirement::AllowDeath, - ) - .unwrap(); - drop(imbalance); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - withdrawed_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { - who: alice(), - amount: withdrawed_amount, - })); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let imbalance = EvmBalances::withdraw( + &alice(), + withdrawed_amount, + WithdrawReasons::FEE, + ExistenceRequirement::AllowDeath, + ) + .unwrap(); + drop(imbalance); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - withdrawed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { + who: alice(), + amount: withdrawed_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); } #[test] fn withdraw_fails_insufficient_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let withdrawed_amount = INIT_BALANCE + 1; - - // Invoke the function under test. - assert_noop!( - EvmBalances::withdraw( - &alice(), - withdrawed_amount, - WithdrawReasons::TRANSFER, - ExistenceRequirement::AllowDeath - ), - Error::::InsufficientBalance - ); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_noop!( + EvmBalances::withdraw( + &alice(), + withdrawed_amount, + WithdrawReasons::TRANSFER, + ExistenceRequirement::AllowDeath + ), + Error::::InsufficientBalance + ); + }); } #[test] fn withdraw_fails_expendability() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let withdrawed_amount = INIT_BALANCE; - - // Invoke the function under test. - assert_noop!( - EvmBalances::withdraw( - &alice(), - withdrawed_amount, - WithdrawReasons::TRANSFER, - ExistenceRequirement::KeepAlive - ), - Error::::Expendability - ); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = INIT_BALANCE; + + // Invoke the function under test. + assert_noop!( + EvmBalances::withdraw( + &alice(), + withdrawed_amount, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive + ), + Error::::Expendability + ); + }); } #[test] fn make_free_balance_be_works() { - new_test_ext().execute_with_ext(|_| { - // Prepare test preconditions. - let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); - let made_free_balance = 100; + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let made_free_balance = 100; - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&charlie), 0); + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&charlie), 0); - // Invoke the function under test. - let _ = EvmBalances::make_free_balance_be(&charlie, made_free_balance); + // Invoke the function under test. + let _ = EvmBalances::make_free_balance_be(&charlie, made_free_balance); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&charlie), made_free_balance); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&charlie), made_free_balance); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn evm_system_account_should_be_reaped() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert!(EvmSystem::account_exists(&bob())); - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_ok!(EvmBalances::transfer( - &bob(), - &alice(), - INIT_BALANCE, - ExistenceRequirement::AllowDeath - )); - - // Assert state changes. - assert_eq!(EvmBalances::free_balance(&bob()), 0); - assert!(!EvmSystem::account_exists(&bob())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: bob() }, - )); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert!(EvmSystem::account_exists(&bob())); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &bob(), + &alice(), + INIT_BALANCE, + ExistenceRequirement::AllowDeath + )); + + // Assert state changes. + assert_eq!(EvmBalances::free_balance(&bob()), 0); + assert!(!EvmSystem::account_exists(&bob())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: bob() }, + )); + + assert_total_issuance_invariant(); + }); } #[test] fn transferring_too_high_value_should_not_panic() { - new_test_ext().execute_with_ext(|_| { - // Prepare test preconditions. - let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); - let eve = H160::from_str("1000000000000000000000000000000000000004").unwrap(); - EvmBalances::make_free_balance_be(&charlie, u64::MAX); - EvmBalances::make_free_balance_be(&eve, 1); - - // Invoke the function under test. - assert_noop!( - EvmBalances::transfer(&charlie, &eve, u64::MAX, ExistenceRequirement::AllowDeath), - ArithmeticError::Overflow, - ); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let eve = H160::from_str("1000000000000000000000000000000000000004").unwrap(); + EvmBalances::make_free_balance_be(&charlie, u64::MAX); + EvmBalances::make_free_balance_be(&eve, 1); + + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer(&charlie, &eve, u64::MAX, ExistenceRequirement::AllowDeath), + ArithmeticError::Overflow, + ); + }); } diff --git a/crates/pallet-evm-balances/src/tests/fungible.rs b/crates/pallet-evm-balances/src/tests/fungible.rs index fa587a885..562c6c5a0 100644 --- a/crates/pallet-evm-balances/src/tests/fungible.rs +++ b/crates/pallet-evm-balances/src/tests/fungible.rs @@ -1,11 +1,11 @@ //! Tests regarding the functionality of the fungible trait set implementations. use frame_support::{ - assert_noop, assert_ok, - traits::{ - fungible::{Balanced, Inspect, Mutate, Unbalanced}, - tokens::Precision, - }, + assert_noop, assert_ok, + traits::{ + fungible::{Balanced, Inspect, Mutate, Unbalanced}, + tokens::Precision, + }, }; use sp_core::H160; use sp_runtime::TokenError; @@ -15,1004 +15,1004 @@ use crate::{mock::*, tests::assert_total_issuance_invariant, *}; #[test] fn total_issuance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the total issuance value. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - }); + new_test_ext().execute_with_ext(|_| { + // Check the total issuance value. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + }); } #[test] fn active_issuance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the active issuance value. - assert_eq!(EvmBalances::active_issuance(), 2 * INIT_BALANCE); - }); + new_test_ext().execute_with_ext(|_| { + // Check the active issuance value. + assert_eq!(EvmBalances::active_issuance(), 2 * INIT_BALANCE); + }); } #[test] fn minimum_balance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the minimum balance value. - assert_eq!(EvmBalances::minimum_balance(), 1); - }); + new_test_ext().execute_with_ext(|_| { + // Check the minimum balance value. + assert_eq!(EvmBalances::minimum_balance(), 1); + }); } #[test] fn total_balance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the total balance value. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - }); + new_test_ext().execute_with_ext(|_| { + // Check the total balance value. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + }); } #[test] fn balance_works() { - new_test_ext().execute_with_ext(|_| { - // Check the balance value. - assert_eq!(EvmBalances::balance(&alice()), INIT_BALANCE); - }); + new_test_ext().execute_with_ext(|_| { + // Check the balance value. + assert_eq!(EvmBalances::balance(&alice()), INIT_BALANCE); + }); } #[test] fn reducable_balance_works() { - new_test_ext().execute_with_ext(|_| { - assert_eq!( - EvmBalances::reducible_balance(&alice(), Preservation::Expendable, Fortitude::Polite), - INIT_BALANCE - ); + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Expendable, Fortitude::Polite), + INIT_BALANCE + ); - assert_eq!( - EvmBalances::reducible_balance(&alice(), Preservation::Expendable, Fortitude::Force), - INIT_BALANCE - ); + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Expendable, Fortitude::Force), + INIT_BALANCE + ); - assert_eq!( - EvmBalances::reducible_balance(&alice(), Preservation::Preserve, Fortitude::Polite), - INIT_BALANCE - 1 - ); + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Preserve, Fortitude::Polite), + INIT_BALANCE - 1 + ); - assert_eq!( - EvmBalances::reducible_balance(&alice(), Preservation::Preserve, Fortitude::Force), - INIT_BALANCE - 1 - ); + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Preserve, Fortitude::Force), + INIT_BALANCE - 1 + ); - assert_eq!( - EvmBalances::reducible_balance(&alice(), Preservation::Protect, Fortitude::Polite), - INIT_BALANCE - 1 - ); + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Protect, Fortitude::Polite), + INIT_BALANCE - 1 + ); - assert_eq!( - EvmBalances::reducible_balance(&alice(), Preservation::Protect, Fortitude::Force), - INIT_BALANCE - 1 - ); - }); + assert_eq!( + EvmBalances::reducible_balance(&alice(), Preservation::Protect, Fortitude::Force), + INIT_BALANCE - 1 + ); + }); } #[test] fn can_deposit_works_success() { - new_test_ext().execute_with_ext(|_| { - assert_eq!( - EvmBalances::can_deposit(&alice(), 10, Provenance::Minted), - DepositConsequence::Success - ); - }); + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_deposit(&alice(), 10, Provenance::Minted), + DepositConsequence::Success + ); + }); } #[test] fn can_deposit_works_overflow() { - new_test_ext().execute_with_ext(|_| { - assert_eq!( - EvmBalances::can_deposit(&alice(), u64::MAX, Provenance::Minted), - DepositConsequence::Overflow - ); - }); + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_deposit(&alice(), u64::MAX, Provenance::Minted), + DepositConsequence::Overflow + ); + }); } #[test] fn can_withdraw_works_success() { - new_test_ext().execute_with_ext(|_| { - assert_eq!( - EvmBalances::can_withdraw(&alice(), 10), - WithdrawConsequence::Success - ); - }); + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_withdraw(&alice(), 10), + WithdrawConsequence::Success + ); + }); } #[test] fn can_withdraw_works_underflow() { - new_test_ext().execute_with_ext(|_| { - assert_eq!( - EvmBalances::can_withdraw(&alice(), u64::MAX), - WithdrawConsequence::Underflow - ); - }); + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_withdraw(&alice(), u64::MAX), + WithdrawConsequence::Underflow + ); + }); } #[test] fn can_withdraw_works_balance_low() { - new_test_ext().execute_with_ext(|_| { - assert_eq!( - EvmBalances::can_withdraw(&alice(), INIT_BALANCE + 1), - WithdrawConsequence::BalanceLow - ); - }); + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_withdraw(&alice(), INIT_BALANCE + 1), + WithdrawConsequence::BalanceLow + ); + }); } #[test] fn can_withdraw_works_reduced_to_zero() { - new_test_ext().execute_with_ext(|_| { - assert_eq!( - EvmBalances::can_withdraw(&alice(), INIT_BALANCE), - WithdrawConsequence::ReducedToZero(0) - ); - }); + new_test_ext().execute_with_ext(|_| { + assert_eq!( + EvmBalances::can_withdraw(&alice(), INIT_BALANCE), + WithdrawConsequence::ReducedToZero(0) + ); + }); } #[test] fn write_balance_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let write_balance = 10; + let write_balance = 10; - // Invoke the function under test. - assert_eq!( - EvmBalances::write_balance(&alice(), write_balance), - Ok(None) - ); + // Invoke the function under test. + assert_eq!( + EvmBalances::write_balance(&alice(), write_balance), + Ok(None) + ); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice()), write_balance); - }); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), write_balance); + }); } #[test] fn set_total_issuance_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - let set_total_issuance_balance = 100; + let set_total_issuance_balance = 100; - // Invoke the function under test. - EvmBalances::set_total_issuance(set_total_issuance_balance); + // Invoke the function under test. + EvmBalances::set_total_issuance(set_total_issuance_balance); - // Assert state changes. - assert_eq!(EvmBalances::total_issuance(), set_total_issuance_balance); - }); + // Assert state changes. + assert_eq!(EvmBalances::total_issuance(), set_total_issuance_balance); + }); } #[test] fn decrease_balance_works_exact_expendable() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let decreased_balance = 100; + let decreased_balance = 100; - // Invoke the function under test. - assert_ok!(EvmBalances::decrease_balance( - &alice(), - decreased_balance, - Precision::Exact, - Preservation::Expendable, - Fortitude::Polite - )); + // Invoke the function under test. + assert_ok!(EvmBalances::decrease_balance( + &alice(), + decreased_balance, + Precision::Exact, + Preservation::Expendable, + Fortitude::Polite + )); - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - decreased_balance - ); - }); + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - decreased_balance + ); + }); } #[test] fn decrease_balance_works_best_effort_preserve() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let decreased_balance = INIT_BALANCE + 1; + let decreased_balance = INIT_BALANCE + 1; - // Invoke the function under test. - assert_ok!(EvmBalances::decrease_balance( - &alice(), - decreased_balance, - Precision::BestEffort, - Preservation::Preserve, - Fortitude::Polite - )); + // Invoke the function under test. + assert_ok!(EvmBalances::decrease_balance( + &alice(), + decreased_balance, + Precision::BestEffort, + Preservation::Preserve, + Fortitude::Polite + )); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice()), 1); - }); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 1); + }); } #[test] fn decrease_balance_works_full_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let decreased_balance = INIT_BALANCE; + let decreased_balance = INIT_BALANCE; - // Invoke the function under test. - assert_ok!(EvmBalances::decrease_balance( - &alice(), - decreased_balance, - Precision::Exact, - Preservation::Expendable, - Fortitude::Polite - )); + // Invoke the function under test. + assert_ok!(EvmBalances::decrease_balance( + &alice(), + decreased_balance, + Precision::Exact, + Preservation::Expendable, + Fortitude::Polite + )); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice()), 0); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); - }); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 0); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + }); } #[test] fn decrease_balance_fails_funds_unavailable() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let decreased_balance = INIT_BALANCE + 1; + let decreased_balance = INIT_BALANCE + 1; - // Invoke the function under test. - assert_noop!( - EvmBalances::decrease_balance( - &alice(), - decreased_balance, - Precision::Exact, - Preservation::Preserve, - Fortitude::Polite - ), - TokenError::FundsUnavailable - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::decrease_balance( + &alice(), + decreased_balance, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite + ), + TokenError::FundsUnavailable + ); + }); } #[test] fn increase_balance_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let increased_balance = 100; + let increased_balance = 100; - // Invoke the function under test. - assert_ok!(EvmBalances::increase_balance( - &alice(), - increased_balance, - Precision::Exact, - )); + // Invoke the function under test. + assert_ok!(EvmBalances::increase_balance( + &alice(), + increased_balance, + Precision::Exact, + )); - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE + increased_balance - ); - }); + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + increased_balance + ); + }); } #[test] fn increase_balance_works_best_effort() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let increased_balance = u64::MAX; + let increased_balance = u64::MAX; - // Invoke the function under test. - assert_ok!(EvmBalances::increase_balance( - &alice(), - increased_balance, - Precision::BestEffort, - )); + // Invoke the function under test. + assert_ok!(EvmBalances::increase_balance( + &alice(), + increased_balance, + Precision::BestEffort, + )); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice()), u64::MAX); - }); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), u64::MAX); + }); } #[test] fn increase_balance_fails_overflow() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let increased_balance = u64::MAX; + let increased_balance = u64::MAX; - // Invoke the function under test. - assert_noop!( - EvmBalances::increase_balance(&alice(), increased_balance, Precision::Exact), - ArithmeticError::Overflow - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::increase_balance(&alice(), increased_balance, Precision::Exact), + ArithmeticError::Overflow + ); + }); } #[test] fn deactivate_reactivate_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(>::get(), 0); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(>::get(), 0); - // Deactivate some balance. - EvmBalances::deactivate(100); - // Assert state changes. - assert_eq!(>::get(), 100); + // Deactivate some balance. + EvmBalances::deactivate(100); + // Assert state changes. + assert_eq!(>::get(), 100); - // Reactivate some balance. - EvmBalances::reactivate(40); - // Assert state changes. - assert_eq!(>::get(), 60); + // Reactivate some balance. + EvmBalances::reactivate(40); + // Assert state changes. + assert_eq!(>::get(), 60); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn mint_into_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let minted_balance = 10; + let minted_balance = 10; - // Invoke the function under test. - assert_ok!(EvmBalances::mint_into(&alice(), minted_balance)); + // Invoke the function under test. + assert_ok!(EvmBalances::mint_into(&alice(), minted_balance)); - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE + minted_balance - ); - assert_eq!( - EvmBalances::total_issuance(), - 2 * INIT_BALANCE + minted_balance - ); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Minted { - who: alice(), - amount: minted_balance, - })); + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + minted_balance + ); + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE + minted_balance + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Minted { + who: alice(), + amount: minted_balance, + })); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn mint_into_fails_overflow() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let minted_balance = u64::MAX; + let minted_balance = u64::MAX; - // Invoke the function under test. - assert_noop!( - EvmBalances::mint_into(&alice(), minted_balance), - ArithmeticError::Overflow - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::mint_into(&alice(), minted_balance), + ArithmeticError::Overflow + ); + }); } #[test] fn burn_from_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - - // Set block number to enable events. - System::set_block_number(1); - - let burned_balance = 10; - - // Invoke the function under test. - assert_ok!(EvmBalances::burn_from( - &alice(), - burned_balance, - Precision::Exact, - Fortitude::Polite - )); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - burned_balance - ); - assert_eq!( - EvmBalances::total_issuance(), - 2 * INIT_BALANCE - burned_balance - ); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { - who: alice(), - amount: burned_balance, - })); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + // Set block number to enable events. + System::set_block_number(1); + + let burned_balance = 10; + + // Invoke the function under test. + assert_ok!(EvmBalances::burn_from( + &alice(), + burned_balance, + Precision::Exact, + Fortitude::Polite + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - burned_balance + ); + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE - burned_balance + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { + who: alice(), + amount: burned_balance, + })); + + assert_total_issuance_invariant(); + }); } #[test] fn burn_from_works_best_effort() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let burned_balance = INIT_BALANCE + 1; + let burned_balance = INIT_BALANCE + 1; - // Invoke the function under test. - assert_ok!(EvmBalances::burn_from( - &alice(), - burned_balance, - Precision::BestEffort, - Fortitude::Polite - )); + // Invoke the function under test. + assert_ok!(EvmBalances::burn_from( + &alice(), + burned_balance, + Precision::BestEffort, + Fortitude::Polite + )); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice()), 0); - assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { - who: alice(), - amount: INIT_BALANCE, - })); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 0); + assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { + who: alice(), + amount: INIT_BALANCE, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn burn_from_works_exact_full_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let burned_balance = INIT_BALANCE; + let burned_balance = INIT_BALANCE; - // Invoke the function under test. - assert_ok!(EvmBalances::burn_from( - &alice(), - burned_balance, - Precision::Exact, - Fortitude::Polite - )); + // Invoke the function under test. + assert_ok!(EvmBalances::burn_from( + &alice(), + burned_balance, + Precision::Exact, + Fortitude::Polite + )); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice()), 0); - assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { - who: alice(), - amount: INIT_BALANCE, - })); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 0); + assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Burned { + who: alice(), + amount: INIT_BALANCE, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn burn_from_fails_funds_unavailable() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let burned_balance = INIT_BALANCE + 1; + let burned_balance = INIT_BALANCE + 1; - // Invoke the function under test. - assert_noop!( - EvmBalances::burn_from( - &alice(), - burned_balance, - Precision::Exact, - Fortitude::Polite - ), - TokenError::FundsUnavailable - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::burn_from( + &alice(), + burned_balance, + Precision::Exact, + Fortitude::Polite + ), + TokenError::FundsUnavailable + ); + }); } #[test] fn shelve_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let shelved_balance = 10; + let shelved_balance = 10; - // Invoke the function under test. - assert_ok!(EvmBalances::shelve(&alice(), shelved_balance)); + // Invoke the function under test. + assert_ok!(EvmBalances::shelve(&alice(), shelved_balance)); - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - shelved_balance - ); - assert_eq!( - EvmBalances::total_issuance(), - 2 * INIT_BALANCE - shelved_balance - ); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Suspended { - who: alice(), - amount: shelved_balance, - })); + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - shelved_balance + ); + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE - shelved_balance + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Suspended { + who: alice(), + amount: shelved_balance, + })); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn shelve_works_exact_full_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let shelved_balance = INIT_BALANCE; + let shelved_balance = INIT_BALANCE; - // Invoke the function under test. - assert_ok!(EvmBalances::shelve(&alice(), shelved_balance)); + // Invoke the function under test. + assert_ok!(EvmBalances::shelve(&alice(), shelved_balance)); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice()), 0); - assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Suspended { - who: alice(), - amount: INIT_BALANCE, - })); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice()), 0); + assert_eq!(EvmBalances::total_issuance(), INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Suspended { + who: alice(), + amount: INIT_BALANCE, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn shelve_fails_funds_unavailable() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let shelved_balance = INIT_BALANCE + 1; + let shelved_balance = INIT_BALANCE + 1; - // Invoke the function under test. - assert_noop!( - EvmBalances::shelve(&alice(), shelved_balance), - TokenError::FundsUnavailable - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::shelve(&alice(), shelved_balance), + TokenError::FundsUnavailable + ); + }); } #[test] fn restore_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let restored_balance = 10; + let restored_balance = 10; - // Invoke the function under test. - assert_ok!(EvmBalances::restore(&alice(), restored_balance)); + // Invoke the function under test. + assert_ok!(EvmBalances::restore(&alice(), restored_balance)); - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE + restored_balance - ); - assert_eq!( - EvmBalances::total_issuance(), - 2 * INIT_BALANCE + restored_balance - ); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Restored { - who: alice(), - amount: restored_balance, - })); + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + restored_balance + ); + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE + restored_balance + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Restored { + who: alice(), + amount: restored_balance, + })); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn restore_fails_overflow() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let restored_balance = u64::MAX; + let restored_balance = u64::MAX; - // Invoke the function under test. - assert_noop!( - EvmBalances::restore(&alice(), restored_balance), - ArithmeticError::Overflow - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::restore(&alice(), restored_balance), + ArithmeticError::Overflow + ); + }); } #[test] fn transfer_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let transfered_amount = 100; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_ok!(EvmBalances::transfer( - &alice(), - &bob(), - transfered_amount, - Preservation::Preserve - )); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - transfered_amount - ); - assert_eq!( - EvmBalances::total_balance(&bob()), - INIT_BALANCE + transfered_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Transfer { - from: alice(), - to: bob(), - amount: transfered_amount, - })); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = 100; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + Preservation::Preserve + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - transfered_amount + ); + assert_eq!( + EvmBalances::total_balance(&bob()), + INIT_BALANCE + transfered_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Transfer { + from: alice(), + to: bob(), + amount: transfered_amount, + })); + + assert_total_issuance_invariant(); + }); } #[test] fn transfer_works_full_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let transfered_amount = INIT_BALANCE; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_ok!(EvmBalances::transfer( - &alice(), - &bob(), - transfered_amount, - Preservation::Expendable - )); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - transfered_amount - ); - assert_eq!( - EvmBalances::total_balance(&bob()), - INIT_BALANCE + transfered_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Transfer { - from: alice(), - to: bob(), - amount: transfered_amount, - })); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let transfered_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmBalances::transfer( + &alice(), + &bob(), + transfered_amount, + Preservation::Expendable + )); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - transfered_amount + ); + assert_eq!( + EvmBalances::total_balance(&bob()), + INIT_BALANCE + transfered_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Transfer { + from: alice(), + to: bob(), + amount: transfered_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + + assert_total_issuance_invariant(); + }); } #[test] fn transfer_fails_funds_unavailable() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let transfered_amount = INIT_BALANCE + 1; + let transfered_amount = INIT_BALANCE + 1; - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - // Invoke the function under test. - assert_noop!( - EvmBalances::transfer(&alice(), &bob(), transfered_amount, Preservation::Preserve), - TokenError::FundsUnavailable - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer(&alice(), &bob(), transfered_amount, Preservation::Preserve), + TokenError::FundsUnavailable + ); + }); } #[test] fn transfer_fails_not_expendable() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let transfered_amount = INIT_BALANCE; + let transfered_amount = INIT_BALANCE; - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - // Invoke the function under test. - assert_noop!( - EvmBalances::transfer(&alice(), &bob(), transfered_amount, Preservation::Preserve), - TokenError::NotExpendable - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer(&alice(), &bob(), transfered_amount, Preservation::Preserve), + TokenError::NotExpendable + ); + }); } #[test] fn transfer_fails_underflow() { - new_test_ext().execute_with_ext(|_| { - // Prepare test preconditions. - let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); - let eve = H160::from_str("1000000000000000000000000000000000000004").unwrap(); - EvmBalances::set_balance(&charlie, u64::MAX); - EvmBalances::set_balance(&eve, 1); + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let eve = H160::from_str("1000000000000000000000000000000000000004").unwrap(); + EvmBalances::set_balance(&charlie, u64::MAX); + EvmBalances::set_balance(&eve, 1); - // Invoke the function under test. - assert_noop!( - EvmBalances::transfer(&charlie, &eve, u64::MAX, Preservation::Expendable), - // Withdraw consequence is checked first by reducing total issuance. - ArithmeticError::Underflow, - ); - }); + // Invoke the function under test. + assert_noop!( + EvmBalances::transfer(&charlie, &eve, u64::MAX, Preservation::Expendable), + // Withdraw consequence is checked first by reducing total issuance. + ArithmeticError::Underflow, + ); + }); } #[test] fn rescind_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let rescinded_balance = 100; + let rescinded_balance = 100; - // Burn some balance. - let imbalance = EvmBalances::rescind(rescinded_balance); + // Burn some balance. + let imbalance = EvmBalances::rescind(rescinded_balance); - // Assert state changes. - assert_eq!( - EvmBalances::total_issuance(), - 2 * INIT_BALANCE - rescinded_balance - ); - drop(imbalance); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Rescinded { - amount: rescinded_balance, - })); + // Assert state changes. + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE - rescinded_balance + ); + drop(imbalance); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Rescinded { + amount: rescinded_balance, + })); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn issue_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - let issued_balance = 100; + let issued_balance = 100; - // Burn some balance. - let imbalance = EvmBalances::issue(issued_balance); + // Burn some balance. + let imbalance = EvmBalances::issue(issued_balance); - // Assert state changes. - assert_eq!( - EvmBalances::total_issuance(), - 2 * INIT_BALANCE + issued_balance - ); - drop(imbalance); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Issued { - amount: issued_balance, - })); + // Assert state changes. + assert_eq!( + EvmBalances::total_issuance(), + 2 * INIT_BALANCE + issued_balance + ); + drop(imbalance); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Issued { + amount: issued_balance, + })); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn deposit_flow_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - let deposited_amount = 10; + let deposited_amount = 10; - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - // Invoke the function under test. - let debt = EvmBalances::deposit(&alice(), deposited_amount, Precision::Exact).unwrap(); + // Invoke the function under test. + let debt = EvmBalances::deposit(&alice(), deposited_amount, Precision::Exact).unwrap(); - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE + deposited_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Deposit { - who: alice(), - amount: deposited_amount, - })); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + deposited_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Deposit { + who: alice(), + amount: deposited_amount, + })); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - let _ = EvmBalances::settle(&bob(), debt, Preservation::Expendable); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + let _ = EvmBalances::settle(&bob(), debt, Preservation::Expendable); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn deposit_works_new_account() { - new_test_ext().execute_with_ext(|_| { - let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + new_test_ext().execute_with_ext(|_| { + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&charlie), 0); + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&charlie), 0); - let deposited_amount = 10; + let deposited_amount = 10; - // Set block number to enable events. - System::set_block_number(1); + // Set block number to enable events. + System::set_block_number(1); - // Invoke the function under test. - let debt = EvmBalances::deposit(&charlie, deposited_amount, Precision::Exact).unwrap(); + // Invoke the function under test. + let debt = EvmBalances::deposit(&charlie, deposited_amount, Precision::Exact).unwrap(); - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&charlie), deposited_amount); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Deposit { - who: charlie, - amount: deposited_amount, - })); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - let _ = EvmBalances::settle(&bob(), debt, Preservation::Expendable); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - assert!(EvmSystem::account_exists(&charlie)); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::NewAccount { account: charlie }, - )); + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&charlie), deposited_amount); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Deposit { + who: charlie, + amount: deposited_amount, + })); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + let _ = EvmBalances::settle(&bob(), debt, Preservation::Expendable); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + assert!(EvmSystem::account_exists(&charlie)); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::NewAccount { account: charlie }, + )); - assert_total_issuance_invariant(); - }); + assert_total_issuance_invariant(); + }); } #[test] fn withdraw_works() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let withdrawed_amount = 1000; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - let credit = EvmBalances::withdraw( - &alice(), - withdrawed_amount, - Precision::Exact, - Preservation::Preserve, - Fortitude::Polite, - ) - .unwrap(); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - withdrawed_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(Event::Withdraw { - who: alice(), - amount: withdrawed_amount, - })); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - let _ = EvmBalances::resolve(&bob(), credit); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = 1000; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let credit = EvmBalances::withdraw( + &alice(), + withdrawed_amount, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + ) + .unwrap(); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - withdrawed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(Event::Withdraw { + who: alice(), + amount: withdrawed_amount, + })); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + let _ = EvmBalances::resolve(&bob(), credit); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + assert_total_issuance_invariant(); + }); } #[test] fn withdraw_works_full_balance() { - new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let withdrawed_amount = INIT_BALANCE; - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - let credit = EvmBalances::withdraw( - &alice(), - withdrawed_amount, - Precision::Exact, - Preservation::Expendable, - Fortitude::Polite, - ) - .unwrap(); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice()), - INIT_BALANCE - withdrawed_amount - ); - System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { - who: alice(), - amount: withdrawed_amount, - })); - assert!(!EvmSystem::account_exists(&alice())); - System::assert_has_event(RuntimeEvent::EvmSystem( - pallet_evm_system::Event::KilledAccount { account: alice() }, - )); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - let _ = EvmBalances::resolve(&bob(), credit); - assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + + let withdrawed_amount = INIT_BALANCE; + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + let credit = EvmBalances::withdraw( + &alice(), + withdrawed_amount, + Precision::Exact, + Preservation::Expendable, + Fortitude::Polite, + ) + .unwrap(); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE - withdrawed_amount + ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Withdraw { + who: alice(), + amount: withdrawed_amount, + })); + assert!(!EvmSystem::account_exists(&alice())); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::KilledAccount { account: alice() }, + )); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + let _ = EvmBalances::resolve(&bob(), credit); + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + + assert_total_issuance_invariant(); + }); } diff --git a/crates/pallet-evm-balances/src/tests/mod.rs b/crates/pallet-evm-balances/src/tests/mod.rs index dc0fcd955..f46a562eb 100644 --- a/crates/pallet-evm-balances/src/tests/mod.rs +++ b/crates/pallet-evm-balances/src/tests/mod.rs @@ -12,63 +12,63 @@ mod currency; mod fungible; fn assert_total_issuance_invariant() { - let iterated_total_issuance: u64 = >::iter_values() - .map(|account_data| account_data.data.total()) - .sum(); + let iterated_total_issuance: u64 = >::iter_values() + .map(|account_data| account_data.data.total()) + .sum(); - let total_issuance = EvmBalances::total_issuance(); + let total_issuance = EvmBalances::total_issuance(); - assert_eq!(iterated_total_issuance, total_issuance); + assert_eq!(iterated_total_issuance, total_issuance); } #[test] fn basic_setup_works() { - new_test_ext().execute_with_ext(|_| { - // Check the accounts. - assert_eq!( - ::account(&alice()), - account_data::AccountData { free: INIT_BALANCE } - ); - assert_eq!( - ::account(&bob()), - account_data::AccountData { free: INIT_BALANCE } - ); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Check the accounts. + assert_eq!( + ::account(&alice()), + account_data::AccountData { free: INIT_BALANCE } + ); + assert_eq!( + ::account(&bob()), + account_data::AccountData { free: INIT_BALANCE } + ); + + assert_total_issuance_invariant(); + }); } #[test] fn evm_system_removing_account_non_zero_balance() { - new_test_ext().execute_with_ext(|_| { - // Prepare test preconditions. - let contract = H160::from_str("1000000000000000000000000000000000000003").unwrap(); - EVM::create_account(contract, vec![1, 2, 3]); - - // Transfer some balance to contract address. - assert_ok!(EvmBalances::transfer( - &alice(), - &contract, - 1000, - ExistenceRequirement::KeepAlive - )); - - assert_eq!(EvmBalances::free_balance(&contract), 1000); - - // Invoke the function under test. - EVM::remove_account(&contract); - - // Assert state changes. - assert_eq!(EvmBalances::free_balance(&contract), 1000); - assert!(EvmSystem::account_exists(&contract)); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test preconditions. + let contract = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + EVM::create_account(contract, vec![1, 2, 3]); + + // Transfer some balance to contract address. + assert_ok!(EvmBalances::transfer( + &alice(), + &contract, + 1000, + ExistenceRequirement::KeepAlive + )); + + assert_eq!(EvmBalances::free_balance(&contract), 1000); + + // Invoke the function under test. + EVM::remove_account(&contract); + + // Assert state changes. + assert_eq!(EvmBalances::free_balance(&contract), 1000); + assert!(EvmSystem::account_exists(&contract)); + + assert_total_issuance_invariant(); + }); } #[test] fn evm_fee_deduction() { - new_test_ext().execute_with_ext(|_| { + new_test_ext().execute_with_ext(|_| { let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); // Seed account @@ -94,184 +94,184 @@ fn evm_fee_deduction() { #[test] fn evm_issuance_after_tip() { - new_test_ext().execute_with_ext(|_| { - let before_tip = ::Currency::total_issuance(); - - let gas_limit: u64 = 1_000_000; - let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); - - assert_ok!(::Runner::call( - alice(), - bob(), - Vec::new(), - U256::from(1), - gas_limit, - Some(U256::from(2_000_000_000)), - None, - None, - Vec::new(), - true, - true, - Some(weight_limit), - Some(0), - ::config(), - )); - - // Only base fee is burned - let base_fee: u64 = ::FeeCalculator::min_gas_price() - .0 - .unique_saturated_into(); - - let after_tip = ::Currency::total_issuance(); - - assert_eq!(after_tip, (before_tip - (base_fee * 21_000))); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + let before_tip = ::Currency::total_issuance(); + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + assert_ok!(::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(U256::from(2_000_000_000)), + None, + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + )); + + // Only base fee is burned + let base_fee: u64 = ::FeeCalculator::min_gas_price() + .0 + .unique_saturated_into(); + + let after_tip = ::Currency::total_issuance(); + + assert_eq!(after_tip, (before_tip - (base_fee * 21_000))); + + assert_total_issuance_invariant(); + }); } #[test] fn evm_refunds_should_work() { - new_test_ext().execute_with_ext(|_| { - let before_call = EVM::account_basic(&alice()).0.balance; - // Gas price is not part of the actual fee calculations anymore, only the base fee. - // - // Because we first deduct max_fee_per_gas * gas_limit (2_000_000_000 * 1000000) we need - // to ensure that the difference (max fee VS base fee) is refunded. - - let gas_limit: u64 = 1_000_000; - let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); - - let _ = ::Runner::call( - alice(), - bob(), - Vec::new(), - U256::from(1), - gas_limit, - Some(U256::from(2_000_000_000)), - None, - None, - Vec::new(), - true, - true, - Some(weight_limit), - Some(0), - ::config(), - ); - - let (base_fee, _) = ::FeeCalculator::min_gas_price(); - let total_cost = (U256::from(21_000) * base_fee) + U256::from(1); - let after_call = EVM::account_basic(&alice()).0.balance; - assert_eq!(after_call, before_call - total_cost); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + let before_call = EVM::account_basic(&alice()).0.balance; + // Gas price is not part of the actual fee calculations anymore, only the base fee. + // + // Because we first deduct max_fee_per_gas * gas_limit (2_000_000_000 * 1000000) we need + // to ensure that the difference (max fee VS base fee) is refunded. + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + let _ = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(U256::from(2_000_000_000)), + None, + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + ); + + let (base_fee, _) = ::FeeCalculator::min_gas_price(); + let total_cost = (U256::from(21_000) * base_fee) + U256::from(1); + let after_call = EVM::account_basic(&alice()).0.balance; + assert_eq!(after_call, before_call - total_cost); + + assert_total_issuance_invariant(); + }); } #[test] fn evm_refunds_and_priority_should_work() { - new_test_ext().execute_with_ext(|_| { - let before_call = EVM::account_basic(&alice()).0.balance; - // We deliberately set a base fee + max tip > max fee. - // The effective priority tip will be 1GWEI instead 1.5GWEI: - // (max_fee_per_gas - base_fee).min(max_priority_fee) - // (2 - 1).min(1.5) - let tip = U256::from(1_500_000_000); - let max_fee_per_gas = U256::from(2_000_000_000); - let used_gas = U256::from(21_000); - - let gas_limit: u64 = 1_000_000; - let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); - - let _ = ::Runner::call( - alice(), - bob(), - Vec::new(), - U256::from(1), - gas_limit, - Some(max_fee_per_gas), - Some(tip), - None, - Vec::new(), - true, - true, - Some(weight_limit), - Some(0), - ::config(), - ); - - let (base_fee, _) = ::FeeCalculator::min_gas_price(); - let actual_tip = (max_fee_per_gas - base_fee).min(tip) * used_gas; - let total_cost = (used_gas * base_fee) + U256::from(actual_tip) + U256::from(1); - let after_call = EVM::account_basic(&alice()).0.balance; - // The tip is deducted but never refunded to the caller. - assert_eq!(after_call, before_call - total_cost); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + let before_call = EVM::account_basic(&alice()).0.balance; + // We deliberately set a base fee + max tip > max fee. + // The effective priority tip will be 1GWEI instead 1.5GWEI: + // (max_fee_per_gas - base_fee).min(max_priority_fee) + // (2 - 1).min(1.5) + let tip = U256::from(1_500_000_000); + let max_fee_per_gas = U256::from(2_000_000_000); + let used_gas = U256::from(21_000); + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + let _ = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(max_fee_per_gas), + Some(tip), + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + ); + + let (base_fee, _) = ::FeeCalculator::min_gas_price(); + let actual_tip = (max_fee_per_gas - base_fee).min(tip) * used_gas; + let total_cost = (used_gas * base_fee) + U256::from(actual_tip) + U256::from(1); + let after_call = EVM::account_basic(&alice()).0.balance; + // The tip is deducted but never refunded to the caller. + assert_eq!(after_call, before_call - total_cost); + + assert_total_issuance_invariant(); + }); } #[test] fn evm_call_should_fail_with_priority_greater_than_max_fee() { - new_test_ext().execute_with_ext(|_| { - // Max priority greater than max fee should fail. - let tip: u128 = 1_100_000_000; - - let gas_limit: u64 = 1_000_000; - let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); - - let result = ::Runner::call( - alice(), - bob(), - Vec::new(), - U256::from(1), - gas_limit, - Some(U256::from(1_000_000_000)), - Some(U256::from(tip)), - None, - Vec::new(), - true, - true, - Some(weight_limit), - Some(0), - ::config(), - ); - assert!(result.is_err()); - // Some used weight is returned as part of the error. - assert_eq!(result.unwrap_err().weight, Weight::from_parts(7, 0)); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + // Max priority greater than max fee should fail. + let tip: u128 = 1_100_000_000; + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + let result = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(U256::from(1_000_000_000)), + Some(U256::from(tip)), + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + ); + assert!(result.is_err()); + // Some used weight is returned as part of the error. + assert_eq!(result.unwrap_err().weight, Weight::from_parts(7, 0)); + + assert_total_issuance_invariant(); + }); } #[test] fn evm_call_should_succeed_with_priority_equal_to_max_fee() { - new_test_ext().execute_with_ext(|_| { - let tip: u128 = 1_000_000_000; - - let gas_limit: u64 = 1_000_000; - let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); - - // Mimics the input for pre-eip-1559 transaction types where `gas_price` - // is used for both `max_fee_per_gas` and `max_priority_fee_per_gas`. - let result = ::Runner::call( - alice(), - bob(), - Vec::new(), - U256::from(1), - gas_limit, - Some(U256::from(1_000_000_000)), - Some(U256::from(tip)), - None, - Vec::new(), - true, - true, - Some(weight_limit), - Some(0), - ::config(), - ); - assert!(result.is_ok()); - - assert_total_issuance_invariant(); - }); + new_test_ext().execute_with_ext(|_| { + let tip: u128 = 1_000_000_000; + + let gas_limit: u64 = 1_000_000; + let weight_limit = FixedGasWeightMapping::::gas_to_weight(gas_limit, true); + + // Mimics the input for pre-eip-1559 transaction types where `gas_price` + // is used for both `max_fee_per_gas` and `max_priority_fee_per_gas`. + let result = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + gas_limit, + Some(U256::from(1_000_000_000)), + Some(U256::from(tip)), + None, + Vec::new(), + true, + true, + Some(weight_limit), + Some(0), + ::config(), + ); + assert!(result.is_ok()); + + assert_total_issuance_invariant(); + }); } diff --git a/crates/pallet-evm-system/src/lib.rs b/crates/pallet-evm-system/src/lib.rs index 78cb897c9..9874c9c46 100644 --- a/crates/pallet-evm-system/src/lib.rs +++ b/crates/pallet-evm-system/src/lib.rs @@ -1,5 +1,3 @@ -// SPDX-License-Identifier: Apache-2.0 - //! # EVM System Pallet. // Ensure we're `no_std` when compiling for Wasm. @@ -9,8 +7,8 @@ use frame_support::traits::StoredMap; use scale_codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{One, Zero}, - DispatchError, RuntimeDebug, + traits::{One, Zero}, + DispatchError, RuntimeDebug, }; #[cfg(test)] @@ -21,236 +19,227 @@ mod tests; pub use pallet::*; /// Account information. -#[derive( - Clone, - Eq, - PartialEq, - Default, - RuntimeDebug, - Encode, - Decode, - TypeInfo, - MaxEncodedLen -)] +#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct AccountInfo { - /// The number of transactions this account has sent. - pub nonce: Index, - /// The additional data that belongs to this account. Used to store the balance(s) in a lot of - /// chains. - pub data: AccountData, + /// The number of transactions this account has sent. + pub nonce: Index, + /// The additional data that belongs to this account. Used to store the balance(s) in a lot of + /// chains. + pub data: AccountData, } #[frame_support::pallet] pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use sp_runtime::traits::{AtLeast32Bit, MaybeDisplay}; - use sp_std::fmt::Debug; - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); - - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The user account identifier type. - type AccountId: Parameter - + Member - + MaybeSerializeDeserialize - + Debug - + MaybeDisplay - + Ord - + MaxEncodedLen; - - /// Account index (aka nonce) type. This stores the number of previous transactions - /// associated with a sender account. - type Index: Parameter - + Member - + MaybeSerializeDeserialize - + Debug - + Default - + MaybeDisplay - + AtLeast32Bit - + Copy - + MaxEncodedLen; - - /// Data to be associated with an account (other than nonce/transaction counter, which this - /// pallet does regardless). - type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; - - /// Handler for when a new account has just been created. - type OnNewAccount: OnNewAccount<::AccountId>; - - /// A function that is invoked when an account has been determined to be dead. - /// - /// All resources should be cleaned up associated with the given account. - type OnKilledAccount: OnKilledAccount<::AccountId>; - } - - /// The full account information for a particular account ID. - #[pallet::storage] - pub type Account = StorageMap< - _, - Blake2_128Concat, - ::AccountId, - AccountInfo<::Index, ::AccountData>, - ValueQuery, - >; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A new account was created. - NewAccount { account: ::AccountId }, - /// An account was reaped. - KilledAccount { account: ::AccountId }, - } + use frame_support::pallet_prelude::*; + use sp_runtime::traits::{AtLeast32Bit, MaybeDisplay}; + use sp_std::fmt::Debug; + + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The user account identifier type. + type AccountId: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + MaybeDisplay + + Ord + + MaxEncodedLen; + + /// Account index (aka nonce) type. This stores the number of previous transactions + /// associated with a sender account. + type Index: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + Default + + MaybeDisplay + + AtLeast32Bit + + Copy + + MaxEncodedLen; + + /// Data to be associated with an account (other than nonce/transaction counter, which this + /// pallet does regardless). + type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; + + /// Handler for when a new account has just been created. + type OnNewAccount: OnNewAccount<::AccountId>; + + /// A function that is invoked when an account has been determined to be dead. + /// + /// All resources should be cleaned up associated with the given account. + type OnKilledAccount: OnKilledAccount<::AccountId>; + } + + /// The full account information for a particular account ID. + #[pallet::storage] + pub type Account = StorageMap< + _, + Blake2_128Concat, + ::AccountId, + AccountInfo<::Index, ::AccountData>, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A new account was created. + NewAccount { account: ::AccountId }, + /// An account was reaped. + KilledAccount { account: ::AccountId }, + } } /// The outcome of the account creation operation. #[derive(Eq, PartialEq, RuntimeDebug)] pub enum AccountCreationOutcome { - /// Account was created. - Created, - /// Account already exists in the system, so action was taken. - AlreadyExists, + /// Account was created. + Created, + /// Account already exists in the system, so action was taken. + AlreadyExists, } impl Pallet { - /// Check the account existence. - pub fn account_exists(who: &::AccountId) -> bool { - Account::::contains_key(who) - } - - /// An account is being created. - fn on_created_account(who: ::AccountId) { - ::OnNewAccount::on_new_account(&who); - Self::deposit_event(Event::NewAccount { account: who }); - } - - /// Do anything that needs to be done after an account has been killed. - fn on_killed_account(who: ::AccountId) { - ::OnKilledAccount::on_killed_account(&who); - Self::deposit_event(Event::KilledAccount { account: who }); - } - - /// Retrieve the account transaction counter from storage. - pub fn account_nonce(who: &::AccountId) -> ::Index { - Account::::get(who).nonce - } - - /// Increment a particular account's nonce by 1. - pub fn inc_account_nonce(who: &::AccountId) { - let is_new_account = Account::::mutate_exists(who, |maybe_account| { - let is_new_account = maybe_account.is_none(); - - let account = maybe_account.get_or_insert_default(); - account.nonce += ::Index::one(); - - is_new_account - }); - - // Meaning that account is being created. - if is_new_account { - Self::on_created_account(who.clone()); - } - } - - /// Create an account. - pub fn create_account(who: &::AccountId) -> AccountCreationOutcome { - if Self::account_exists(who) { - return AccountCreationOutcome::AlreadyExists; - } - - Account::::insert(who.clone(), AccountInfo::<_, _>::default()); - Self::on_created_account(who.clone()); - AccountCreationOutcome::Created - } + /// Check the account existence. + pub fn account_exists(who: &::AccountId) -> bool { + Account::::contains_key(who) + } + + /// An account is being created. + fn on_created_account(who: ::AccountId) { + ::OnNewAccount::on_new_account(&who); + Self::deposit_event(Event::NewAccount { account: who }); + } + + /// Do anything that needs to be done after an account has been killed. + fn on_killed_account(who: ::AccountId) { + ::OnKilledAccount::on_killed_account(&who); + Self::deposit_event(Event::KilledAccount { account: who }); + } + + /// Retrieve the account transaction counter from storage. + pub fn account_nonce(who: &::AccountId) -> ::Index { + Account::::get(who).nonce + } + + /// Increment a particular account's nonce by 1. + pub fn inc_account_nonce(who: &::AccountId) { + let is_new_account = Account::::mutate_exists(who, |maybe_account| { + let is_new_account = maybe_account.is_none(); + + let account = maybe_account.get_or_insert_default(); + account.nonce += ::Index::one(); + + is_new_account + }); + + // Meaning that account is being created. + if is_new_account { + Self::on_created_account(who.clone()); + } + } + + /// Create an account. + pub fn create_account(who: &::AccountId) -> AccountCreationOutcome { + if Self::account_exists(who) { + return AccountCreationOutcome::AlreadyExists; + } + + Account::::insert(who.clone(), AccountInfo::<_, _>::default()); + Self::on_created_account(who.clone()); + AccountCreationOutcome::Created + } } impl StoredMap<::AccountId, ::AccountData> for Pallet { - fn get(k: &::AccountId) -> ::AccountData { - Account::::get(k).data - } - - fn try_mutate_exists>( - k: &::AccountId, - f: impl FnOnce(&mut Option<::AccountData>) -> Result, - ) -> Result { - let (mut maybe_account_data, nonce, was_providing) = if Self::account_exists(k) { - let account_info = Account::::get(k); - (Some(account_info.data), account_info.nonce, true) - } else { - (None, ::Index::zero(), false) - }; - - let result = f(&mut maybe_account_data)?; - - match (maybe_account_data, was_providing) { - (Some(data), false) => { - Account::::mutate(k, |a| a.data = data); - Self::on_created_account(k.clone()); - } - (Some(data), true) => { - Account::::mutate(k, |a| a.data = data); - } - (None, true) => { - if nonce != ::Index::zero() { - Account::::mutate(k, |a| a.data = Default::default()); - } else { - Account::::remove(k); - Self::on_killed_account(k.clone()); - } - } - (None, false) => { - // Do nothing. - } - } - - Ok(result) - } + fn get(k: &::AccountId) -> ::AccountData { + Account::::get(k).data + } + + fn try_mutate_exists>( + k: &::AccountId, + f: impl FnOnce(&mut Option<::AccountData>) -> Result, + ) -> Result { + let (mut maybe_account_data, nonce, was_providing) = if Self::account_exists(k) { + let account_info = Account::::get(k); + (Some(account_info.data), account_info.nonce, true) + } else { + (None, ::Index::zero(), false) + }; + + let result = f(&mut maybe_account_data)?; + + match (maybe_account_data, was_providing) { + (Some(data), false) => { + Account::::mutate(k, |a| a.data = data); + Self::on_created_account(k.clone()); + } + (Some(data), true) => { + Account::::mutate(k, |a| a.data = data); + } + (None, true) => { + if nonce != ::Index::zero() { + Account::::mutate(k, |a| a.data = Default::default()); + } else { + Account::::remove(k); + Self::on_killed_account(k.clone()); + } + } + (None, false) => { + // Do nothing. + } + } + + Ok(result) + } } impl fp_evm::AccountProvider for Pallet { - type AccountId = ::AccountId; - type Index = ::Index; + type AccountId = ::AccountId; + type Index = ::Index; - fn create_account(who: &Self::AccountId) { - let _ = Self::create_account(who); - } + fn create_account(who: &Self::AccountId) { + let _ = Self::create_account(who); + } - fn on_account_self_destruct(_who: &Self::AccountId) { - // Do nothing as account state shouldn't be modified. - } + fn on_account_self_destruct(_who: &Self::AccountId) { + // Do nothing as account state shouldn't be modified. + } - fn account_nonce(who: &Self::AccountId) -> Self::Index { - Self::account_nonce(who) - } + fn account_nonce(who: &Self::AccountId) -> Self::Index { + Self::account_nonce(who) + } - fn inc_account_nonce(who: &Self::AccountId) { - Self::inc_account_nonce(who); - } + fn inc_account_nonce(who: &Self::AccountId) { + Self::inc_account_nonce(who); + } } /// Interface to handle account creation. pub trait OnNewAccount { - /// A new account `who` has been registered. - fn on_new_account(who: &AccountId); + /// A new account `who` has been registered. + fn on_new_account(who: &AccountId); } impl OnNewAccount for () { - fn on_new_account(_who: &AccountId) {} + fn on_new_account(_who: &AccountId) {} } /// Interface to handle account killing. pub trait OnKilledAccount { - /// The account with the given id was reaped. - fn on_killed_account(who: &AccountId); + /// The account with the given id was reaped. + fn on_killed_account(who: &AccountId); } impl OnKilledAccount for () { - fn on_killed_account(_who: &AccountId) {} + fn on_killed_account(_who: &AccountId) {} } diff --git a/crates/pallet-evm-system/src/mock.rs b/crates/pallet-evm-system/src/mock.rs index 7688a55ef..b05b5298d 100644 --- a/crates/pallet-evm-system/src/mock.rs +++ b/crates/pallet-evm-system/src/mock.rs @@ -21,120 +21,120 @@ use frame_support::traits::{ConstU32, ConstU64}; use mockall::mock; use sp_core::{H160, H256}; use sp_runtime::{ - generic, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + generic, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; use sp_std::{boxed::Box, prelude::*}; use crate::{self as pallet_evm_system, *}; mock! { - #[derive(Debug)] - pub DummyOnNewAccount {} + #[derive(Debug)] + pub DummyOnNewAccount {} - impl OnNewAccount for DummyOnNewAccount { - pub fn on_new_account(who: &H160); - } + impl OnNewAccount for DummyOnNewAccount { + pub fn on_new_account(who: &H160); + } } mock! { - #[derive(Debug)] - pub DummyOnKilledAccount {} + #[derive(Debug)] + pub DummyOnKilledAccount {} - impl OnKilledAccount for DummyOnKilledAccount { - pub fn on_killed_account(who: &H160); - } + impl OnKilledAccount for DummyOnKilledAccount { + pub fn on_killed_account(who: &H160); + } } 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, - EvmSystem: pallet_evm_system, - } + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + EvmSystem: pallet_evm_system, + } } impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = H160; - type Lookup = IdentityLookup; - type Header = generic::Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = H160; + type Lookup = IdentityLookup; + type Header = generic::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; } impl pallet_evm_system::Config for Test { - type RuntimeEvent = RuntimeEvent; - type AccountId = H160; - type Index = u64; - type AccountData = u64; - type OnNewAccount = MockDummyOnNewAccount; - type OnKilledAccount = MockDummyOnKilledAccount; + type RuntimeEvent = RuntimeEvent; + type AccountId = H160; + type Index = u64; + type AccountData = u64; + type OnNewAccount = MockDummyOnNewAccount; + type OnKilledAccount = MockDummyOnKilledAccount; } /// Build test externalities from the custom genesis. /// Using this call requires manual assertions on the genesis init logic. pub fn new_test_ext() -> sp_io::TestExternalities { - // Build genesis. - let config = GenesisConfig { - ..Default::default() - }; - let storage = config.build_storage().unwrap(); - - // Make test externalities from the storage. - storage.into() + // Build genesis. + let config = GenesisConfig { + ..Default::default() + }; + let storage = config.build_storage().unwrap(); + + // Make test externalities from the storage. + storage.into() } 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(), - } + 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(), + } } pub trait TestExternalitiesExt { - fn execute_with_ext(&mut self, execute: E) -> R - where - E: for<'e> FnOnce(&'e ()) -> R; + 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 - } + 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-evm-system/src/tests.rs b/crates/pallet-evm-system/src/tests.rs index a07609536..bedef9b8b 100644 --- a/crates/pallet-evm-system/src/tests.rs +++ b/crates/pallet-evm-system/src/tests.rs @@ -1,10 +1,9 @@ //! Unit tests. -use sp_std::str::FromStr; - use frame_support::{assert_noop, assert_storage_noop}; use mockall::predicate; use sp_core::H160; +use sp_std::str::FromStr; use crate::{mock::*, *}; @@ -12,336 +11,336 @@ use crate::{mock::*, *}; /// in case a new account should be created. #[test] fn create_account_created() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - - // Check test preconditions. - assert!(!EvmSystem::account_exists(&account_id)); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); - on_new_account_ctx - .expect() - .once() - .with(predicate::eq(account_id)) - .return_const(()); - - // Invoke the function under test. - assert_eq!( - EvmSystem::create_account(&account_id), - AccountCreationOutcome::Created - ); - - // Assert state changes. - assert!(EvmSystem::account_exists(&account_id)); - assert_eq!( - >::get(&account_id), - AccountInfo::<_, _>::default() - ); - System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { - account: account_id, - })); - - // Assert mock invocations. - on_new_account_ctx.checkpoint(); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Check test preconditions. + assert!(!EvmSystem::account_exists(&account_id)); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); + on_new_account_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(()); + + // Invoke the function under test. + assert_eq!( + EvmSystem::create_account(&account_id), + AccountCreationOutcome::Created + ); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!( + >::get(&account_id), + AccountInfo::<_, _>::default() + ); + System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { + account: account_id, + })); + + // Assert mock invocations. + on_new_account_ctx.checkpoint(); + }); } /// This test verifies that creating an account works as expected /// when the account already exists. #[test] fn create_account_already_exists() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - >::insert(account_id.clone(), AccountInfo::<_, _>::default()); - - // Invoke the function under test. - assert_storage_noop!(assert_eq!( - EvmSystem::create_account(&account_id), - AccountCreationOutcome::AlreadyExists - )); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + >::insert(account_id.clone(), AccountInfo::<_, _>::default()); + + // Invoke the function under test. + assert_storage_noop!(assert_eq!( + EvmSystem::create_account(&account_id), + AccountCreationOutcome::AlreadyExists + )); + }); } /// This test verifies that incrementing account nonce works in the happy path /// in case a new account should be created. #[test] fn inc_account_nonce_account_created() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - - // Check test preconditions. - assert!(!EvmSystem::account_exists(&account_id)); - - let nonce_before = EvmSystem::account_nonce(&account_id); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); - on_new_account_ctx - .expect() - .once() - .with(predicate::eq(account_id)) - .return_const(()); - - // Invoke the function under test. - EvmSystem::inc_account_nonce(&account_id); - - // Assert state changes. - assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 1); - assert!(EvmSystem::account_exists(&account_id)); - System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { - account: account_id, - })); - - // Invoke the function under test again to check that the account is not being created now. - EvmSystem::inc_account_nonce(&account_id); - // Assert state changes. - assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 2); - assert!(EvmSystem::account_exists(&account_id)); - - // Assert mock invocations. - on_new_account_ctx.checkpoint(); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Check test preconditions. + assert!(!EvmSystem::account_exists(&account_id)); + + let nonce_before = EvmSystem::account_nonce(&account_id); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); + on_new_account_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(()); + + // Invoke the function under test. + EvmSystem::inc_account_nonce(&account_id); + + // Assert state changes. + assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 1); + assert!(EvmSystem::account_exists(&account_id)); + System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { + account: account_id, + })); + + // Invoke the function under test again to check that the account is not being created now. + EvmSystem::inc_account_nonce(&account_id); + // Assert state changes. + assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 2); + assert!(EvmSystem::account_exists(&account_id)); + + // Assert mock invocations. + on_new_account_ctx.checkpoint(); + }); } /// This test verifies that incrementing account nonce works in the happy path /// in case an account already exists. #[test] fn inc_account_nonce_account_exists() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - >::insert(account_id.clone(), AccountInfo::<_, _>::default()); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + >::insert(account_id.clone(), AccountInfo::<_, _>::default()); - // Check test preconditions. - assert!(EvmSystem::account_exists(&account_id)); + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); - let nonce_before = EvmSystem::account_nonce(&account_id); + let nonce_before = EvmSystem::account_nonce(&account_id); - // Invoke the function under test. - EvmSystem::inc_account_nonce(&account_id); + // Invoke the function under test. + EvmSystem::inc_account_nonce(&account_id); - // Assert state changes. - assert!(EvmSystem::account_exists(&account_id)); - assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 1); - }); + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 1); + }); } /// This test verifies that try_mutate_exists works as expected in case data wasn't providing /// and returned data is `Some`. As a result, a new account has been created. #[test] fn try_mutate_exists_account_created() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - - // Check test preconditions. - assert!(!EvmSystem::account_exists(&account_id)); - - // Set mock expectations. - let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); - on_new_account_ctx - .expect() - .once() - .with(predicate::eq(account_id)) - .return_const(()); - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { - *maybe_data = Some(1); - Ok(()) - }) - .unwrap(); - - // Assert state changes. - assert!(EvmSystem::account_exists(&account_id)); - assert_eq!( - >::get(&account_id), - AccountInfo { - data: 1, - ..Default::default() - } - ); - System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { - account: account_id, - })); - - // Assert mock invocations. - on_new_account_ctx.checkpoint(); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Check test preconditions. + assert!(!EvmSystem::account_exists(&account_id)); + + // Set mock expectations. + let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); + on_new_account_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(()); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { + *maybe_data = Some(1); + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!( + >::get(&account_id), + AccountInfo { + data: 1, + ..Default::default() + } + ); + System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { + account: account_id, + })); + + // Assert mock invocations. + on_new_account_ctx.checkpoint(); + }); } /// This test verifies that try_mutate_exists works as expected in case data was providing /// and returned data is `Some`. As a result, the account has been updated. #[test] fn try_mutate_exists_account_updated() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - let nonce = 1; - let data = 1; - >::insert(account_id.clone(), AccountInfo { nonce, data }); - - // Check test preconditions. - assert!(EvmSystem::account_exists(&account_id)); - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { - if let Some(ref mut data) = maybe_data { - *data += 1; - } - Ok(()) - }) - .unwrap(); - - // Assert state changes. - assert!(EvmSystem::account_exists(&account_id)); - assert_eq!( - >::get(&account_id), - AccountInfo { - nonce, - data: data + 1, - } - ); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + let nonce = 1; + let data = 1; + >::insert(account_id.clone(), AccountInfo { nonce, data }); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { + if let Some(ref mut data) = maybe_data { + *data += 1; + } + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!( + >::get(&account_id), + AccountInfo { + nonce, + data: data + 1, + } + ); + }); } /// This test verifies that try_mutate_exists works as expected in case data was providing /// and returned data is `None`, account has zero nonce. As a result, the account has been removed. #[test] fn try_mutate_exists_account_removed() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - >::insert(account_id.clone(), AccountInfo::<_, _>::default()); - - // Check test preconditions. - assert!(EvmSystem::account_exists(&account_id)); - - // Set mock expectations. - let on_killed_account_ctx = MockDummyOnKilledAccount::on_killed_account_context(); - on_killed_account_ctx - .expect() - .once() - .with(predicate::eq(account_id)) - .return_const(()); - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { - *maybe_data = None; - Ok(()) - }) - .unwrap(); - - // Assert state changes. - assert!(!EvmSystem::account_exists(&account_id)); - System::assert_has_event(RuntimeEvent::EvmSystem(Event::KilledAccount { - account: account_id, - })); - - // Assert mock invocations. - on_killed_account_ctx.checkpoint(); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + >::insert(account_id.clone(), AccountInfo::<_, _>::default()); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + // Set mock expectations. + let on_killed_account_ctx = MockDummyOnKilledAccount::on_killed_account_context(); + on_killed_account_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(()); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { + *maybe_data = None; + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(!EvmSystem::account_exists(&account_id)); + System::assert_has_event(RuntimeEvent::EvmSystem(Event::KilledAccount { + account: account_id, + })); + + // Assert mock invocations. + on_killed_account_ctx.checkpoint(); + }); } /// This test verifies that try_mutate_exists works as expected in case data was providing /// and returned data is `None`, account has non zero nonce. As a result, the account has been retained. #[test] fn try_mutate_exists_account_retained() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - let nonce = 10; - let data = 100; - - let account_info = AccountInfo { nonce, data }; - >::insert(account_id.clone(), account_info); - - // Check test preconditions. - assert!(EvmSystem::account_exists(&account_id)); - - // Invoke the function under test. - EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { - *maybe_data = None; - Ok(()) - }) - .unwrap(); - - // Assert state changes. - assert!(EvmSystem::account_exists(&account_id)); - assert_eq!( - >::get(&account_id), - AccountInfo { - nonce, - ..Default::default() - } - ); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + let nonce = 10; + let data = 100; + + let account_info = AccountInfo { nonce, data }; + >::insert(account_id.clone(), account_info); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> { + *maybe_data = None; + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(EvmSystem::account_exists(&account_id)); + assert_eq!( + >::get(&account_id), + AccountInfo { + nonce, + ..Default::default() + } + ); + }); } /// This test verifies that try_mutate_exists works as expected in case data wasn't providing /// and returned data is `None`. As a result, the account hasn't been created. #[test] fn try_mutate_exists_account_not_created() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - - // Check test preconditions. - assert!(!EvmSystem::account_exists(&account_id)); - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_storage_noop!(>::try_mutate_exists( - account_id, - |maybe_data| -> Result<(), ()> { - *maybe_data = None; - Ok(()) - } - ) - .unwrap()); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Check test preconditions. + assert!(!EvmSystem::account_exists(&account_id)); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_storage_noop!(>::try_mutate_exists( + account_id, + |maybe_data| -> Result<(), ()> { + *maybe_data = None; + Ok(()) + } + ) + .unwrap()); + }); } /// This test verifies that try_mutate_exists works as expected in case getting error /// during data mutation. #[test] fn try_mutate_exists_without_changes() { - new_test_ext().execute_with_ext(|_| { - // Prepare test data. - let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - >::insert(account_id.clone(), AccountInfo::<_, _>::default()); - - // Check test preconditions. - assert!(EvmSystem::account_exists(&account_id)); - - // Invoke the function under test. - assert_noop!( - >::try_mutate_exists(account_id, |maybe_data| -> Result<(), ()> { - *maybe_data = None; - Err(()) - }), - () - ); - }); + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + >::insert(account_id.clone(), AccountInfo::<_, _>::default()); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&account_id)); + + // Invoke the function under test. + assert_noop!( + >::try_mutate_exists(account_id, |maybe_data| -> Result<(), ()> { + *maybe_data = None; + Err(()) + }), + () + ); + }); }