From de3f257b5d03326bd4c8493d2b407e852854853f Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Thu, 29 Jun 2023 17:19:52 -0300 Subject: [PATCH 01/17] Initial currency swap precompile implementation --- Cargo.lock | 22 ++++ crates/precompile-currency-swap/Cargo.toml | 46 ++++++++ .../precompile-currency-swap/CurrencySwap.sol | 22 ++++ crates/precompile-currency-swap/src/lib.rs | 111 ++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 crates/precompile-currency-swap/Cargo.toml create mode 100644 crates/precompile-currency-swap/CurrencySwap.sol create mode 100644 crates/precompile-currency-swap/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index bcea145a7..74eb05dfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6635,6 +6635,28 @@ dependencies = [ "sp-std", ] +[[package]] +name = "precompile-currency-swap" +version = "0.1.0" +dependencies = [ + "fp-evm", + "frame-support", + "frame-system", + "getrandom 0.2.9", + "mockall", + "num_enum 0.6.0", + "pallet-evm", + "parity-scale-codec", + "precompile-utils", + "primitives-currency-swap", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "precompile-evm-accounts-mapping" version = "0.1.0" diff --git a/crates/precompile-currency-swap/Cargo.toml b/crates/precompile-currency-swap/Cargo.toml new file mode 100644 index 000000000..c37ef7483 --- /dev/null +++ b/crates/precompile-currency-swap/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "precompile-currency-swap" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +precompile-utils = { path = "../precompile-utils", default-features = false } +primitives-currency-swap = { path = "../primitives-currency-swap", default-features = false } + +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +fp-evm = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38", default-features = false } +frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } +frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } +getrandom = { version = "0.2", features = ["js"] } +num_enum = { version = "0.6.0", default-features = false } +pallet-evm = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1", features = ["derive"], optional = true } +sp-core = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } +sp-runtime = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } +sp-std = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } + +[dev-dependencies] +mockall = "0.11" +sp-io = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } + +[features] +default = ["std"] +std = [ + "codec/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "getrandom/std", + "num_enum/std", + "pallet-evm/std", + "precompile-utils/std", + "primitives-currency-swap/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/crates/precompile-currency-swap/CurrencySwap.sol b/crates/precompile-currency-swap/CurrencySwap.sol new file mode 100644 index 000000000..cc8a9cb40 --- /dev/null +++ b/crates/precompile-currency-swap/CurrencySwap.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title Currency Swap Interface + * + * An interface enabling swapping the funds from EVM accounts to + * native Substrate accounts. + * + * Address: 0x0000000000000000000000000000000000000900 + */ +interface CurrencySwap { + /** + * Transfer the funds from an EVM account to native substrate account. + * Selector: 76467cbd + * + * @param nativeAddress The native address to send the funds to. + * @return success Whether or not the swap was successful. + */ + function swap(bytes32 nativeAddress) external payable returns (bool success); +} diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs new file mode 100644 index 000000000..b91ca7c55 --- /dev/null +++ b/crates/precompile-currency-swap/src/lib.rs @@ -0,0 +1,111 @@ +//! A precompile to swap EVM tokens with native chain tokens. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::tokens::currency::Currency; +use pallet_evm::{ + ExitError, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, +}; +use precompile_utils::{succeed, EvmDataWriter, EvmResult, PrecompileHandleExt}; +use sp_core::{Get, H160, H256, U256}; +use sp_std::marker::PhantomData; +use sp_std::prelude::*; + +/// Possible actions for this interface. +#[precompile_utils::generate_function_selector] +#[derive(Debug, PartialEq)] +pub enum Action { + /// Swap EVM tokens to native tokens. + Swap = "swap(bytes32)", +} + +/// Exposes the currency swap interface to EVM. +pub struct CurrencySwap( + PhantomData<(CurrencySwapT, AccountIdFrom, AccountIdTo, GasCost)>, +); + +impl Precompile + for CurrencySwap +where + AccountIdFrom: From, + AccountIdTo: From, + CurrencySwapT: primitives_currency_swap::CurrencySwap, + FromBalanceFor: From, + GasCost: Get, +{ + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + handle.record_cost(GasCost::get())?; + + let selector = handle + .read_selector() + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("invalid function selector".into()), + })?; + + match selector { + Action::Swap => Self::swap(handle), + } + } +} + +/// Utility alias for easy access to [`CurrencySwap::From::Balance`] type. +type FromBalanceFor = + as Currency>::Balance; + +/// Utility alias for easy access to [`CurrencySwap::From`] type. +type FromCurrencyFor = + >::From; + +impl + CurrencySwap +where + AccountIdFrom: From, + AccountIdTo: From, + CurrencySwapT: primitives_currency_swap::CurrencySwap, + FromBalanceFor: From, +{ + /// Swap EVM tokens to native tokens. + fn swap(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + + let fp_evm::Context { + caller: from, + apparent_value: value, + .. + } = handle.context(); + + let from: AccountIdFrom = (*from).into(); + + let value: FromBalanceFor = (*value).into(); + + input + .expect_arguments(1) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("exactly one argument is expected".into()), + })?; + + let to_bytes: sp_core::H256 = input.read()?; + + let to: AccountIdTo = to_bytes.try_into().map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("input must be a valid account id".into()), + })?; + + let imbalance = CurrencySwapT::From::withdraw( + &from, + value, + frame_support::traits::WithdrawReasons::TRANSFER, + frame_support::traits::ExistenceRequirement::AllowDeath, + ) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("unable to withdrwaw funds".into()), + })?; + + let imbalance = CurrencySwapT::swap(imbalance).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("unable to swap funds".into()), + })?; + + CurrencySwapT::To::resolve_creating(&to, imbalance); + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } +} From 5c70ec9881ab781d2804d726846be24667280601 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Thu, 29 Jun 2023 18:30:00 -0300 Subject: [PATCH 02/17] Add initial tests setup --- crates/precompile-currency-swap/src/lib.rs | 6 ++ crates/precompile-currency-swap/src/mock.rs | 102 +++++++++++++++++++ crates/precompile-currency-swap/src/tests.rs | 5 + 3 files changed, 113 insertions(+) create mode 100644 crates/precompile-currency-swap/src/mock.rs create mode 100644 crates/precompile-currency-swap/src/tests.rs diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs index b91ca7c55..ad7e959c8 100644 --- a/crates/precompile-currency-swap/src/lib.rs +++ b/crates/precompile-currency-swap/src/lib.rs @@ -11,6 +11,12 @@ use sp_core::{Get, H160, H256, U256}; use sp_std::marker::PhantomData; use sp_std::prelude::*; +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + /// Possible actions for this interface. #[precompile_utils::generate_function_selector] #[derive(Debug, PartialEq)] diff --git a/crates/precompile-currency-swap/src/mock.rs b/crates/precompile-currency-swap/src/mock.rs new file mode 100644 index 000000000..a765e3e74 --- /dev/null +++ b/crates/precompile-currency-swap/src/mock.rs @@ -0,0 +1,102 @@ +// Allow simple integer arithmetic in tests. +#![allow(clippy::integer_arithmetic)] + +use fp_evm::{Context, ExitError, ExitReason, PrecompileHandle, Transfer}; +use frame_support::traits::{ConstU16, ConstU64}; +use frame_system as system; +use mockall::predicate::*; +use mockall::*; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +/// An index to a block. +pub type BlockNumber = u64; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + } +); + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +mock! { + pub PrecompileHandle {} + + impl PrecompileHandle for PrecompileHandle { + fn call( + &mut self, + to: sp_core::H160, + transfer: Option, + input: Vec, + gas_limit: Option, + is_static: bool, + context: &Context, + ) -> (ExitReason, Vec); + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError>; + + fn remaining_gas(&self) -> u64; + + fn log(&mut self, address: sp_core::H160, topics: Vec, data: Vec) -> Result<(), ExitError>; + + fn code_address(&self) -> sp_core::H160; + + fn input(&self) -> &[u8]; + + fn context(&self) -> &Context; + + fn is_static(&self) -> bool; + + fn gas_limit(&self) -> Option; + } +} + +/// Build test externalities from the default genesis. +pub fn new_test_ext() -> sp_io::TestExternalities { + // Build genesis. + let config = GenesisConfig { + ..Default::default() + }; + let storage = config.build_storage().unwrap(); + + // Make test externalities from the storage. + storage.into() +} diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs new file mode 100644 index 000000000..8f86e42d9 --- /dev/null +++ b/crates/precompile-currency-swap/src/tests.rs @@ -0,0 +1,5 @@ +use frame_support::{traits::ConstU32, BoundedVec}; +use pallet_evm::ExitSucceed; +use precompile_utils::{Bytes, EvmDataWriter}; + +use crate::{mock::*, *}; From 9c5cfb64322639b702f5ba23bb037a4954af951e Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Thu, 29 Jun 2023 21:36:27 -0300 Subject: [PATCH 03/17] wip --- Cargo.lock | 33 ++++ crates/precompile-currency-swap/Cargo.toml | 5 + crates/precompile-currency-swap/src/mock.rs | 173 +++++++++++++++++-- crates/precompile-currency-swap/src/tests.rs | 89 ++++++++++ 4 files changed, 281 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74eb05dfe..94cc79eb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5974,6 +5974,20 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-evm-balances" +version = "1.0.0-dev" +source = "git+https://github.com/humanode-network/frontier?branch=locked/polkadot-v0.9.38#07429a3b559c7277fa20024587f67ae809556054" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-precompile-modexp" version = "2.0.0-dev" @@ -6002,6 +6016,21 @@ dependencies = [ "sp-io", ] +[[package]] +name = "pallet-evm-system" +version = "1.0.0-dev" +source = "git+https://github.com/humanode-network/frontier?branch=locked/polkadot-v0.9.38#07429a3b559c7277fa20024587f67ae809556054" +dependencies = [ + "fp-evm", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-grandpa" version = "4.0.0-dev" @@ -6645,7 +6674,11 @@ dependencies = [ "getrandom 0.2.9", "mockall", "num_enum 0.6.0", + "pallet-balances", "pallet-evm", + "pallet-evm-balances", + "pallet-evm-system", + "pallet-timestamp", "parity-scale-codec", "precompile-utils", "primitives-currency-swap", diff --git a/crates/precompile-currency-swap/Cargo.toml b/crates/precompile-currency-swap/Cargo.toml index c37ef7483..bac5aab8f 100644 --- a/crates/precompile-currency-swap/Cargo.toml +++ b/crates/precompile-currency-swap/Cargo.toml @@ -23,6 +23,11 @@ sp-std = { default-features = false, git = "https://github.com/humanode-network/ [dev-dependencies] mockall = "0.11" +pallet-evm = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } +pallet-evm-system = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } +pallet-evm-balances = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } +pallet-balances = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } +pallet-timestamp = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } sp-io = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } [features] diff --git a/crates/precompile-currency-swap/src/mock.rs b/crates/precompile-currency-swap/src/mock.rs index a765e3e74..56abc6033 100644 --- a/crates/precompile-currency-swap/src/mock.rs +++ b/crates/precompile-currency-swap/src/mock.rs @@ -1,23 +1,28 @@ +//! The mock for the precompile. + // Allow simple integer arithmetic in tests. #![allow(clippy::integer_arithmetic)] use fp_evm::{Context, ExitError, ExitReason, PrecompileHandle, Transfer}; -use frame_support::traits::{ConstU16, ConstU64}; -use frame_system as system; -use mockall::predicate::*; -use mockall::*; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, +use frame_support::{ + sp_io, + sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, DispatchError, + }, + traits::{ConstU64, Currency}, }; +use frame_system as system; +use mockall::{mock, predicate::*, *}; +use sp_core::{H160, H256, U256}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; -/// An index to a block. -pub type BlockNumber = u64; +pub(crate) type AccountId = u64; +pub(crate) type EvmAccountId = H160; +pub(crate) type Balance = u64; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -26,7 +31,12 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + EvmSystem: pallet_evm_system, + EvmBalances: pallet_evm_balances, + EVM: pallet_evm, } ); @@ -57,7 +67,103 @@ impl system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } +frame_support::parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +impl pallet_evm_system::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AccountId = EvmAccountId; + type Index = u64; + type AccountData = pallet_evm_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} + +impl pallet_evm_balances::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AccountId = EvmAccountId; + type Balance = Balance; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = EvmSystem; + type DustRemoval = (); +} + +pub struct FixedGasPrice; +impl fp_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)) + } +} + +frame_support::parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub WeightPerGas: Weight = Weight::from_ref_time(20_000); + pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet; +} + +impl pallet_evm::Config for Test { + type AccountProvider = pallet_evm::NativeSystemAccountProvider; + type FeeCalculator = FixedGasPrice; + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type CallOrigin = pallet_evm::EnsureAddressNever< + ::AccountId, + >; + type WithdrawOrigin = pallet_evm::EnsureAddressNever< + ::AccountId, + >; + type AddressMapping = pallet_evm::IdentityAddressMapping; + type Currency = Balances; + + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = MockPrecompileSet; + type PrecompilesValue = MockPrecompiles; + type ChainId = (); + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = (); +} + mock! { + #[derive(Debug)] + pub CurrencySwap {} + impl primitives_currency_swap::CurrencySwap for CurrencySwap { + type From = Balances; + type To = EvmBalances; + type Error = DispatchError; + + fn swap( + imbalance: >::NegativeImbalance, + ) -> Result<>::NegativeImbalance, DispatchError>; + } +} + +mock! { + #[derive(Debug)] pub PrecompileHandle {} impl PrecompileHandle for PrecompileHandle { @@ -89,14 +195,43 @@ mock! { } } -/// Build test externalities from the default genesis. pub fn new_test_ext() -> sp_io::TestExternalities { - // Build genesis. - let config = GenesisConfig { - ..Default::default() - }; - let storage = config.build_storage().unwrap(); + let genesis_config = GenesisConfig::default(); + new_test_ext_with(genesis_config) +} - // Make test externalities from the storage. +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext_with(genesis_config: GenesisConfig) -> sp_io::TestExternalities { + let storage = genesis_config.build_storage().unwrap(); storage.into() } + +pub fn runtime_lock() -> std::sync::MutexGuard<'static, ()> { + static MOCK_RUNTIME_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + // Ignore the poisoning for the tests that panic. + // We only care about concurrency here, not about the poisoning. + match MOCK_RUNTIME_MUTEX.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + } +} + +pub trait TestExternalitiesExt { + fn execute_with_ext(&mut self, execute: E) -> R + where + E: for<'e> FnOnce(&'e ()) -> R; +} + +impl TestExternalitiesExt for frame_support::sp_io::TestExternalities { + fn execute_with_ext(&mut self, execute: E) -> R + where + E: for<'e> FnOnce(&'e ()) -> R, + { + let guard = runtime_lock(); + let result = self.execute_with(|| execute(&guard)); + drop(guard); + result + } +} diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 8f86e42d9..dae7c9aa5 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -3,3 +3,92 @@ use pallet_evm::ExitSucceed; use precompile_utils::{Bytes, EvmDataWriter}; use crate::{mock::*, *}; + +/// This test verifies that swap call works in the happy path. +#[test] +fn swap_works() { + new_test_ext().execute_with_ext(|_| { + let alice = 42; + let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + let alice_balance = 1000; + let swap_balance = 100; + + // Prepare the test state. + Balances::make_free_balance_be(&alice, alice_balance); + + // Check test preconditions. + assert_eq!(Balances::total_balance(&alice), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx + .expect() + .once() + .with(predicate::eq( + >::NegativeImbalance::new(swap_balance), + )) + .return_once(move |_| { + Ok(>::NegativeImbalance::new(swap_balance)) + }); + + // Invoke the function under test. + assert_ok!(CurrencySwap::swap( + RuntimeOrigin::signed(alice), + alice_evm, + swap_balance + )); + + // Assert state changes. + assert_eq!( + Balances::total_balance(&alice), + alice_balance - swap_balance + ); + assert_eq!(EvmBalances::total_balance(&alice_evm), swap_balance); + System::assert_has_event(RuntimeEvent::CurrencySwap(Event::BalancesSwapped { + from: alice, + withdrawed_amount: swap_balance, + to: alice_evm, + deposited_amount: swap_balance, + })); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that swap call fails in case some error happens during the actual swap logic. +#[test] +fn swap_fails() { + new_test_ext().execute_with_ext(|_| { + let alice = 42; + let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + let alice_balance = 1000; + let swap_balance = 100; + + // Prepare the test state. + Balances::make_free_balance_be(&alice, alice_balance); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx + .expect() + .once() + .with(predicate::eq( + >::NegativeImbalance::new(swap_balance), + )) + .return_once(move |_| Err(DispatchError::Other("currency swap failed"))); + + // Invoke the function under test. + assert_noop!( + CurrencySwap::swap(RuntimeOrigin::signed(alice), alice_evm, swap_balance), + DispatchError::Other("currency swap failed") + ); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} From 6378e7fd4735b9576c32ec6077a479b86b3df7c9 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 00:12:24 -0300 Subject: [PATCH 04/17] Implement tests and corrections --- Cargo.lock | 1 + crates/precompile-currency-swap/Cargo.toml | 9 +- crates/precompile-currency-swap/src/lib.rs | 47 ++-- crates/precompile-currency-swap/src/mock.rs | 78 +++++-- crates/precompile-currency-swap/src/tests.rs | 228 +++++++++++++++---- 5 files changed, 285 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94cc79eb5..7c3b61bd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6672,6 +6672,7 @@ dependencies = [ "frame-support", "frame-system", "getrandom 0.2.9", + "hex-literal", "mockall", "num_enum 0.6.0", "pallet-balances", diff --git a/crates/precompile-currency-swap/Cargo.toml b/crates/precompile-currency-swap/Cargo.toml index bac5aab8f..185f77a2b 100644 --- a/crates/precompile-currency-swap/Cargo.toml +++ b/crates/precompile-currency-swap/Cargo.toml @@ -22,11 +22,12 @@ sp-runtime = { default-features = false, git = "https://github.com/humanode-netw sp-std = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } [dev-dependencies] +hex-literal = "0.4" mockall = "0.11" +pallet-balances = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } pallet-evm = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } -pallet-evm-system = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } pallet-evm-balances = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } -pallet-balances = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } +pallet-evm-system = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } pallet-timestamp = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } sp-io = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } @@ -39,7 +40,11 @@ std = [ "frame-system/std", "getrandom/std", "num_enum/std", + "pallet-balances/std", + "pallet-evm-balances/std", + "pallet-evm-system/std", "pallet-evm/std", + "pallet-timestamp/std", "precompile-utils/std", "primitives-currency-swap/std", "scale-info/std", diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs index ad7e959c8..8eb58b416 100644 --- a/crates/precompile-currency-swap/src/lib.rs +++ b/crates/precompile-currency-swap/src/lib.rs @@ -28,15 +28,21 @@ pub enum Action { /// Exposes the currency swap interface to EVM. pub struct CurrencySwap( PhantomData<(CurrencySwapT, AccountIdFrom, AccountIdTo, GasCost)>, -); +) +where + AccountIdFrom: From, + AccountIdTo: From<[u8; 32]>, + CurrencySwapT: primitives_currency_swap::CurrencySwap, + FromBalanceFor: TryFrom, + GasCost: Get; impl Precompile for CurrencySwap where AccountIdFrom: From, - AccountIdTo: From, + AccountIdTo: From<[u8; 32]>, CurrencySwapT: primitives_currency_swap::CurrencySwap, - FromBalanceFor: From, + FromBalanceFor: TryFrom, GasCost: Get, { fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { @@ -66,23 +72,29 @@ impl CurrencySwap where AccountIdFrom: From, - AccountIdTo: From, + AccountIdTo: From<[u8; 32]>, CurrencySwapT: primitives_currency_swap::CurrencySwap, - FromBalanceFor: From, + FromBalanceFor: TryFrom, + GasCost: Get, { /// Swap EVM tokens to native tokens. fn swap(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; let fp_evm::Context { - caller: from, + address, apparent_value: value, .. } = handle.context(); - let from: AccountIdFrom = (*from).into(); + // Here we must withdraw from self (i.e. from the precompile address, not from the caller + // address), since the funds have already been transferred to us (precompile) as this point. + let from: AccountIdFrom = (*address).into(); - let value: FromBalanceFor = (*value).into(); + let value: FromBalanceFor = + (*value).try_into().map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("value is out of bounds".into()), + })?; input .expect_arguments(1) @@ -90,11 +102,9 @@ where exit_status: ExitError::Other("exactly one argument is expected".into()), })?; - let to_bytes: sp_core::H256 = input.read()?; - - let to: AccountIdTo = to_bytes.try_into().map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("input must be a valid account id".into()), - })?; + let to: H256 = input.read()?; + let to: [u8; 32] = to.into(); + let to: AccountIdTo = to.into(); let imbalance = CurrencySwapT::From::withdraw( &from, @@ -102,8 +112,15 @@ where frame_support::traits::WithdrawReasons::TRANSFER, frame_support::traits::ExistenceRequirement::AllowDeath, ) - .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("unable to withdrwaw funds".into()), + .map_err(|error| match error { + sp_runtime::DispatchError::Token(sp_runtime::TokenError::NoFunds) => { + PrecompileFailure::Error { + exit_status: ExitError::OutOfFund, + } + } + _ => PrecompileFailure::Error { + exit_status: ExitError::Other("unable to withdrwaw funds".into()), + }, })?; let imbalance = CurrencySwapT::swap(imbalance).map_err(|_| PrecompileFailure::Error { diff --git a/crates/precompile-currency-swap/src/mock.rs b/crates/precompile-currency-swap/src/mock.rs index 56abc6033..6f20d7381 100644 --- a/crates/precompile-currency-swap/src/mock.rs +++ b/crates/precompile-currency-swap/src/mock.rs @@ -5,24 +5,26 @@ use fp_evm::{Context, ExitError, ExitReason, PrecompileHandle, Transfer}; use frame_support::{ + once_cell::sync::Lazy, sp_io, sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, DispatchError, }, - traits::{ConstU64, Currency}, + traits::{ConstU16, ConstU32, ConstU64, Currency}, + weights::Weight, }; use frame_system as system; -use mockall::{mock, predicate::*, *}; -use sp_core::{H160, H256, U256}; +use mockall::mock; +use sp_core::{ConstU128, H160, H256, U256}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; -pub(crate) type AccountId = u64; +pub(crate) type AccountId = sp_runtime::AccountId32; pub(crate) type EvmAccountId = H160; -pub(crate) type Balance = u64; +pub(crate) type Balance = u128; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -48,23 +50,23 @@ impl system::Config for Test { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type Index = u64; - type BlockNumber = BlockNumber; + type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; - type AccountData = (); + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; + type SS58Prefix = ConstU16<1>; type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; } frame_support::parameter_types! { @@ -78,10 +80,10 @@ impl pallet_timestamp::Config for Test { } impl pallet_balances::Config for Test { - type Balance = u64; + type Balance = Balance; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ConstU64<1>; + type ExistentialDeposit = ConstU128<1>; type AccountStore = System; type MaxLocks = (); type MaxReserves = (); @@ -102,16 +104,18 @@ impl pallet_evm_balances::Config for Test { type RuntimeEvent = RuntimeEvent; type AccountId = EvmAccountId; type Balance = Balance; - type ExistentialDeposit = ConstU64<1>; + type ExistentialDeposit = ConstU128<1>; type AccountStore = EvmSystem; type DustRemoval = (); } +pub(crate) static GAS_PRICE: Lazy = Lazy::new(|| 1_000_000_000u128.into()); + pub struct FixedGasPrice; impl fp_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)) + (*GAS_PRICE, Weight::from_ref_time(7u64)) } } @@ -122,11 +126,10 @@ frame_support::parameter_types! { } impl pallet_evm::Config for Test { - type AccountProvider = pallet_evm::NativeSystemAccountProvider; + type AccountProvider = EvmSystem; type FeeCalculator = FixedGasPrice; type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type WeightPerGas = WeightPerGas; - type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; type CallOrigin = pallet_evm::EnsureAddressNever< ::AccountId, @@ -135,8 +138,7 @@ impl pallet_evm::Config for Test { ::AccountId, >; type AddressMapping = pallet_evm::IdentityAddressMapping; - type Currency = Balances; - + type Currency = EvmBalances; type RuntimeEvent = RuntimeEvent; type PrecompilesType = MockPrecompileSet; type PrecompilesValue = MockPrecompiles; @@ -148,17 +150,47 @@ impl pallet_evm::Config for Test { type FindAuthor = (); } +type CurrencySwapPrecompile = + crate::CurrencySwap>; + +/// The precompile set containing the precompile under test. +pub struct MockPrecompileSet; + +pub(crate) static PRECOMPILE_ADDRESS: Lazy = Lazy::new(|| H160::from_low_u64_be(0x900)); + +impl pallet_evm::PrecompileSet for MockPrecompileSet { + /// Tries to execute a precompile in the precompile set. + /// If the provided address is not a precompile, returns None. + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + use pallet_evm::Precompile; + let address = handle.code_address(); + + if address == *PRECOMPILE_ADDRESS { + return Some(CurrencySwapPrecompile::execute(handle)); + } + + None + } + + /// Check if the given address is a precompile. Should only be called to + /// perform the check while not executing the precompile afterward, since + /// `execute` already performs a check internally. + fn is_precompile(&self, address: H160) -> bool { + address == *PRECOMPILE_ADDRESS + } +} + mock! { #[derive(Debug)] pub CurrencySwap {} - impl primitives_currency_swap::CurrencySwap for CurrencySwap { - type From = Balances; - type To = EvmBalances; + impl primitives_currency_swap::CurrencySwap for CurrencySwap { + type From = EvmBalances; + type To = Balances; type Error = DispatchError; fn swap( - imbalance: >::NegativeImbalance, - ) -> Result<>::NegativeImbalance, DispatchError>; + imbalance: >::NegativeImbalance, + ) -> Result<>::NegativeImbalance, DispatchError>; } } diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index dae7c9aa5..489a6e82e 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -1,24 +1,39 @@ -use frame_support::{traits::ConstU32, BoundedVec}; -use pallet_evm::ExitSucceed; -use precompile_utils::{Bytes, EvmDataWriter}; +#![allow(clippy::integer_arithmetic)] // not a problem in tests + +use mockall::predicate; +use pallet_evm::Runner; +use precompile_utils::EvmDataWriter; use crate::{mock::*, *}; -/// This test verifies that swap call works in the happy path. +/// A utility that performs gas to fee computation. +/// Might not be explicitly correct, but does the job. +fn gas_to_fee(gas: u64) -> Balance { + u128::from(gas) * u128::try_from(*GAS_PRICE).unwrap() +} + +/// This test verifies that the swap precompile call works in the happy path. #[test] fn swap_works() { new_test_ext().execute_with_ext(|_| { - let alice = 42; - let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - let alice_balance = 1000; - let swap_balance = 100; + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 10 * 10u128.pow(18); + + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. - Balances::make_free_balance_be(&alice, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); // Check test preconditions. - assert_eq!(Balances::total_balance(&alice), alice_balance); - assert_eq!(EvmBalances::total_balance(&alice_evm), 0); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. System::set_block_number(1); @@ -29,48 +44,153 @@ fn swap_works() { .expect() .once() .with(predicate::eq( - >::NegativeImbalance::new(swap_balance), + >::NegativeImbalance::new(swap_balance), )) .return_once(move |_| { - Ok(>::NegativeImbalance::new(swap_balance)) + Ok(>::NegativeImbalance::new( + swap_balance, + )) }); + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(alice.as_ref())) + .build(); + // Invoke the function under test. - assert_ok!(CurrencySwap::swap( - RuntimeOrigin::signed(alice), + let config = ::config(); + let execinfo = ::Runner::call( alice_evm, - swap_balance - )); + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) + ); + assert_eq!(execinfo.used_gas, expected_gas_usage.into()); + assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); + assert_eq!(execinfo.logs, Vec::new()); // Assert state changes. assert_eq!( - Balances::total_balance(&alice), - alice_balance - swap_balance + EvmBalances::total_balance(&alice_evm), + alice_balance - swap_balance - expected_fee ); - assert_eq!(EvmBalances::total_balance(&alice_evm), swap_balance); - System::assert_has_event(RuntimeEvent::CurrencySwap(Event::BalancesSwapped { - from: alice, - withdrawed_amount: swap_balance, - to: alice_evm, - deposited_amount: swap_balance, - })); + assert_eq!(Balances::total_balance(&alice), swap_balance); // Assert mock invocations. swap_ctx.checkpoint(); }); } -/// This test verifies that swap call fails in case some error happens during the actual swap logic. +/// This test verifies that the swap precompile call behaves as expected when called without +/// the sufficient balance. +/// The fee is not withdrawn, and neither is the value. #[test] -fn swap_fails() { +fn swap_fail_no_funds() { new_test_ext().execute_with_ext(|_| { - let alice = 42; - let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - let alice_balance = 1000; - let swap_balance = 100; + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 1000 * 10u128.pow(18); // more than we have // Prepare the test state. - Balances::make_free_balance_be(&alice, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(alice.as_ref())) + .build(); + + // Invoke the function under test. + let config = ::config(); + + let storage_root = frame_support::storage_root(sp_runtime::StateVersion::V1); + let execerr = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap_err(); + assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); + assert_eq!( + storage_root, + frame_support::storage_root(sp_runtime::StateVersion::V1), + "storage changed" + ); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that the swap precompile call behaves as expected when the currency swap +/// implementation fails. +/// The fee is withdrawn, but the value is not. +/// The error message is checked to be correct. +#[test] +fn swap_fail_trait_error() { + new_test_ext().execute_with_ext(|_| { + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 10 * 10u128.pow(18); + + let expected_gas_usage: u64 = 50_123; // all the allowed fee will be withdrawn + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + // Prepare the test state. + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); // Set mock expectations. let swap_ctx = MockCurrencySwap::swap_context(); @@ -78,15 +198,47 @@ fn swap_fails() { .expect() .once() .with(predicate::eq( - >::NegativeImbalance::new(swap_balance), + >::NegativeImbalance::new(swap_balance), )) - .return_once(move |_| Err(DispatchError::Other("currency swap failed"))); + .return_once(move |_| Err(sp_runtime::DispatchError::Other("test"))); + + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(alice.as_ref())) + .build(); // Invoke the function under test. - assert_noop!( - CurrencySwap::swap(RuntimeOrigin::signed(alice), alice_evm, swap_balance), - DispatchError::Other("currency swap failed") + let config = ::config(); + + let execinfo = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + 50_123, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Error(fp_evm::ExitError::Other("unable to swap funds".into())) + ); + assert_eq!(execinfo.used_gas, expected_gas_usage.into()); + assert_eq!(execinfo.value, Vec::::new()); + assert_eq!(execinfo.logs, Vec::new()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice_evm), + alice_balance - expected_fee ); + assert_eq!(Balances::total_balance(&alice), 0); // Assert mock invocations. swap_ctx.checkpoint(); From 024506fcd935cf8664f0cf68edf64b6802926da5 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 00:30:32 -0300 Subject: [PATCH 05/17] Remove unused PrecompileHandle mock --- crates/precompile-currency-swap/src/mock.rs | 35 +-------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/crates/precompile-currency-swap/src/mock.rs b/crates/precompile-currency-swap/src/mock.rs index 6f20d7381..2c26f7a00 100644 --- a/crates/precompile-currency-swap/src/mock.rs +++ b/crates/precompile-currency-swap/src/mock.rs @@ -3,7 +3,7 @@ // Allow simple integer arithmetic in tests. #![allow(clippy::integer_arithmetic)] -use fp_evm::{Context, ExitError, ExitReason, PrecompileHandle, Transfer}; +use fp_evm::PrecompileHandle; use frame_support::{ once_cell::sync::Lazy, sp_io, @@ -194,39 +194,6 @@ mock! { } } -mock! { - #[derive(Debug)] - pub PrecompileHandle {} - - impl PrecompileHandle for PrecompileHandle { - fn call( - &mut self, - to: sp_core::H160, - transfer: Option, - input: Vec, - gas_limit: Option, - is_static: bool, - context: &Context, - ) -> (ExitReason, Vec); - - fn record_cost(&mut self, cost: u64) -> Result<(), ExitError>; - - fn remaining_gas(&self) -> u64; - - fn log(&mut self, address: sp_core::H160, topics: Vec, data: Vec) -> Result<(), ExitError>; - - fn code_address(&self) -> sp_core::H160; - - fn input(&self) -> &[u8]; - - fn context(&self) -> &Context; - - fn is_static(&self) -> bool; - - fn gas_limit(&self) -> Option; - } -} - pub fn new_test_ext() -> sp_io::TestExternalities { let genesis_config = GenesisConfig::default(); new_test_ext_with(genesis_config) From 845cef6bc3dac2677ec52238fbb4b8c8e56cafa3 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 00:42:53 -0300 Subject: [PATCH 06/17] More tests --- crates/precompile-currency-swap/src/mock.rs | 2 +- crates/precompile-currency-swap/src/tests.rs | 162 +++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) diff --git a/crates/precompile-currency-swap/src/mock.rs b/crates/precompile-currency-swap/src/mock.rs index 2c26f7a00..72fa22356 100644 --- a/crates/precompile-currency-swap/src/mock.rs +++ b/crates/precompile-currency-swap/src/mock.rs @@ -83,7 +83,7 @@ impl pallet_balances::Config for Test { type Balance = Balance; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ConstU128<1>; + type ExistentialDeposit = ConstU128<2>; // 2 because we test the account kills via 1 balance type AccountStore = System; type MaxLocks = (); type MaxReserves = (); diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 489a6e82e..7bc9ea52a 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -94,6 +94,92 @@ fn swap_works() { }); } +/// This test verifies that the swap precompile call works when we transfer *almost* the full +/// account balance. +/// Almost because we leave one token left on the source account. +#[test] +fn swap_works_almost_full_balance() { + new_test_ext().execute_with_ext(|_| { + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 100 * 10u128.pow(18) - expected_fee - 1; + + // Prepare the test state. + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx + .expect() + .once() + .with(predicate::eq( + >::NegativeImbalance::new(swap_balance), + )) + .return_once(move |_| { + Ok(>::NegativeImbalance::new( + swap_balance, + )) + }); + + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(alice.as_ref())) + .build(); + + // Invoke the function under test. + let config = ::config(); + let execinfo = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + expected_gas_usage, // the exact amount of fee we'll be using + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) + ); + assert_eq!(execinfo.used_gas, expected_gas_usage.into()); + assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); + assert_eq!(execinfo.logs, Vec::new()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice_evm), + alice_balance - swap_balance - expected_fee + ); + assert_eq!(EvmBalances::total_balance(&alice_evm), 1); + assert_eq!(Balances::total_balance(&alice), swap_balance); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} + /// This test verifies that the swap precompile call behaves as expected when called without /// the sufficient balance. /// The fee is not withdrawn, and neither is the value. @@ -244,3 +330,79 @@ fn swap_fail_trait_error() { swap_ctx.checkpoint(); }); } + +/// This test verifies that the swap precompile call is unable to swap the whole account balance. +/// This is not so much the desired behaviour, but rather an undesired effect of the current +/// implementation that is nonetheless specified and verified with tests. +/// The precense of this test ensures that when/if this behaviour is fixed, this test will start +/// failing and will have to be replaced with another test that verified the new behaviour. +/// See also: [`swap_works_almost_full_balance`]. +#[test] +fn swap_fail_full_balance() { + new_test_ext().execute_with_ext(|_| { + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 100 * 10u128.pow(18) - expected_fee; + + // Prepare the test state. + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(alice.as_ref())) + .build(); + + // Invoke the function under test. + let config = ::config(); + + let storage_root = frame_support::storage_root(sp_runtime::StateVersion::V1); + let execerr = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + expected_gas_usage, // the exact amount of fee we'll be using + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap_err(); + assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); + assert_eq!( + storage_root, + frame_support::storage_root(sp_runtime::StateVersion::V1), + "storage changed" + ); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} From d269f45ad70b2d66f30e48b5fe693b21eb5f50f5 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 00:52:58 -0300 Subject: [PATCH 07/17] Correct the wording in the tests --- crates/precompile-currency-swap/src/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 7bc9ea52a..797d6e259 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -182,7 +182,7 @@ fn swap_works_almost_full_balance() { /// This test verifies that the swap precompile call behaves as expected when called without /// the sufficient balance. -/// The fee is not withdrawn, and neither is the value. +/// The fee is not consumed, and neither is the value. #[test] fn swap_fail_no_funds() { new_test_ext().execute_with_ext(|_| { @@ -251,7 +251,7 @@ fn swap_fail_no_funds() { /// This test verifies that the swap precompile call behaves as expected when the currency swap /// implementation fails. -/// The fee is withdrawn, but the value is not. +/// The fee is consumed, but the value is not. /// The error message is checked to be correct. #[test] fn swap_fail_trait_error() { @@ -265,7 +265,7 @@ fn swap_fail_trait_error() { let alice_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); - let expected_gas_usage: u64 = 50_123; // all the allowed fee will be withdrawn + let expected_gas_usage: u64 = 50_123; // all the allowed fee will be consumed let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. From a8e66ed60bfc8d773c2421b48ffac68649550083 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 01:00:30 -0300 Subject: [PATCH 08/17] More tests --- crates/precompile-currency-swap/src/tests.rs | 146 +++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 797d6e259..0a12aa9cd 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -406,3 +406,149 @@ fn swap_fail_full_balance() { swap_ctx.checkpoint(); }); } + +/// This test verifies that the swap precompile call behaves as expected when a bad selector is +/// passed. +/// Fee will be consumed, but not the value. +#[test] +fn swap_fail_bad_selector() { + new_test_ext().execute_with_ext(|_| { + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 10 * 10u128.pow(18); + + let expected_gas_usage: u64 = 50_123; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + // Prepare the test state. + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(123u32) + .write(H256::from(alice.as_ref())) + .build(); + + // Invoke the function under test. + let config = ::config(); + let execinfo = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + 50_123, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Error(fp_evm::ExitError::Other("invalid function selector".into())) + ); + assert_eq!(execinfo.used_gas, expected_gas_usage.into()); + assert_eq!(execinfo.value, Vec::::new()); + assert_eq!(execinfo.logs, Vec::new()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice_evm), + alice_balance - expected_fee + ); + assert_eq!(Balances::total_balance(&alice), 0); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that the swap precompile call behaves as expected when the call value +/// is overflowing the underlying balance type. +/// This test actually unable to invoke the condition, as it fails prior to that error due to +/// a failing balance check. Nonetheless, this behaviour is verified in this test. +/// The test name could be misleading, but the idea here is that this test is a demonstration of how +/// we tried to test the value overflow and could not. +/// Fee will be consumed, but not the value. +#[test] +fn swap_fail_value_overflow() { + new_test_ext().execute_with_ext(|_| { + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + let alice_balance = u128::MAX; + let swap_balance_u256: U256 = U256::from(u128::MAX) + U256::from(1); + + // Prepare the test state. + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(123u32) + .write(H256::from(alice.as_ref())) + .build(); + + // Invoke the function under test. + let config = ::config(); + let storage_root = frame_support::storage_root(sp_runtime::StateVersion::V1); + let execerr = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance_u256, + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap_err(); + assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); + assert_eq!( + storage_root, + frame_support::storage_root(sp_runtime::StateVersion::V1), + "storage changed" + ); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} From c5694e58e2c36d737563af1818f3942480d5258b Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 01:03:52 -0300 Subject: [PATCH 09/17] Fix a typo at the error message --- crates/precompile-currency-swap/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs index 8eb58b416..37c2fedd3 100644 --- a/crates/precompile-currency-swap/src/lib.rs +++ b/crates/precompile-currency-swap/src/lib.rs @@ -119,7 +119,7 @@ where } } _ => PrecompileFailure::Error { - exit_status: ExitError::Other("unable to withdrwaw funds".into()), + exit_status: ExitError::Other("unable to withdraw funds".into()), }, })?; From 33ec84b81cce110aacbf7e90c8cddd48fa639a1d Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 01:20:20 -0300 Subject: [PATCH 10/17] Rework the CurrencySwap trait error behaviour --- crates/precompile-currency-swap/src/lib.rs | 8 +++++--- crates/precompile-currency-swap/src/tests.rs | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs index 37c2fedd3..0ae858e3d 100644 --- a/crates/precompile-currency-swap/src/lib.rs +++ b/crates/precompile-currency-swap/src/lib.rs @@ -4,7 +4,8 @@ use frame_support::traits::tokens::currency::Currency; use pallet_evm::{ - ExitError, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, + ExitError, ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, + PrecompileResult, }; use precompile_utils::{succeed, EvmDataWriter, EvmResult, PrecompileHandleExt}; use sp_core::{Get, H160, H256, U256}; @@ -123,8 +124,9 @@ where }, })?; - let imbalance = CurrencySwapT::swap(imbalance).map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("unable to swap funds".into()), + let imbalance = CurrencySwapT::swap(imbalance).map_err(|_| PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: "unable to swap the currency".into(), })?; CurrencySwapT::To::resolve_creating(&to, imbalance); diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 0a12aa9cd..5922af2ec 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -251,7 +251,7 @@ fn swap_fail_no_funds() { /// This test verifies that the swap precompile call behaves as expected when the currency swap /// implementation fails. -/// The fee is consumed, but the value is not. +/// The fee is consumed (and not all of it - just what was actually used), but the value is not. /// The error message is checked to be correct. #[test] fn swap_fail_trait_error() { @@ -265,7 +265,7 @@ fn swap_fail_trait_error() { let alice_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); - let expected_gas_usage: u64 = 50_123; // all the allowed fee will be consumed + let expected_gas_usage: u64 = 21216 + 200; let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. @@ -301,7 +301,7 @@ fn swap_fail_trait_error() { *PRECOMPILE_ADDRESS, input, swap_balance.into(), - 50_123, // a reasonable upper bound for tests + 50_000, // a reasonable upper bound for tests Some(*GAS_PRICE), Some(*GAS_PRICE), None, @@ -313,10 +313,10 @@ fn swap_fail_trait_error() { .unwrap(); assert_eq!( execinfo.exit_reason, - fp_evm::ExitReason::Error(fp_evm::ExitError::Other("unable to swap funds".into())) + fp_evm::ExitReason::Revert(ExitRevert::Reverted) ); assert_eq!(execinfo.used_gas, expected_gas_usage.into()); - assert_eq!(execinfo.value, Vec::::new()); + assert_eq!(execinfo.value, "unable to swap the currency".as_bytes()); assert_eq!(execinfo.logs, Vec::new()); // Assert state changes. From c7994174e4ccf70a90e6d8f028d83dc700ec70cc Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 01:20:32 -0300 Subject: [PATCH 11/17] Add more comments to the tests --- crates/precompile-currency-swap/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 5922af2ec..8311a760a 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -409,7 +409,7 @@ fn swap_fail_full_balance() { /// This test verifies that the swap precompile call behaves as expected when a bad selector is /// passed. -/// Fee will be consumed, but not the value. +/// All fee (up to specified max fee limit!) will be consumed, but not the value. #[test] fn swap_fail_bad_selector() { new_test_ext().execute_with_ext(|_| { @@ -422,7 +422,7 @@ fn swap_fail_bad_selector() { let alice_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); - let expected_gas_usage: u64 = 50_123; + let expected_gas_usage: u64 = 50_123; // all fee will be consumed let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. From 94a6f024df496737c3a958ea53f12d9169d709da Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 01:40:05 -0300 Subject: [PATCH 12/17] More tests --- crates/precompile-currency-swap/src/tests.rs | 149 +++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 8311a760a..49e2ff419 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -552,3 +552,152 @@ fn swap_fail_value_overflow() { swap_ctx.checkpoint(); }); } + +/// This test verifies that the swap precompile call behaves as expected when the call has no +/// arguments. +/// All fee (up to specified max fee limit!) will be consumed, but not the value. +#[test] +fn swap_fail_no_arguments() { + new_test_ext().execute_with_ext(|_| { + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 10 * 10u128.pow(18); + + let expected_gas_usage: u64 = 50_123; // all fee will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + // Prepare the test state. + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(Action::Swap).build(); + + // Invoke the function under test. + let config = ::config(); + let execinfo = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + 50_123, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Error(fp_evm::ExitError::Other( + "exactly one argument is expected".into() + )) + ); + assert_eq!(execinfo.used_gas, expected_gas_usage.into()); + assert_eq!(execinfo.value, Vec::::new()); + assert_eq!(execinfo.logs, Vec::new()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice_evm), + alice_balance - expected_fee + ); + assert_eq!(Balances::total_balance(&alice), 0); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that the swap precompile call behaves as expected when the call has +/// an incomplete argument. +/// All fee (up to specified max fee limit!) will be consumed, but not the value. +#[test] +fn swap_fail_short_argument() { + new_test_ext().execute_with_ext(|_| { + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 10 * 10u128.pow(18); + + let expected_gas_usage: u64 = 50_123; // all fee will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + // Prepare the test state. + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Prepare EVM call. + let mut input = EvmDataWriter::new_with_selector(Action::Swap).build(); + input.extend_from_slice(&hex_literal::hex!("1000")); // bad input + + // Invoke the function under test. + let config = ::config(); + let execinfo = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + 50_123, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Error(fp_evm::ExitError::Other( + "exactly one argument is expected".into() + )) + ); + assert_eq!(execinfo.used_gas, expected_gas_usage.into()); + assert_eq!(execinfo.value, Vec::::new()); + assert_eq!(execinfo.logs, Vec::new()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice_evm), + alice_balance - expected_fee + ); + assert_eq!(Balances::total_balance(&alice), 0); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} From 57bd82568139aac2b88f5ca557fef31d0b06b878 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 01:40:28 -0300 Subject: [PATCH 13/17] Add a check for junk at the end of the input --- crates/precompile-currency-swap/src/lib.rs | 7 ++ crates/precompile-currency-swap/src/tests.rs | 75 ++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs index 0ae858e3d..edea43820 100644 --- a/crates/precompile-currency-swap/src/lib.rs +++ b/crates/precompile-currency-swap/src/lib.rs @@ -107,6 +107,13 @@ where let to: [u8; 32] = to.into(); let to: AccountIdTo = to.into(); + let junk_data = input.read_till_end()?; + if !junk_data.is_empty() { + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("junk at the end of input".into()), + }); + } + let imbalance = CurrencySwapT::From::withdraw( &from, value, diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 49e2ff419..6fa1166a6 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -701,3 +701,78 @@ fn swap_fail_short_argument() { swap_ctx.checkpoint(); }); } + +/// This test verifies that the swap precompile call behaves as expected when the call has +/// extra data after the end of the first argument. +/// All fee (up to specified max fee limit!) will be consumed, but not the value. +#[test] +fn swap_fail_trailing_junk() { + new_test_ext().execute_with_ext(|_| { + let alice_evm = H160::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )); + let alice = AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )); + let alice_balance = 100 * 10u128.pow(18); + let swap_balance = 10 * 10u128.pow(18); + + let expected_gas_usage: u64 = 50_123; // all fee will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + // Prepare the test state. + EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(Balances::total_balance(&alice), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Prepare EVM call. + let mut input = EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(alice.as_ref())) + .build(); + input.extend_from_slice(&hex_literal::hex!("1000")); // bad input + + // Invoke the function under test. + let config = ::config(); + let execinfo = ::Runner::call( + alice_evm, + *PRECOMPILE_ADDRESS, + input, + swap_balance.into(), + 50_123, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + config, + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Error(fp_evm::ExitError::Other("junk at the end of input".into())) + ); + assert_eq!(execinfo.used_gas, expected_gas_usage.into()); + assert_eq!(execinfo.value, Vec::::new()); + assert_eq!(execinfo.logs, Vec::new()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice_evm), + alice_balance - expected_fee + ); + assert_eq!(Balances::total_balance(&alice), 0); + + // Assert mock invocations. + swap_ctx.checkpoint(); + }); +} From a847caf155006ae6ae00450097d0dc7461ed5c46 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 02:05:38 -0300 Subject: [PATCH 14/17] Fix the docs --- crates/precompile-currency-swap/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs index edea43820..d491dadd7 100644 --- a/crates/precompile-currency-swap/src/lib.rs +++ b/crates/precompile-currency-swap/src/lib.rs @@ -61,11 +61,11 @@ where } } -/// Utility alias for easy access to [`CurrencySwap::From::Balance`] type. +/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::From::Balance`] type. type FromBalanceFor = as Currency>::Balance; -/// Utility alias for easy access to [`CurrencySwap::From`] type. +/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::From`] type. type FromCurrencyFor = >::From; From dc1ff81313e3995b220eb0c4856831456c54fdd7 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 12:15:00 -0300 Subject: [PATCH 15/17] Rename the balance var to be better correlated with the account naming --- crates/precompile-currency-swap/src/tests.rs | 80 ++++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/precompile-currency-swap/src/tests.rs b/crates/precompile-currency-swap/src/tests.rs index 6fa1166a6..0644f5cf5 100644 --- a/crates/precompile-currency-swap/src/tests.rs +++ b/crates/precompile-currency-swap/src/tests.rs @@ -22,17 +22,17 @@ fn swap_works() { let alice = AccountId::from(hex_literal::hex!( "1000000000000000000000000000000000000000000000000000000000000001" )); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); let expected_gas_usage: u64 = 21216 + 200; let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -85,7 +85,7 @@ fn swap_works() { // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice_evm), - alice_balance - swap_balance - expected_fee + alice_evm_balance - swap_balance - expected_fee ); assert_eq!(Balances::total_balance(&alice), swap_balance); @@ -110,14 +110,14 @@ fn swap_works_almost_full_balance() { let expected_gas_usage: u64 = 21216 + 200; let expected_fee: Balance = gas_to_fee(expected_gas_usage); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 100 * 10u128.pow(18) - expected_fee - 1; // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -170,7 +170,7 @@ fn swap_works_almost_full_balance() { // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice_evm), - alice_balance - swap_balance - expected_fee + alice_evm_balance - swap_balance - expected_fee ); assert_eq!(EvmBalances::total_balance(&alice_evm), 1); assert_eq!(Balances::total_balance(&alice), swap_balance); @@ -192,14 +192,14 @@ fn swap_fail_no_funds() { let alice = AccountId::from(hex_literal::hex!( "1000000000000000000000000000000000000000000000000000000000000001" )); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 1000 * 10u128.pow(18); // more than we have // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -241,7 +241,7 @@ fn swap_fail_no_funds() { ); // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Assert mock invocations. @@ -262,17 +262,17 @@ fn swap_fail_trait_error() { let alice = AccountId::from(hex_literal::hex!( "1000000000000000000000000000000000000000000000000000000000000001" )); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); let expected_gas_usage: u64 = 21216 + 200; let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -322,7 +322,7 @@ fn swap_fail_trait_error() { // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice_evm), - alice_balance - expected_fee + alice_evm_balance - expected_fee ); assert_eq!(Balances::total_balance(&alice), 0); @@ -350,14 +350,14 @@ fn swap_fail_full_balance() { let expected_gas_usage: u64 = 21216 + 200; let expected_fee: Balance = gas_to_fee(expected_gas_usage); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 100 * 10u128.pow(18) - expected_fee; // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -399,7 +399,7 @@ fn swap_fail_full_balance() { ); // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Assert mock invocations. @@ -419,17 +419,17 @@ fn swap_fail_bad_selector() { let alice = AccountId::from(hex_literal::hex!( "1000000000000000000000000000000000000000000000000000000000000001" )); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); let expected_gas_usage: u64 = 50_123; // all fee will be consumed let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -472,7 +472,7 @@ fn swap_fail_bad_selector() { // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice_evm), - alice_balance - expected_fee + alice_evm_balance - expected_fee ); assert_eq!(Balances::total_balance(&alice), 0); @@ -497,14 +497,14 @@ fn swap_fail_value_overflow() { let alice = AccountId::from(hex_literal::hex!( "1000000000000000000000000000000000000000000000000000000000000001" )); - let alice_balance = u128::MAX; + let alice_evm_balance = u128::MAX; let swap_balance_u256: U256 = U256::from(u128::MAX) + U256::from(1); // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -545,7 +545,7 @@ fn swap_fail_value_overflow() { ); // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Assert mock invocations. @@ -565,17 +565,17 @@ fn swap_fail_no_arguments() { let alice = AccountId::from(hex_literal::hex!( "1000000000000000000000000000000000000000000000000000000000000001" )); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); let expected_gas_usage: u64 = 50_123; // all fee will be consumed let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -618,7 +618,7 @@ fn swap_fail_no_arguments() { // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice_evm), - alice_balance - expected_fee + alice_evm_balance - expected_fee ); assert_eq!(Balances::total_balance(&alice), 0); @@ -639,17 +639,17 @@ fn swap_fail_short_argument() { let alice = AccountId::from(hex_literal::hex!( "1000000000000000000000000000000000000000000000000000000000000001" )); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); let expected_gas_usage: u64 = 50_123; // all fee will be consumed let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -693,7 +693,7 @@ fn swap_fail_short_argument() { // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice_evm), - alice_balance - expected_fee + alice_evm_balance - expected_fee ); assert_eq!(Balances::total_balance(&alice), 0); @@ -714,17 +714,17 @@ fn swap_fail_trailing_junk() { let alice = AccountId::from(hex_literal::hex!( "1000000000000000000000000000000000000000000000000000000000000001" )); - let alice_balance = 100 * 10u128.pow(18); + let alice_evm_balance = 100 * 10u128.pow(18); let swap_balance = 10 * 10u128.pow(18); let expected_gas_usage: u64 = 50_123; // all fee will be consumed let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_balance); + EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm), alice_balance); + assert_eq!(EvmBalances::total_balance(&alice_evm), alice_evm_balance); assert_eq!(Balances::total_balance(&alice), 0); // Set block number to enable events. @@ -768,7 +768,7 @@ fn swap_fail_trailing_junk() { // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice_evm), - alice_balance - expected_fee + alice_evm_balance - expected_fee ); assert_eq!(Balances::total_balance(&alice), 0); From 382afc1c3082e848bf348160c22cc10c053015d2 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 12:32:18 -0300 Subject: [PATCH 16/17] Reduce the dependencies set --- Cargo.lock | 5 ----- crates/precompile-currency-swap/Cargo.toml | 12 +----------- crates/precompile-currency-swap/src/lib.rs | 8 +++++--- crates/precompile-currency-swap/src/mock.rs | 1 + 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c3b61bd3..d97db8612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6671,7 +6671,6 @@ dependencies = [ "fp-evm", "frame-support", "frame-system", - "getrandom 0.2.9", "hex-literal", "mockall", "num_enum 0.6.0", @@ -6684,11 +6683,7 @@ dependencies = [ "precompile-utils", "primitives-currency-swap", "scale-info", - "serde", "sp-core", - "sp-io", - "sp-runtime", - "sp-std", ] [[package]] diff --git a/crates/precompile-currency-swap/Cargo.toml b/crates/precompile-currency-swap/Cargo.toml index 185f77a2b..5818a81a8 100644 --- a/crates/precompile-currency-swap/Cargo.toml +++ b/crates/precompile-currency-swap/Cargo.toml @@ -11,17 +11,13 @@ primitives-currency-swap = { path = "../primitives-currency-swap", default-featu codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } fp-evm = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38", default-features = false } frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } -frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } -getrandom = { version = "0.2", features = ["js"] } num_enum = { version = "0.6.0", default-features = false } pallet-evm = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38", default-features = false } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } -serde = { version = "1", features = ["derive"], optional = true } sp-core = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } -sp-runtime = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } -sp-std = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } [dev-dependencies] +frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } hex-literal = "0.4" mockall = "0.11" pallet-balances = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } @@ -29,7 +25,6 @@ pallet-evm = { default-features = false, git = "https://github.com/humanode-netw pallet-evm-balances = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } pallet-evm-system = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" } pallet-timestamp = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } -sp-io = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" } [features] default = ["std"] @@ -38,7 +33,6 @@ std = [ "fp-evm/std", "frame-support/std", "frame-system/std", - "getrandom/std", "num_enum/std", "pallet-balances/std", "pallet-evm-balances/std", @@ -48,9 +42,5 @@ std = [ "precompile-utils/std", "primitives-currency-swap/std", "scale-info/std", - "serde", "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", ] diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs index d491dadd7..a7f05c534 100644 --- a/crates/precompile-currency-swap/src/lib.rs +++ b/crates/precompile-currency-swap/src/lib.rs @@ -2,15 +2,17 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::tokens::currency::Currency; +use frame_support::{ + sp_runtime, + sp_std::{marker::PhantomData, prelude::*}, + traits::tokens::currency::Currency, +}; use pallet_evm::{ ExitError, ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, }; use precompile_utils::{succeed, EvmDataWriter, EvmResult, PrecompileHandleExt}; use sp_core::{Get, H160, H256, U256}; -use sp_std::marker::PhantomData; -use sp_std::prelude::*; #[cfg(test)] mod mock; diff --git a/crates/precompile-currency-swap/src/mock.rs b/crates/precompile-currency-swap/src/mock.rs index 72fa22356..a71652f26 100644 --- a/crates/precompile-currency-swap/src/mock.rs +++ b/crates/precompile-currency-swap/src/mock.rs @@ -8,6 +8,7 @@ use frame_support::{ once_cell::sync::Lazy, sp_io, sp_runtime::{ + self, testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, DispatchError, From 0ba50ca1730344bdf856d5230be0fb821eb8f98a Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Fri, 30 Jun 2023 12:38:38 -0300 Subject: [PATCH 17/17] Fix the docs again --- crates/precompile-currency-swap/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/precompile-currency-swap/src/lib.rs b/crates/precompile-currency-swap/src/lib.rs index a7f05c534..34ad450a8 100644 --- a/crates/precompile-currency-swap/src/lib.rs +++ b/crates/precompile-currency-swap/src/lib.rs @@ -63,7 +63,8 @@ where } } -/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::From::Balance`] type. +/// Utility alias for easy access to the [`Currency::Balance`] of +/// the [`primitives_currency_swap::CurrencySwap::From`] type. type FromBalanceFor = as Currency>::Balance;