From a6e2ce27ac72f81482433246fabc20916ae16d74 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 16 May 2023 21:48:15 +0300 Subject: [PATCH 01/43] Add initital frame balances structure --- Cargo.lock | 16 +++++++ Cargo.toml | 2 + frame/evm-balances/Cargo.toml | 39 ++++++++++++++++ frame/evm-balances/src/lib.rs | 79 +++++++++++++++++++++++++++++++++ frame/evm-balances/src/mock.rs | 18 ++++++++ frame/evm-balances/src/tests.rs | 1 + 6 files changed, 155 insertions(+) create mode 100644 frame/evm-balances/Cargo.toml create mode 100644 frame/evm-balances/src/lib.rs create mode 100644 frame/evm-balances/src/mock.rs create mode 100644 frame/evm-balances/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 0de6bf9e5d..94b14cce7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4838,6 +4838,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-evm-balances" +version = "1.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "log", + "mockall", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-chain-id" version = "1.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 8ebea92164..1962d1381b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "frame/dynamic-fee", "frame/ethereum", "frame/evm", + "frame/evm-balances", "frame/evm-chain-id", "frame/evm-system", "frame/hotfix-sufficients", @@ -137,6 +138,7 @@ pallet-base-fee = { version = "1.0.0", path = "frame/base-fee", default-features pallet-dynamic-fee = { version = "4.0.0-dev", path = "frame/dynamic-fee", default-features = false } pallet-ethereum = { version = "4.0.0-dev", path = "frame/ethereum", default-features = false } pallet-evm = { version = "6.0.0-dev", path = "frame/evm", default-features = false } +pallet-evm-balances = { version = "1.0.0-dev", path = "frame/evm-balances", default-features = false } pallet-evm-chain-id = { version = "1.0.0-dev", path = "frame/evm-chain-id", default-features = false } pallet-evm-system = { version = "1.0.0-dev", path = "frame/evm-system", default-features = false } pallet-evm-precompile-modexp = { version = "2.0.0-dev", path = "frame/evm/precompile/modexp", default-features = false } diff --git a/frame/evm-balances/Cargo.toml b/frame/evm-balances/Cargo.toml new file mode 100644 index 0000000000..c29253cbce --- /dev/null +++ b/frame/evm-balances/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "pallet-evm-balances" +version = "1.0.0-dev" +license = "Apache-2.0" +description = "FRAME EVM BALANCES pallet." +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { version = "0.4.17", 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] +mockall = "0.11" +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", +] diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs new file mode 100644 index 0000000000..0d6fcbe8d5 --- /dev/null +++ b/frame/evm-balances/src/lib.rs @@ -0,0 +1,79 @@ +// 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. + +//! # EVM Balances Pallet. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_runtime::{traits::One, RuntimeDebug, DispatchResult}; +use scale_codec::{Encode, Decode, MaxEncodedLen, FullCodec}; +use scale_info::TypeInfo; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use sp_runtime::traits::{MaybeDisplay, AtLeast32Bit}; + use sp_std::fmt::Debug; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[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; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event {} + + #[pallet::error] + pub enum Error {} +} diff --git a/frame/evm-balances/src/mock.rs b/frame/evm-balances/src/mock.rs new file mode 100644 index 0000000000..2058ad0e59 --- /dev/null +++ b/frame/evm-balances/src/mock.rs @@ -0,0 +1,18 @@ +// 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. diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs new file mode 100644 index 0000000000..e2a2eb6b37 --- /dev/null +++ b/frame/evm-balances/src/tests.rs @@ -0,0 +1 @@ +//! Unit tests. From c7fd24017da3391d3c8a7ea75c00fde8c1e492f8 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 16 May 2023 21:53:48 +0300 Subject: [PATCH 02/43] Add account data balances logic --- frame/evm-balances/src/account_data.rs | 75 ++++++++++++++++++++++++++ frame/evm-balances/src/lib.rs | 5 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 frame/evm-balances/src/account_data.rs diff --git a/frame/evm-balances/src/account_data.rs b/frame/evm-balances/src/account_data.rs new file mode 100644 index 0000000000..26deb97578 --- /dev/null +++ b/frame/evm-balances/src/account_data.rs @@ -0,0 +1,75 @@ +//! 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, + /// Balance which is reserved and may not be used at all. + /// + /// This can still get slashed, but gets slashed last of all. + /// + /// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens + /// that are still 'owned' by the account holder, but which are suspendable. + /// This includes named reserve and unnamed reserve. + pub reserved: Balance, + /// The amount that `free` may not drop below when withdrawing for *anything except transaction + /// fee payment*. + pub misc_frozen: Balance, + /// The amount that `free` may not drop below when withdrawing specifically for transaction + /// fee payment. + pub fee_frozen: Balance, +} + +impl AccountData { + /// How much this account's balance can be reduced for the given `reasons`. + pub(crate) fn usable(&self, reasons: Reasons) -> Balance { + self.free.saturating_sub(self.frozen(reasons)) + } + + /// The amount that this account's free balance may not be reduced beyond for the given + /// `reasons`. + pub(crate) fn frozen(&self, reasons: Reasons) -> Balance { + match reasons { + Reasons::All => self.misc_frozen.max(self.fee_frozen), + Reasons::Misc => self.misc_frozen, + Reasons::Fee => self.fee_frozen, + } + } + + /// The total balance in this account including any that is reserved and ignoring any frozen. + pub(crate) fn total(&self) -> Balance { + self.free.saturating_add(self.reserved) + } +} + +/// 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/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index 0d6fcbe8d5..337879b313 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -20,10 +20,13 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use sp_runtime::{traits::One, RuntimeDebug, DispatchResult}; +use sp_runtime::{traits::One, RuntimeDebug, DispatchResult, Saturating}; use scale_codec::{Encode, Decode, MaxEncodedLen, FullCodec}; use scale_info::TypeInfo; +pub mod account_data; +use account_data::AccountData; + #[cfg(test)] mod mock; #[cfg(test)] From 0f76b9a1002503e67dc425ae95d88e6cdaaf2769 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 16 May 2023 21:59:44 +0300 Subject: [PATCH 03/43] Define main types --- frame/evm-balances/src/lib.rs | 60 +++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index 337879b313..d92a41bca5 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -20,8 +20,9 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] +use frame_support::traits::{StorageVersion, OnUnbalanced, StoredMap}; use sp_runtime::{traits::One, RuntimeDebug, DispatchResult, Saturating}; -use scale_codec::{Encode, Decode, MaxEncodedLen, FullCodec}; +use scale_codec::{Codec, Encode, Decode, MaxEncodedLen}; use scale_info::TypeInfo; pub mod account_data; @@ -34,22 +35,28 @@ 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::{MaybeDisplay, AtLeast32Bit}; + use sp_runtime::{ + traits::{AtLeast32BitUnsigned, MaybeDisplay}, + FixedPointOperand, + }; use sp_std::fmt::Debug; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The user account identifier type. type AccountId: Parameter @@ -60,23 +67,34 @@ pub mod pallet { + 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; + /// 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>; } #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event {} + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> {} #[pallet::error] - pub enum Error {} + pub enum Error {} } From 4f31e104e0e2f18971c3adc2109bcf8c395dc146 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 16 May 2023 22:03:38 +0300 Subject: [PATCH 04/43] Add imbalances logic --- frame/evm-balances/src/imbalances.rs | 172 +++++++++++++++++++++++++++ frame/evm-balances/src/lib.rs | 15 ++- 2 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 frame/evm-balances/src/imbalances.rs diff --git a/frame/evm-balances/src/imbalances.rs b/frame/evm-balances/src/imbalances.rs new file mode 100644 index 0000000000..77529d439c --- /dev/null +++ b/frame/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/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index d92a41bca5..7373384f8d 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -20,14 +20,18 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{StorageVersion, OnUnbalanced, StoredMap}; -use sp_runtime::{traits::One, RuntimeDebug, DispatchResult, Saturating}; +use frame_support::traits::{StorageVersion, OnUnbalanced, StoredMap, Imbalance}; +use sp_runtime::{traits::{One, Zero}, RuntimeDebug, DispatchResult, Saturating}; use scale_codec::{Codec, Encode, Decode, MaxEncodedLen}; use scale_info::TypeInfo; +use sp_std::{cmp, fmt::Debug, result}; pub mod account_data; use account_data::AccountData; +mod imbalances; +pub use imbalances::{NegativeImbalance, PositiveImbalance}; + #[cfg(test)] mod mock; #[cfg(test)] @@ -91,6 +95,13 @@ pub mod pallet { type DustRemoval: OnUnbalanced>; } + /// The total units issued. + #[pallet::storage] + #[pallet::getter(fn total_issuance)] + #[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 = ()> {} From 3531801775186a5aa42f9fc0a8868eb2787cd48e Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 16 May 2023 22:05:46 +0300 Subject: [PATCH 05/43] Add DustCleaner --- frame/evm-balances/src/lib.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index 7373384f8d..e8c48373ae 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -101,11 +101,41 @@ pub mod pallet { #[pallet::whitelist_storage] pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; + /// The total units of outstanding deactivated balance. + #[pallet::storage] + #[pallet::getter(fn inactive_issuance)] + #[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 = ()> {} + pub enum Event, I: 'static = ()> { + /// An account was removed whose balance was non-zero but below ExistentialDeposit, + /// resulting in an outright loss. + DustLost { + account: >::AccountId, + amount: T::Balance, + }, + } #[pallet::error] pub enum Error {} } + +/// Removes a dust account whose balance was non-zero but below `ExistentialDeposit`. +pub struct DustCleaner, I: 'static = ()>( + Option<(>::AccountId, NegativeImbalance)>, +); + +impl, I: 'static> Drop for DustCleaner { + fn drop(&mut self) { + if let Some((who, dust)) = self.0.take() { + Pallet::::deposit_event(Event::DustLost { + account: who, + amount: dust.peek(), + }); + T::DustRemoval::on_unbalanced(dust); + } + } +} From af57f3a689d4819c7ef59eb93d95dd0a6f93c574 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 16 May 2023 22:09:31 +0300 Subject: [PATCH 06/43] Implement balances related operations --- frame/evm-balances/src/lib.rs | 216 +++++++++++++++++++++++++++++++++- 1 file changed, 213 insertions(+), 3 deletions(-) diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index e8c48373ae..9a3d456e46 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -20,14 +20,26 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{StorageVersion, OnUnbalanced, StoredMap, Imbalance}; -use sp_runtime::{traits::{One, Zero}, RuntimeDebug, DispatchResult, Saturating}; +use frame_support::{ + ensure, + traits::{ + fungible, + tokens::{DepositConsequence, WithdrawConsequence}, + Currency, ExistenceRequirement, + ExistenceRequirement::AllowDeath, + Get, Imbalance, OnUnbalanced, SignedImbalance, StorageVersion, WithdrawReasons, StoredMap + }, +}; +use sp_runtime::{ + traits::{Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Zero}, + ArithmeticError, DispatchError, DispatchResult, RuntimeDebug, Saturating, +}; use scale_codec::{Codec, Encode, Decode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_std::{cmp, fmt::Debug, result}; pub mod account_data; -use account_data::AccountData; +use account_data::{AccountData, Reasons}; mod imbalances; pub use imbalances::{NegativeImbalance, PositiveImbalance}; @@ -111,6 +123,11 @@ pub mod pallet { #[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 { @@ -139,3 +156,196 @@ impl, I: 'static> Drop for DustCleaner { } } } + +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 balance of an account that can be used for transfers, reservations, or any other + /// non-locking, non-transaction-fee activity. Will be at most `free_balance`. + pub fn usable_balance(who: impl sp_std::borrow::Borrow<>::AccountId>) -> T::Balance { + Self::account(who.borrow()).usable(Reasons::Misc) + } + + /// Get the balance of an account that can be used for paying transaction fees (not tipping, + /// or any other kind of fees, though). Will be at most `free_balance`. + pub fn usable_balance_for_fees(who: impl sp_std::borrow::Borrow<>::AccountId>) -> T::Balance { + Self::account(who.borrow()).usable(Reasons::Fee) + } + + /// Get the reserved balance of an account. + pub fn reserved_balance(who: impl sp_std::borrow::Borrow<>::AccountId>) -> T::Balance { + Self::account(who.borrow()).reserved + } + + /// Get all balance 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. This will do nothing if the + /// result of `f` is an `Err`. + /// + /// 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 { + Self::try_mutate_account_with_dust(who, f).map(|(result, dust_cleaner)| { + drop(dust_cleaner); + result + }) + } + + /// 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 `DustCleaner` instance which + /// should be dropped 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_with_dust>( + who: &>::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> Result<(R, DustCleaner), 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 }; + let maybe_account_maybe_dust = Self::post_mutation(who, account); + *maybe_account = maybe_account_maybe_dust.0; + (maybe_endowed, maybe_account_maybe_dust.1, 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, + }); + } + let dust_cleaner = DustCleaner(maybe_dust.map(|dust| (who.clone(), dust))); + (result, dust_cleaner) + }) + } + + /// Handles 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. + /// + /// Returns two values: + /// - `Some` containing the the `new` account, iff the account has sufficient balance. + /// - `Some` containing the dust to be dropped, iff some dust should be dropped. + fn post_mutation( + _who: &>::AccountId, + new: AccountData, + ) -> ( + Option>, + Option>, + ) { + let total = new.total(); + if total < T::ExistentialDeposit::get() { + if total.is_zero() { + (None, None) + } else { + (None, Some(NegativeImbalance::new(total))) + } + } else { + (Some(new), None) + } + } + + fn deposit_consequence( + _who: &>::AccountId, + amount: T::Balance, + account: &AccountData, + mint: bool, + ) -> DepositConsequence { + if amount.is_zero() { + return DepositConsequence::Success; + } + + if mint && TotalIssuance::::get().checked_add(&amount).is_none() { + return DepositConsequence::Overflow; + } + + let new_total_balance = match account.total().checked_add(&amount) { + Some(x) => x, + None => return DepositConsequence::Overflow, + }; + + if new_total_balance < T::ExistentialDeposit::get() { + return DepositConsequence::BelowMinimum; + } + + // 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 withdraw_consequence( + _who: &>::AccountId, + amount: T::Balance, + account: &AccountData, + ) -> WithdrawConsequence { + if amount.is_zero() { + return WithdrawConsequence::Success; + } + + if TotalIssuance::::get().checked_sub(&amount).is_none() { + return WithdrawConsequence::Underflow; + } + + let new_total_balance = match account.total().checked_sub(&amount) { + Some(x) => x, + None => return WithdrawConsequence::NoFunds, + }; + + // 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(); + let success = if new_total_balance < ed { + // ATTENTION. CHECK. + // if frame_system::Pallet::::can_dec_provider(who) { + // WithdrawConsequence::ReducedToZero(new_total_balance) + // } else { + // return WithdrawConsequence::WouldDie; + // } + WithdrawConsequence::ReducedToZero(new_total_balance) + } else { + WithdrawConsequence::Success + }; + + // Enough free funds to have them be reduced. + let new_free_balance = match account.free.checked_sub(&amount) { + Some(b) => b, + None => return WithdrawConsequence::NoFunds, + }; + + // Eventual free funds must be no less than the frozen balance. + let min_balance = account.frozen(Reasons::All); + if new_free_balance < min_balance { + return WithdrawConsequence::Frozen; + } + + success + } +} From 7f321c34a24d219d37b6a3ac44f8bf7b47275426 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 16 May 2023 22:17:06 +0300 Subject: [PATCH 07/43] Implement currencies for the pallet --- frame/evm-balances/src/lib.rs | 447 +++++++++++++++++++++++++++++++++- 1 file changed, 435 insertions(+), 12 deletions(-) diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index 9a3d456e46..4bac59f240 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -133,11 +133,49 @@ pub mod pallet { 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, + reserved: 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, }, } #[pallet::error] - pub enum 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 + KeepAlive, + /// Beneficiary account must pre-exist + DeadAccount, + } } /// Removes a dust account whose balance was non-zero but below `ExistentialDeposit`. @@ -322,17 +360,10 @@ impl, I: 'static> Pallet { // 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(); - let success = if new_total_balance < ed { - // ATTENTION. CHECK. - // if frame_system::Pallet::::can_dec_provider(who) { - // WithdrawConsequence::ReducedToZero(new_total_balance) - // } else { - // return WithdrawConsequence::WouldDie; - // } - WithdrawConsequence::ReducedToZero(new_total_balance) - } else { - WithdrawConsequence::Success - }; + if new_total_balance < ed { + return WithdrawConsequence::WouldDie; + } + let success = WithdrawConsequence::Success; // Enough free funds to have them be reduced. let new_free_balance = match account.free.checked_sub(&amount) { @@ -349,3 +380,395 @@ impl, I: 'static> Pallet { success } } + +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() + } + + // Check if `value` amount of free balance can be slashed from `who`. + 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 { + >::AccountId>>::active_issuance() + } + + 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 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 + } + + // Ensure that an account can withdraw from their free balance given any existing withdrawal + // restrictions like locks and vesting balance. + // Is a no-op if amount to be withdrawn is zero. + // + // # + // Despite iterating over a list of locks, they are limited by the number of + // lock IDs, which means the number of runtime pallets that intend to use and create locks. + // # + fn ensure_can_withdraw( + who: &>::AccountId, + amount: T::Balance, + reasons: WithdrawReasons, + new_balance: T::Balance, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + let min_balance = Self::account(who).frozen(reasons.into()); + ensure!( + new_balance >= min_balance, + Error::::LiquidityRestrictions + ); + 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(()); + } + + Self::try_mutate_account_with_dust( + dest, + |to_account, _| -> Result, DispatchError> { + Self::try_mutate_account_with_dust( + transactor, + |from_account, _| -> DispatchResult { + from_account.free = from_account + .free + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + + // NOTE: total stake being stored in the same type means that this could + // never overflow but better to be safe than sorry. + to_account.free = to_account + .free + .checked_add(&value) + .ok_or(ArithmeticError::Overflow)?; + + let ed = T::ExistentialDeposit::get(); + ensure!(to_account.total() >= ed, Error::::ExistentialDeposit); + + Self::ensure_can_withdraw( + transactor, + value, + WithdrawReasons::TRANSFER, + from_account.free, + ) + .map_err(|_| Error::::LiquidityRestrictions)?; + + // TODO: This is over-conservative. There may now be other providers, and + // this pallet may not even be a provider. + let allow_death = existence_requirement == ExistenceRequirement::AllowDeath; + ensure!( + allow_death || from_account.total() >= ed, + Error::::KeepAlive + ); + + Ok(()) + }, + ) + .map(|(_, maybe_dust_cleaner)| maybe_dust_cleaner) + }, + )?; + + // Emit transfer event. + Self::deposit_event(Event::Transfer { + from: transactor.clone(), + to: dest.clone(), + amount: value, + }); + + 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); + } + + for attempt in 0..2 { + match Self::try_mutate_account( + who, + |account, + _is_new| + -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { + // Best value is the most amount we can slash following liveness rules. + let best_value = match attempt { + // First attempt we try to slash the full amount, and see if liveness issues + // happen. + 0 => value, + // If acting as a critical provider (i.e. first attempt failed), then slash + // as much as possible while leaving at least at ED. + _ => value.min( + (account.free + account.reserved) + .saturating_sub(T::ExistentialDeposit::get()), + ), + }; + + let free_slash = cmp::min(account.free, best_value); + account.free -= free_slash; // Safe because of above check + let remaining_slash = best_value - free_slash; // Safe because of above check + + if !remaining_slash.is_zero() { + // If we have remaining slash, take it from reserved balance. + let reserved_slash = cmp::min(account.reserved, remaining_slash); + account.reserved -= reserved_slash; // Safe because of above check + Ok(( + NegativeImbalance::new(free_slash + reserved_slash), + value - free_slash - reserved_slash, /* Safe because value is gt or + * eq total slashed */ + )) + } else { + // Else we are done! + Ok(( + NegativeImbalance::new(free_slash), + value - free_slash, // Safe because value is gt or eq to total slashed + )) + } + }, + ) { + Ok((imbalance, not_slashed)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(not_slashed), + }); + return (imbalance, not_slashed) + }, + Err(_) => (), + } + } + + // Should never get here. But we'll be defensive anyway. + (Self::NegativeImbalance::zero(), value) + } + + /// 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( + 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( + 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( + 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 + account.reserved < ed; + let would_kill = would_be_dead && account.free + account.reserved >= ed; + ensure!( + liveness == AllowDeath || !would_kill, + Error::::KeepAlive + ); + + 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( + who, + |account, + is_new| + -> Result, DispatchError> { + let ed = T::ExistentialDeposit::get(); + let total = value.saturating_add(account.reserved); + // 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!(total >= 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, + reserved: account.reserved, + }); + Ok(imbalance) + }, + ) + .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) + } +} From 7a9ca5e34b273f8863be65305ee947dbeb2029d9 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 16 May 2023 22:27:52 +0300 Subject: [PATCH 08/43] Implement Inspect for the pallet --- frame/evm-balances/src/lib.rs | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index 4bac59f240..a0b6fe6803 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -772,3 +772,53 @@ where .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) } } + +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 balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).total() + } + + fn reducible_balance(who: &>::AccountId, keep_alive: bool) -> Self::Balance { + let a = Self::account(who); + // Liquid balance is what is neither reserved nor locked/frozen. + let liquid = a.free.saturating_sub(a.fee_frozen.max(a.misc_frozen)); + if !keep_alive { + liquid + } else { + // `must_remain_to_exist` is the part of liquid balance which must remain to keep total + // over ED. + let must_remain_to_exist = + T::ExistentialDeposit::get().saturating_sub(a.total() - liquid); + liquid.saturating_sub(must_remain_to_exist) + } + } + + fn can_deposit( + who: &>::AccountId, + amount: Self::Balance, + mint: bool, + ) -> DepositConsequence { + Self::deposit_consequence(who, amount, &Self::account(who), mint) + } + + fn can_withdraw( + who: &>::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + Self::withdraw_consequence(who, amount, &Self::account(who)) + } +} From 1bcec1e65e45fc57026d0ca7ea6788c19bf5ad78 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 17 May 2023 12:13:15 +0300 Subject: [PATCH 09/43] Make account_data mod private --- frame/evm-balances/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index a0b6fe6803..75e72748a4 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -38,8 +38,8 @@ use scale_codec::{Codec, Encode, Decode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_std::{cmp, fmt::Debug, result}; -pub mod account_data; -use account_data::{AccountData, Reasons}; +mod account_data; +pub use account_data::{AccountData, Reasons}; mod imbalances; pub use imbalances::{NegativeImbalance, PositiveImbalance}; From f49e17359f7c621845e38e6a522a61bba140a6a6 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 24 May 2023 10:30:04 +0200 Subject: [PATCH 10/43] Leave only free balance data for account --- frame/evm-balances/src/account_data.rs | 37 +-------- frame/evm-balances/src/lib.rs | 102 +++++-------------------- 2 files changed, 23 insertions(+), 116 deletions(-) diff --git a/frame/evm-balances/src/account_data.rs b/frame/evm-balances/src/account_data.rs index 26deb97578..08b4e9ca88 100644 --- a/frame/evm-balances/src/account_data.rs +++ b/frame/evm-balances/src/account_data.rs @@ -13,42 +13,13 @@ pub struct AccountData { /// 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, - /// Balance which is reserved and may not be used at all. - /// - /// This can still get slashed, but gets slashed last of all. - /// - /// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens - /// that are still 'owned' by the account holder, but which are suspendable. - /// This includes named reserve and unnamed reserve. - pub reserved: Balance, - /// The amount that `free` may not drop below when withdrawing for *anything except transaction - /// fee payment*. - pub misc_frozen: Balance, - /// The amount that `free` may not drop below when withdrawing specifically for transaction - /// fee payment. - pub fee_frozen: Balance, } -impl AccountData { - /// How much this account's balance can be reduced for the given `reasons`. - pub(crate) fn usable(&self, reasons: Reasons) -> Balance { - self.free.saturating_sub(self.frozen(reasons)) +impl AccountData { + /// The total balance in this account. + pub(crate) fn total(&self) -> Balance { + self.free } - - /// The amount that this account's free balance may not be reduced beyond for the given - /// `reasons`. - pub(crate) fn frozen(&self, reasons: Reasons) -> Balance { - match reasons { - Reasons::All => self.misc_frozen.max(self.fee_frozen), - Reasons::Misc => self.misc_frozen, - Reasons::Fee => self.fee_frozen, - } - } - - /// The total balance in this account including any that is reserved and ignoring any frozen. - pub(crate) fn total(&self) -> Balance { - self.free.saturating_add(self.reserved) - } } /// Simplified reasons for withdrawing balance. diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index 75e72748a4..b2ae0ce4e4 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -144,7 +144,6 @@ pub mod pallet { BalanceSet { who: >::AccountId, free: T::Balance, - reserved: T::Balance, }, /// Some amount was deposited (e.g. for transaction fees). Deposit { @@ -201,24 +200,7 @@ impl, I: 'static> Pallet { Self::account(who.borrow()).free } - /// Get the balance of an account that can be used for transfers, reservations, or any other - /// non-locking, non-transaction-fee activity. Will be at most `free_balance`. - pub fn usable_balance(who: impl sp_std::borrow::Borrow<>::AccountId>) -> T::Balance { - Self::account(who.borrow()).usable(Reasons::Misc) - } - - /// Get the balance of an account that can be used for paying transaction fees (not tipping, - /// or any other kind of fees, though). Will be at most `free_balance`. - pub fn usable_balance_for_fees(who: impl sp_std::borrow::Borrow<>::AccountId>) -> T::Balance { - Self::account(who.borrow()).usable(Reasons::Fee) - } - - /// Get the reserved balance of an account. - pub fn reserved_balance(who: impl sp_std::borrow::Borrow<>::AccountId>) -> T::Balance { - Self::account(who.borrow()).reserved - } - - /// Get all balance information for an account. + /// Get all data information for an account. fn account(who: &>::AccountId) -> AccountData { T::AccountStore::get(who) } @@ -363,21 +345,12 @@ impl, I: 'static> Pallet { if new_total_balance < ed { return WithdrawConsequence::WouldDie; } - let success = WithdrawConsequence::Success; // Enough free funds to have them be reduced. - let new_free_balance = match account.free.checked_sub(&amount) { - Some(b) => b, - None => return WithdrawConsequence::NoFunds, - }; - - // Eventual free funds must be no less than the frozen balance. - let min_balance = account.frozen(Reasons::All); - if new_free_balance < min_balance { - return WithdrawConsequence::Frozen; + match account.free.checked_sub(&amount) { + Some(_) => WithdrawConsequence::Success, + None => WithdrawConsequence::NoFunds, } - - success } } @@ -393,7 +366,6 @@ where Self::account(who).total() } - // Check if `value` amount of free balance can be slashed from `who`. fn can_slash(who: &>::AccountId, value: Self::Balance) -> bool { if value.is_zero() { return true; @@ -421,8 +393,6 @@ where 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(); @@ -436,9 +406,6 @@ where 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(); @@ -456,33 +423,16 @@ where Self::account(who).free } - // Ensure that an account can withdraw from their free balance given any existing withdrawal - // restrictions like locks and vesting balance. - // Is a no-op if amount to be withdrawn is zero. - // - // # - // Despite iterating over a list of locks, they are limited by the number of - // lock IDs, which means the number of runtime pallets that intend to use and create locks. - // # + // We don't have any existing withdrawal restrictions like locks and vesting balance. fn ensure_can_withdraw( - who: &>::AccountId, - amount: T::Balance, - reasons: WithdrawReasons, - new_balance: T::Balance, + _who: &>::AccountId, + _amount: T::Balance, + _reasons: WithdrawReasons, + _new_balance: T::Balance, ) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - let min_balance = Self::account(who).frozen(reasons.into()); - ensure!( - new_balance >= min_balance, - Error::::LiquidityRestrictions - ); 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, @@ -581,31 +531,18 @@ where // If acting as a critical provider (i.e. first attempt failed), then slash // as much as possible while leaving at least at ED. _ => value.min( - (account.free + account.reserved) + account.free .saturating_sub(T::ExistentialDeposit::get()), ), }; let free_slash = cmp::min(account.free, best_value); account.free -= free_slash; // Safe because of above check - let remaining_slash = best_value - free_slash; // Safe because of above check - - if !remaining_slash.is_zero() { - // If we have remaining slash, take it from reserved balance. - let reserved_slash = cmp::min(account.reserved, remaining_slash); - account.reserved -= reserved_slash; // Safe because of above check - Ok(( - NegativeImbalance::new(free_slash + reserved_slash), - value - free_slash - reserved_slash, /* Safe because value is gt or - * eq total slashed */ - )) - } else { - // Else we are done! - Ok(( - NegativeImbalance::new(free_slash), - value - free_slash, // Safe because value is gt or eq to total slashed - )) - } + + Ok(( + NegativeImbalance::new(free_slash), + value - free_slash, // Safe because value is gt or eq to total slashed + )) }, ) { Ok((imbalance, not_slashed)) => { @@ -714,8 +651,8 @@ where // 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 + account.reserved < ed; - let would_kill = would_be_dead && account.free + account.reserved >= ed; + let would_be_dead = new_free_account < ed; + let would_kill = would_be_dead && account.free >= ed; ensure!( liveness == AllowDeath || !would_kill, Error::::KeepAlive @@ -745,7 +682,7 @@ where is_new| -> Result, DispatchError> { let ed = T::ExistentialDeposit::get(); - let total = value.saturating_add(account.reserved); + let total = value; // 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 @@ -764,7 +701,6 @@ where Self::deposit_event(Event::BalanceSet { who: who.clone(), free: account.free, - reserved: account.reserved, }); Ok(imbalance) }, @@ -795,7 +731,7 @@ impl, I: 'static> fungible::Inspect<>::AccountId> fo fn reducible_balance(who: &>::AccountId, keep_alive: bool) -> Self::Balance { let a = Self::account(who); // Liquid balance is what is neither reserved nor locked/frozen. - let liquid = a.free.saturating_sub(a.fee_frozen.max(a.misc_frozen)); + let liquid = a.free; if !keep_alive { liquid } else { From 31d8301417827e7d5111594bf71df67ba8c0e4da Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 29 May 2023 17:29:23 +0200 Subject: [PATCH 11/43] Support try-runtime features --- frame/evm-balances/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frame/evm-balances/Cargo.toml b/frame/evm-balances/Cargo.toml index c29253cbce..48aebd1388 100644 --- a/frame/evm-balances/Cargo.toml +++ b/frame/evm-balances/Cargo.toml @@ -37,3 +37,7 @@ std = [ "sp-runtime/std", "sp-std/std", ] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] From 27faf1d059608b918cf182fb85a910ead69fae49 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 30 May 2023 13:12:26 +0200 Subject: [PATCH 12/43] Apply formatting --- frame/evm-balances/src/account_data.rs | 30 +- frame/evm-balances/src/imbalances.rs | 248 ++--- frame/evm-balances/src/lib.rs | 1164 ++++++++++++------------ 3 files changed, 719 insertions(+), 723 deletions(-) diff --git a/frame/evm-balances/src/account_data.rs b/frame/evm-balances/src/account_data.rs index 08b4e9ca88..3b70e21bc7 100644 --- a/frame/evm-balances/src/account_data.rs +++ b/frame/evm-balances/src/account_data.rs @@ -25,22 +25,22 @@ impl AccountData { /// 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, + /// 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/frame/evm-balances/src/imbalances.rs b/frame/evm-balances/src/imbalances.rs index 77529d439c..1a5c619ab0 100644 --- a/frame/evm-balances/src/imbalances.rs +++ b/frame/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/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index b2ae0ce4e4..2f4587affb 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -21,21 +21,21 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ - ensure, - traits::{ - fungible, - tokens::{DepositConsequence, WithdrawConsequence}, - Currency, ExistenceRequirement, - ExistenceRequirement::AllowDeath, - Get, Imbalance, OnUnbalanced, SignedImbalance, StorageVersion, WithdrawReasons, StoredMap - }, + ensure, + traits::{ + fungible, + tokens::{DepositConsequence, WithdrawConsequence}, + Currency, ExistenceRequirement, + ExistenceRequirement::AllowDeath, + 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 scale_codec::{Codec, Encode, Decode, MaxEncodedLen}; -use scale_info::TypeInfo; use sp_std::{cmp, fmt::Debug, result}; mod account_data; @@ -59,20 +59,21 @@ pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, MaybeDisplay}, - FixedPointOperand, - }; + traits::{AtLeast32BitUnsigned, MaybeDisplay}, + FixedPointOperand, + }; use sp_std::fmt::Debug; #[pallet::pallet] - #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData<(T, I)>); + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// The user account identifier type. type AccountId: Parameter @@ -84,441 +85,439 @@ pub mod pallet { + MaxEncodedLen; /// The balance of an account. - type Balance: Parameter - + Member - + AtLeast32BitUnsigned - + Codec - + Default - + Copy - + MaybeSerializeDeserialize - + Debug - + MaxEncodedLen - + TypeInfo - + FixedPointOperand; + 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; + #[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>; + /// Handler for the unbalanced reduction when removing a dust account. + type DustRemoval: OnUnbalanced>; } /// The total units issued. - #[pallet::storage] - #[pallet::getter(fn total_issuance)] - #[pallet::whitelist_storage] - pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn total_issuance)] + #[pallet::whitelist_storage] + pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; /// The total units of outstanding deactivated balance. - #[pallet::storage] - #[pallet::getter(fn inactive_issuance)] - #[pallet::whitelist_storage] - pub type InactiveIssuance, I: 'static = ()> = - StorageValue<_, T::Balance, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn inactive_issuance)] + #[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 = ()> { + #[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, - }, + 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, - }, + /// resulting in an outright loss. + DustLost { + account: >::AccountId, + amount: T::Balance, + }, /// Transfer succeeded. - Transfer { - from: >::AccountId, - to: >::AccountId, - amount: T::Balance, - }, + Transfer { + from: >::AccountId, + to: >::AccountId, + amount: T::Balance, + }, /// A balance was set by root. - BalanceSet { - who: >::AccountId, - free: T::Balance, - }, + 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, - }, + 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, - }, + Slashed { + who: >::AccountId, + amount: T::Balance, + }, } - #[pallet::error] - pub enum Error { + #[pallet::error] + pub enum Error { /// Account liquidity restrictions prevent withdrawal - LiquidityRestrictions, + LiquidityRestrictions, /// Balance too low to send value. - InsufficientBalance, - /// Value too low to create account due to existential deposit - ExistentialDeposit, + InsufficientBalance, + /// Value too low to create account due to existential deposit + ExistentialDeposit, /// Transfer/payment would kill account - KeepAlive, + KeepAlive, /// Beneficiary account must pre-exist - DeadAccount, + DeadAccount, } } /// Removes a dust account whose balance was non-zero but below `ExistentialDeposit`. pub struct DustCleaner, I: 'static = ()>( - Option<(>::AccountId, NegativeImbalance)>, + Option<(>::AccountId, NegativeImbalance)>, ); impl, I: 'static> Drop for DustCleaner { - fn drop(&mut self) { - if let Some((who, dust)) = self.0.take() { - Pallet::::deposit_event(Event::DustLost { - account: who, - amount: dust.peek(), - }); - T::DustRemoval::on_unbalanced(dust); - } - } + fn drop(&mut self) { + if let Some((who, dust)) = self.0.take() { + Pallet::::deposit_event(Event::DustLost { + account: who, + amount: dust.peek(), + }); + T::DustRemoval::on_unbalanced(dust); + } + } } impl, I: 'static> Pallet { /// Get the free balance of an account. - pub fn free_balance(who: impl sp_std::borrow::Borrow<>::AccountId>) -> T::Balance { + 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. This will do nothing if the - /// result of `f` is an `Err`. - /// - /// 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 { - Self::try_mutate_account_with_dust(who, f).map(|(result, dust_cleaner)| { - drop(dust_cleaner); - result - }) - } - - /// 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 `DustCleaner` instance which - /// should be dropped 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_with_dust>( - who: &>::AccountId, - f: impl FnOnce(&mut AccountData, bool) -> Result, - ) -> Result<(R, DustCleaner), 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 }; - let maybe_account_maybe_dust = Self::post_mutation(who, account); - *maybe_account = maybe_account_maybe_dust.0; - (maybe_endowed, maybe_account_maybe_dust.1, 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, - }); - } - let dust_cleaner = DustCleaner(maybe_dust.map(|dust| (who.clone(), dust))); - (result, dust_cleaner) - }) - } - - /// Handles 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. - /// - /// Returns two values: - /// - `Some` containing the the `new` account, iff the account has sufficient balance. - /// - `Some` containing the dust to be dropped, iff some dust should be dropped. - fn post_mutation( - _who: &>::AccountId, - new: AccountData, - ) -> ( - Option>, - Option>, - ) { - let total = new.total(); - if total < T::ExistentialDeposit::get() { - if total.is_zero() { - (None, None) - } else { - (None, Some(NegativeImbalance::new(total))) - } - } else { - (Some(new), None) - } - } - - fn deposit_consequence( - _who: &>::AccountId, - amount: T::Balance, - account: &AccountData, - mint: bool, - ) -> DepositConsequence { - if amount.is_zero() { - return DepositConsequence::Success; - } - - if mint && TotalIssuance::::get().checked_add(&amount).is_none() { - return DepositConsequence::Overflow; - } - - let new_total_balance = match account.total().checked_add(&amount) { - Some(x) => x, - None => return DepositConsequence::Overflow, - }; - - if new_total_balance < T::ExistentialDeposit::get() { - return DepositConsequence::BelowMinimum; - } - - // 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 withdraw_consequence( - _who: &>::AccountId, - amount: T::Balance, - account: &AccountData, - ) -> WithdrawConsequence { - if amount.is_zero() { - return WithdrawConsequence::Success; - } - - if TotalIssuance::::get().checked_sub(&amount).is_none() { - return WithdrawConsequence::Underflow; - } - - let new_total_balance = match account.total().checked_sub(&amount) { - Some(x) => x, - None => return WithdrawConsequence::NoFunds, - }; - - // 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_total_balance < ed { - return WithdrawConsequence::WouldDie; - } - - // Enough free funds to have them be reduced. - match account.free.checked_sub(&amount) { - Some(_) => WithdrawConsequence::Success, - None => WithdrawConsequence::NoFunds, - } - } + /// 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. This will do nothing if the + /// result of `f` is an `Err`. + /// + /// 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 { + Self::try_mutate_account_with_dust(who, f).map(|(result, dust_cleaner)| { + drop(dust_cleaner); + result + }) + } + + /// 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 `DustCleaner` instance which + /// should be dropped 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_with_dust>( + who: &>::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> Result<(R, DustCleaner), 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 }; + let maybe_account_maybe_dust = Self::post_mutation(who, account); + *maybe_account = maybe_account_maybe_dust.0; + (maybe_endowed, maybe_account_maybe_dust.1, 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, + }); + } + let dust_cleaner = DustCleaner(maybe_dust.map(|dust| (who.clone(), dust))); + (result, dust_cleaner) + }) + } + + /// Handles 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. + /// + /// Returns two values: + /// - `Some` containing the the `new` account, iff the account has sufficient balance. + /// - `Some` containing the dust to be dropped, iff some dust should be dropped. + fn post_mutation( + _who: &>::AccountId, + new: AccountData, + ) -> ( + Option>, + Option>, + ) { + let total = new.total(); + if total < T::ExistentialDeposit::get() { + if total.is_zero() { + (None, None) + } else { + (None, Some(NegativeImbalance::new(total))) + } + } else { + (Some(new), None) + } + } + + fn deposit_consequence( + _who: &>::AccountId, + amount: T::Balance, + account: &AccountData, + mint: bool, + ) -> DepositConsequence { + if amount.is_zero() { + return DepositConsequence::Success; + } + + if mint && TotalIssuance::::get().checked_add(&amount).is_none() { + return DepositConsequence::Overflow; + } + + let new_total_balance = match account.total().checked_add(&amount) { + Some(x) => x, + None => return DepositConsequence::Overflow, + }; + + if new_total_balance < T::ExistentialDeposit::get() { + return DepositConsequence::BelowMinimum; + } + + // 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 withdraw_consequence( + _who: &>::AccountId, + amount: T::Balance, + account: &AccountData, + ) -> WithdrawConsequence { + if amount.is_zero() { + return WithdrawConsequence::Success; + } + + if TotalIssuance::::get().checked_sub(&amount).is_none() { + return WithdrawConsequence::Underflow; + } + + let new_total_balance = match account.total().checked_sub(&amount) { + Some(x) => x, + None => return WithdrawConsequence::NoFunds, + }; + + // 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_total_balance < ed { + return WithdrawConsequence::WouldDie; + } + + // Enough free funds to have them be reduced. + match account.free.checked_sub(&amount) { + Some(_) => WithdrawConsequence::Success, + None => WithdrawConsequence::NoFunds, + } + } } 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 { - >::AccountId>>::active_issuance() - } - - 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 minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - - 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) - } - - 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 - } + 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 { + >::AccountId>>::active_issuance() + } + + 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 minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + + 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) + } + + 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 locks and vesting balance. - fn ensure_can_withdraw( - _who: &>::AccountId, - _amount: T::Balance, - _reasons: WithdrawReasons, - _new_balance: T::Balance, - ) -> DispatchResult { - Ok(()) - } - - fn transfer( - transactor: &>::AccountId, - dest: &>::AccountId, - value: Self::Balance, - existence_requirement: ExistenceRequirement, - ) -> DispatchResult { - if value.is_zero() || transactor == dest { - return Ok(()); - } - - Self::try_mutate_account_with_dust( - dest, - |to_account, _| -> Result, DispatchError> { - Self::try_mutate_account_with_dust( - transactor, - |from_account, _| -> DispatchResult { - from_account.free = from_account - .free - .checked_sub(&value) - .ok_or(Error::::InsufficientBalance)?; - - // NOTE: total stake being stored in the same type means that this could - // never overflow but better to be safe than sorry. - to_account.free = to_account - .free - .checked_add(&value) - .ok_or(ArithmeticError::Overflow)?; - - let ed = T::ExistentialDeposit::get(); - ensure!(to_account.total() >= ed, Error::::ExistentialDeposit); - - Self::ensure_can_withdraw( - transactor, - value, - WithdrawReasons::TRANSFER, - from_account.free, - ) - .map_err(|_| Error::::LiquidityRestrictions)?; - - // TODO: This is over-conservative. There may now be other providers, and - // this pallet may not even be a provider. - let allow_death = existence_requirement == ExistenceRequirement::AllowDeath; - ensure!( - allow_death || from_account.total() >= ed, - Error::::KeepAlive - ); - - Ok(()) - }, - ) - .map(|(_, maybe_dust_cleaner)| maybe_dust_cleaner) - }, - )?; - - // Emit transfer event. - Self::deposit_event(Event::Transfer { - from: transactor.clone(), - to: dest.clone(), - amount: value, - }); - - 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); - } - - for attempt in 0..2 { - match Self::try_mutate_account( + fn ensure_can_withdraw( + _who: &>::AccountId, + _amount: T::Balance, + _reasons: WithdrawReasons, + _new_balance: T::Balance, + ) -> DispatchResult { + Ok(()) + } + + fn transfer( + transactor: &>::AccountId, + dest: &>::AccountId, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult { + if value.is_zero() || transactor == dest { + return Ok(()); + } + + Self::try_mutate_account_with_dust( + dest, + |to_account, _| -> Result, DispatchError> { + Self::try_mutate_account_with_dust( + transactor, + |from_account, _| -> DispatchResult { + from_account.free = from_account + .free + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + + to_account.free = to_account + .free + .checked_add(&value) + .ok_or(ArithmeticError::Overflow)?; + + let ed = T::ExistentialDeposit::get(); + ensure!(to_account.total() >= ed, Error::::ExistentialDeposit); + + Self::ensure_can_withdraw( + transactor, + value, + WithdrawReasons::TRANSFER, + from_account.free, + ) + .map_err(|_| Error::::LiquidityRestrictions)?; + + let allow_death = existence_requirement == ExistenceRequirement::AllowDeath; + ensure!( + allow_death || from_account.total() >= ed, + Error::::KeepAlive + ); + + Ok(()) + }, + ) + .map(|(_, maybe_dust_cleaner)| maybe_dust_cleaner) + }, + )?; + + // Emit transfer event. + Self::deposit_event(Event::Transfer { + from: transactor.clone(), + to: dest.clone(), + amount: value, + }); + + 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); + } + + for attempt in 0..2 { + match Self::try_mutate_account( who, |account, _is_new| @@ -530,10 +529,7 @@ where 0 => value, // If acting as a critical provider (i.e. first attempt failed), then slash // as much as possible while leaving at least at ED. - _ => value.min( - account.free - .saturating_sub(T::ExistentialDeposit::get()), - ), + _ => value.min(account.free.saturating_sub(T::ExistentialDeposit::get())), }; let free_slash = cmp::min(account.free, best_value); @@ -550,133 +546,133 @@ where who: who.clone(), amount: value.saturating_sub(not_slashed), }); - return (imbalance, not_slashed) - }, + return (imbalance, not_slashed); + } Err(_) => (), } - } - - // Should never get here. But we'll be defensive anyway. - (Self::NegativeImbalance::zero(), value) - } - - /// 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( - 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( - 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( - 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 == AllowDeath || !would_kill, - Error::::KeepAlive - ); - - 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( + } + + // Should never get here. But we'll be defensive anyway. + (Self::NegativeImbalance::zero(), value) + } + + /// 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( + 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( + 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( + 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 == AllowDeath || !would_kill, + Error::::KeepAlive + ); + + 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( who, |account, is_new| @@ -706,55 +702,55 @@ where }, ) .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) - } + } } 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 balance(who: &>::AccountId) -> Self::Balance { - Self::account(who).total() - } - - fn reducible_balance(who: &>::AccountId, keep_alive: bool) -> Self::Balance { - let a = Self::account(who); - // Liquid balance is what is neither reserved nor locked/frozen. - let liquid = a.free; - if !keep_alive { - liquid - } else { - // `must_remain_to_exist` is the part of liquid balance which must remain to keep total - // over ED. - let must_remain_to_exist = - T::ExistentialDeposit::get().saturating_sub(a.total() - liquid); - liquid.saturating_sub(must_remain_to_exist) - } - } - - fn can_deposit( - who: &>::AccountId, - amount: Self::Balance, - mint: bool, - ) -> DepositConsequence { - Self::deposit_consequence(who, amount, &Self::account(who), mint) - } - - fn can_withdraw( - who: &>::AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - Self::withdraw_consequence(who, amount, &Self::account(who)) - } + 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 balance(who: &>::AccountId) -> Self::Balance { + Self::account(who).total() + } + + fn reducible_balance(who: &>::AccountId, keep_alive: bool) -> Self::Balance { + let a = Self::account(who); + // Liquid balance is what is neither reserved nor locked/frozen. + let liquid = a.free; + if !keep_alive { + liquid + } else { + // `must_remain_to_exist` is the part of liquid balance which must remain to keep total + // over ED. + let must_remain_to_exist = + T::ExistentialDeposit::get().saturating_sub(a.total() - liquid); + liquid.saturating_sub(must_remain_to_exist) + } + } + + fn can_deposit( + who: &>::AccountId, + amount: Self::Balance, + mint: bool, + ) -> DepositConsequence { + Self::deposit_consequence(who, amount, &Self::account(who), mint) + } + + fn can_withdraw( + who: &>::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + Self::withdraw_consequence(who, amount, &Self::account(who)) + } } From 939c4ed23582656b5e94c35d408a080186e42181 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 30 May 2023 13:59:36 +0200 Subject: [PATCH 13/43] Fix comment --- frame/evm-balances/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/evm-balances/src/lib.rs b/frame/evm-balances/src/lib.rs index 2f4587affb..45ff96546d 100644 --- a/frame/evm-balances/src/lib.rs +++ b/frame/evm-balances/src/lib.rs @@ -426,7 +426,7 @@ where Self::account(who).free } - // We don't have any existing withdrawal restrictions like locks and vesting balance. + // We don't have any existing withdrawal restrictions like locked and reserved balance. fn ensure_can_withdraw( _who: &>::AccountId, _amount: T::Balance, From 533a73585605fa8ec7065e64a48ffa7ebc43be5c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 30 May 2023 17:31:02 +0200 Subject: [PATCH 14/43] Add mock with evm, evm-system, evm-balances configs --- Cargo.lock | 5 +- frame/evm-balances/Cargo.toml | 10 +- frame/evm-balances/src/mock.rs | 224 +++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94b14cce7e..a831e67d8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4842,10 +4842,13 @@ dependencies = [ name = "pallet-evm-balances" version = "1.0.0-dev" dependencies = [ + "fp-evm", "frame-support", "frame-system", "log", - "mockall", + "pallet-evm", + "pallet-evm-system", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core", diff --git a/frame/evm-balances/Cargo.toml b/frame/evm-balances/Cargo.toml index 48aebd1388..6c9720cdcd 100644 --- a/frame/evm-balances/Cargo.toml +++ b/frame/evm-balances/Cargo.toml @@ -21,7 +21,10 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] -mockall = "0.11" +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 } @@ -34,8 +37,13 @@ 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", diff --git a/frame/evm-balances/src/mock.rs b/frame/evm-balances/src/mock.rs index 2058ad0e59..86e9329b9a 100644 --- a/frame/evm-balances/src/mock.rs +++ b/frame/evm-balances/src/mock.rs @@ -16,3 +16,227 @@ // 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; + +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, + 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<500>; + 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_ref_time(7u64)) + } +} + +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()) + } +} + +parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub WeightPerGas: Weight = Weight::from_ref_time(20_000); +} + +pub struct EvmAccountProvider; + +impl pallet_evm::AccountProvider for EvmAccountProvider { + type AccountId = H160; + type Index = u64; + + fn create_account(who: &Self::AccountId) { + let _ = EvmSystem::create_account(who); + } + + fn remove_account(who: &Self::AccountId) { + let _ = EvmSystem::remove_account(who); + } + + fn account_nonce(who: &Self::AccountId) -> Self::Index { + EvmSystem::account_nonce(who) + } + + fn inc_account_nonce(who: &Self::AccountId) { + EvmSystem::inc_account_nonce(who); + } +} + +impl pallet_evm::Config for Test { + type AccountProvider = EvmAccountProvider; + 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; +} + +/// 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( + H160::from_str("1000000000000000000000000000000000000000").unwrap(), + init_genesis_account.clone(), + ); + map.insert( + H160::from_str("2000000000000000000000000000000000000000").unwrap(), + 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 + } +} From 3800686261e1bce40541bcb45c6676557bcfba68 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 30 May 2023 17:55:35 +0200 Subject: [PATCH 15/43] Add basic setup test --- frame/evm-balances/src/mock.rs | 18 ++++++++++-------- frame/evm-balances/src/tests.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/frame/evm-balances/src/mock.rs b/frame/evm-balances/src/mock.rs index 86e9329b9a..13fa36a76b 100644 --- a/frame/evm-balances/src/mock.rs +++ b/frame/evm-balances/src/mock.rs @@ -37,6 +37,14 @@ use crate::{self as pallet_evm_balances, *}; pub(crate) const INIT_BALANCE: u64 = 10_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; @@ -193,14 +201,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { nonce: Default::default(), storage: Default::default(), }; - map.insert( - H160::from_str("1000000000000000000000000000000000000000").unwrap(), - init_genesis_account.clone(), - ); - map.insert( - H160::from_str("2000000000000000000000000000000000000000").unwrap(), - init_genesis_account, - ); + map.insert(alice(), init_genesis_account.clone()); + map.insert(bob(), init_genesis_account); map }, }, diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index e2a2eb6b37..3879dd0d09 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -1 +1,27 @@ //! Unit tests. + +use crate::{mock::*, *}; + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with_ext(|_| { + // Check the accounts. + assert_eq!( + ::full_account(&alice()), + pallet_evm_system::AccountInfo { + nonce: 0, + data: account_data::AccountData { free: INIT_BALANCE } + } + ); + assert_eq!( + ::full_account(&bob()), + pallet_evm_system::AccountInfo { + nonce: 0, + data: account_data::AccountData { free: INIT_BALANCE } + } + ); + + // Check the total balance value. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + }); +} From 9d3b6c2bc7a5ccecd7bc8a5c336899c0df4fb474 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 30 May 2023 18:05:26 +0200 Subject: [PATCH 16/43] Add fee deduction test --- frame/evm-balances/src/mock.rs | 2 +- frame/evm-balances/src/tests.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/frame/evm-balances/src/mock.rs b/frame/evm-balances/src/mock.rs index 13fa36a76b..1b05d60f34 100644 --- a/frame/evm-balances/src/mock.rs +++ b/frame/evm-balances/src/mock.rs @@ -101,7 +101,7 @@ impl pallet_evm_balances::Config for Test { type RuntimeEvent = RuntimeEvent; type AccountId = H160; type Balance = u64; - type ExistentialDeposit = ConstU64<500>; + type ExistentialDeposit = ConstU64<1>; type AccountStore = EvmSystem; type DustRemoval = (); } diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 3879dd0d09..ce2a2b7abc 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -1,5 +1,8 @@ //! Unit tests. +use sp_core::{H160, U256}; +use sp_std::str::FromStr; + use crate::{mock::*, *}; #[test] @@ -25,3 +28,27 @@ fn basic_setup_works() { assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); }); } + +#[test] +fn fee_deduction() { + new_test_ext().execute_with(|| { + 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); + }); +} From d730d011839ebe5d2ef3a6201b0c3f4c74f9bf60 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 31 May 2023 14:22:16 +0200 Subject: [PATCH 17/43] Add issuance_after_tip test --- frame/evm-balances/src/mock.rs | 2 +- frame/evm-balances/src/tests.rs | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/frame/evm-balances/src/mock.rs b/frame/evm-balances/src/mock.rs index 1b05d60f34..d732897bee 100644 --- a/frame/evm-balances/src/mock.rs +++ b/frame/evm-balances/src/mock.rs @@ -35,7 +35,7 @@ use sp_std::{boxed::Box, prelude::*, str::FromStr}; use crate::{self as pallet_evm_balances, *}; -pub(crate) const INIT_BALANCE: u64 = 10_000_000; +pub(crate) const INIT_BALANCE: u64 = 10_000_000_000_000_000; pub(crate) fn alice() -> H160 { H160::from_str("1000000000000000000000000000000000000000").unwrap() diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index ce2a2b7abc..9c13ec1a17 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -1,6 +1,9 @@ //! Unit tests. +use frame_support::{assert_noop, assert_ok}; +use pallet_evm::{FeeCalculator, Runner}; use sp_core::{H160, U256}; +use sp_runtime::traits::UniqueSaturatedInto; use sp_std::str::FromStr; use crate::{mock::*, *}; @@ -52,3 +55,37 @@ fn fee_deduction() { assert_eq!(EvmBalances::free_balance(&charlie), 95); }); } + +#[test] +fn issuance_after_tip() { + new_test_ext().execute_with(|| { + let before_tip = ::Currency::total_issuance(); + + // Set block number to enable events. + System::set_block_number(1); + + assert_ok!(::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + 1000000, + Some(U256::from(2_000_000_000)), + None, + None, + Vec::new(), + true, + true, + ::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))); + }); +} From c1f198c254471fcb0dee269004f2301b3ceadc49 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 31 May 2023 14:25:43 +0200 Subject: [PATCH 18/43] Add refunds_should_work test --- frame/evm-balances/src/tests.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 9c13ec1a17..86f2fe2f14 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -89,3 +89,34 @@ fn issuance_after_tip() { assert_eq!(after_tip, (before_tip - (base_fee * 21_000))); }); } + +#[test] +fn refunds_should_work() { + new_test_ext().execute_with(|| { + 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 _ = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + 1000000, + Some(U256::from(2_000_000_000)), + None, + None, + Vec::new(), + true, + true, + ::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); + }); +} From 2937fc3e7650c54ec4c5a4d5be5ba1edcf9e2e95 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 31 May 2023 14:33:51 +0200 Subject: [PATCH 19/43] Add refunds_and_priority_should_work test --- frame/evm-balances/src/tests.rs | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 86f2fe2f14..eea4d98c5f 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -120,3 +120,39 @@ fn refunds_should_work() { assert_eq!(after_call, before_call - total_cost); }); } + +#[test] +fn refunds_and_priority_should_work() { + new_test_ext().execute_with(|| { + 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 _ = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + 1000000, + Some(max_fee_per_gas), + Some(tip), + None, + Vec::new(), + true, + true, + ::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); + }); +} From ccc5d22030540bb1d5d459eecd47cc8a3fff0b60 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 31 May 2023 14:34:24 +0200 Subject: [PATCH 20/43] Fix clippy in tests --- frame/evm-balances/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index eea4d98c5f..ca9b88fab3 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -1,6 +1,6 @@ //! Unit tests. -use frame_support::{assert_noop, assert_ok}; +use frame_support::assert_ok; use pallet_evm::{FeeCalculator, Runner}; use sp_core::{H160, U256}; use sp_runtime::traits::UniqueSaturatedInto; From 307d484699ccb97776b8e2c989d7ffc5bd7579f2 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 1 Jun 2023 18:35:41 +0300 Subject: [PATCH 21/43] Fix basec setup works test with evm balances checks --- frame/evm-balances/src/tests.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index ca9b88fab3..e8024d4e64 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -13,18 +13,12 @@ fn basic_setup_works() { new_test_ext().execute_with_ext(|_| { // Check the accounts. assert_eq!( - ::full_account(&alice()), - pallet_evm_system::AccountInfo { - nonce: 0, - data: account_data::AccountData { free: INIT_BALANCE } - } + ::account(&alice()), + account_data::AccountData { free: INIT_BALANCE } ); assert_eq!( - ::full_account(&bob()), - pallet_evm_system::AccountInfo { - nonce: 0, - data: account_data::AccountData { free: INIT_BALANCE } - } + ::account(&bob()), + account_data::AccountData { free: INIT_BALANCE } ); // Check the total balance value. From dd3044aa0ef392c3418a3e2ad433384d940636ac Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 1 Jun 2023 18:37:37 +0300 Subject: [PATCH 22/43] Remove redundant set block in tests --- frame/evm-balances/src/tests.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index e8024d4e64..46a5014b52 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -55,9 +55,6 @@ fn issuance_after_tip() { new_test_ext().execute_with(|| { let before_tip = ::Currency::total_issuance(); - // Set block number to enable events. - System::set_block_number(1); - assert_ok!(::Runner::call( alice(), bob(), From 87f457c4ec17144d34afe50190cab9683543b738 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 1 Jun 2023 18:44:01 +0300 Subject: [PATCH 23/43] Add call_should_fail_with_priority_greater_than_max_fee test --- frame/evm-balances/src/tests.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 46a5014b52..63d9c14fa4 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -1,6 +1,6 @@ //! Unit tests. -use frame_support::assert_ok; +use frame_support::{assert_ok, weights::Weight}; use pallet_evm::{FeeCalculator, Runner}; use sp_core::{H160, U256}; use sp_runtime::traits::UniqueSaturatedInto; @@ -147,3 +147,29 @@ fn refunds_and_priority_should_work() { assert_eq!(after_call, before_call - total_cost); }); } + +#[test] +fn call_should_fail_with_priority_greater_than_max_fee() { + new_test_ext().execute_with(|| { + // Max priority greater than max fee should fail. + let tip: u128 = 1_100_000_000; + + let result = ::Runner::call( + alice(), + bob(), + Vec::new(), + U256::from(1), + 1000000, + Some(U256::from(1_000_000_000)), + Some(U256::from(tip)), + None, + Vec::new(), + true, + true, + ::config(), + ); + assert!(result.is_err()); + // Some used weight is returned as part of the error. + assert_eq!(result.unwrap_err().weight, Weight::from_ref_time(7)); + }); +} From 67c596117a6490ddcd2eec8438556f09ca4615f8 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 1 Jun 2023 18:45:22 +0300 Subject: [PATCH 24/43] Add call_should_succeed_with_priority_equal_to_max_fee test --- frame/evm-balances/src/tests.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 63d9c14fa4..b583c823ca 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -173,3 +173,27 @@ fn call_should_fail_with_priority_greater_than_max_fee() { assert_eq!(result.unwrap_err().weight, Weight::from_ref_time(7)); }); } + +#[test] +fn call_should_succeed_with_priority_equal_to_max_fee() { + new_test_ext().execute_with(|| { + let tip: u128 = 1_000_000_000; + // 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), + 1000000, + Some(U256::from(1_000_000_000)), + Some(U256::from(tip)), + None, + Vec::new(), + true, + true, + ::config(), + ); + assert!(result.is_ok()); + }); +} From 5e5060b5b7586d0e92236d2cc40b93ca0e3de649 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 15:33:58 +0300 Subject: [PATCH 25/43] Use EvmSystem as AccountProvider in tests --- frame/evm-balances/src/mock.rs | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/frame/evm-balances/src/mock.rs b/frame/evm-balances/src/mock.rs index d732897bee..58aa09c91f 100644 --- a/frame/evm-balances/src/mock.rs +++ b/frame/evm-balances/src/mock.rs @@ -141,31 +141,8 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_ref_time(20_000); } -pub struct EvmAccountProvider; - -impl pallet_evm::AccountProvider for EvmAccountProvider { - type AccountId = H160; - type Index = u64; - - fn create_account(who: &Self::AccountId) { - let _ = EvmSystem::create_account(who); - } - - fn remove_account(who: &Self::AccountId) { - let _ = EvmSystem::remove_account(who); - } - - fn account_nonce(who: &Self::AccountId) -> Self::Index { - EvmSystem::account_nonce(who) - } - - fn inc_account_nonce(who: &Self::AccountId) { - EvmSystem::inc_account_nonce(who); - } -} - impl pallet_evm::Config for Test { - type AccountProvider = EvmAccountProvider; + type AccountProvider = EvmSystem; type FeeCalculator = FixedGasPrice; type GasWeightMapping = FixedGasWeightMapping; type WeightPerGas = WeightPerGas; From 52fef633e05d0c603dabe6d63c78ca2f5fe13cff Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 16:10:19 +0300 Subject: [PATCH 26/43] Add account_should_be_reaped test --- frame/evm-balances/src/tests.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index b583c823ca..0dc4e529d5 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -26,6 +26,26 @@ fn basic_setup_works() { }); } +#[test] +fn account_should_be_reaped() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert!(EvmSystem::account_exists(&bob())); + + // Invoke the function under test. + assert_ok!(>::transfer( + &bob(), + &alice(), + INIT_BALANCE, + AllowDeath + )); + + // Assert state changes. + assert_eq!(EvmBalances::free_balance(&bob()), 0); + assert!(!EvmSystem::account_exists(&bob())); + }); +} + #[test] fn fee_deduction() { new_test_ext().execute_with(|| { From 3953750d64369f90c1dc419955a619d818de85b5 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 16:29:09 +0300 Subject: [PATCH 27/43] Add deposit_into_existing test --- frame/evm-balances/src/tests.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 0dc4e529d5..56365efca6 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -46,6 +46,39 @@ fn account_should_be_reaped() { }); } +#[test] +fn deposit_into_existing() { + 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. + assert_ok!(EvmBalances::deposit_into_existing( + &alice(), + deposited_amount + )); + + // 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: 10, + })); + assert_eq!( + EvmBalances::total_balance(&alice()), + INIT_BALANCE + deposited_amount + ); + }); +} + #[test] fn fee_deduction() { new_test_ext().execute_with(|| { From 3741cb9aea2ac346892c21dd06666d57497cac4c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 16:41:05 +0300 Subject: [PATCH 28/43] Add balance_transfer_works test --- frame/evm-balances/src/tests.rs | 56 +++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 56365efca6..824295dc47 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -27,27 +27,43 @@ fn basic_setup_works() { } #[test] -fn account_should_be_reaped() { +fn balance_transfer_works() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. - assert!(EvmSystem::account_exists(&bob())); + 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!(>::transfer( - &bob(), + assert_ok!(EvmBalances::transfer( &alice(), - INIT_BALANCE, - AllowDeath + &bob(), + transfered_amount, + ExistenceRequirement::KeepAlive )); // Assert state changes. - assert_eq!(EvmBalances::free_balance(&bob()), 0); - assert!(!EvmSystem::account_exists(&bob())); + 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, + })); }); } #[test] -fn deposit_into_existing() { +fn deposit_into_existing_works() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); @@ -70,7 +86,7 @@ fn deposit_into_existing() { ); System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Deposit { who: alice(), - amount: 10, + amount: deposited_amount, })); assert_eq!( EvmBalances::total_balance(&alice()), @@ -79,6 +95,26 @@ fn deposit_into_existing() { }); } +#[test] +fn account_should_be_reaped() { + new_test_ext().execute_with_ext(|_| { + // Check test preconditions. + assert!(EvmSystem::account_exists(&bob())); + + // 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())); + }); +} + #[test] fn fee_deduction() { new_test_ext().execute_with(|| { From d121519100a513348989c874fd353a112edb2112 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 16:48:32 +0300 Subject: [PATCH 29/43] Add slashing_balance_works test --- frame/evm-balances/src/tests.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 824295dc47..b58f447dae 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -88,10 +88,32 @@ fn deposit_into_existing_works() { who: alice(), amount: deposited_amount, })); + }); +} + +#[test] +fn slashing_balance_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(), 1000).1.is_zero()); + + // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice()), - INIT_BALANCE + deposited_amount + INIT_BALANCE - slashed_amount ); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Slashed { + who: alice(), + amount: slashed_amount, + })); }); } From f1ba4cc53a2ddc858ce493ca06978d1d4331db22 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 16:50:51 +0300 Subject: [PATCH 30/43] Add withdraw_balance_works test --- frame/evm-balances/src/tests.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index b58f447dae..3003d3b1eb 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -117,6 +117,37 @@ fn slashing_balance_works() { }); } +#[test] +fn withdraw_balance_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. + assert_ok!(EvmBalances::withdraw( + &alice(), + 1000, + WithdrawReasons::FEE, + ExistenceRequirement::KeepAlive + )); + + // 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, + })); + }); +} + #[test] fn account_should_be_reaped() { new_test_ext().execute_with_ext(|_| { From 1c9e18321650d4b583acbdef410b56c1dc8c6b3c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 16:56:05 +0300 Subject: [PATCH 31/43] Add transferring_too_high_value_should_not_panic test --- frame/evm-balances/src/tests.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 3003d3b1eb..343eb6c14d 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -1,6 +1,6 @@ //! Unit tests. -use frame_support::{assert_ok, weights::Weight}; +use frame_support::{assert_noop, assert_ok, weights::Weight}; use pallet_evm::{FeeCalculator, Runner}; use sp_core::{H160, U256}; use sp_runtime::traits::UniqueSaturatedInto; @@ -167,6 +167,22 @@ fn account_should_be_reaped() { assert!(!EvmSystem::account_exists(&bob())); }); } +#[test] +fn transferring_too_high_value_should_not_panic() { + new_test_ext().execute_with(|| { + // 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, + ); + }); +} #[test] fn fee_deduction() { From 6581eb5c666f8f43176c523fa0931958e062d993 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:00:15 +0300 Subject: [PATCH 32/43] Rename test to transfer_works --- frame/evm-balances/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 343eb6c14d..cb056a823f 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -27,7 +27,7 @@ fn basic_setup_works() { } #[test] -fn balance_transfer_works() { +fn transfer_works() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); From 71f5fed1df8a9b989a25c18007110d057ef23d53 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:21:20 +0300 Subject: [PATCH 33/43] Add basic tests for currency --- frame/evm-balances/src/tests.rs | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index cb056a823f..67e6ccd85d 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -26,6 +26,55 @@ fn basic_setup_works() { }); } +#[test] +fn currency_total_balance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the total balance value. + assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); + }); +} + +#[test] +fn currency_can_slash_works() { + new_test_ext().execute_with_ext(|_| { + // Check slashing. + assert!(EvmBalances::can_slash(&alice(), 100)); + }); +} + +#[test] +fn currency_total_issuance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the total issuance value. + assert_eq!(EvmBalances::total_issuance(), 2 * INIT_BALANCE); + }); +} + +#[test] +fn currency_active_issuance_works() { + new_test_ext().execute_with_ext(|_| { + // Check the active issuance value. + assert_eq!(EvmBalances::active_issuance(), 2 * INIT_BALANCE); + }); +} + +#[test] +fn currency_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); + }); +} + #[test] fn transfer_works() { new_test_ext().execute_with_ext(|_| { From 6b61ee453ff150681a4673070044a9bdaf954fee Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:47:24 +0300 Subject: [PATCH 34/43] Add burn and issue related tests --- frame/evm-balances/src/tests.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 67e6ccd85d..a0a050b02d 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -75,6 +75,38 @@ fn currency_deactivate_reactivate_works() { }); } +#[test] +fn currency_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); + }); +} + +#[test] +fn currency_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); + }); +} + #[test] fn transfer_works() { new_test_ext().execute_with_ext(|_| { From 3b19e5498e6aab03afa49fce577057f18bf2be3d Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:51:05 +0300 Subject: [PATCH 35/43] Add deposit_creating_works test --- frame/evm-balances/src/tests.rs | 48 ++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index a0a050b02d..d085a791ff 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -143,6 +143,32 @@ fn transfer_works() { }); } +#[test] +fn slashing_balance_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(), 1000).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, + })); + }); +} + #[test] fn deposit_into_existing_works() { new_test_ext().execute_with_ext(|_| { @@ -173,27 +199,23 @@ fn deposit_into_existing_works() { } #[test] -fn slashing_balance_works() { +fn deposit_creating_works() { new_test_ext().execute_with_ext(|_| { - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); - - let slashed_amount = 1000; + // Prepare test preconditions. + let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); + let deposited_amount = 10; // Set block number to enable events. System::set_block_number(1); // Invoke the function under test. - assert!(EvmBalances::slash(&alice(), 1000).1.is_zero()); + let _ = EvmBalances::deposit_creating(&charlie, deposited_amount); // 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_eq!(EvmBalances::total_balance(&charlie), deposited_amount); + System::assert_has_event(RuntimeEvent::EvmBalances(crate::Event::Deposit { + who: charlie, + amount: deposited_amount, })); }); } From 36c4a3e70f96eda8b0e6026964e8435cf10d6486 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:55:11 +0300 Subject: [PATCH 36/43] Add currency_make_free_balance_be test --- frame/evm-balances/src/tests.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index d085a791ff..485ef04d88 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -108,7 +108,7 @@ fn currency_issue_works() { } #[test] -fn transfer_works() { +fn currency_transfer_works() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); @@ -144,7 +144,7 @@ fn transfer_works() { } #[test] -fn slashing_balance_works() { +fn currency_slashing_balance_works() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); @@ -170,7 +170,7 @@ fn slashing_balance_works() { } #[test] -fn deposit_into_existing_works() { +fn currency_deposit_into_existing_works() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); @@ -199,7 +199,7 @@ fn deposit_into_existing_works() { } #[test] -fn deposit_creating_works() { +fn currency_deposit_creating_works() { new_test_ext().execute_with_ext(|_| { // Prepare test preconditions. let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); @@ -221,7 +221,7 @@ fn deposit_creating_works() { } #[test] -fn withdraw_balance_works() { +fn currency_withdraw_works() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); @@ -251,6 +251,24 @@ fn withdraw_balance_works() { }); } +#[test] +fn currency_make_free_balance_be() { + new_test_ext().execute_with(|| { + // 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); + }); +} + #[test] fn account_should_be_reaped() { new_test_ext().execute_with_ext(|_| { @@ -270,6 +288,7 @@ fn account_should_be_reaped() { assert!(!EvmSystem::account_exists(&bob())); }); } + #[test] fn transferring_too_high_value_should_not_panic() { new_test_ext().execute_with(|| { From 91a600ebfb18d073b66d19086a6f6bf895d251ad Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:56:42 +0300 Subject: [PATCH 37/43] Rename evm logic related tests --- frame/evm-balances/src/tests.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 485ef04d88..bcf0d8a3af 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -270,7 +270,7 @@ fn currency_make_free_balance_be() { } #[test] -fn account_should_be_reaped() { +fn evm_system_account_should_be_reaped() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert!(EvmSystem::account_exists(&bob())); @@ -290,7 +290,7 @@ fn account_should_be_reaped() { } #[test] -fn transferring_too_high_value_should_not_panic() { +fn evm_transferring_too_high_value_should_not_panic() { new_test_ext().execute_with(|| { // Prepare test preconditions. let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); @@ -307,7 +307,7 @@ fn transferring_too_high_value_should_not_panic() { } #[test] -fn fee_deduction() { +fn evm_fee_deduction() { new_test_ext().execute_with(|| { let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); @@ -331,7 +331,7 @@ fn fee_deduction() { } #[test] -fn issuance_after_tip() { +fn evm_issuance_after_tip() { new_test_ext().execute_with(|| { let before_tip = ::Currency::total_issuance(); @@ -362,7 +362,7 @@ fn issuance_after_tip() { } #[test] -fn refunds_should_work() { +fn evm_refunds_should_work() { new_test_ext().execute_with(|| { let before_call = EVM::account_basic(&alice()).0.balance; // Gas price is not part of the actual fee calculations anymore, only the base fee. @@ -393,7 +393,7 @@ fn refunds_should_work() { } #[test] -fn refunds_and_priority_should_work() { +fn evm_refunds_and_priority_should_work() { new_test_ext().execute_with(|| { let before_call = EVM::account_basic(&alice()).0.balance; // We deliberately set a base fee + max tip > max fee. @@ -429,7 +429,7 @@ fn refunds_and_priority_should_work() { } #[test] -fn call_should_fail_with_priority_greater_than_max_fee() { +fn evm_call_should_fail_with_priority_greater_than_max_fee() { new_test_ext().execute_with(|| { // Max priority greater than max fee should fail. let tip: u128 = 1_100_000_000; @@ -455,7 +455,7 @@ fn call_should_fail_with_priority_greater_than_max_fee() { } #[test] -fn call_should_succeed_with_priority_equal_to_max_fee() { +fn evm_call_should_succeed_with_priority_equal_to_max_fee() { new_test_ext().execute_with(|| { let tip: u128 = 1_000_000_000; // Mimics the input for pre-eip-1559 transaction types where `gas_price` From da78ae86a30d488d98aabbb3a00537effcd296d6 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:57:33 +0300 Subject: [PATCH 38/43] Fix comment --- frame/evm-balances/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index bcf0d8a3af..e6e4002849 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -37,7 +37,7 @@ fn currency_total_balance_works() { #[test] fn currency_can_slash_works() { new_test_ext().execute_with_ext(|_| { - // Check slashing. + // Check possible slashing. assert!(EvmBalances::can_slash(&alice(), 100)); }); } From c43f3e04a947213f01f74d0008131efebd172225 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:58:40 +0300 Subject: [PATCH 39/43] Rename slashing related test --- frame/evm-balances/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index e6e4002849..5d2444b2bb 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -144,7 +144,7 @@ fn currency_transfer_works() { } #[test] -fn currency_slashing_balance_works() { +fn currency_slash_works() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert_eq!(EvmBalances::total_balance(&alice()), INIT_BALANCE); From ac01a02e77e57edc7490221adb8410f95f0dbb76 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 17:59:27 +0300 Subject: [PATCH 40/43] Rename test with make free balance --- frame/evm-balances/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 5d2444b2bb..507ec42628 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -252,7 +252,7 @@ fn currency_withdraw_works() { } #[test] -fn currency_make_free_balance_be() { +fn currency_make_free_balance_be_works() { new_test_ext().execute_with(|| { // Prepare test preconditions. let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); From 294eb6d22a00f5ad48c6e0397afff0433667fc93 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 18:00:28 +0300 Subject: [PATCH 41/43] Rename test with transferring too high value --- frame/evm-balances/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 507ec42628..860d7f450d 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -290,7 +290,7 @@ fn evm_system_account_should_be_reaped() { } #[test] -fn evm_transferring_too_high_value_should_not_panic() { +fn evm_balances_transferring_too_high_value_should_not_panic() { new_test_ext().execute_with(|| { // Prepare test preconditions. let charlie = H160::from_str("1000000000000000000000000000000000000003").unwrap(); From 9ccbaf9cae2b75fb78585e0cc77ed7cb8635d326 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 18:49:54 +0300 Subject: [PATCH 42/43] Assert evm system account existence for currency_deposit_creating_works test --- frame/evm-balances/src/tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index 860d7f450d..cc2d6a22a4 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -204,6 +204,7 @@ fn currency_deposit_creating_works() { // 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); @@ -217,6 +218,7 @@ fn currency_deposit_creating_works() { who: charlie, amount: deposited_amount, })); + assert!(EvmSystem::account_exists(&charlie)); }); } From 30d164e3545155d92bb962d78050753ecf48fd09 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 19 Jun 2023 18:53:49 +0300 Subject: [PATCH 43/43] Add EvmSystem events check --- frame/evm-balances/src/tests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frame/evm-balances/src/tests.rs b/frame/evm-balances/src/tests.rs index cc2d6a22a4..2fe270c5e5 100644 --- a/frame/evm-balances/src/tests.rs +++ b/frame/evm-balances/src/tests.rs @@ -219,6 +219,9 @@ fn currency_deposit_creating_works() { amount: deposited_amount, })); assert!(EvmSystem::account_exists(&charlie)); + System::assert_has_event(RuntimeEvent::EvmSystem( + pallet_evm_system::Event::NewAccount { account: charlie }, + )); }); } @@ -277,6 +280,9 @@ fn evm_system_account_should_be_reaped() { // 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(), @@ -288,6 +294,9 @@ fn evm_system_account_should_be_reaped() { // 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() }, + )); }); }