From de01cd354f33ccfae594f708348547f8ec938474 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 17:50:13 +0300 Subject: [PATCH 001/102] Use pallet-currency-swap as template --- Cargo.lock | 19 + .../Cargo.toml | 56 +++ .../src/benchmarking.rs | 130 ++++++ .../src/lib.rs | 172 ++++++++ .../src/mock.rs | 172 ++++++++ .../src/tests.rs | 393 ++++++++++++++++++ .../src/weights.rs | 22 + 7 files changed, 964 insertions(+) create mode 100644 crates/pallet-native-to-evm-currency-swap/Cargo.toml create mode 100644 crates/pallet-native-to-evm-currency-swap/src/benchmarking.rs create mode 100644 crates/pallet-native-to-evm-currency-swap/src/lib.rs create mode 100644 crates/pallet-native-to-evm-currency-swap/src/mock.rs create mode 100644 crates/pallet-native-to-evm-currency-swap/src/tests.rs create mode 100644 crates/pallet-native-to-evm-currency-swap/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 555dca189..1661c5bd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6289,6 +6289,25 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-native-to-evm-currency-swap" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "mockall", + "pallet-balances", + "pallet-evm-balances", + "pallet-evm-system", + "parity-scale-codec", + "primitives-currency-swap", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-pot" version = "0.1.0" diff --git a/crates/pallet-native-to-evm-currency-swap/Cargo.toml b/crates/pallet-native-to-evm-currency-swap/Cargo.toml new file mode 100644 index 000000000..c4c071656 --- /dev/null +++ b/crates/pallet-native-to-evm-currency-swap/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "pallet-native-to-evm-currency-swap" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +primitives-currency-swap = { path = "../primitives-currency-swap", default-features = false } + +codec = { workspace = true, features = ["derive"] } +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +scale-info = { workspace = true, features = ["derive"] } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +pallet-evm-balances = { path = "../pallet-evm-balances", features = ["default"] } +pallet-evm-system = { path = "../pallet-evm-system", features = ["default"] } + +mockall = { workspace = true } +pallet-balances = { workspace = true, features = ["default"] } +sp-core = { workspace = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-evm-balances/std", + "pallet-evm-system/std", + "primitives-currency-swap/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-evm-balances/try-runtime", + "pallet-evm-system/try-runtime", + "primitives-currency-swap/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/crates/pallet-native-to-evm-currency-swap/src/benchmarking.rs b/crates/pallet-native-to-evm-currency-swap/src/benchmarking.rs new file mode 100644 index 000000000..db75376b2 --- /dev/null +++ b/crates/pallet-native-to-evm-currency-swap/src/benchmarking.rs @@ -0,0 +1,130 @@ +//! The benchmarks for the pallet. + +// Allow integer and float arithmetic in tests. +#![allow(clippy::arithmetic_side_effects, clippy::float_arithmetic)] + +use frame_benchmarking::benchmarks; +use frame_support::{assert_ok, dispatch::DispatchResult, traits::Currency}; +use frame_system::RawOrigin; + +use crate::*; + +/// The benchmark interface into the environment. +pub trait Interface: super::Config { + /// The data to be passed from `prepare` to `verify`. + type Data; + + /// Prepare currency swap environment. + fn prepare() -> Self::Data; + + /// Verify currency swap environment, + fn verify(data: Self::Data) -> DispatchResult; + + /// Obtain the Account ID the balance is swapped from. + fn from_account_id() -> ::AccountId; + + /// Obtain the amount of balance to withdraw from the swap source account. + fn from_balance() -> FromBalanceOf; + + /// Obtain the Account ID the balance is swapped to. + fn to_account_id() -> ::AccountIdTo; + + /// Obtain the amount of balance to deposit to the swap destination account. + fn to_balance() -> ToBalanceOf; +} + +benchmarks! { + where_clause { + where + T: Interface, + } + + swap { + let from = ::from_account_id(); + let from_balance = ::from_balance(); + let to = ::to_account_id(); + let to_balance = ::to_balance(); + let init_balance: u32 = 1000; + + let _ = >::deposit_creating(&from, init_balance.into()); + + let from_balance_before = as Currency<_>>::total_balance(&from); + let to_balance_before = as Currency<_>>::total_balance(&to); + + let currency_swap = ::prepare(); + + let origin = RawOrigin::Signed(from.clone()); + + }: _(origin, to.clone(), from_balance) + verify { + let from_balance_after = as Currency<_>>::total_balance(&from); + let to_balance_after = as Currency<_>>::total_balance(&to); + + assert_eq!(from_balance_before - from_balance_after, from_balance); + assert_eq!(to_balance_after - to_balance_before, to_balance); + + assert_ok!(::verify(currency_swap)); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Test, + ); +} + +#[cfg(test)] +impl Interface for crate::mock::Test { + type Data = ( + std::sync::MutexGuard<'static, ()>, + mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__estimate_swapped_balance::Context, + mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__swap::Context, + ); + + fn prepare() -> Self::Data { + let mock_runtime_guard = mock::runtime_lock(); + + let estimate_swapped_balance_ctx = + mock::MockCurrencySwap::estimate_swapped_balance_context(); + estimate_swapped_balance_ctx + .expect() + .times(1..) + .return_const(Self::to_balance()); + let swap_ctx = mock::MockCurrencySwap::swap_context(); + swap_ctx.expect().times(1..).return_once(move |_| { + Ok( + >::NegativeImbalance::new( + Self::to_balance(), + ), + ) + }); + + (mock_runtime_guard, estimate_swapped_balance_ctx, swap_ctx) + } + + fn verify(data: Self::Data) -> DispatchResult { + let (mock_runtime_guard, estimate_swapped_balance_ctx, swap_ctx) = data; + estimate_swapped_balance_ctx.checkpoint(); + swap_ctx.checkpoint(); + drop(mock_runtime_guard); + Ok(()) + } + + fn from_account_id() -> ::AccountId { + 42 + } + + fn from_balance() -> FromBalanceOf { + 100 + } + + fn to_account_id() -> ::AccountIdTo { + use sp_std::str::FromStr; + + mock::EvmAccountId::from_str("1000000000000000000000000000000000000001").unwrap() + } + + fn to_balance() -> ToBalanceOf { + 100 + } +} diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs new file mode 100644 index 000000000..4d4ac1882 --- /dev/null +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -0,0 +1,172 @@ +//! A substrate pallet containing the currency swap integration. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::{fungible::Inspect, tokens::Provenance, Currency}; +pub use pallet::*; +use primitives_currency_swap::CurrencySwap as CurrencySwapT; +pub use weights::*; + +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::From`] type from a given config. +type FromCurrencyOf = <::CurrencySwap as CurrencySwapT< + ::AccountId, + ::AccountIdTo, +>>::From; + +/// Utility alias for easy access to the [`Currency::Balance`] of +/// the [`primitives_currency_swap::CurrencySwap::From`] type. +type FromBalanceOf = + as Currency<::AccountId>>::Balance; + +/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::To`] type from a given config. +type ToCurrencyOf = <::CurrencySwap as CurrencySwapT< + ::AccountId, + ::AccountIdTo, +>>::To; + +/// Utility alias for easy access to the [`Currency::Balance`] of +/// the [`primitives_currency_swap::CurrencySwap::To`] type. +type ToBalanceOf = as Currency<::AccountIdTo>>::Balance; + +// We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to +// fix them at their end. +#[allow(clippy::missing_docs_in_private_items)] +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ + pallet_prelude::*, + storage::with_storage_layer, + traits::{ExistenceRequirement, Imbalance, WithdrawReasons}, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::MaybeDisplay; + use sp_std::fmt::Debug; + + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Configuration trait of this pallet. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The user account identifier type to convert to. + type AccountIdTo: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + MaybeDisplay + + Ord + + MaxEncodedLen; + + /// Interface into currency swap implementation. + type CurrencySwap: CurrencySwapT; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Balances were swapped. + BalancesSwapped { + /// The account id balances withdrawed from. + from: T::AccountId, + /// The withdrawed balances amount. + withdrawed_amount: FromBalanceOf, + /// The account id balances deposited to. + to: T::AccountIdTo, + /// The deposited balances amount. + deposited_amount: ToBalanceOf, + }, + } + + #[pallet::call(weight(T::WeightInfo))] + impl Pallet { + /// Swap balances. + #[pallet::call_index(0)] + pub fn swap( + origin: OriginFor, + to: T::AccountIdTo, + #[pallet::compact] amount: FromBalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + with_storage_layer(move || { + Self::do_swap(who, to, amount, ExistenceRequirement::AllowDeath)?; + + Ok(()) + }) + } + + /// Same as the swap call, but with a check that the swap will not kill the origin account. + #[pallet::call_index(1)] + pub fn swap_keep_alive( + origin: OriginFor, + to: T::AccountIdTo, + #[pallet::compact] amount: FromBalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + with_storage_layer(move || { + Self::do_swap(who, to, amount, ExistenceRequirement::KeepAlive)?; + + Ok(()) + }) + } + } + + impl Pallet { + /// General swap balances implementation. + pub fn do_swap( + who: T::AccountId, + to: T::AccountIdTo, + amount: FromBalanceOf, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult { + let estimated_swapped_balance = T::CurrencySwap::estimate_swapped_balance(amount); + ToCurrencyOf::::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) + .into_result()?; + + let withdrawed_imbalance = FromCurrencyOf::::withdraw( + &who, + amount, + WithdrawReasons::TRANSFER, + existence_requirement, + )?; + let withdrawed_amount = withdrawed_imbalance.peek(); + + let deposited_imbalance = + T::CurrencySwap::swap(withdrawed_imbalance).map_err(|error| { + // Here we undo the withdrawal to avoid having a dangling imbalance. + FromCurrencyOf::::resolve_creating(&who, error.incoming_imbalance); + error.cause.into() + })?; + let deposited_amount = deposited_imbalance.peek(); + + ToCurrencyOf::::resolve_creating(&to, deposited_imbalance); + + Self::deposit_event(Event::BalancesSwapped { + from: who, + withdrawed_amount, + to, + deposited_amount, + }); + + Ok(()) + } + } +} diff --git a/crates/pallet-native-to-evm-currency-swap/src/mock.rs b/crates/pallet-native-to-evm-currency-swap/src/mock.rs new file mode 100644 index 000000000..e76cf0a2e --- /dev/null +++ b/crates/pallet-native-to-evm-currency-swap/src/mock.rs @@ -0,0 +1,172 @@ +//! The mock for the pallet. + +// Allow simple integer arithmetic in tests. +#![allow(clippy::arithmetic_side_effects)] + +use frame_support::{ + sp_io, + sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, DispatchError, + }, + traits::{ConstU32, ConstU64, Currency}, +}; +use mockall::mock; +use sp_core::{H160, H256}; + +use crate::{self as pallet_currency_swap}; + +pub(crate) const EXISTENTIAL_DEPOSIT: u64 = 10; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub(crate) type AccountId = u64; +pub(crate) type EvmAccountId = H160; +type Balance = u64; + +frame_support::construct_runtime!( + pub struct Test + where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + EvmSystem: pallet_evm_system, + EvmBalances: pallet_evm_balances, + CurrencySwap: pallet_currency_swap, + } +); + +impl frame_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 = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64; + type AccountStore = System; + type MaxLocks = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxReserves = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; + 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; + type AccountStore = EvmSystem; + type DustRemoval = (); +} + +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< + primitives_currency_swap::ToNegativeImbalanceFor, + primitives_currency_swap::ErrorFor + >; + + fn estimate_swapped_balance( + balance: >::Balance, + ) -> >::Balance; + } +} + +impl pallet_currency_swap::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AccountIdTo = H160; + type CurrencySwap = MockCurrencySwap; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let genesis_config = GenesisConfig::default(); + new_test_ext_with(genesis_config) +} + +// 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/pallet-native-to-evm-currency-swap/src/tests.rs b/crates/pallet-native-to-evm-currency-swap/src/tests.rs new file mode 100644 index 000000000..2a97623b0 --- /dev/null +++ b/crates/pallet-native-to-evm-currency-swap/src/tests.rs @@ -0,0 +1,393 @@ +//! The tests for the pallet. + +use frame_support::{assert_noop, assert_ok, traits::Currency}; +use mockall::predicate; +use sp_core::H160; +use sp_runtime::{DispatchError, TokenError}; +use sp_std::str::FromStr; + +use crate::{mock::*, *}; + +/// This test verifies that swap call works as expected in case origin left balances amount +/// is greater or equal than existential deposit. +#[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!( + >::total_balance(&alice), + alice_balance + ); + assert_eq!(>::total_balance(&alice_evm), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); + estimate_swapped_balance_ctx + .expect() + .once() + .with(predicate::eq(swap_balance)) + .return_const(swap_balance); + 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!( + >::total_balance(&alice), + alice_balance - swap_balance + ); + assert_eq!( + >::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. + estimate_swapped_balance_ctx.checkpoint(); + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that swap call works as expected in case origin left balances amount +/// is less than existential deposit. The origin account should be killed. +#[test] +fn swap_works_kill_origin() { + new_test_ext().execute_with_ext(|_| { + let alice = 42; + let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + let alice_balance = 1000; + let swap_balance = 999; + + // Prepare the test state. + Balances::make_free_balance_be(&alice, alice_balance); + + // Check test preconditions. + assert_eq!( + >::total_balance(&alice), + alice_balance + ); + assert_eq!(>::total_balance(&alice_evm), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); + estimate_swapped_balance_ctx + .expect() + .once() + .with(predicate::eq(swap_balance)) + .return_const(swap_balance); + 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!(!System::account_exists(&alice)); + assert_eq!( + >::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. + estimate_swapped_balance_ctx.checkpoint(); + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that `swap_keep_alive` call works in the happy path. +#[test] +fn swap_keep_alive_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!( + >::total_balance(&alice), + alice_balance + ); + assert_eq!(>::total_balance(&alice_evm), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Set mock expectations. + let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); + estimate_swapped_balance_ctx + .expect() + .once() + .with(predicate::eq(swap_balance)) + .return_const(swap_balance); + 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!( + >::total_balance(&alice), + alice_balance - swap_balance + ); + assert_eq!( + >::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. + estimate_swapped_balance_ctx.checkpoint(); + 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 estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); + estimate_swapped_balance_ctx + .expect() + .once() + .with(predicate::eq(swap_balance)) + .return_const(swap_balance); + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx + .expect() + .once() + .with(predicate::eq( + >::NegativeImbalance::new(swap_balance), + )) + .return_once(move |incoming_imbalance| { + Err(primitives_currency_swap::Error { + cause: sp_runtime::DispatchError::Other("currency swap failed"), + incoming_imbalance, + }) + }); + + // Invoke the function under test. + assert_noop!( + CurrencySwap::swap(RuntimeOrigin::signed(alice), alice_evm, swap_balance), + DispatchError::Other("currency swap failed") + ); + + // Assert state changes. + assert_eq!( + >::total_balance(&alice), + alice_balance + ); + assert_eq!(>::total_balance(&alice_evm), 0); + + // Assert mock invocations. + estimate_swapped_balance_ctx.checkpoint(); + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that swap call fails in case estimated swapped balance less or equal +/// than target currency existential deposit. +#[test] +fn swap_below_ed_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 estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); + estimate_swapped_balance_ctx + .expect() + .once() + .with(predicate::eq(swap_balance)) + .return_const(EXISTENTIAL_DEPOSIT - 1); + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Invoke the function under test. + assert_noop!( + CurrencySwap::swap(RuntimeOrigin::signed(alice), alice_evm, swap_balance), + DispatchError::Token(TokenError::BelowMinimum) + ); + + // Assert state changes. + assert_eq!( + >::total_balance(&alice), + alice_balance + ); + assert_eq!(>::total_balance(&alice_evm), 0); + + // Assert mock invocations. + estimate_swapped_balance_ctx.checkpoint(); + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that `swap_keep_alive` call fails in case origin left balances amount +/// is less than existential deposit. The call should prevent swap operation. +#[test] +fn swap_keep_alive_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 = 999; + + // Prepare the test state. + Balances::make_free_balance_be(&alice, alice_balance); + + // Set mock expectations. + let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); + estimate_swapped_balance_ctx + .expect() + .once() + .with(predicate::eq(swap_balance)) + .return_const(swap_balance); + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Invoke the function under test. + assert_noop!( + CurrencySwap::swap_keep_alive(RuntimeOrigin::signed(alice), alice_evm, swap_balance), + pallet_balances::Error::::Expendability + ); + + // Assert state changes. + assert_eq!( + >::total_balance(&alice), + alice_balance + ); + assert_eq!(>::total_balance(&alice_evm), 0); + + // Assert mock invocations. + estimate_swapped_balance_ctx.checkpoint(); + swap_ctx.checkpoint(); + }); +} + +/// This test verifies that `swap_keep_alive` call fails in case estimated swapped balance less or equal +/// than target currency existential deposit. +#[test] +fn swap_keep_alive_below_ed_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 estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); + estimate_swapped_balance_ctx + .expect() + .once() + .with(predicate::eq(swap_balance)) + .return_const(EXISTENTIAL_DEPOSIT - 1); + let swap_ctx = MockCurrencySwap::swap_context(); + swap_ctx.expect().never(); + + // Invoke the function under test. + assert_noop!( + CurrencySwap::swap_keep_alive(RuntimeOrigin::signed(alice), alice_evm, swap_balance), + DispatchError::Token(TokenError::BelowMinimum) + ); + + // Assert state changes. + assert_eq!( + >::total_balance(&alice), + alice_balance + ); + assert_eq!(>::total_balance(&alice_evm), 0); + + // Assert mock invocations. + estimate_swapped_balance_ctx.checkpoint(); + swap_ctx.checkpoint(); + }); +} diff --git a/crates/pallet-native-to-evm-currency-swap/src/weights.rs b/crates/pallet-native-to-evm-currency-swap/src/weights.rs new file mode 100644 index 000000000..11071103e --- /dev/null +++ b/crates/pallet-native-to-evm-currency-swap/src/weights.rs @@ -0,0 +1,22 @@ +//! Weights definition for pallet-currency-swap. + +use frame_support::weights::Weight; + +/// Weight functions needed for pallet-currency-swap. +pub trait WeightInfo { + /// A function to calculate required weights for swap call. + fn swap() -> Weight; + + /// A function to calculate required weights for `swap_keep_alive` call. + fn swap_keep_alive() -> Weight; +} + +impl WeightInfo for () { + fn swap() -> Weight { + Weight::zero() + } + + fn swap_keep_alive() -> Weight { + Weight::zero() + } +} From 5f3928920f7a165504ddbfae2676a8c4a27c5b9b Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 20:09:16 +0300 Subject: [PATCH 002/102] Minimal design implementation --- Cargo.lock | 9 +- crates/humanode-runtime/Cargo.toml | 4 + crates/humanode-runtime/src/lib.rs | 12 + .../Cargo.toml | 26 -- .../src/benchmarking.rs | 130 ------ .../src/lib.rs | 119 +++--- .../src/mock.rs | 172 -------- .../src/tests.rs | 393 ------------------ .../src/weights.rs | 2 +- 9 files changed, 68 insertions(+), 799 deletions(-) delete mode 100644 crates/pallet-native-to-evm-currency-swap/src/benchmarking.rs delete mode 100644 crates/pallet-native-to-evm-currency-swap/src/mock.rs delete mode 100644 crates/pallet-native-to-evm-currency-swap/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 1661c5bd2..a60e69a65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3817,6 +3817,7 @@ dependencies = [ "pallet-humanode-session", "pallet-im-online", "pallet-multisig", + "pallet-native-to-evm-currency-swap", "pallet-pot", "pallet-session", "pallet-sudo", @@ -6296,16 +6297,8 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "mockall", - "pallet-balances", - "pallet-evm-balances", - "pallet-evm-system", "parity-scale-codec", - "primitives-currency-swap", "scale-info", - "sp-core", - "sp-runtime", - "sp-std", ] [[package]] diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index 5dad34cb0..53ca2a6b5 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -32,6 +32,7 @@ pallet-evm-balances = { path = "../pallet-evm-balances", default-features = fals pallet-evm-system = { path = "../pallet-evm-system", default-features = false } pallet-humanode-offences = { path = "../pallet-humanode-offences", default-features = false } pallet-humanode-session = { path = "../pallet-humanode-session", default-features = false } +pallet-native-to-evm-currency-swap = { path = "../pallet-native-to-evm-currency-swap", default-features = false } pallet-pot = { path = "../pallet-pot", default-features = false } pallet-token-claims = { path = "../pallet-token-claims", default-features = false } pallet-vesting = { path = "../pallet-vesting", default-features = false } @@ -132,6 +133,7 @@ runtime-benchmarks = [ "pallet-im-online/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", + "pallet-native-to-evm-currency-swap/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-token-claims/runtime-benchmarks", "pallet-utility/runtime-benchmarks", @@ -190,6 +192,7 @@ std = [ "pallet-humanode-session/std", "pallet-im-online/std", "pallet-multisig/std", + "pallet-native-to-evm-currency-swap/std", "pallet-pot/std", "pallet-session/std", "pallet-sudo/std", @@ -261,6 +264,7 @@ try-runtime = [ "pallet-humanode-session/try-runtime", "pallet-im-online/try-runtime", "pallet-multisig/try-runtime", + "pallet-native-to-evm-currency-swap/try-runtime", "pallet-pot/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index dc54fd4ee..885d76ddc 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -635,6 +635,17 @@ impl pallet_currency_swap::Config for Runtime { type WeightInfo = (); } +impl pallet_native_to_evm_currency_swap::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type EvmAccountId = EvmAccountId; + type NativeCurrency = Balances; + type EvmCurrency = EvmBalances; + type BalanceConverter = Identity; + type PotNativeBrige = NativeToEvmSwapBridgePotAccountId; + type PotEvmBridge = EvmToNativeSwapBridgePotAccountId; + type WeightInfo = (); +} + /// A simple fixed fee per gas calculator. pub struct EvmFeePerGas; @@ -842,6 +853,7 @@ construct_runtime!( EvmBalancesErc20Support: pallet_erc20_support = 37, DummyPrecompilesCode: pallet_dummy_precompiles_code = 38, HumanodeOffences: pallet_humanode_offences = 39, + NativeToEvmCurrencySwap: pallet_native_to_evm_currency_swap = 40, } ); diff --git a/crates/pallet-native-to-evm-currency-swap/Cargo.toml b/crates/pallet-native-to-evm-currency-swap/Cargo.toml index c4c071656..8e15b2a50 100644 --- a/crates/pallet-native-to-evm-currency-swap/Cargo.toml +++ b/crates/pallet-native-to-evm-currency-swap/Cargo.toml @@ -5,23 +5,11 @@ edition = "2021" publish = false [dependencies] -primitives-currency-swap = { path = "../primitives-currency-swap", default-features = false } - codec = { workspace = true, features = ["derive"] } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } scale-info = { workspace = true, features = ["derive"] } -sp-runtime = { workspace = true } -sp-std = { workspace = true } - -[dev-dependencies] -pallet-evm-balances = { path = "../pallet-evm-balances", features = ["default"] } -pallet-evm-system = { path = "../pallet-evm-system", features = ["default"] } - -mockall = { workspace = true } -pallet-balances = { workspace = true, features = ["default"] } -sp-core = { workspace = true } [features] default = ["std"] @@ -29,28 +17,14 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] std = [ "codec/std", "frame-support/std", "frame-system/std", - "pallet-balances/std", - "pallet-evm-balances/std", - "pallet-evm-system/std", - "primitives-currency-swap/std", "scale-info/std", - "sp-core/std", - "sp-runtime/std", - "sp-std/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", - "pallet-evm-balances/try-runtime", - "pallet-evm-system/try-runtime", - "primitives-currency-swap/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/crates/pallet-native-to-evm-currency-swap/src/benchmarking.rs b/crates/pallet-native-to-evm-currency-swap/src/benchmarking.rs deleted file mode 100644 index db75376b2..000000000 --- a/crates/pallet-native-to-evm-currency-swap/src/benchmarking.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! The benchmarks for the pallet. - -// Allow integer and float arithmetic in tests. -#![allow(clippy::arithmetic_side_effects, clippy::float_arithmetic)] - -use frame_benchmarking::benchmarks; -use frame_support::{assert_ok, dispatch::DispatchResult, traits::Currency}; -use frame_system::RawOrigin; - -use crate::*; - -/// The benchmark interface into the environment. -pub trait Interface: super::Config { - /// The data to be passed from `prepare` to `verify`. - type Data; - - /// Prepare currency swap environment. - fn prepare() -> Self::Data; - - /// Verify currency swap environment, - fn verify(data: Self::Data) -> DispatchResult; - - /// Obtain the Account ID the balance is swapped from. - fn from_account_id() -> ::AccountId; - - /// Obtain the amount of balance to withdraw from the swap source account. - fn from_balance() -> FromBalanceOf; - - /// Obtain the Account ID the balance is swapped to. - fn to_account_id() -> ::AccountIdTo; - - /// Obtain the amount of balance to deposit to the swap destination account. - fn to_balance() -> ToBalanceOf; -} - -benchmarks! { - where_clause { - where - T: Interface, - } - - swap { - let from = ::from_account_id(); - let from_balance = ::from_balance(); - let to = ::to_account_id(); - let to_balance = ::to_balance(); - let init_balance: u32 = 1000; - - let _ = >::deposit_creating(&from, init_balance.into()); - - let from_balance_before = as Currency<_>>::total_balance(&from); - let to_balance_before = as Currency<_>>::total_balance(&to); - - let currency_swap = ::prepare(); - - let origin = RawOrigin::Signed(from.clone()); - - }: _(origin, to.clone(), from_balance) - verify { - let from_balance_after = as Currency<_>>::total_balance(&from); - let to_balance_after = as Currency<_>>::total_balance(&to); - - assert_eq!(from_balance_before - from_balance_after, from_balance); - assert_eq!(to_balance_after - to_balance_before, to_balance); - - assert_ok!(::verify(currency_swap)); - } - - impl_benchmark_test_suite!( - Pallet, - crate::mock::new_test_ext(), - crate::mock::Test, - ); -} - -#[cfg(test)] -impl Interface for crate::mock::Test { - type Data = ( - std::sync::MutexGuard<'static, ()>, - mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__estimate_swapped_balance::Context, - mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__swap::Context, - ); - - fn prepare() -> Self::Data { - let mock_runtime_guard = mock::runtime_lock(); - - let estimate_swapped_balance_ctx = - mock::MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .times(1..) - .return_const(Self::to_balance()); - let swap_ctx = mock::MockCurrencySwap::swap_context(); - swap_ctx.expect().times(1..).return_once(move |_| { - Ok( - >::NegativeImbalance::new( - Self::to_balance(), - ), - ) - }); - - (mock_runtime_guard, estimate_swapped_balance_ctx, swap_ctx) - } - - fn verify(data: Self::Data) -> DispatchResult { - let (mock_runtime_guard, estimate_swapped_balance_ctx, swap_ctx) = data; - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - drop(mock_runtime_guard); - Ok(()) - } - - fn from_account_id() -> ::AccountId { - 42 - } - - fn from_balance() -> FromBalanceOf { - 100 - } - - fn to_account_id() -> ::AccountIdTo { - use sp_std::str::FromStr; - - mock::EvmAccountId::from_str("1000000000000000000000000000000000000001").unwrap() - } - - fn to_balance() -> ToBalanceOf { - 100 - } -} diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 4d4ac1882..2384ff9cb 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -1,41 +1,20 @@ -//! A substrate pallet containing the currency swap integration. +//! A substrate pallet containing the native to evm currency swap integration. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{fungible::Inspect, tokens::Provenance, Currency}; +use frame_support::traits::fungible::Inspect; pub use pallet::*; -use primitives_currency_swap::CurrencySwap as CurrencySwapT; pub use weights::*; pub mod weights; -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - -/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::From`] type from a given config. -type FromCurrencyOf = <::CurrencySwap as CurrencySwapT< - ::AccountId, - ::AccountIdTo, ->>::From; - -/// Utility alias for easy access to the [`Currency::Balance`] of -/// the [`primitives_currency_swap::CurrencySwap::From`] type. -type FromBalanceOf = - as Currency<::AccountId>>::Balance; - -/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::To`] type from a given config. -type ToCurrencyOf = <::CurrencySwap as CurrencySwapT< - ::AccountId, - ::AccountIdTo, ->>::To; - -/// Utility alias for easy access to the [`Currency::Balance`] of -/// the [`primitives_currency_swap::CurrencySwap::To`] type. -type ToBalanceOf = as Currency<::AccountIdTo>>::Balance; +/// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::NativeCurrency`] type. +type NativeBalanceOf = + <::NativeCurrency as Inspect<::AccountId>>::Balance; + +/// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::EvmCurrency`] type. +type EvmBalanceOf = + <::EvmCurrency as Inspect<::EvmAccountId>>::Balance; // We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to // fix them at their end. @@ -44,12 +23,15 @@ type ToBalanceOf = as Currency<::AccountIdTo>>: pub mod pallet { use frame_support::{ pallet_prelude::*, + sp_runtime::traits::{Convert, MaybeDisplay}, + sp_std::fmt::Debug, storage::with_storage_layer, - traits::{ExistenceRequirement, Imbalance, WithdrawReasons}, + traits::{ + fungible::Mutate, + tokens::{Preservation, Provenance}, + }, }; use frame_system::pallet_prelude::*; - use sp_runtime::traits::MaybeDisplay; - use sp_std::fmt::Debug; use super::*; @@ -62,8 +44,8 @@ pub mod pallet { /// Overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// The user account identifier type to convert to. - type AccountIdTo: Parameter + /// The EVM user account identifier type. + type EvmAccountId: Parameter + Member + MaybeSerializeDeserialize + Debug @@ -71,8 +53,21 @@ pub mod pallet { + Ord + MaxEncodedLen; - /// Interface into currency swap implementation. - type CurrencySwap: CurrencySwapT; + /// Native currency. + type NativeCurrency: Inspect + Mutate; + + /// EVM currency. + type EvmCurrency: Inspect; + + /// The converter to determine how the balance amount should be converted from one currency to + /// another. + type BalanceConverter: Convert, EvmBalanceOf>; + + /// The account to land the balances to when receiving the funds as part of the swap operation. + type PotNativeBrige: Get; + + /// The account to take the balances from when sending the funds as part of the swap operation. + type PotEvmBridge: Get; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -86,11 +81,11 @@ pub mod pallet { /// The account id balances withdrawed from. from: T::AccountId, /// The withdrawed balances amount. - withdrawed_amount: FromBalanceOf, + withdrawed_amount: NativeBalanceOf, /// The account id balances deposited to. - to: T::AccountIdTo, + to: T::EvmAccountId, /// The deposited balances amount. - deposited_amount: ToBalanceOf, + deposited_amount: EvmBalanceOf, }, } @@ -100,13 +95,13 @@ pub mod pallet { #[pallet::call_index(0)] pub fn swap( origin: OriginFor, - to: T::AccountIdTo, - #[pallet::compact] amount: FromBalanceOf, + to: T::EvmAccountId, + #[pallet::compact] amount: NativeBalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; with_storage_layer(move || { - Self::do_swap(who, to, amount, ExistenceRequirement::AllowDeath)?; + Self::do_swap(who, to, amount, Preservation::Expendable)?; Ok(()) }) @@ -116,13 +111,13 @@ pub mod pallet { #[pallet::call_index(1)] pub fn swap_keep_alive( origin: OriginFor, - to: T::AccountIdTo, - #[pallet::compact] amount: FromBalanceOf, + to: T::EvmAccountId, + #[pallet::compact] amount: NativeBalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; with_storage_layer(move || { - Self::do_swap(who, to, amount, ExistenceRequirement::KeepAlive)?; + Self::do_swap(who, to, amount, Preservation::Preserve)?; Ok(()) }) @@ -133,37 +128,23 @@ pub mod pallet { /// General swap balances implementation. pub fn do_swap( who: T::AccountId, - to: T::AccountIdTo, - amount: FromBalanceOf, - existence_requirement: ExistenceRequirement, + to: T::EvmAccountId, + amount: NativeBalanceOf, + preservation: Preservation, ) -> DispatchResult { - let estimated_swapped_balance = T::CurrencySwap::estimate_swapped_balance(amount); - ToCurrencyOf::::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) + let estimated_swapped_balance = T::BalanceConverter::convert(amount); + T::EvmCurrency::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; - let withdrawed_imbalance = FromCurrencyOf::::withdraw( - &who, - amount, - WithdrawReasons::TRANSFER, - existence_requirement, - )?; - let withdrawed_amount = withdrawed_imbalance.peek(); - - let deposited_imbalance = - T::CurrencySwap::swap(withdrawed_imbalance).map_err(|error| { - // Here we undo the withdrawal to avoid having a dangling imbalance. - FromCurrencyOf::::resolve_creating(&who, error.incoming_imbalance); - error.cause.into() - })?; - let deposited_amount = deposited_imbalance.peek(); + T::NativeCurrency::transfer(&who, &T::PotNativeBrige::get(), amount, preservation)?; - ToCurrencyOf::::resolve_creating(&to, deposited_imbalance); + // TODO: do visible ethereum transaction. Self::deposit_event(Event::BalancesSwapped { from: who, - withdrawed_amount, + withdrawed_amount: amount, to, - deposited_amount, + deposited_amount: estimated_swapped_balance, }); Ok(()) diff --git a/crates/pallet-native-to-evm-currency-swap/src/mock.rs b/crates/pallet-native-to-evm-currency-swap/src/mock.rs deleted file mode 100644 index e76cf0a2e..000000000 --- a/crates/pallet-native-to-evm-currency-swap/src/mock.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! The mock for the pallet. - -// Allow simple integer arithmetic in tests. -#![allow(clippy::arithmetic_side_effects)] - -use frame_support::{ - sp_io, - sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, DispatchError, - }, - traits::{ConstU32, ConstU64, Currency}, -}; -use mockall::mock; -use sp_core::{H160, H256}; - -use crate::{self as pallet_currency_swap}; - -pub(crate) const EXISTENTIAL_DEPOSIT: u64 = 10; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -pub(crate) type AccountId = u64; -pub(crate) type EvmAccountId = H160; -type Balance = u64; - -frame_support::construct_runtime!( - pub struct Test - where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system, - Balances: pallet_balances, - EvmSystem: pallet_evm_system, - EvmBalances: pallet_evm_balances, - CurrencySwap: pallet_currency_swap, - } -); - -impl frame_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 = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; -} - -impl pallet_balances::Config for Test { - type Balance = u64; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ConstU64; - type AccountStore = System; - type MaxLocks = (); - type HoldIdentifier = (); - type FreezeIdentifier = (); - type MaxReserves = (); - type MaxHolds = ConstU32<0>; - type MaxFreezes = ConstU32<0>; - 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; - type AccountStore = EvmSystem; - type DustRemoval = (); -} - -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< - primitives_currency_swap::ToNegativeImbalanceFor, - primitives_currency_swap::ErrorFor - >; - - fn estimate_swapped_balance( - balance: >::Balance, - ) -> >::Balance; - } -} - -impl pallet_currency_swap::Config for Test { - type RuntimeEvent = RuntimeEvent; - type AccountIdTo = H160; - type CurrencySwap = MockCurrencySwap; - type WeightInfo = (); -} - -pub fn new_test_ext() -> sp_io::TestExternalities { - let genesis_config = GenesisConfig::default(); - new_test_ext_with(genesis_config) -} - -// 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/pallet-native-to-evm-currency-swap/src/tests.rs b/crates/pallet-native-to-evm-currency-swap/src/tests.rs deleted file mode 100644 index 2a97623b0..000000000 --- a/crates/pallet-native-to-evm-currency-swap/src/tests.rs +++ /dev/null @@ -1,393 +0,0 @@ -//! The tests for the pallet. - -use frame_support::{assert_noop, assert_ok, traits::Currency}; -use mockall::predicate; -use sp_core::H160; -use sp_runtime::{DispatchError, TokenError}; -use sp_std::str::FromStr; - -use crate::{mock::*, *}; - -/// This test verifies that swap call works as expected in case origin left balances amount -/// is greater or equal than existential deposit. -#[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!( - >::total_balance(&alice), - alice_balance - ); - assert_eq!(>::total_balance(&alice_evm), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - 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!( - >::total_balance(&alice), - alice_balance - swap_balance - ); - assert_eq!( - >::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. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that swap call works as expected in case origin left balances amount -/// is less than existential deposit. The origin account should be killed. -#[test] -fn swap_works_kill_origin() { - new_test_ext().execute_with_ext(|_| { - let alice = 42; - let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - let alice_balance = 1000; - let swap_balance = 999; - - // Prepare the test state. - Balances::make_free_balance_be(&alice, alice_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice), - alice_balance - ); - assert_eq!(>::total_balance(&alice_evm), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - 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!(!System::account_exists(&alice)); - assert_eq!( - >::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. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that `swap_keep_alive` call works in the happy path. -#[test] -fn swap_keep_alive_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!( - >::total_balance(&alice), - alice_balance - ); - assert_eq!(>::total_balance(&alice_evm), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - 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!( - >::total_balance(&alice), - alice_balance - swap_balance - ); - assert_eq!( - >::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. - estimate_swapped_balance_ctx.checkpoint(); - 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 estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - let swap_ctx = MockCurrencySwap::swap_context(); - swap_ctx - .expect() - .once() - .with(predicate::eq( - >::NegativeImbalance::new(swap_balance), - )) - .return_once(move |incoming_imbalance| { - Err(primitives_currency_swap::Error { - cause: sp_runtime::DispatchError::Other("currency swap failed"), - incoming_imbalance, - }) - }); - - // Invoke the function under test. - assert_noop!( - CurrencySwap::swap(RuntimeOrigin::signed(alice), alice_evm, swap_balance), - DispatchError::Other("currency swap failed") - ); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice), - alice_balance - ); - assert_eq!(>::total_balance(&alice_evm), 0); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that swap call fails in case estimated swapped balance less or equal -/// than target currency existential deposit. -#[test] -fn swap_below_ed_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 estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(EXISTENTIAL_DEPOSIT - 1); - let swap_ctx = MockCurrencySwap::swap_context(); - swap_ctx.expect().never(); - - // Invoke the function under test. - assert_noop!( - CurrencySwap::swap(RuntimeOrigin::signed(alice), alice_evm, swap_balance), - DispatchError::Token(TokenError::BelowMinimum) - ); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice), - alice_balance - ); - assert_eq!(>::total_balance(&alice_evm), 0); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that `swap_keep_alive` call fails in case origin left balances amount -/// is less than existential deposit. The call should prevent swap operation. -#[test] -fn swap_keep_alive_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 = 999; - - // Prepare the test state. - Balances::make_free_balance_be(&alice, alice_balance); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - let swap_ctx = MockCurrencySwap::swap_context(); - swap_ctx.expect().never(); - - // Invoke the function under test. - assert_noop!( - CurrencySwap::swap_keep_alive(RuntimeOrigin::signed(alice), alice_evm, swap_balance), - pallet_balances::Error::::Expendability - ); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice), - alice_balance - ); - assert_eq!(>::total_balance(&alice_evm), 0); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that `swap_keep_alive` call fails in case estimated swapped balance less or equal -/// than target currency existential deposit. -#[test] -fn swap_keep_alive_below_ed_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 estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(EXISTENTIAL_DEPOSIT - 1); - let swap_ctx = MockCurrencySwap::swap_context(); - swap_ctx.expect().never(); - - // Invoke the function under test. - assert_noop!( - CurrencySwap::swap_keep_alive(RuntimeOrigin::signed(alice), alice_evm, swap_balance), - DispatchError::Token(TokenError::BelowMinimum) - ); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice), - alice_balance - ); - assert_eq!(>::total_balance(&alice_evm), 0); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} diff --git a/crates/pallet-native-to-evm-currency-swap/src/weights.rs b/crates/pallet-native-to-evm-currency-swap/src/weights.rs index 11071103e..da08f451d 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/weights.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/weights.rs @@ -1,4 +1,4 @@ -//! Weights definition for pallet-currency-swap. +//! Weights definition for pallet-native-to-evm-currency-swap. use frame_support::weights::Weight; From 3940e5762c54de44c5e5067462655fbf6a94ba14 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 21:16:48 +0300 Subject: [PATCH 003/102] Add etheruem execution logic --- Cargo.lock | 4 ++ .../Cargo.toml | 11 ++++++ .../src/lib.rs | 37 ++++++++++++++++--- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a60e69a65..5c071dc19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6294,11 +6294,15 @@ dependencies = [ name = "pallet-native-to-evm-currency-swap" version = "0.1.0" dependencies = [ + "ethereum", "frame-benchmarking", "frame-support", "frame-system", + "pallet-ethereum", + "pallet-evm", "parity-scale-codec", "scale-info", + "sp-core", ] [[package]] diff --git a/crates/pallet-native-to-evm-currency-swap/Cargo.toml b/crates/pallet-native-to-evm-currency-swap/Cargo.toml index 8e15b2a50..1eabec43b 100644 --- a/crates/pallet-native-to-evm-currency-swap/Cargo.toml +++ b/crates/pallet-native-to-evm-currency-swap/Cargo.toml @@ -6,10 +6,14 @@ publish = false [dependencies] codec = { workspace = true, features = ["derive"] } +ethereum = { workspace = true } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } +pallet-ethereum = { workspace = true } +pallet-evm = { workspace = true } scale-info = { workspace = true, features = ["derive"] } +sp-core = { workspace = true } [features] default = ["std"] @@ -17,14 +21,21 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-ethereum/runtime-benchmarks", + "pallet-evm/runtime-benchmarks", ] std = [ "codec/std", "frame-support/std", "frame-system/std", + "pallet-ethereum/std", + "pallet-evm/std", "scale-info/std", + "sp-core/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-ethereum/try-runtime", + "pallet-evm/try-runtime", ] diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 2384ff9cb..b1d4d9d81 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -23,7 +23,7 @@ type EvmBalanceOf = pub mod pallet { use frame_support::{ pallet_prelude::*, - sp_runtime::traits::{Convert, MaybeDisplay}, + sp_runtime::traits::{Convert, MaybeDisplay, UniqueSaturatedInto}, sp_std::fmt::Debug, storage::with_storage_layer, traits::{ @@ -32,6 +32,8 @@ pub mod pallet { }, }; use frame_system::pallet_prelude::*; + use pallet_evm::FeeCalculator; + use sp_core::{H160, U256}; use super::*; @@ -40,7 +42,7 @@ pub mod pallet { /// Configuration trait of this pallet. #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_ethereum::Config { /// Overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -51,7 +53,8 @@ pub mod pallet { + Debug + MaybeDisplay + Ord - + MaxEncodedLen; + + MaxEncodedLen + + Into; /// Native currency. type NativeCurrency: Inspect + Mutate; @@ -89,7 +92,7 @@ pub mod pallet { }, } - #[pallet::call(weight(T::WeightInfo))] + #[pallet::call(weight(::WeightInfo))] impl Pallet { /// Swap balances. #[pallet::call_index(0)] @@ -136,9 +139,33 @@ pub mod pallet { T::EvmCurrency::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; + let balance_to_be_deposited: u64 = estimated_swapped_balance.unique_saturated_into(); + T::NativeCurrency::transfer(&who, &T::PotNativeBrige::get(), amount, preservation)?; - // TODO: do visible ethereum transaction. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&T::PotEvmBridge::get().into()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: ::FeeCalculator::min_gas_price().0, + gas_limit: 21000.into(), // simple transfer + action: ethereum::TransactionAction::Call(to.clone().into()), + value: U256::from(balance_to_be_deposited), + input: Default::default(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + + pallet_ethereum::Pallet::::execute( + T::PotEvmBridge::get().into(), + &transaction, + None, + ) + .unwrap(); Self::deposit_event(Event::BalancesSwapped { from: who, From e1d1ee788502b194eb3f261bb2eeaaa040b2a093 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 22:33:42 +0300 Subject: [PATCH 004/102] Use ValidatedTransaction apply instead of execute --- Cargo.lock | 1 + Cargo.toml | 1 + crates/pallet-native-to-evm-currency-swap/Cargo.toml | 2 ++ crates/pallet-native-to-evm-currency-swap/src/lib.rs | 6 +++--- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c071dc19..68e053f13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6295,6 +6295,7 @@ name = "pallet-native-to-evm-currency-swap" version = "0.1.0" dependencies = [ "ethereum", + "fp-ethereum", "frame-benchmarking", "frame-support", "frame-system", diff --git a/Cargo.toml b/Cargo.toml index d3ae8efba..11594f0d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,6 +150,7 @@ fc-mapping-sync = { git = "https://github.com/humanode-network/frontier", tag = fc-rpc = { git = "https://github.com/humanode-network/frontier", tag = "locked/polkadot-v0.9.43-2025-03-22", default-features = false } fc-rpc-core = { git = "https://github.com/humanode-network/frontier", tag = "locked/polkadot-v0.9.43-2025-03-22", default-features = false } fc-storage = { git = "https://github.com/humanode-network/frontier", tag = "locked/polkadot-v0.9.43-2025-03-22", default-features = false } +fp-ethereum = { git = "https://github.com/humanode-network/frontier", tag = "locked/polkadot-v0.9.43-2025-03-22", default-features = false } fp-evm = { git = "https://github.com/humanode-network/frontier", tag = "locked/polkadot-v0.9.43-2025-03-22", default-features = false } fp-rpc = { git = "https://github.com/humanode-network/frontier", tag = "locked/polkadot-v0.9.43-2025-03-22", default-features = false } fp-self-contained = { git = "https://github.com/humanode-network/frontier", tag = "locked/polkadot-v0.9.43-2025-03-22", default-features = false } diff --git a/crates/pallet-native-to-evm-currency-swap/Cargo.toml b/crates/pallet-native-to-evm-currency-swap/Cargo.toml index 1eabec43b..48a741b12 100644 --- a/crates/pallet-native-to-evm-currency-swap/Cargo.toml +++ b/crates/pallet-native-to-evm-currency-swap/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] codec = { workspace = true, features = ["derive"] } ethereum = { workspace = true } +fp-ethereum = { workspace = true } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -26,6 +27,7 @@ runtime-benchmarks = [ ] std = [ "codec/std", + "fp-ethereum/std", "frame-support/std", "frame-system/std", "pallet-ethereum/std", diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index b1d4d9d81..177e049e6 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -21,6 +21,7 @@ type EvmBalanceOf = #[allow(clippy::missing_docs_in_private_items)] #[frame_support::pallet] pub mod pallet { + use fp_ethereum::ValidatedTransaction; use frame_support::{ pallet_prelude::*, sp_runtime::traits::{Convert, MaybeDisplay, UniqueSaturatedInto}, @@ -160,10 +161,9 @@ pub mod pallet { s: Default::default(), }); - pallet_ethereum::Pallet::::execute( + pallet_ethereum::ValidatedTransaction::::apply( T::PotEvmBridge::get().into(), - &transaction, - None, + transaction, ) .unwrap(); From 96d6174bfb0db4cdac09d54367792a0e29480d79 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 22:37:57 +0300 Subject: [PATCH 005/102] Fix deposited balance type --- crates/pallet-native-to-evm-currency-swap/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 177e049e6..5c60e0122 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -140,7 +140,7 @@ pub mod pallet { T::EvmCurrency::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; - let balance_to_be_deposited: u64 = estimated_swapped_balance.unique_saturated_into(); + let balance_to_be_deposited: u128 = estimated_swapped_balance.unique_saturated_into(); T::NativeCurrency::transfer(&who, &T::PotNativeBrige::get(), amount, preservation)?; From f1a5082940791e8c04a84adf299497c3096145b4 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 22:44:22 +0300 Subject: [PATCH 006/102] Fix features --- crates/pallet-native-to-evm-currency-swap/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/pallet-native-to-evm-currency-swap/Cargo.toml b/crates/pallet-native-to-evm-currency-swap/Cargo.toml index 48a741b12..c0ececd70 100644 --- a/crates/pallet-native-to-evm-currency-swap/Cargo.toml +++ b/crates/pallet-native-to-evm-currency-swap/Cargo.toml @@ -27,6 +27,7 @@ runtime-benchmarks = [ ] std = [ "codec/std", + "ethereum/std", "fp-ethereum/std", "frame-support/std", "frame-system/std", From 0cc5c34f4bcc018f9c4ce82303a49aaef31d1ba5 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 22:44:44 +0300 Subject: [PATCH 007/102] Fix features-snapshot --- utils/checks/snapshots/features.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils/checks/snapshots/features.yaml b/utils/checks/snapshots/features.yaml index 0e9b0c6fb..9e9dd6cf5 100644 --- a/utils/checks/snapshots/features.yaml +++ b/utils/checks/snapshots/features.yaml @@ -2008,6 +2008,10 @@ - name: pallet-multisig 4.0.0-dev features: - std +- name: pallet-native-to-evm-currency-swap 0.1.0 + features: + - default + - std - name: pallet-pot 0.1.0 features: - default From e770e54edbaf5a8d00fecb66a32b9a645b8591f5 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 22:46:50 +0300 Subject: [PATCH 008/102] Fix cargo machete --- Cargo.lock | 1 - crates/pallet-native-to-evm-currency-swap/Cargo.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68e053f13..51719df0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6296,7 +6296,6 @@ version = "0.1.0" dependencies = [ "ethereum", "fp-ethereum", - "frame-benchmarking", "frame-support", "frame-system", "pallet-ethereum", diff --git a/crates/pallet-native-to-evm-currency-swap/Cargo.toml b/crates/pallet-native-to-evm-currency-swap/Cargo.toml index c0ececd70..943e068e0 100644 --- a/crates/pallet-native-to-evm-currency-swap/Cargo.toml +++ b/crates/pallet-native-to-evm-currency-swap/Cargo.toml @@ -8,7 +8,6 @@ publish = false codec = { workspace = true, features = ["derive"] } ethereum = { workspace = true } fp-ethereum = { workspace = true } -frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-ethereum = { workspace = true } @@ -19,7 +18,6 @@ sp-core = { workspace = true } [features] default = ["std"] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-ethereum/runtime-benchmarks", From 8e98914498efc7ec957c085cae66aaad071c205c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 22:48:55 +0300 Subject: [PATCH 009/102] Fix typo --- crates/humanode-runtime/src/lib.rs | 2 +- crates/pallet-native-to-evm-currency-swap/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 885d76ddc..442056b8f 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -641,7 +641,7 @@ impl pallet_native_to_evm_currency_swap::Config for Runtime { type NativeCurrency = Balances; type EvmCurrency = EvmBalances; type BalanceConverter = Identity; - type PotNativeBrige = NativeToEvmSwapBridgePotAccountId; + type PotNativeBridge = NativeToEvmSwapBridgePotAccountId; type PotEvmBridge = EvmToNativeSwapBridgePotAccountId; type WeightInfo = (); } diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 5c60e0122..662e47227 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -68,7 +68,7 @@ pub mod pallet { type BalanceConverter: Convert, EvmBalanceOf>; /// The account to land the balances to when receiving the funds as part of the swap operation. - type PotNativeBrige: Get; + type PotNativeBridge: Get; /// The account to take the balances from when sending the funds as part of the swap operation. type PotEvmBridge: Get; @@ -142,7 +142,7 @@ pub mod pallet { let balance_to_be_deposited: u128 = estimated_swapped_balance.unique_saturated_into(); - T::NativeCurrency::transfer(&who, &T::PotNativeBrige::get(), amount, preservation)?; + T::NativeCurrency::transfer(&who, &T::PotNativeBridge::get(), amount, preservation)?; let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), From 001d9c42c61a983222fc8dc35f4f4f6e0b438860 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 19 Feb 2025 22:52:38 +0300 Subject: [PATCH 010/102] Some naming improvements --- .../pallet-native-to-evm-currency-swap/src/lib.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 662e47227..23804d110 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -63,14 +63,14 @@ pub mod pallet { /// EVM currency. type EvmCurrency: Inspect; - /// The converter to determine how the balance amount should be converted from one currency to - /// another. + /// The converter to determine how the balance amount should be converted from native + /// to evm currency. type BalanceConverter: Convert, EvmBalanceOf>; - /// The account to land the balances to when receiving the funds as part of the swap operation. + /// The pot native bridge account. type PotNativeBridge: Get; - /// The account to take the balances from when sending the funds as part of the swap operation. + /// The pot evm bridge account. type PotEvmBridge: Get; /// Weight information for extrinsics in this pallet. @@ -140,10 +140,11 @@ pub mod pallet { T::EvmCurrency::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; - let balance_to_be_deposited: u128 = estimated_swapped_balance.unique_saturated_into(); - T::NativeCurrency::transfer(&who, &T::PotNativeBridge::get(), amount, preservation)?; + let evm_balance_to_be_deposited: u128 = + estimated_swapped_balance.unique_saturated_into(); + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), nonce: pallet_evm::Pallet::::account_basic(&T::PotEvmBridge::get().into()) @@ -153,7 +154,7 @@ pub mod pallet { max_fee_per_gas: ::FeeCalculator::min_gas_price().0, gas_limit: 21000.into(), // simple transfer action: ethereum::TransactionAction::Call(to.clone().into()), - value: U256::from(balance_to_be_deposited), + value: U256::from(evm_balance_to_be_deposited), input: Default::default(), access_list: Default::default(), odd_y_parity: false, From e4da0c4123a0330851edf1bebc3eee79ddb2d96e Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 11:49:17 +0300 Subject: [PATCH 011/102] Add the corresponding transaction hash executed in evm to BalancesSwapped event --- crates/pallet-native-to-evm-currency-swap/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 23804d110..91c01534e 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -34,7 +34,7 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use pallet_evm::FeeCalculator; - use sp_core::{H160, U256}; + use sp_core::{H160, H256, U256}; use super::*; @@ -90,6 +90,8 @@ pub mod pallet { to: T::EvmAccountId, /// The deposited balances amount. deposited_amount: EvmBalanceOf, + /// The corresponding transaction hash executed in evm. + evm_trx_hash: H256, }, } @@ -162,6 +164,8 @@ pub mod pallet { s: Default::default(), }); + let evm_trx_hash = transaction.hash(); + pallet_ethereum::ValidatedTransaction::::apply( T::PotEvmBridge::get().into(), transaction, @@ -173,6 +177,7 @@ pub mod pallet { withdrawed_amount: amount, to, deposited_amount: estimated_swapped_balance, + evm_trx_hash, }); Ok(()) From 719bff15f4049369f17ef96684249107373cbb2c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 12:06:34 +0300 Subject: [PATCH 012/102] Use zero as max fee per gas --- crates/pallet-native-to-evm-currency-swap/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 91c01534e..3d4141cc2 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -33,7 +33,6 @@ pub mod pallet { }, }; use frame_system::pallet_prelude::*; - use pallet_evm::FeeCalculator; use sp_core::{H160, H256, U256}; use super::*; @@ -153,7 +152,7 @@ pub mod pallet { .0 .nonce, max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: ::FeeCalculator::min_gas_price().0, + max_fee_per_gas: 0.into(), gas_limit: 21000.into(), // simple transfer action: ethereum::TransactionAction::Call(to.clone().into()), value: U256::from(evm_balance_to_be_deposited), From 8075ab605102599c1d0f0a8043565d5a7c98bffd Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 12:15:33 +0300 Subject: [PATCH 013/102] Handle dispatch result with post info --- crates/pallet-native-to-evm-currency-swap/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 3d4141cc2..1641e3b7a 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -165,11 +165,11 @@ pub mod pallet { let evm_trx_hash = transaction.hash(); - pallet_ethereum::ValidatedTransaction::::apply( + let _post_info = pallet_ethereum::ValidatedTransaction::::apply( T::PotEvmBridge::get().into(), transaction, ) - .unwrap(); + .map_err(|dispatch_error_with_post_info| dispatch_error_with_post_info.error)?; Self::deposit_event(Event::BalancesSwapped { from: who, From a997f192705f242f20f1ec17a12beb6c5b1a7d30 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 14:50:33 +0300 Subject: [PATCH 014/102] Rename evm_trx_hash to evm_transaction_hash --- crates/pallet-native-to-evm-currency-swap/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 1641e3b7a..509fdf88f 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -90,7 +90,7 @@ pub mod pallet { /// The deposited balances amount. deposited_amount: EvmBalanceOf, /// The corresponding transaction hash executed in evm. - evm_trx_hash: H256, + evm_transaction_hash: H256, }, } @@ -163,7 +163,7 @@ pub mod pallet { s: Default::default(), }); - let evm_trx_hash = transaction.hash(); + let evm_transaction_hash = transaction.hash(); let _post_info = pallet_ethereum::ValidatedTransaction::::apply( T::PotEvmBridge::get().into(), @@ -176,7 +176,7 @@ pub mod pallet { withdrawed_amount: amount, to, deposited_amount: estimated_swapped_balance, - evm_trx_hash, + evm_transaction_hash, }); Ok(()) From 211f5063ab6452451520d8c5578632a257b2e4b3 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 14:52:18 +0300 Subject: [PATCH 015/102] Rename bridge pots account types --- crates/humanode-runtime/src/lib.rs | 4 ++-- .../pallet-native-to-evm-currency-swap/src/lib.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 442056b8f..b51f272f9 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -641,8 +641,8 @@ impl pallet_native_to_evm_currency_swap::Config for Runtime { type NativeCurrency = Balances; type EvmCurrency = EvmBalances; type BalanceConverter = Identity; - type PotNativeBridge = NativeToEvmSwapBridgePotAccountId; - type PotEvmBridge = EvmToNativeSwapBridgePotAccountId; + type BridgePotNative = NativeToEvmSwapBridgePotAccountId; + type BridgePotEvm = EvmToNativeSwapBridgePotAccountId; type WeightInfo = (); } diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-currency-swap/src/lib.rs index 509fdf88f..45a1f8f9a 100644 --- a/crates/pallet-native-to-evm-currency-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-currency-swap/src/lib.rs @@ -66,11 +66,11 @@ pub mod pallet { /// to evm currency. type BalanceConverter: Convert, EvmBalanceOf>; - /// The pot native bridge account. - type PotNativeBridge: Get; + /// The bridge pot native account. + type BridgePotNative: Get; - /// The pot evm bridge account. - type PotEvmBridge: Get; + /// The bridge pot evm account. + type BridgePotEvm: Get; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -141,14 +141,14 @@ pub mod pallet { T::EvmCurrency::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; - T::NativeCurrency::transfer(&who, &T::PotNativeBridge::get(), amount, preservation)?; + T::NativeCurrency::transfer(&who, &T::BridgePotNative::get(), amount, preservation)?; let evm_balance_to_be_deposited: u128 = estimated_swapped_balance.unique_saturated_into(); let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&T::PotEvmBridge::get().into()) + nonce: pallet_evm::Pallet::::account_basic(&T::BridgePotEvm::get().into()) .0 .nonce, max_priority_fee_per_gas: 0.into(), @@ -166,7 +166,7 @@ pub mod pallet { let evm_transaction_hash = transaction.hash(); let _post_info = pallet_ethereum::ValidatedTransaction::::apply( - T::PotEvmBridge::get().into(), + T::BridgePotEvm::get().into(), transaction, ) .map_err(|dispatch_error_with_post_info| dispatch_error_with_post_info.error)?; From 343ca7c178be832d205886c19fdec2b7974c9778 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 14:53:53 +0300 Subject: [PATCH 016/102] Remove currency from pallet name --- Cargo.lock | 4 ++-- crates/humanode-runtime/Cargo.toml | 8 ++++---- crates/humanode-runtime/src/lib.rs | 4 ++-- .../Cargo.toml | 2 +- .../src/lib.rs | 0 .../src/weights.rs | 0 utils/checks/snapshots/features.yaml | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) rename crates/{pallet-native-to-evm-currency-swap => pallet-native-to-evm-swap}/Cargo.toml (95%) rename crates/{pallet-native-to-evm-currency-swap => pallet-native-to-evm-swap}/src/lib.rs (100%) rename crates/{pallet-native-to-evm-currency-swap => pallet-native-to-evm-swap}/src/weights.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 51719df0a..4779d505e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3817,7 +3817,7 @@ dependencies = [ "pallet-humanode-session", "pallet-im-online", "pallet-multisig", - "pallet-native-to-evm-currency-swap", + "pallet-native-to-evm-swap", "pallet-pot", "pallet-session", "pallet-sudo", @@ -6291,7 +6291,7 @@ dependencies = [ ] [[package]] -name = "pallet-native-to-evm-currency-swap" +name = "pallet-native-to-evm-swap" version = "0.1.0" dependencies = [ "ethereum", diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index 53ca2a6b5..136dc0df4 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -32,7 +32,7 @@ pallet-evm-balances = { path = "../pallet-evm-balances", default-features = fals pallet-evm-system = { path = "../pallet-evm-system", default-features = false } pallet-humanode-offences = { path = "../pallet-humanode-offences", default-features = false } pallet-humanode-session = { path = "../pallet-humanode-session", default-features = false } -pallet-native-to-evm-currency-swap = { path = "../pallet-native-to-evm-currency-swap", default-features = false } +pallet-native-to-evm-swap = { path = "../pallet-native-to-evm-swap", default-features = false } pallet-pot = { path = "../pallet-pot", default-features = false } pallet-token-claims = { path = "../pallet-token-claims", default-features = false } pallet-vesting = { path = "../pallet-vesting", default-features = false } @@ -133,7 +133,7 @@ runtime-benchmarks = [ "pallet-im-online/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", - "pallet-native-to-evm-currency-swap/runtime-benchmarks", + "pallet-native-to-evm-swap/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-token-claims/runtime-benchmarks", "pallet-utility/runtime-benchmarks", @@ -192,7 +192,7 @@ std = [ "pallet-humanode-session/std", "pallet-im-online/std", "pallet-multisig/std", - "pallet-native-to-evm-currency-swap/std", + "pallet-native-to-evm-swap/std", "pallet-pot/std", "pallet-session/std", "pallet-sudo/std", @@ -264,7 +264,7 @@ try-runtime = [ "pallet-humanode-session/try-runtime", "pallet-im-online/try-runtime", "pallet-multisig/try-runtime", - "pallet-native-to-evm-currency-swap/try-runtime", + "pallet-native-to-evm-swap/try-runtime", "pallet-pot/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index b51f272f9..63ca6df54 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -635,7 +635,7 @@ impl pallet_currency_swap::Config for Runtime { type WeightInfo = (); } -impl pallet_native_to_evm_currency_swap::Config for Runtime { +impl pallet_native_to_evm_swap::Config for Runtime { type RuntimeEvent = RuntimeEvent; type EvmAccountId = EvmAccountId; type NativeCurrency = Balances; @@ -853,7 +853,7 @@ construct_runtime!( EvmBalancesErc20Support: pallet_erc20_support = 37, DummyPrecompilesCode: pallet_dummy_precompiles_code = 38, HumanodeOffences: pallet_humanode_offences = 39, - NativeToEvmCurrencySwap: pallet_native_to_evm_currency_swap = 40, + NativeToEvmSwap: pallet_native_to_evm_swap = 40, } ); diff --git a/crates/pallet-native-to-evm-currency-swap/Cargo.toml b/crates/pallet-native-to-evm-swap/Cargo.toml similarity index 95% rename from crates/pallet-native-to-evm-currency-swap/Cargo.toml rename to crates/pallet-native-to-evm-swap/Cargo.toml index 943e068e0..a1b6fc851 100644 --- a/crates/pallet-native-to-evm-currency-swap/Cargo.toml +++ b/crates/pallet-native-to-evm-swap/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-native-to-evm-currency-swap" +name = "pallet-native-to-evm-swap" version = "0.1.0" edition = "2021" publish = false diff --git a/crates/pallet-native-to-evm-currency-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs similarity index 100% rename from crates/pallet-native-to-evm-currency-swap/src/lib.rs rename to crates/pallet-native-to-evm-swap/src/lib.rs diff --git a/crates/pallet-native-to-evm-currency-swap/src/weights.rs b/crates/pallet-native-to-evm-swap/src/weights.rs similarity index 100% rename from crates/pallet-native-to-evm-currency-swap/src/weights.rs rename to crates/pallet-native-to-evm-swap/src/weights.rs diff --git a/utils/checks/snapshots/features.yaml b/utils/checks/snapshots/features.yaml index 9e9dd6cf5..2fc97c75d 100644 --- a/utils/checks/snapshots/features.yaml +++ b/utils/checks/snapshots/features.yaml @@ -2008,7 +2008,7 @@ - name: pallet-multisig 4.0.0-dev features: - std -- name: pallet-native-to-evm-currency-swap 0.1.0 +- name: pallet-native-to-evm-swap 0.1.0 features: - default - std From 23c1ae5d28390fd2c9df6e809655be20caf68f60 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 16:20:47 +0300 Subject: [PATCH 017/102] Remove unused required interfaces --- crates/pallet-native-to-evm-swap/src/lib.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/pallet-native-to-evm-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs index 45a1f8f9a..d749da207 100644 --- a/crates/pallet-native-to-evm-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-swap/src/lib.rs @@ -24,8 +24,7 @@ pub mod pallet { use fp_ethereum::ValidatedTransaction; use frame_support::{ pallet_prelude::*, - sp_runtime::traits::{Convert, MaybeDisplay, UniqueSaturatedInto}, - sp_std::fmt::Debug, + sp_runtime::traits::{Convert, UniqueSaturatedInto}, storage::with_storage_layer, traits::{ fungible::Mutate, @@ -47,14 +46,7 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The EVM user account identifier type. - type EvmAccountId: Parameter - + Member - + MaybeSerializeDeserialize - + Debug - + MaybeDisplay - + Ord - + MaxEncodedLen - + Into; + type EvmAccountId: Parameter + Into; /// Native currency. type NativeCurrency: Inspect + Mutate; From 316d5e9f01ee185afaeed626097c0f312579b81e Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 17:01:12 +0300 Subject: [PATCH 018/102] Use gas_limit as gas_transaction_call value from evm config --- crates/pallet-native-to-evm-swap/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/pallet-native-to-evm-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs index d749da207..bb3e28ce7 100644 --- a/crates/pallet-native-to-evm-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-swap/src/lib.rs @@ -145,7 +145,9 @@ pub mod pallet { .nonce, max_priority_fee_per_gas: 0.into(), max_fee_per_gas: 0.into(), - gas_limit: 21000.into(), // simple transfer + gas_limit: ::config() + .gas_transaction_call + .into(), // simple transfer action: ethereum::TransactionAction::Call(to.clone().into()), value: U256::from(evm_balance_to_be_deposited), input: Default::default(), From 3ee261d94164da905e2e9b33e83d5624242ee0eb Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 18:27:31 +0300 Subject: [PATCH 019/102] Use token terminology instead of currency --- crates/humanode-runtime/src/lib.rs | 4 ++-- crates/pallet-native-to-evm-swap/src/lib.rs | 25 ++++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 63ca6df54..671f46296 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -638,8 +638,8 @@ impl pallet_currency_swap::Config for Runtime { impl pallet_native_to_evm_swap::Config for Runtime { type RuntimeEvent = RuntimeEvent; type EvmAccountId = EvmAccountId; - type NativeCurrency = Balances; - type EvmCurrency = EvmBalances; + type NativeToken = Balances; + type EvmToken = EvmBalances; type BalanceConverter = Identity; type BridgePotNative = NativeToEvmSwapBridgePotAccountId; type BridgePotEvm = EvmToNativeSwapBridgePotAccountId; diff --git a/crates/pallet-native-to-evm-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs index bb3e28ce7..9aecbb92a 100644 --- a/crates/pallet-native-to-evm-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-swap/src/lib.rs @@ -1,4 +1,4 @@ -//! A substrate pallet containing the native to evm currency swap integration. +//! A substrate pallet containing the native to evm token swap integration. #![cfg_attr(not(feature = "std"), no_std)] @@ -8,13 +8,12 @@ pub use weights::*; pub mod weights; -/// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::NativeCurrency`] type. +/// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::NativeToken`] type. type NativeBalanceOf = - <::NativeCurrency as Inspect<::AccountId>>::Balance; + <::NativeToken as Inspect<::AccountId>>::Balance; -/// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::EvmCurrency`] type. -type EvmBalanceOf = - <::EvmCurrency as Inspect<::EvmAccountId>>::Balance; +/// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::EvmToken`] type. +type EvmBalanceOf = <::EvmToken as Inspect<::EvmAccountId>>::Balance; // We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to // fix them at their end. @@ -48,14 +47,14 @@ pub mod pallet { /// The EVM user account identifier type. type EvmAccountId: Parameter + Into; - /// Native currency. - type NativeCurrency: Inspect + Mutate; + /// Native token. + type NativeToken: Inspect + Mutate; - /// EVM currency. - type EvmCurrency: Inspect; + /// EVM token. + type EvmToken: Inspect; /// The converter to determine how the balance amount should be converted from native - /// to evm currency. + /// to evm token. type BalanceConverter: Convert, EvmBalanceOf>; /// The bridge pot native account. @@ -130,10 +129,10 @@ pub mod pallet { preservation: Preservation, ) -> DispatchResult { let estimated_swapped_balance = T::BalanceConverter::convert(amount); - T::EvmCurrency::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) + T::EvmToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; - T::NativeCurrency::transfer(&who, &T::BridgePotNative::get(), amount, preservation)?; + T::NativeToken::transfer(&who, &T::BridgePotNative::get(), amount, preservation)?; let evm_balance_to_be_deposited: u128 = estimated_swapped_balance.unique_saturated_into(); From 47e35c4db060a5215c5e8c2f6d4ea2afd359a3ae Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 20 Feb 2025 18:28:58 +0300 Subject: [PATCH 020/102] Uppercase for evm --- crates/pallet-native-to-evm-swap/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/pallet-native-to-evm-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs index 9aecbb92a..d6f62cf98 100644 --- a/crates/pallet-native-to-evm-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-swap/src/lib.rs @@ -1,4 +1,4 @@ -//! A substrate pallet containing the native to evm token swap integration. +//! A substrate pallet containing the Native to EVM token swap integration. #![cfg_attr(not(feature = "std"), no_std)] @@ -54,13 +54,13 @@ pub mod pallet { type EvmToken: Inspect; /// The converter to determine how the balance amount should be converted from native - /// to evm token. + /// to EVM token. type BalanceConverter: Convert, EvmBalanceOf>; /// The bridge pot native account. type BridgePotNative: Get; - /// The bridge pot evm account. + /// The bridge pot EVM account. type BridgePotEvm: Get; /// Weight information for extrinsics in this pallet. @@ -80,7 +80,7 @@ pub mod pallet { to: T::EvmAccountId, /// The deposited balances amount. deposited_amount: EvmBalanceOf, - /// The corresponding transaction hash executed in evm. + /// The corresponding transaction hash executed in EVM. evm_transaction_hash: H256, }, } From 50b81d5aed2daaa302e93cf8f20d635356797e7d Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 21 Feb 2025 12:49:06 +0300 Subject: [PATCH 021/102] Add debug assert on resulted post_info ethereum transaction --- crates/pallet-native-to-evm-swap/src/lib.rs | 24 +++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/pallet-native-to-evm-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs index d6f62cf98..ff415f72d 100644 --- a/crates/pallet-native-to-evm-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-swap/src/lib.rs @@ -22,6 +22,7 @@ type EvmBalanceOf = <::EvmToken as Inspect<::EvmAcc pub mod pallet { use fp_ethereum::ValidatedTransaction; use frame_support::{ + dispatch::PostDispatchInfo, pallet_prelude::*, sp_runtime::traits::{Convert, UniqueSaturatedInto}, storage::with_storage_layer, @@ -31,6 +32,7 @@ pub mod pallet { }, }; use frame_system::pallet_prelude::*; + use pallet_evm::GasWeightMapping; use sp_core::{H160, H256, U256}; use super::*; @@ -137,6 +139,8 @@ pub mod pallet { let evm_balance_to_be_deposited: u128 = estimated_swapped_balance.unique_saturated_into(); + let gas_simple_transfer_call = ::config().gas_transaction_call; + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), nonce: pallet_evm::Pallet::::account_basic(&T::BridgePotEvm::get().into()) @@ -144,9 +148,7 @@ pub mod pallet { .nonce, max_priority_fee_per_gas: 0.into(), max_fee_per_gas: 0.into(), - gas_limit: ::config() - .gas_transaction_call - .into(), // simple transfer + gas_limit: gas_simple_transfer_call.into(), action: ethereum::TransactionAction::Call(to.clone().into()), value: U256::from(evm_balance_to_be_deposited), input: Default::default(), @@ -158,12 +160,26 @@ pub mod pallet { let evm_transaction_hash = transaction.hash(); - let _post_info = pallet_ethereum::ValidatedTransaction::::apply( + let post_info = pallet_ethereum::ValidatedTransaction::::apply( T::BridgePotEvm::get().into(), transaction, ) .map_err(|dispatch_error_with_post_info| dispatch_error_with_post_info.error)?; + debug_assert!( + post_info + == PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + gas_simple_transfer_call.unique_saturated_into(), + true, + ) + ), + pays_fee: Pays::No + }, + "we must ensure that actual weight corresponds to gas used for simple transfer call" + ); + Self::deposit_event(Event::BalancesSwapped { from: who, withdrawed_amount: amount, From 40f67775a684325d46501bfe9c1ec3cadfb3d3d5 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 21 Feb 2025 13:10:38 +0300 Subject: [PATCH 022/102] Move ethereum transfer logic to separate function --- crates/pallet-native-to-evm-swap/src/lib.rs | 51 +++++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/pallet-native-to-evm-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs index ff415f72d..f7c6962fd 100644 --- a/crates/pallet-native-to-evm-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-swap/src/lib.rs @@ -136,21 +136,42 @@ pub mod pallet { T::NativeToken::transfer(&who, &T::BridgePotNative::get(), amount, preservation)?; - let evm_balance_to_be_deposited: u128 = - estimated_swapped_balance.unique_saturated_into(); + let evm_transaction_hash = Self::execute_ethereum_transfer( + T::BridgePotEvm::get().into(), + to.clone().into(), + estimated_swapped_balance.unique_saturated_into(), + )?; + + Self::deposit_event(Event::BalancesSwapped { + from: who, + withdrawed_amount: amount, + to, + deposited_amount: estimated_swapped_balance, + evm_transaction_hash, + }); + + Ok(()) + } + /// Execute ethereum transfer from source address to target EVM address with provided + /// balance to be sent. + fn execute_ethereum_transfer( + source_address: H160, + target_address: H160, + balance: u128, + ) -> Result { let gas_simple_transfer_call = ::config().gas_transaction_call; let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&T::BridgePotEvm::get().into()) + nonce: pallet_evm::Pallet::::account_basic(&source_address) .0 .nonce, max_priority_fee_per_gas: 0.into(), max_fee_per_gas: 0.into(), gas_limit: gas_simple_transfer_call.into(), - action: ethereum::TransactionAction::Call(to.clone().into()), - value: U256::from(evm_balance_to_be_deposited), + action: ethereum::TransactionAction::Call(target_address), + value: U256::from(balance), input: Default::default(), access_list: Default::default(), odd_y_parity: false, @@ -158,13 +179,11 @@ pub mod pallet { s: Default::default(), }); - let evm_transaction_hash = transaction.hash(); + let transaction_hash = transaction.hash(); - let post_info = pallet_ethereum::ValidatedTransaction::::apply( - T::BridgePotEvm::get().into(), - transaction, - ) - .map_err(|dispatch_error_with_post_info| dispatch_error_with_post_info.error)?; + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(source_address, transaction) + .map_err(|dispatch_error_with_post_info| dispatch_error_with_post_info.error)?; debug_assert!( post_info @@ -180,15 +199,7 @@ pub mod pallet { "we must ensure that actual weight corresponds to gas used for simple transfer call" ); - Self::deposit_event(Event::BalancesSwapped { - from: who, - withdrawed_amount: amount, - to, - deposited_amount: estimated_swapped_balance, - evm_transaction_hash, - }); - - Ok(()) + Ok(transaction_hash) } } } From 50459ae6a1c51e87245d180989d5039b5cd1e160 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 24 Feb 2025 12:38:34 +0300 Subject: [PATCH 023/102] Rename to pallet-evm-swap --- Cargo.lock | 32 +++++++++---------- crates/humanode-runtime/Cargo.toml | 8 ++--- crates/humanode-runtime/src/lib.rs | 4 +-- .../Cargo.toml | 2 +- .../src/lib.rs | 0 .../src/weights.rs | 0 6 files changed, 23 insertions(+), 23 deletions(-) rename crates/{pallet-native-to-evm-swap => pallet-evm-swap}/Cargo.toml (96%) rename crates/{pallet-native-to-evm-swap => pallet-evm-swap}/src/lib.rs (100%) rename crates/{pallet-native-to-evm-swap => pallet-evm-swap}/src/weights.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 4779d505e..393ceb0f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3811,13 +3811,13 @@ dependencies = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-evm-swap", "pallet-evm-system", "pallet-grandpa", "pallet-humanode-offences", "pallet-humanode-session", "pallet-im-online", "pallet-multisig", - "pallet-native-to-evm-swap", "pallet-pot", "pallet-session", "pallet-sudo", @@ -6165,6 +6165,21 @@ dependencies = [ "sp-io", ] +[[package]] +name = "pallet-evm-swap" +version = "0.1.0" +dependencies = [ + "ethereum", + "fp-ethereum", + "frame-support", + "frame-system", + "pallet-ethereum", + "pallet-evm", + "parity-scale-codec", + "scale-info", + "sp-core", +] + [[package]] name = "pallet-evm-system" version = "0.1.0" @@ -6290,21 +6305,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-native-to-evm-swap" -version = "0.1.0" -dependencies = [ - "ethereum", - "fp-ethereum", - "frame-support", - "frame-system", - "pallet-ethereum", - "pallet-evm", - "parity-scale-codec", - "scale-info", - "sp-core", -] - [[package]] name = "pallet-pot" version = "0.1.0" diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index 136dc0df4..22aaeffd6 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -32,7 +32,7 @@ pallet-evm-balances = { path = "../pallet-evm-balances", default-features = fals pallet-evm-system = { path = "../pallet-evm-system", default-features = false } pallet-humanode-offences = { path = "../pallet-humanode-offences", default-features = false } pallet-humanode-session = { path = "../pallet-humanode-session", default-features = false } -pallet-native-to-evm-swap = { path = "../pallet-native-to-evm-swap", default-features = false } +pallet-evm-swap = { path = "../pallet-evm-swap", default-features = false } pallet-pot = { path = "../pallet-pot", default-features = false } pallet-token-claims = { path = "../pallet-token-claims", default-features = false } pallet-vesting = { path = "../pallet-vesting", default-features = false } @@ -133,7 +133,7 @@ runtime-benchmarks = [ "pallet-im-online/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", - "pallet-native-to-evm-swap/runtime-benchmarks", + "pallet-evm-swap/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-token-claims/runtime-benchmarks", "pallet-utility/runtime-benchmarks", @@ -192,7 +192,7 @@ std = [ "pallet-humanode-session/std", "pallet-im-online/std", "pallet-multisig/std", - "pallet-native-to-evm-swap/std", + "pallet-evm-swap/std", "pallet-pot/std", "pallet-session/std", "pallet-sudo/std", @@ -264,7 +264,7 @@ try-runtime = [ "pallet-humanode-session/try-runtime", "pallet-im-online/try-runtime", "pallet-multisig/try-runtime", - "pallet-native-to-evm-swap/try-runtime", + "pallet-evm-swap/try-runtime", "pallet-pot/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 671f46296..004d75e1a 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -635,7 +635,7 @@ impl pallet_currency_swap::Config for Runtime { type WeightInfo = (); } -impl pallet_native_to_evm_swap::Config for Runtime { +impl pallet_evm_swap::Config for Runtime { type RuntimeEvent = RuntimeEvent; type EvmAccountId = EvmAccountId; type NativeToken = Balances; @@ -853,7 +853,7 @@ construct_runtime!( EvmBalancesErc20Support: pallet_erc20_support = 37, DummyPrecompilesCode: pallet_dummy_precompiles_code = 38, HumanodeOffences: pallet_humanode_offences = 39, - NativeToEvmSwap: pallet_native_to_evm_swap = 40, + EvmSwap: pallet_evm_swap = 40, } ); diff --git a/crates/pallet-native-to-evm-swap/Cargo.toml b/crates/pallet-evm-swap/Cargo.toml similarity index 96% rename from crates/pallet-native-to-evm-swap/Cargo.toml rename to crates/pallet-evm-swap/Cargo.toml index a1b6fc851..5f48db1ea 100644 --- a/crates/pallet-native-to-evm-swap/Cargo.toml +++ b/crates/pallet-evm-swap/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-native-to-evm-swap" +name = "pallet-evm-swap" version = "0.1.0" edition = "2021" publish = false diff --git a/crates/pallet-native-to-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs similarity index 100% rename from crates/pallet-native-to-evm-swap/src/lib.rs rename to crates/pallet-evm-swap/src/lib.rs diff --git a/crates/pallet-native-to-evm-swap/src/weights.rs b/crates/pallet-evm-swap/src/weights.rs similarity index 100% rename from crates/pallet-native-to-evm-swap/src/weights.rs rename to crates/pallet-evm-swap/src/weights.rs From d75599fbeec3854de71d092156d906302b62f63b Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 24 Feb 2025 15:13:13 +0300 Subject: [PATCH 024/102] Introduce precompile to execute swap from evm to native --- Cargo.lock | 3 + crates/humanode-runtime/Cargo.toml | 8 +- .../src/frontier_precompiles.rs | 15 +- crates/humanode-runtime/src/lib.rs | 3 +- crates/pallet-evm-swap/Cargo.toml | 7 + crates/pallet-evm-swap/src/lib.rs | 31 ++-- crates/pallet-evm-swap/src/precompile.rs | 143 ++++++++++++++++++ utils/checks/snapshots/features.yaml | 8 +- 8 files changed, 199 insertions(+), 19 deletions(-) create mode 100644 crates/pallet-evm-swap/src/precompile.rs diff --git a/Cargo.lock b/Cargo.lock index 393ceb0f9..e2fee3ec6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6171,11 +6171,14 @@ version = "0.1.0" dependencies = [ "ethereum", "fp-ethereum", + "fp-evm", "frame-support", "frame-system", + "num_enum 0.7.3", "pallet-ethereum", "pallet-evm", "parity-scale-codec", + "precompile-utils", "scale-info", "sp-core", ] diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index 22aaeffd6..804f58316 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -29,10 +29,10 @@ pallet-erc20-support = { path = "../pallet-erc20-support", default-features = fa pallet-ethereum-chain-id = { path = "../pallet-ethereum-chain-id", default-features = false } pallet-evm-accounts-mapping = { path = "../pallet-evm-accounts-mapping", default-features = false } pallet-evm-balances = { path = "../pallet-evm-balances", default-features = false } +pallet-evm-swap = { path = "../pallet-evm-swap", default-features = false } pallet-evm-system = { path = "../pallet-evm-system", default-features = false } pallet-humanode-offences = { path = "../pallet-humanode-offences", default-features = false } pallet-humanode-session = { path = "../pallet-humanode-session", default-features = false } -pallet-evm-swap = { path = "../pallet-evm-swap", default-features = false } pallet-pot = { path = "../pallet-pot", default-features = false } pallet-token-claims = { path = "../pallet-token-claims", default-features = false } pallet-vesting = { path = "../pallet-vesting", default-features = false } @@ -127,13 +127,13 @@ runtime-benchmarks = [ "pallet-currency-swap/runtime-benchmarks", "pallet-ethereum/runtime-benchmarks", "pallet-evm-accounts-mapping/runtime-benchmarks", + "pallet-evm-swap/runtime-benchmarks", "pallet-evm/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-humanode-session/runtime-benchmarks", "pallet-im-online/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", - "pallet-evm-swap/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-token-claims/runtime-benchmarks", "pallet-utility/runtime-benchmarks", @@ -186,13 +186,13 @@ std = [ "pallet-evm-precompile-simple/std", "pallet-evm/std", "pallet-evm-balances/std", + "pallet-evm-swap/std", "pallet-evm-system/std", "pallet-grandpa/std", "pallet-humanode-offences/std", "pallet-humanode-session/std", "pallet-im-online/std", "pallet-multisig/std", - "pallet-evm-swap/std", "pallet-pot/std", "pallet-session/std", "pallet-sudo/std", @@ -258,13 +258,13 @@ try-runtime = [ "pallet-evm-accounts-mapping/try-runtime", "pallet-evm/try-runtime", "pallet-evm-balances/try-runtime", + "pallet-evm-swap/try-runtime", "pallet-evm-system/try-runtime", "pallet-grandpa/try-runtime", "pallet-humanode-offences/try-runtime", "pallet-humanode-session/try-runtime", "pallet-im-online/try-runtime", "pallet-multisig/try-runtime", - "pallet-evm-swap/try-runtime", "pallet-pot/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", diff --git a/crates/humanode-runtime/src/frontier_precompiles.rs b/crates/humanode-runtime/src/frontier_precompiles.rs index 2b578a5af..c1f486cd3 100644 --- a/crates/humanode-runtime/src/frontier_precompiles.rs +++ b/crates/humanode-runtime/src/frontier_precompiles.rs @@ -7,6 +7,7 @@ use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_modexp::Modexp; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; +use pallet_evm_swap::precompile::EvmSwap; use precompile_bioauth::Bioauth; use precompile_bls12381::{ Bls12381G1Add, Bls12381G1Mul, Bls12381G1MultiExp, Bls12381G2Add, Bls12381G2Mul, @@ -76,6 +77,8 @@ pub mod precompiles_constants { pub const NATIVE_CURRENCY: u64 = 2050; /// `CurrencySwap` precompile constant. pub const CURRENCY_SWAP: u64 = 2304; + /// `EvmSwap` precompile constant. + pub const EVM_SWAP: u64 = 2305; } use precompiles_constants::*; @@ -117,7 +120,8 @@ where BIOAUTH, EVM_ACCOUNTS_MAPPING, NATIVE_CURRENCY, - CURRENCY_SWAP + CURRENCY_SWAP, + EVM_SWAP, ] .into_iter() .map(hash) @@ -132,11 +136,15 @@ where R: pallet_evm_accounts_mapping::Config, R: pallet_evm_balances::Config, R: pallet_erc20_support::Config, + R: pallet_evm_swap::Config, ::AccountId: From, <::Currency as Currency< ::AccountId, >>::Balance: Into + TryFrom, ::Allowance: TryFrom + EvmData, + pallet_evm_swap::EvmBalanceOf: TryFrom, + ::EvmAccountId: From, + ::AccountId: From<[u8; 32]>, R::ValidatorPublicKey: for<'a> TryFrom<&'a [u8]> + Eq, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { @@ -181,6 +189,11 @@ where ConstU64<200>, >::execute(handle)) } + a if a == hash(EVM_SWAP) => Some(EvmSwap::< + R, + // TODO(#697): implement proper dynamic gas cost estimation. + ConstU64<200>, + >::execute(handle)), // Fallback _ => None, } diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 004d75e1a..7a97db8c5 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -640,7 +640,8 @@ impl pallet_evm_swap::Config for Runtime { type EvmAccountId = EvmAccountId; type NativeToken = Balances; type EvmToken = EvmBalances; - type BalanceConverter = Identity; + type BalanceConverterNativeToEvm = Identity; + type BalanceConverterEvmToNative = Identity; type BridgePotNative = NativeToEvmSwapBridgePotAccountId; type BridgePotEvm = EvmToNativeSwapBridgePotAccountId; type WeightInfo = (); diff --git a/crates/pallet-evm-swap/Cargo.toml b/crates/pallet-evm-swap/Cargo.toml index 5f48db1ea..ed4819939 100644 --- a/crates/pallet-evm-swap/Cargo.toml +++ b/crates/pallet-evm-swap/Cargo.toml @@ -5,11 +5,15 @@ edition = "2021" publish = false [dependencies] +precompile-utils = { path = "../precompile-utils", default-features = false } + codec = { workspace = true, features = ["derive"] } ethereum = { workspace = true } fp-ethereum = { workspace = true } +fp-evm = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +num_enum = { workspace = true } pallet-ethereum = { workspace = true } pallet-evm = { workspace = true } scale-info = { workspace = true, features = ["derive"] } @@ -27,10 +31,13 @@ std = [ "codec/std", "ethereum/std", "fp-ethereum/std", + "fp-evm/std", "frame-support/std", "frame-system/std", + "num_enum/std", "pallet-ethereum/std", "pallet-evm/std", + "precompile-utils/std", "scale-info/std", "sp-core/std", ] diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index f7c6962fd..d38aeb1e7 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -6,14 +6,16 @@ use frame_support::traits::fungible::Inspect; pub use pallet::*; pub use weights::*; +pub mod precompile; pub mod weights; /// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::NativeToken`] type. -type NativeBalanceOf = +pub type NativeBalanceOf = <::NativeToken as Inspect<::AccountId>>::Balance; /// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::EvmToken`] type. -type EvmBalanceOf = <::EvmToken as Inspect<::EvmAccountId>>::Balance; +pub type EvmBalanceOf = + <::EvmToken as Inspect<::EvmAccountId>>::Balance; // We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to // fix them at their end. @@ -27,8 +29,8 @@ pub mod pallet { sp_runtime::traits::{Convert, UniqueSaturatedInto}, storage::with_storage_layer, traits::{ - fungible::Mutate, - tokens::{Preservation, Provenance}, + fungible::Balanced, + tokens::{Fortitude, Precision, Preservation, Provenance}, }, }; use frame_system::pallet_prelude::*; @@ -50,14 +52,18 @@ pub mod pallet { type EvmAccountId: Parameter + Into; /// Native token. - type NativeToken: Inspect + Mutate; + type NativeToken: Inspect + Balanced; /// EVM token. - type EvmToken: Inspect; + type EvmToken: Inspect + Balanced; /// The converter to determine how the balance amount should be converted from native /// to EVM token. - type BalanceConverter: Convert, EvmBalanceOf>; + type BalanceConverterNativeToEvm: Convert, EvmBalanceOf>; + + /// The converter to determine how the balance amount should be converted from EVM + /// to native token. + type BalanceConverterEvmToNative: Convert, NativeBalanceOf>; /// The bridge pot native account. type BridgePotNative: Get; @@ -130,11 +136,18 @@ pub mod pallet { amount: NativeBalanceOf, preservation: Preservation, ) -> DispatchResult { - let estimated_swapped_balance = T::BalanceConverter::convert(amount); + let estimated_swapped_balance = T::BalanceConverterNativeToEvm::convert(amount); T::EvmToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; - T::NativeToken::transfer(&who, &T::BridgePotNative::get(), amount, preservation)?; + let credit = T::NativeToken::withdraw( + &who, + amount, + Precision::Exact, + preservation, + Fortitude::Polite, + )?; + let _ = T::NativeToken::resolve(&T::BridgePotNative::get(), credit); let evm_transaction_hash = Self::execute_ethereum_transfer( T::BridgePotEvm::get().into(), diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs new file mode 100644 index 000000000..89029b1d9 --- /dev/null +++ b/crates/pallet-evm-swap/src/precompile.rs @@ -0,0 +1,143 @@ +//! A precompile to swap EVM tokens with native chain tokens using `Balanced` and `Inspect` +//! fungible interfaces. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + sp_runtime::traits::Convert, + sp_std::{marker::PhantomData, prelude::*}, + traits::{ + fungible::{Balanced, Inspect}, + tokens::{Fortitude, Precision, Preservation, Provenance}, + }, +}; +use pallet_evm::{ + ExitError, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, +}; +use precompile_utils::{succeed, EvmDataWriter, EvmResult, PrecompileHandleExt}; +use sp_core::{Get, H160, H256, U256}; + +use crate::{Config, EvmBalanceOf}; + +/// 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 EVM swap interface. +pub struct EvmSwap(PhantomData<(EvmSwapT, GasCost)>) +where + EvmSwapT: Config, + EvmBalanceOf: TryFrom, + ::EvmAccountId: From, + ::AccountId: From<[u8; 32]>, + GasCost: Get; + +impl Precompile for EvmSwap +where + EvmSwapT: Config, + EvmBalanceOf: TryFrom, + ::EvmAccountId: From, + ::AccountId: From<[u8; 32]>, + 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), + } + } +} + +impl EvmSwap +where + EvmSwapT: Config, + EvmBalanceOf: TryFrom, + ::EvmAccountId: From, + ::AccountId: From<[u8; 32]>, + 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 { + address, + apparent_value: value, + .. + } = handle.context(); + + let value: EvmBalanceOf = + (*value).try_into().map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("value is out of bounds".into()), + })?; + + input + .expect_arguments(1) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("exactly one argument is expected".into()), + })?; + + let to_h256: H256 = input.read()?; + let to: [u8; 32] = to_h256.into(); + let to: EvmSwapT::AccountId = 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()), + }); + } + + // 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: EvmSwapT::EvmAccountId = (*address).into(); + + let estimated_swapped_balance = EvmSwapT::BalanceConverterEvmToNative::convert(value); + + EvmSwapT::NativeToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) + .into_result() + .map_err(|error| match error { + frame_support::sp_runtime::DispatchError::Token( + frame_support::sp_runtime::TokenError::BelowMinimum, + ) => PrecompileFailure::Error { + exit_status: ExitError::OutOfFund, + }, + _ => PrecompileFailure::Error { + exit_status: ExitError::Other("unable to deposit funds".into()), + }, + })?; + + let credit = EvmSwapT::EvmToken::withdraw( + &from, + value, + Precision::Exact, + Preservation::Expendable, + Fortitude::Polite, + ) + .unwrap(); + let _ = EvmSwapT::EvmToken::resolve(&EvmSwapT::BridgePotEvm::get(), credit); + + let credit = EvmSwapT::NativeToken::withdraw( + &EvmSwapT::BridgePotNative::get(), + estimated_swapped_balance, + Precision::Exact, + Preservation::Expendable, + Fortitude::Polite, + ) + .unwrap(); + let _ = EvmSwapT::NativeToken::resolve(&to, credit); + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } +} diff --git a/utils/checks/snapshots/features.yaml b/utils/checks/snapshots/features.yaml index 2fc97c75d..c6f230008 100644 --- a/utils/checks/snapshots/features.yaml +++ b/utils/checks/snapshots/features.yaml @@ -1984,6 +1984,10 @@ - name: pallet-evm-precompile-simple 2.0.0-dev features: - std +- name: pallet-evm-swap 0.1.0 + features: + - default + - std - name: pallet-evm-system 0.1.0 features: - default @@ -2008,10 +2012,6 @@ - name: pallet-multisig 4.0.0-dev features: - std -- name: pallet-native-to-evm-swap 0.1.0 - features: - - default - - std - name: pallet-pot 0.1.0 features: - default From f9cc338d4eff8b345e4a7f77c2d17ca32e68dd37 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 24 Feb 2025 19:46:57 +0300 Subject: [PATCH 025/102] Implement helper function to execute balanced transfer --- crates/pallet-evm-swap/src/lib.rs | 44 +++++++++++++++++++----- crates/pallet-evm-swap/src/precompile.rs | 18 ++++------ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index d38aeb1e7..3b2b0ba5f 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -2,7 +2,13 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::fungible::Inspect; +use frame_support::{ + dispatch::DispatchError, + traits::{ + fungible::{Balanced, Inspect}, + tokens::{Fortitude, Precision, Preservation, Provenance}, + }, +}; pub use pallet::*; pub use weights::*; @@ -28,10 +34,6 @@ pub mod pallet { pallet_prelude::*, sp_runtime::traits::{Convert, UniqueSaturatedInto}, storage::with_storage_layer, - traits::{ - fungible::Balanced, - tokens::{Fortitude, Precision, Preservation, Provenance}, - }, }; use frame_system::pallet_prelude::*; use pallet_evm::GasWeightMapping; @@ -140,14 +142,12 @@ pub mod pallet { T::EvmToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; - let credit = T::NativeToken::withdraw( + balanced_transfer::<_, T::NativeToken>( &who, + &T::BridgePotNative::get(), amount, - Precision::Exact, preservation, - Fortitude::Polite, )?; - let _ = T::NativeToken::resolve(&T::BridgePotNative::get(), credit); let evm_transaction_hash = Self::execute_ethereum_transfer( T::BridgePotEvm::get().into(), @@ -216,3 +216,29 @@ pub mod pallet { } } } + +/// A helper function to execute balanced transfer. +pub(crate) fn balanced_transfer + Balanced>( + from: &AccountId, + to: &AccountId, + amount: Token::Balance, + preservation: Preservation, +) -> Result<(), DispatchError> { + let credit = Token::withdraw( + from, + amount, + Precision::Exact, + preservation, + Fortitude::Polite, + )?; + + if let Err(credit) = Token::resolve(to, credit) { + // Here we undo the withdrawal to avoid having a dangling credit. + // + // Drop the result which will trigger the `OnDrop` of the credit in case of + // resolving back fails. + let _ = Token::resolve(from, credit); + } + + Ok(()) +} diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs index 89029b1d9..a0a816fa5 100644 --- a/crates/pallet-evm-swap/src/precompile.rs +++ b/crates/pallet-evm-swap/src/precompile.rs @@ -7,8 +7,8 @@ use frame_support::{ sp_runtime::traits::Convert, sp_std::{marker::PhantomData, prelude::*}, traits::{ - fungible::{Balanced, Inspect}, - tokens::{Fortitude, Precision, Preservation, Provenance}, + fungible::Inspect, + tokens::{Preservation, Provenance}, }, }; use pallet_evm::{ @@ -17,7 +17,7 @@ use pallet_evm::{ use precompile_utils::{succeed, EvmDataWriter, EvmResult, PrecompileHandleExt}; use sp_core::{Get, H160, H256, U256}; -use crate::{Config, EvmBalanceOf}; +use crate::{balanced_transfer, Config, EvmBalanceOf}; /// Possible actions for this interface. #[precompile_utils::generate_function_selector] @@ -118,25 +118,21 @@ where }, })?; - let credit = EvmSwapT::EvmToken::withdraw( + balanced_transfer::<_, EvmSwapT::EvmToken>( &from, + &EvmSwapT::BridgePotEvm::get(), value, - Precision::Exact, Preservation::Expendable, - Fortitude::Polite, ) .unwrap(); - let _ = EvmSwapT::EvmToken::resolve(&EvmSwapT::BridgePotEvm::get(), credit); - let credit = EvmSwapT::NativeToken::withdraw( + balanced_transfer::<_, EvmSwapT::NativeToken>( &EvmSwapT::BridgePotNative::get(), + &to, estimated_swapped_balance, - Precision::Exact, Preservation::Expendable, - Fortitude::Polite, ) .unwrap(); - let _ = EvmSwapT::NativeToken::resolve(&to, credit); Ok(succeed(EvmDataWriter::new().write(true).build())) } From eec85666cc34c30d107ad06894d96131e027ff4a Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 24 Feb 2025 19:48:22 +0300 Subject: [PATCH 026/102] Implement a helper function to process dispatch error --- crates/pallet-evm-swap/src/precompile.rs | 30 ++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs index a0a816fa5..eb5e39314 100644 --- a/crates/pallet-evm-swap/src/precompile.rs +++ b/crates/pallet-evm-swap/src/precompile.rs @@ -4,6 +4,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ + dispatch::DispatchError, sp_runtime::traits::Convert, sp_std::{marker::PhantomData, prelude::*}, traits::{ @@ -107,16 +108,7 @@ where EvmSwapT::NativeToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result() - .map_err(|error| match error { - frame_support::sp_runtime::DispatchError::Token( - frame_support::sp_runtime::TokenError::BelowMinimum, - ) => PrecompileFailure::Error { - exit_status: ExitError::OutOfFund, - }, - _ => PrecompileFailure::Error { - exit_status: ExitError::Other("unable to deposit funds".into()), - }, - })?; + .map_err(process_dispatch_error)?; balanced_transfer::<_, EvmSwapT::EvmToken>( &from, @@ -124,7 +116,7 @@ where value, Preservation::Expendable, ) - .unwrap(); + .map_err(process_dispatch_error)?; balanced_transfer::<_, EvmSwapT::NativeToken>( &EvmSwapT::BridgePotNative::get(), @@ -132,8 +124,22 @@ where estimated_swapped_balance, Preservation::Expendable, ) - .unwrap(); + .map_err(process_dispatch_error)?; Ok(succeed(EvmDataWriter::new().write(true).build())) } } + +/// A helper function to process dispatch related errors. +fn process_dispatch_error(error: DispatchError) -> PrecompileFailure { + match error { + DispatchError::Token(frame_support::sp_runtime::TokenError::FundsUnavailable) => { + PrecompileFailure::Error { + exit_status: ExitError::OutOfFund, + } + } + _ => PrecompileFailure::Error { + exit_status: ExitError::Other("unable to swap funds".into()), + }, + } +} From 58b4dc232017776db5881de1f2e3444cc5895a31 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 24 Feb 2025 19:50:12 +0300 Subject: [PATCH 027/102] Fix pallet docs --- crates/pallet-evm-swap/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index 3b2b0ba5f..7cba93b35 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -1,4 +1,4 @@ -//! A substrate pallet containing the Native to EVM token swap integration. +//! A substrate pallet containing the EVM swap integration. #![cfg_attr(not(feature = "std"), no_std)] From 824e740720e77f25e8f886e4edaff609929f8644 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 11:46:18 +0300 Subject: [PATCH 028/102] Implement mock testing environment with basic setup works test --- Cargo.lock | 5 + crates/pallet-evm-swap/Cargo.toml | 16 ++ crates/pallet-evm-swap/src/lib.rs | 6 + crates/pallet-evm-swap/src/mock.rs | 327 ++++++++++++++++++++++++ crates/pallet-evm-swap/src/tests/mod.rs | 24 ++ 5 files changed, 378 insertions(+) create mode 100644 crates/pallet-evm-swap/src/mock.rs create mode 100644 crates/pallet-evm-swap/src/tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index e2fee3ec6..ece05105f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6174,9 +6174,14 @@ dependencies = [ "fp-evm", "frame-support", "frame-system", + "hex-literal", "num_enum 0.7.3", + "pallet-balances", "pallet-ethereum", "pallet-evm", + "pallet-evm-balances", + "pallet-evm-system", + "pallet-timestamp", "parity-scale-codec", "precompile-utils", "scale-info", diff --git a/crates/pallet-evm-swap/Cargo.toml b/crates/pallet-evm-swap/Cargo.toml index ed4819939..11c281960 100644 --- a/crates/pallet-evm-swap/Cargo.toml +++ b/crates/pallet-evm-swap/Cargo.toml @@ -19,13 +19,23 @@ pallet-evm = { workspace = true } scale-info = { workspace = true, features = ["derive"] } sp-core = { workspace = true } +[dev-dependencies] +pallet-evm-balances = { path = "../pallet-evm-balances", features = ["default"] } +pallet-evm-system = { path = "../pallet-evm-system", features = ["default"] } + +hex-literal = { workspace = true } +pallet-balances = { workspace = true, features = ["default"] } +pallet-timestamp = { workspace = true, features = ["default"] } + [features] default = ["std"] runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", "pallet-ethereum/runtime-benchmarks", "pallet-evm/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", ] std = [ "codec/std", @@ -35,8 +45,10 @@ std = [ "frame-support/std", "frame-system/std", "num_enum/std", + "pallet-balances/std", "pallet-ethereum/std", "pallet-evm/std", + "pallet-timestamp/std", "precompile-utils/std", "scale-info/std", "sp-core/std", @@ -44,6 +56,10 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-balances/try-runtime", "pallet-ethereum/try-runtime", + "pallet-evm-balances/try-runtime", + "pallet-evm-system/try-runtime", "pallet-evm/try-runtime", + "pallet-timestamp/try-runtime", ] diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index 7cba93b35..4ffd35b39 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -15,6 +15,12 @@ pub use weights::*; pub mod precompile; pub mod weights; +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + /// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::NativeToken`] type. pub type NativeBalanceOf = <::NativeToken as Inspect<::AccountId>>::Balance; diff --git a/crates/pallet-evm-swap/src/mock.rs b/crates/pallet-evm-swap/src/mock.rs new file mode 100644 index 000000000..f53049551 --- /dev/null +++ b/crates/pallet-evm-swap/src/mock.rs @@ -0,0 +1,327 @@ +// Allow simple integer arithmetic in tests. +#![allow(clippy::arithmetic_side_effects)] + +use std::collections::BTreeMap; + +use fp_evm::{IsPrecompileResult, PrecompileHandle}; +use frame_support::{ + once_cell::sync::Lazy, + parameter_types, sp_io, + sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, Identity, IdentityLookup}, + BuildStorage, + }, + traits::{ConstU128, ConstU32, ConstU64}, + weights::Weight, +}; +use pallet_ethereum::PostLogContent as EthereumPostLogContent; +use sp_core::{Get, H160, H256, U256}; + +use crate::{self as pallet_evm_swap, precompile}; + +pub(crate) const INIT_BALANCE: u128 = 10_000_000_000_000_000; + +pub(crate) fn alice() -> AccountId { + AccountId::from(hex_literal::hex!( + "1100000000000000000000000000000000000000000000000000000000000011" + )) +} + +pub(crate) fn bob() -> AccountId { + AccountId::from(hex_literal::hex!( + "2200000000000000000000000000000000000000000000000000000000000022" + )) +} + +pub(crate) fn alice_evm() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "1100000000000000000000000000000000000011" + )) +} + +pub(crate) fn bob_evm() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "2200000000000000000000000000000000000022" + )) +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub(crate) type AccountId = frame_support::sp_runtime::AccountId32; +pub(crate) type EvmAccountId = H160; +pub(crate) type Balance = u128; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub struct Test + where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + EvmSystem: pallet_evm_system, + EvmBalances: pallet_evm_balances, + EVM: pallet_evm, + Ethereum: pallet_ethereum, + EvmSwap: pallet_evm_swap, + } +); + +impl frame_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 = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<2>; // 2 because we test the account kills via 1 balance + type AccountStore = System; + type MaxLocks = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxReserves = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; + 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 = ConstU128<1>; + type AccountStore = EvmSystem; + type DustRemoval = (); +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +pub(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 + (*GAS_PRICE, Weight::from_parts(7u64, 0)) + } +} + +parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub GasLimitPovSizeRatio: u64 = 0; + pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); + pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet; +} + +impl pallet_evm::Config for Test { + 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, + >; + type WithdrawOrigin = pallet_evm::EnsureAddressNever< + ::AccountId, + >; + type AddressMapping = pallet_evm::IdentityAddressMapping; + type Currency = EvmBalances; + 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 = (); + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type Timestamp = Timestamp; + type WeightInfo = (); +} + +parameter_types! { + pub const PostBlockAndTxnHashes: EthereumPostLogContent = EthereumPostLogContent::BlockAndTxnHashes; +} + +impl pallet_ethereum::Config for Test { + type RuntimeEvent = RuntimeEvent; + type StateRoot = pallet_ethereum::IntermediateStateRoot; + type PostLogContent = PostBlockAndTxnHashes; + type ExtraDataLength = ConstU32<30>; +} + +pub struct BridgePotNative; + +impl Get for BridgePotNative { + fn get() -> AccountId { + AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )) + } +} + +pub struct BridgePotEvm; + +impl Get for BridgePotEvm { + fn get() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )) + } +} + +impl pallet_evm_swap::Config for Test { + type RuntimeEvent = RuntimeEvent; + type EvmAccountId = EvmAccountId; + type NativeToken = Balances; + type EvmToken = EvmBalances; + type BalanceConverterNativeToEvm = Identity; + type BalanceConverterEvmToNative = Identity; + type BridgePotNative = BridgePotNative; + type BridgePotEvm = BridgePotEvm; + type WeightInfo = (); +} + +type EvmSwapPrecompile = precompile::EvmSwap>; + +/// 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(EvmSwapPrecompile::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, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == *PRECOMPILE_ADDRESS, + extra_cost: 0, + } + } +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + // Build genesis. + let config = GenesisConfig { + balances: BalancesConfig { + balances: vec![ + (BridgePotNative::get(), INIT_BALANCE), + (alice(), INIT_BALANCE), + (bob(), INIT_BALANCE), + ], + }, + 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(BridgePotEvm::get(), init_genesis_account.clone()); + map.insert(alice_evm(), init_genesis_account.clone()); + map.insert(bob_evm(), init_genesis_account.clone()); + map + }, + }, + ..Default::default() + }; + let storage = config.build_storage().unwrap(); + + // Make test externalities from the storage. + storage.into() +} + +pub fn runtime_lock() -> std::sync::MutexGuard<'static, ()> { + static MOCK_RUNTIME_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + // Ignore the poisoning for the tests that panic. + // We only care about concurrency here, not about the poisoning. + match MOCK_RUNTIME_MUTEX.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + } +} + +pub trait TestExternalitiesExt { + fn execute_with_ext(&mut self, execute: E) -> R + where + E: for<'e> FnOnce(&'e ()) -> R; +} + +impl TestExternalitiesExt for frame_support::sp_io::TestExternalities { + fn execute_with_ext(&mut self, execute: E) -> R + where + E: for<'e> FnOnce(&'e ()) -> R, + { + let guard = runtime_lock(); + let result = self.execute_with(|| execute(&guard)); + drop(guard); + result + } +} diff --git a/crates/pallet-evm-swap/src/tests/mod.rs b/crates/pallet-evm-swap/src/tests/mod.rs new file mode 100644 index 000000000..8f16b95f2 --- /dev/null +++ b/crates/pallet-evm-swap/src/tests/mod.rs @@ -0,0 +1,24 @@ +use sp_core::Get; + +use crate::{mock::*, *}; + +/// This test verifies that basic genesis setup works in the happy path. +#[test] +fn basic_setup_works() { + new_test_ext().execute_with_ext(|_| { + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + INIT_BALANCE + ); + assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(Balances::total_balance(&bob()), INIT_BALANCE); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + INIT_BALANCE + ); + assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(EvmBalances::total_balance(&bob_evm()), INIT_BALANCE); + + assert_eq!(Balances::total_issuance(), EvmBalances::total_issuance()); + }); +} From aa7dd746e4ce805b4adb99ac2c1db9fac123c4d9 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 12:10:53 +0300 Subject: [PATCH 029/102] Introduce a helper function to prepare simple ethereum transfer transaction --- crates/pallet-evm-swap/src/lib.rs | 52 ++++++++++++++++++------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index 4ffd35b39..1041815ef 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -10,6 +10,7 @@ use frame_support::{ }, }; pub use pallet::*; +use sp_core::{Get, H160, U256}; pub use weights::*; pub mod precompile; @@ -43,7 +44,7 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use pallet_evm::GasWeightMapping; - use sp_core::{H160, H256, U256}; + use sp_core::H256; use super::*; @@ -179,25 +180,8 @@ pub mod pallet { target_address: H160, balance: u128, ) -> Result { - let gas_simple_transfer_call = ::config().gas_transaction_call; - - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_address) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: gas_simple_transfer_call.into(), - action: ethereum::TransactionAction::Call(target_address), - value: U256::from(balance), - input: Default::default(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - + let transaction = + ethereum_transfer_transaction::(source_address, target_address, balance); let transaction_hash = transaction.hash(); let post_info = @@ -208,8 +192,8 @@ pub mod pallet { post_info == PostDispatchInfo { actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - gas_simple_transfer_call.unique_saturated_into(), + T::GasWeightMapping::gas_to_weight( + T::config().gas_transaction_call.unique_saturated_into(), true, ) ), @@ -223,6 +207,30 @@ pub mod pallet { } } +/// A helper function to prepare simple ethereum transfer transaction. +pub(crate) fn ethereum_transfer_transaction( + source_address: H160, + target_address: H160, + balance: u128, +) -> pallet_ethereum::Transaction { + pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: T::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&source_address) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: T::config().gas_transaction_call.into(), + action: ethereum::TransactionAction::Call(target_address), + value: U256::from(balance), + input: Default::default(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }) +} + /// A helper function to execute balanced transfer. pub(crate) fn balanced_transfer + Balanced>( from: &AccountId, From b97d64643783e36027ab1a7c66028c992d5106a5 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 12:24:13 +0300 Subject: [PATCH 030/102] Add basic swap works for native to evm case --- .../src/tests/evm_to_native.rs | 1 + crates/pallet-evm-swap/src/tests/mod.rs | 3 + .../src/tests/native_to_evm.rs | 70 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 crates/pallet-evm-swap/src/tests/evm_to_native.rs create mode 100644 crates/pallet-evm-swap/src/tests/native_to_evm.rs diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -0,0 +1 @@ + diff --git a/crates/pallet-evm-swap/src/tests/mod.rs b/crates/pallet-evm-swap/src/tests/mod.rs index 8f16b95f2..5de17fa7e 100644 --- a/crates/pallet-evm-swap/src/tests/mod.rs +++ b/crates/pallet-evm-swap/src/tests/mod.rs @@ -2,6 +2,9 @@ use sp_core::Get; use crate::{mock::*, *}; +mod evm_to_native; +mod native_to_evm; + /// This test verifies that basic genesis setup works in the happy path. #[test] fn basic_setup_works() { diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs new file mode 100644 index 000000000..eb19ecd16 --- /dev/null +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -0,0 +1,70 @@ +use fp_evm::{ExitReason, ExitSucceed}; +use frame_support::assert_ok; +use sp_core::Get; + +use crate::{mock::*, *}; + +/// This test verifies that swap call works as expected in case origin left balances amount +/// is greater or equal than existential deposit. +#[test] +fn swap_works() { + new_test_ext().execute_with_ext(|_| { + let swap_evm_address = EvmAccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000077" + )); + let swap_balance = 100; + + // Check test preconditions. + assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_balance(&swap_evm_address), 0); + + // We should remember expected evm transaction hash before execution as nonce is increased + // after the execution. + let expected_evm_transaction_hash = ethereum_transfer_transaction::( + BridgePotEvm::get(), + swap_evm_address, + swap_balance, + ) + .hash(); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_ok!(EvmSwap::swap( + RuntimeOrigin::signed(alice()), + swap_evm_address, + swap_balance + )); + + // Assert state changes. + assert_eq!( + Balances::total_balance(&alice()), + INIT_BALANCE - swap_balance + ); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + INIT_BALANCE + swap_balance + ); + assert_eq!(EvmBalances::total_balance(&swap_evm_address), swap_balance); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + INIT_BALANCE - swap_balance + ); + assert_eq!(Balances::total_issuance(), EvmBalances::total_issuance()); + System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { + from: alice(), + withdrawed_amount: swap_balance, + to: swap_evm_address, + deposited_amount: swap_balance, + evm_transaction_hash: expected_evm_transaction_hash, + })); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: BridgePotEvm::get(), + to: swap_evm_address, + transaction_hash: expected_evm_transaction_hash, + exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), + extra_data: vec![], + })); + }); +} From 7be9fc30bf2248ddf1c027d4cca584773d9ac357 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 13:05:34 +0300 Subject: [PATCH 031/102] Add evm to native basic swap works test --- crates/pallet-evm-swap/src/mock.rs | 3 - .../src/tests/evm_to_native.rs | 76 +++++++++++++++++++ crates/pallet-evm-swap/src/tests/mod.rs | 5 +- .../src/tests/native_to_evm.rs | 17 ++--- 4 files changed, 87 insertions(+), 14 deletions(-) diff --git a/crates/pallet-evm-swap/src/mock.rs b/crates/pallet-evm-swap/src/mock.rs index f53049551..0b6e616b1 100644 --- a/crates/pallet-evm-swap/src/mock.rs +++ b/crates/pallet-evm-swap/src/mock.rs @@ -1,6 +1,3 @@ -// Allow simple integer arithmetic in tests. -#![allow(clippy::arithmetic_side_effects)] - use std::collections::BTreeMap; use fp_evm::{IsPrecompileResult, PrecompileHandle}; diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 8b1378917..955623dcc 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -1 +1,77 @@ +use pallet_evm::{Config, Runner}; +use precompile_utils::EvmDataWriter; +use sp_core::H256; +use crate::{mock::*, *}; + +/// A test 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 swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + let swap_balance = 100; + + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + // Check test preconditions. + assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(Balances::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let input = EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(swap_native_account.as_ref())) + .build(); + + // Invoke the function under test. + let execinfo = ::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, + None, + None, + Test::config(), + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) + ); + assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); + assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice_evm()), + INIT_BALANCE - swap_balance - expected_fee + ); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + INIT_BALANCE + swap_balance + expected_fee, + ); + assert_eq!(Balances::total_balance(&swap_native_account), swap_balance); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + INIT_BALANCE - swap_balance - expected_fee + ); + }); +} diff --git a/crates/pallet-evm-swap/src/tests/mod.rs b/crates/pallet-evm-swap/src/tests/mod.rs index 5de17fa7e..333c9cecc 100644 --- a/crates/pallet-evm-swap/src/tests/mod.rs +++ b/crates/pallet-evm-swap/src/tests/mod.rs @@ -1,3 +1,6 @@ +// Allow simple integer arithmetic in tests. +#![allow(clippy::arithmetic_side_effects)] + use sp_core::Get; use crate::{mock::*, *}; @@ -21,7 +24,5 @@ fn basic_setup_works() { ); assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); assert_eq!(EvmBalances::total_balance(&bob_evm()), INIT_BALANCE); - - assert_eq!(Balances::total_issuance(), EvmBalances::total_issuance()); }); } diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index eb19ecd16..a981cfa49 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -4,25 +4,24 @@ use sp_core::Get; use crate::{mock::*, *}; -/// This test verifies that swap call works as expected in case origin left balances amount -/// is greater or equal than existential deposit. +/// This test verifies that the swap call works in the happy path. #[test] fn swap_works() { new_test_ext().execute_with_ext(|_| { - let swap_evm_address = EvmAccountId::from(hex_literal::hex!( + let swap_evm_account = EvmAccountId::from(hex_literal::hex!( "7700000000000000000000000000000000000077" )); let swap_balance = 100; // Check test preconditions. assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_balance(&swap_evm_address), 0); + assert_eq!(EvmBalances::total_balance(&swap_evm_account), 0); // We should remember expected evm transaction hash before execution as nonce is increased // after the execution. let expected_evm_transaction_hash = ethereum_transfer_transaction::( BridgePotEvm::get(), - swap_evm_address, + swap_evm_account, swap_balance, ) .hash(); @@ -33,7 +32,7 @@ fn swap_works() { // Invoke the function under test. assert_ok!(EvmSwap::swap( RuntimeOrigin::signed(alice()), - swap_evm_address, + swap_evm_account, swap_balance )); @@ -46,7 +45,7 @@ fn swap_works() { Balances::total_balance(&BridgePotNative::get()), INIT_BALANCE + swap_balance ); - assert_eq!(EvmBalances::total_balance(&swap_evm_address), swap_balance); + assert_eq!(EvmBalances::total_balance(&swap_evm_account), swap_balance); assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), INIT_BALANCE - swap_balance @@ -55,13 +54,13 @@ fn swap_works() { System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { from: alice(), withdrawed_amount: swap_balance, - to: swap_evm_address, + to: swap_evm_account, deposited_amount: swap_balance, evm_transaction_hash: expected_evm_transaction_hash, })); System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { from: BridgePotEvm::get(), - to: swap_evm_address, + to: swap_evm_account, transaction_hash: expected_evm_transaction_hash, exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), extra_data: vec![], From ea1acf69138ad4b1d9f4c6a8c1bf4f2ce6a86c73 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 13:11:57 +0300 Subject: [PATCH 032/102] Add logs builder at precompile --- crates/pallet-evm-swap/src/precompile.rs | 19 ++++++++++++++++++- .../src/tests/evm_to_native.rs | 15 ++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs index eb5e39314..3a9d209b9 100644 --- a/crates/pallet-evm-swap/src/precompile.rs +++ b/crates/pallet-evm-swap/src/precompile.rs @@ -15,11 +15,16 @@ use frame_support::{ use pallet_evm::{ ExitError, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, }; -use precompile_utils::{succeed, EvmDataWriter, EvmResult, PrecompileHandleExt}; +use precompile_utils::{ + keccak256, succeed, EvmDataWriter, EvmResult, LogExt, LogsBuilder, PrecompileHandleExt, +}; use sp_core::{Get, H160, H256, U256}; use crate::{balanced_transfer, Config, EvmBalanceOf}; +/// Solidity selector of the Swap log, which is the Keccak of the Log signature. +pub const SELECTOR_LOG_SWAP: [u8; 32] = keccak256!("Swap(address,bytes32,uint256)"); + /// Possible actions for this interface. #[precompile_utils::generate_function_selector] #[derive(Debug, PartialEq)] @@ -78,6 +83,7 @@ where .. } = handle.context(); + let value_u256 = *value; let value: EvmBalanceOf = (*value).try_into().map_err(|_| PrecompileFailure::Error { exit_status: ExitError::Other("value is out of bounds".into()), @@ -126,6 +132,17 @@ where ) .map_err(process_dispatch_error)?; + let logs_builder = LogsBuilder::new(handle.context().address); + + logs_builder + .log3( + SELECTOR_LOG_SWAP, + handle.context().caller, + to_h256, + EvmDataWriter::new().write(value_u256).build(), + ) + .record(handle)?; + Ok(succeed(EvmDataWriter::new().write(true).build())) } } diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 955623dcc..bf9e10eb4 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -1,5 +1,5 @@ use pallet_evm::{Config, Runner}; -use precompile_utils::EvmDataWriter; +use precompile_utils::{EvmDataWriter, LogsBuilder}; use sp_core::H256; use crate::{mock::*, *}; @@ -58,6 +58,15 @@ fn swap_works() { ); assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); + assert_eq!( + execinfo.logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + precompile::SELECTOR_LOG_SWAP, + alice_evm(), + H256::from(swap_native_account.as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] + ); // Assert state changes. assert_eq!( @@ -66,12 +75,12 @@ fn swap_works() { ); assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), - INIT_BALANCE + swap_balance + expected_fee, + INIT_BALANCE + swap_balance, ); assert_eq!(Balances::total_balance(&swap_native_account), swap_balance); assert_eq!( Balances::total_balance(&BridgePotNative::get()), - INIT_BALANCE - swap_balance - expected_fee + INIT_BALANCE - swap_balance ); }); } From 44843f940ac626c9e90c752e0f52e8ae7f29aebb Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 13:12:47 +0300 Subject: [PATCH 033/102] Leave just alice accounts --- crates/pallet-evm-swap/src/mock.rs | 14 -------------- crates/pallet-evm-swap/src/tests/mod.rs | 2 -- 2 files changed, 16 deletions(-) diff --git a/crates/pallet-evm-swap/src/mock.rs b/crates/pallet-evm-swap/src/mock.rs index 0b6e616b1..579f84f33 100644 --- a/crates/pallet-evm-swap/src/mock.rs +++ b/crates/pallet-evm-swap/src/mock.rs @@ -25,24 +25,12 @@ pub(crate) fn alice() -> AccountId { )) } -pub(crate) fn bob() -> AccountId { - AccountId::from(hex_literal::hex!( - "2200000000000000000000000000000000000000000000000000000000000022" - )) -} - pub(crate) fn alice_evm() -> EvmAccountId { EvmAccountId::from(hex_literal::hex!( "1100000000000000000000000000000000000011" )) } -pub(crate) fn bob_evm() -> EvmAccountId { - EvmAccountId::from(hex_literal::hex!( - "2200000000000000000000000000000000000022" - )) -} - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -268,7 +256,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { balances: vec![ (BridgePotNative::get(), INIT_BALANCE), (alice(), INIT_BALANCE), - (bob(), INIT_BALANCE), ], }, evm: EVMConfig { @@ -282,7 +269,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { }; map.insert(BridgePotEvm::get(), init_genesis_account.clone()); map.insert(alice_evm(), init_genesis_account.clone()); - map.insert(bob_evm(), init_genesis_account.clone()); map }, }, diff --git a/crates/pallet-evm-swap/src/tests/mod.rs b/crates/pallet-evm-swap/src/tests/mod.rs index 333c9cecc..c179e5771 100644 --- a/crates/pallet-evm-swap/src/tests/mod.rs +++ b/crates/pallet-evm-swap/src/tests/mod.rs @@ -17,12 +17,10 @@ fn basic_setup_works() { INIT_BALANCE ); assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(Balances::total_balance(&bob()), INIT_BALANCE); assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), INIT_BALANCE ); assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(EvmBalances::total_balance(&bob_evm()), INIT_BALANCE); }); } From 27d3958b84974cd0027094323a30a7ceb0bc0f57 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 14:23:44 +0300 Subject: [PATCH 034/102] Add more ethereum checks for evm to native swap tests --- .../src/tests/evm_to_native.rs | 108 +++++++++++------- 1 file changed, 66 insertions(+), 42 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index bf9e10eb4..fb25e1b6a 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -1,15 +1,15 @@ -use pallet_evm::{Config, Runner}; +use fp_ethereum::ValidatedTransaction; +use fp_evm::{ExitReason, ExitSucceed}; +use frame_support::{ + dispatch::{Pays, PostDispatchInfo}, + traits::OnFinalize, +}; +use pallet_evm::GasWeightMapping; use precompile_utils::{EvmDataWriter, LogsBuilder}; use sp_core::H256; use crate::{mock::*, *}; -/// A test 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() { @@ -20,7 +20,6 @@ fn swap_works() { let swap_balance = 100; let expected_gas_usage: u64 = 21216 + 200; - let expected_fee: Balance = gas_to_fee(expected_gas_usage); // Check test preconditions. assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); @@ -30,48 +29,47 @@ fn swap_works() { System::set_block_number(1); // Prepare EVM call. - let input = EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(swap_native_account.as_ref())) - .build(); + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(swap_native_account.as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); // Invoke the function under test. - let execinfo = ::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, - None, - None, - Test::config(), - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + assert_eq!( - execinfo.logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile::SELECTOR_LOG_SWAP, - alice_evm(), - H256::from(swap_native_account.as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, ); // Assert state changes. assert_eq!( EvmBalances::total_balance(&alice_evm()), - INIT_BALANCE - swap_balance - expected_fee + INIT_BALANCE - swap_balance ); assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), @@ -82,5 +80,31 @@ fn swap_works() { Balances::total_balance(&BridgePotNative::get()), INIT_BALANCE - swap_balance ); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Succeed(ExitSucceed::Returned), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!( + transaction_logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + precompile::SELECTOR_LOG_SWAP, + alice_evm(), + H256::from(swap_native_account.as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] + ); }); } From 4cf46009cbcd2dcc516ad7fbefc1b23c64c50523 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 16:59:14 +0300 Subject: [PATCH 035/102] Implement swap_works_almost_full_balance test for evm to native flow --- crates/pallet-evm-swap/src/mock.rs | 30 +++-- .../src/tests/evm_to_native.rs | 103 +++++++++++++++++- crates/pallet-evm-swap/src/tests/mod.rs | 4 +- .../src/tests/native_to_evm.rs | 4 +- 4 files changed, 127 insertions(+), 14 deletions(-) diff --git a/crates/pallet-evm-swap/src/mock.rs b/crates/pallet-evm-swap/src/mock.rs index 579f84f33..74b43c700 100644 --- a/crates/pallet-evm-swap/src/mock.rs +++ b/crates/pallet-evm-swap/src/mock.rs @@ -18,6 +18,8 @@ use sp_core::{Get, H160, H256, U256}; use crate::{self as pallet_evm_swap, precompile}; pub(crate) const INIT_BALANCE: u128 = 10_000_000_000_000_000; +// Add some tokens to test swap with full balance. +pub(crate) const BRIDGE_INIT_BALANCE: u128 = INIT_BALANCE + 100; pub(crate) fn alice() -> AccountId { AccountId::from(hex_literal::hex!( @@ -254,21 +256,31 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let config = GenesisConfig { balances: BalancesConfig { balances: vec![ - (BridgePotNative::get(), INIT_BALANCE), + (BridgePotNative::get(), BRIDGE_INIT_BALANCE), (alice(), INIT_BALANCE), ], }, 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(BridgePotEvm::get(), init_genesis_account.clone()); - map.insert(alice_evm(), init_genesis_account.clone()); + map.insert( + BridgePotEvm::get(), + fp_evm::GenesisAccount { + balance: BRIDGE_INIT_BALANCE.into(), + code: Default::default(), + nonce: Default::default(), + storage: Default::default(), + }, + ); + map.insert( + alice_evm(), + fp_evm::GenesisAccount { + balance: INIT_BALANCE.into(), + code: Default::default(), + nonce: Default::default(), + storage: Default::default(), + }, + ); map }, }, diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index fb25e1b6a..a8feec8a7 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -73,13 +73,114 @@ fn swap_works() { ); assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), - INIT_BALANCE + swap_balance, + BRIDGE_INIT_BALANCE + swap_balance, ); assert_eq!(Balances::total_balance(&swap_native_account), swap_balance); assert_eq!( Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE - swap_balance + ); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Succeed(ExitSucceed::Returned), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!( + transaction_logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + precompile::SELECTOR_LOG_SWAP, + alice_evm(), + H256::from(swap_native_account.as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] + ); + }); +} + +/// 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 swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + + let expected_gas_usage: u64 = 21216 + 200; + let swap_balance = INIT_BALANCE - 1; + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(swap_native_account.as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!( + EvmBalances::total_balance(&alice_evm()), INIT_BALANCE - swap_balance ); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE + swap_balance, + ); + assert_eq!(Balances::total_balance(&swap_native_account), swap_balance); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE - swap_balance + ); System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { from: alice_evm(), to: *PRECOMPILE_ADDRESS, diff --git a/crates/pallet-evm-swap/src/tests/mod.rs b/crates/pallet-evm-swap/src/tests/mod.rs index c179e5771..d954f7487 100644 --- a/crates/pallet-evm-swap/src/tests/mod.rs +++ b/crates/pallet-evm-swap/src/tests/mod.rs @@ -14,12 +14,12 @@ fn basic_setup_works() { new_test_ext().execute_with_ext(|_| { assert_eq!( Balances::total_balance(&BridgePotNative::get()), - INIT_BALANCE + BRIDGE_INIT_BALANCE ); assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), - INIT_BALANCE + BRIDGE_INIT_BALANCE ); assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); }); diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index a981cfa49..2bf0c3d28 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -43,12 +43,12 @@ fn swap_works() { ); assert_eq!( Balances::total_balance(&BridgePotNative::get()), - INIT_BALANCE + swap_balance + BRIDGE_INIT_BALANCE + swap_balance ); assert_eq!(EvmBalances::total_balance(&swap_evm_account), swap_balance); assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), - INIT_BALANCE - swap_balance + BRIDGE_INIT_BALANCE - swap_balance ); assert_eq!(Balances::total_issuance(), EvmBalances::total_issuance()); System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { From 088e8a9b7882dc62bec0f733f53a1564959d0b07 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 17:13:08 +0300 Subject: [PATCH 036/102] Add swap_fail_no_funds test for evm to native flow --- .../src/tests/evm_to_native.rs | 93 ++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index a8feec8a7..4df998bd4 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -1,5 +1,5 @@ use fp_ethereum::ValidatedTransaction; -use fp_evm::{ExitReason, ExitSucceed}; +use fp_evm::{ExitError, ExitReason, ExitSucceed}; use frame_support::{ dispatch::{Pays, PostDispatchInfo}, traits::OnFinalize, @@ -118,9 +118,9 @@ fn swap_works_almost_full_balance() { let swap_native_account = AccountId::from(hex_literal::hex!( "7700000000000000000000000000000000000000000000000000000000000077" )); + let swap_balance = INIT_BALANCE - 1; let expected_gas_usage: u64 = 21216 + 200; - let swap_balance = INIT_BALANCE - 1; // Check test preconditions. assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); @@ -209,3 +209,92 @@ fn swap_works_almost_full_balance() { ); }); } + +/// This test verifies that the swap precompile call behaves as expected when called without +/// the sufficient balance. +#[test] +fn swap_fail_no_funds() { + new_test_ext().execute_with_ext(|_| { + let swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + let swap_balance = INIT_BALANCE + 1; // more than we have + + let expected_gas_usage: u64 = 21216; // precompile gas cost is not included + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(swap_native_account.as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&swap_native_account), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Error(ExitError::OutOfFund), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!(transaction_logs, vec![]); + }); +} From c657794ca36b2c58c0b1fa630d18ea4ce7cb43aa Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 17:33:40 +0300 Subject: [PATCH 037/102] Add swap_fail_below_ed test for evm to native flow --- .../src/tests/evm_to_native.rs | 93 +++++++++++++++++++ crates/pallet-evm-swap/src/tests/mod.rs | 1 + .../src/tests/native_to_evm.rs | 2 +- 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 4df998bd4..42841ca38 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -80,6 +80,7 @@ fn swap_works() { Balances::total_balance(&BridgePotNative::get()), BRIDGE_INIT_BALANCE - swap_balance ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { from: alice_evm(), to: *PRECOMPILE_ADDRESS, @@ -181,6 +182,7 @@ fn swap_works_almost_full_balance() { Balances::total_balance(&BridgePotNative::get()), BRIDGE_INIT_BALANCE - swap_balance ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { from: alice_evm(), to: *PRECOMPILE_ADDRESS, @@ -278,6 +280,7 @@ fn swap_fail_no_funds() { Balances::total_balance(&BridgePotNative::get()), BRIDGE_INIT_BALANCE, ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { from: alice_evm(), to: *PRECOMPILE_ADDRESS, @@ -298,3 +301,93 @@ fn swap_fail_no_funds() { assert_eq!(transaction_logs, vec![]); }); } + +/// This test verifies that the swap precompile call behaves as expected when +/// estimated swapped balance less or equal than target currency existential deposit. +#[test] +fn swap_fail_below_ed() { + new_test_ext().execute_with_ext(|_| { + let swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + let swap_balance = 1; + + let expected_gas_usage: u64 = 50_000; // all fee will be consumed + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(swap_native_account.as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&swap_native_account), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Error(ExitError::Other("unable to swap funds".into())), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!(transaction_logs, vec![]); + }); +} diff --git a/crates/pallet-evm-swap/src/tests/mod.rs b/crates/pallet-evm-swap/src/tests/mod.rs index d954f7487..835bf8279 100644 --- a/crates/pallet-evm-swap/src/tests/mod.rs +++ b/crates/pallet-evm-swap/src/tests/mod.rs @@ -22,5 +22,6 @@ fn basic_setup_works() { BRIDGE_INIT_BALANCE ); assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); }); } diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 2bf0c3d28..88ca398cb 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -50,7 +50,7 @@ fn swap_works() { EvmBalances::total_balance(&BridgePotEvm::get()), BRIDGE_INIT_BALANCE - swap_balance ); - assert_eq!(Balances::total_issuance(), EvmBalances::total_issuance()); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { from: alice(), withdrawed_amount: swap_balance, From ea22f0f69c8c54e8e9fe2f78a21cb9c715f7aadc Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 17:46:03 +0300 Subject: [PATCH 038/102] Add swap_works_full_balance test for evm to native flow --- .../src/tests/evm_to_native.rs | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 42841ca38..1c6d642b6 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -212,6 +212,103 @@ fn swap_works_almost_full_balance() { }); } +/// This test verifies that the swap precompile call works when we transfer the full account balance. +#[test] +fn swap_works_full_balance() { + new_test_ext().execute_with_ext(|_| { + let swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + let swap_balance = INIT_BALANCE; + + let expected_gas_usage: u64 = 21216 + 200; + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(swap_native_account.as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!(EvmBalances::total_balance(&alice_evm()), 0); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE + swap_balance, + ); + assert_eq!(Balances::total_balance(&swap_native_account), swap_balance); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE - swap_balance + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Succeed(ExitSucceed::Returned), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!( + transaction_logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + precompile::SELECTOR_LOG_SWAP, + alice_evm(), + H256::from(swap_native_account.as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] + ); + }); +} + /// This test verifies that the swap precompile call behaves as expected when called without /// the sufficient balance. #[test] From e799577c521645f7a1acaf64bbd116f2bfcbd81c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 17:50:29 +0300 Subject: [PATCH 039/102] Add swap_fail_bad_selector test --- .../src/tests/evm_to_native.rs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 1c6d642b6..d4966b1ee 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -488,3 +488,93 @@ fn swap_fail_below_ed() { assert_eq!(transaction_logs, vec![]); }); } + +/// This test verifies that the swap precompile call behaves as expected when a bad selector is +/// passed. +#[test] +fn swap_fail_bad_selector() { + new_test_ext().execute_with_ext(|_| { + let swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + let swap_balance = 1; + + let expected_gas_usage: u64 = 50_000; // all fee will be consumed + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(111_u32) + .write(H256::from(swap_native_account.as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&swap_native_account), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Error(ExitError::Other("invalid function selector".into())), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!(transaction_logs, vec![]); + }); +} From ac7c943715010e78a538839e16d75eb593351231 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 18:25:50 +0300 Subject: [PATCH 040/102] Add swap_fail_value_overflow test for evm to native flow --- .../src/tests/evm_to_native.rs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index d4966b1ee..a7be2fd09 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -578,3 +578,85 @@ fn swap_fail_bad_selector() { assert_eq!(transaction_logs, vec![]); }); } + +/// 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. +#[test] +fn swap_fail_value_overflow() { + new_test_ext().execute_with_ext(|_| { + let swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + + let expected_gas_usage: u64 = 21216; // precompile gas cost is not included + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::MAX, + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(swap_native_account.as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&swap_native_account), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Error(ExitError::OutOfFund), + extra_data: vec![], + })); + }); +} From 070d29c58d54e68cf5cb05f571a3d30e83681136 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 18:28:59 +0300 Subject: [PATCH 041/102] Add swap_fail_no_arguments test for evm to native flow --- .../src/tests/evm_to_native.rs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index a7be2fd09..f3943de12 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -660,3 +660,93 @@ fn swap_fail_value_overflow() { })); }); } + +/// This test verifies that the swap precompile call behaves as expected when the call has no +/// arguments. +#[test] +fn swap_fail_no_arguments() { + new_test_ext().execute_with_ext(|_| { + let swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + let swap_balance = 1; + + let expected_gas_usage: u64 = 50_000; // all fee will be consumed + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap).build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&swap_native_account), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Error(ExitError::Other( + "exactly one argument is expected".into(), + )), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!(transaction_logs, vec![]); + }); +} From 8c1b4a5470189ce5ac5085d80a69fc360dcce418 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 18:31:19 +0300 Subject: [PATCH 042/102] Add swap_fail_short_argument for evm to native flow --- .../src/tests/evm_to_native.rs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index f3943de12..4bd67951b 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -750,3 +750,96 @@ fn swap_fail_no_arguments() { assert_eq!(transaction_logs, vec![]); }); } + +/// This test verifies that the swap precompile call behaves as expected when the call has +/// an incomplete argument. +#[test] +fn swap_fail_short_argument() { + new_test_ext().execute_with_ext(|_| { + let swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + let swap_balance = 1; + + let expected_gas_usage: u64 = 50_000; // all fee will be consumed + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let mut input = EvmDataWriter::new_with_selector(precompile::Action::Swap).build(); + input.extend_from_slice(&hex_literal::hex!("1000")); // bad input + + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input, + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&swap_native_account), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Error(ExitError::Other( + "exactly one argument is expected".into(), + )), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!(transaction_logs, vec![]); + }); +} From 7e0749a4de2c3b325b39cc6f50ff323ce4493167 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 18:33:46 +0300 Subject: [PATCH 043/102] Add swap_fail_trailing_junk test for evm to native flow --- .../src/tests/evm_to_native.rs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 4bd67951b..fae6fdca8 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -843,3 +843,96 @@ fn swap_fail_short_argument() { assert_eq!(transaction_logs, vec![]); }); } + +/// This test verifies that the swap precompile call behaves as expected when the call has +/// extra data after the end of the first argument. +#[test] +fn swap_fail_trailing_junk() { + new_test_ext().execute_with_ext(|_| { + let swap_native_account = AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )); + let swap_balance = 1; + + let expected_gas_usage: u64 = 50_000; // all fee will be consumed + + // Check test preconditions. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!(::total_balance(&swap_native_account), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let mut input = EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(swap_native_account.as_ref())) + .build(); + input.extend_from_slice(&hex_literal::hex!("1000")); // bad input + + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input, + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = + pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&swap_native_account), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: alice_evm(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Error(ExitError::Other("junk at the end of input".into())), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!(transaction_logs, vec![]); + }); +} From 983b43636618bc862222867fcdd7cb10aafb7d9f Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 18:57:57 +0300 Subject: [PATCH 044/102] Some refactoring of evm_to_native tests --- .../src/tests/evm_to_native.rs | 934 +++++------------- 1 file changed, 244 insertions(+), 690 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index fae6fdca8..2388f400a 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -8,105 +8,130 @@ use pallet_evm::GasWeightMapping; use precompile_utils::{EvmDataWriter, LogsBuilder}; use sp_core::H256; -use crate::{mock::*, *}; +use crate::{ + mock::{alice_evm as source_swap_evm_account, *}, + *, +}; + +/// Returns target swap native account used in tests. +fn target_swap_native_account() -> AccountId { + AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )) +} + +/// A helper function to run succeeded test and assert state changes. +fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) { + // Check test preconditions. + assert_eq!( + EvmBalances::total_balance(&source_swap_evm_account()), + INIT_BALANCE + ); + assert_eq!(Balances::total_balance(&target_swap_native_account()), 0); + + // Set block number to enable events. + System::set_block_number(1); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = pallet_ethereum::ValidatedTransaction::::apply( + source_swap_evm_account(), + transaction, + ) + .unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Assert state changes. + + // Verify that source swap evm account balance has been decreased by swap value. + assert_eq!( + ::total_balance(&source_swap_evm_account()), + INIT_BALANCE - swap_balance, + ); + // Verify that bridge pot evm balance has been increased by swap value. + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE + swap_balance, + ); + // Verify that target swap native balance has been increased by swap value. + assert_eq!( + ::total_balance(&target_swap_native_account()), + swap_balance + ); + // Verify that bridge pot native balance has been decreased by swap value. + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE - swap_balance, + ); + // Verify that precompile balance remains the same. + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + // Verify that we have a corresponding ethereum event about succeeded transaction. + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: source_swap_evm_account(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason: ExitReason::Succeed(ExitSucceed::Returned), + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + // Verify that we have expected transaction log corresponding to swap execution. + let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() + .unwrap() + .first() + .cloned() + .unwrap() + .logs; + assert_eq!( + transaction_logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + precompile::SELECTOR_LOG_SWAP, + source_swap_evm_account(), + H256::from(target_swap_native_account().as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] + ); +} /// This test verifies that the swap precompile call works in the happy path. #[test] fn swap_works() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); - let swap_balance = 100; - - let expected_gas_usage: u64 = 21216 + 200; - - // Check test preconditions. - assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(Balances::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(swap_native_account.as_ref())) - .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice_evm()), - INIT_BALANCE - swap_balance - ); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE + swap_balance, - ); - assert_eq!(Balances::total_balance(&swap_native_account), swap_balance); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE - swap_balance - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Succeed(ExitSucceed::Returned), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!( - transaction_logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile::SELECTOR_LOG_SWAP, - alice_evm(), - H256::from(swap_native_account.as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); + run_succeeded_test_and_assert(100, 21216 + 200); }); } @@ -116,99 +141,7 @@ fn swap_works() { #[test] fn swap_works_almost_full_balance() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); - let swap_balance = INIT_BALANCE - 1; - - let expected_gas_usage: u64 = 21216 + 200; - - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(swap_native_account.as_ref())) - .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - - // Assert state changes. - assert_eq!( - EvmBalances::total_balance(&alice_evm()), - INIT_BALANCE - swap_balance - ); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE + swap_balance, - ); - assert_eq!(Balances::total_balance(&swap_native_account), swap_balance); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE - swap_balance - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Succeed(ExitSucceed::Returned), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!( - transaction_logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile::SELECTOR_LOG_SWAP, - alice_evm(), - H256::from(swap_native_account.as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); + run_succeeded_test_and_assert(INIT_BALANCE - 1, 21216 + 200); }); } @@ -216,97 +149,89 @@ fn swap_works_almost_full_balance() { #[test] fn swap_works_full_balance() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); - let swap_balance = INIT_BALANCE; - - let expected_gas_usage: u64 = 21216 + 200; - - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(swap_native_account.as_ref())) - .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); + run_succeeded_test_and_assert(INIT_BALANCE, 21216 + 200); + }); +} - // Assert state changes. - assert_eq!(EvmBalances::total_balance(&alice_evm()), 0); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE + swap_balance, - ); - assert_eq!(Balances::total_balance(&swap_native_account), swap_balance); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE - swap_balance - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Succeed(ExitSucceed::Returned), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() +/// A helper function to run failed test and assert state changes. +fn run_failed_test_and_assert( + expected_gas_usage: u64, + transaction: pallet_ethereum::Transaction, + exit_reason: ExitReason, +) { + // Check test preconditions. + assert_eq!( + EvmBalances::total_balance(&source_swap_evm_account()), + INIT_BALANCE + ); + assert_eq!(Balances::total_balance(&target_swap_native_account()), 0); + + // Set block number to enable events. + System::set_block_number(1); + + let transaction_hash = transaction.hash(); + + // Invoke the function under test. + let post_info = pallet_ethereum::ValidatedTransaction::::apply( + source_swap_evm_account(), + transaction, + ) + .unwrap(); + + assert_eq!( + post_info, + PostDispatchInfo { + actual_weight: Some( + ::GasWeightMapping::gas_to_weight( + expected_gas_usage, + true + ) + ), + pays_fee: Pays::No + }, + ); + + // Verify that source swap evm balance remains the same. + assert_eq!( + ::total_balance(&source_swap_evm_account()), + INIT_BALANCE + ); + // Verify that bridge pot evm balance remains the same. + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + // Verify that target swap native balance remains the same. + assert_eq!(::total_balance(&target_swap_native_account()), 0); + // Verify that bridge pot native balance remains the same. + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + // Verify that precompile balance remains the same. + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + // Verify that we have a corresponding ethereum event about failed transaction. + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: source_swap_evm_account(), + to: *PRECOMPILE_ADDRESS, + transaction_hash, + exit_reason, + extra_data: vec![], + })); + + // Finalize block to check transaction logs. + Ethereum::on_finalize(1); + + // Verify that we don't have a transaction log corresponding to swap execution. + assert_eq!( + pallet_ethereum::pallet::CurrentTransactionStatuses::::get() .unwrap() .first() .cloned() .unwrap() - .logs; - assert_eq!( - transaction_logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile::SELECTOR_LOG_SWAP, - alice_evm(), - H256::from(swap_native_account.as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); - }); + .logs, + vec![] + ); } /// This test verifies that the swap precompile call behaves as expected when called without @@ -314,24 +239,13 @@ fn swap_works_full_balance() { #[test] fn swap_fail_no_funds() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); let swap_balance = INIT_BALANCE + 1; // more than we have - let expected_gas_usage: u64 = 21216; // precompile gas cost is not included - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); - // Prepare EVM call. let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) .0 .nonce, max_priority_fee_per_gas: 0.into(), @@ -340,62 +254,19 @@ fn swap_fail_no_funds() { action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), value: U256::from(swap_balance), input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(swap_native_account.as_ref())) + .write(H256::from(target_swap_native_account().as_ref())) .build(), access_list: Default::default(), odd_y_parity: false, r: Default::default(), s: Default::default(), }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - // Assert state changes. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(::total_balance(&swap_native_account), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::OutOfFund), ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Error(ExitError::OutOfFund), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!(transaction_logs, vec![]); }); } @@ -404,24 +275,13 @@ fn swap_fail_no_funds() { #[test] fn swap_fail_below_ed() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); let swap_balance = 1; - - let expected_gas_usage: u64 = 50_000; // all fee will be consumed - - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); + let expected_gas_usage: u64 = 50_000; // all gas will be consumed // Prepare EVM call. let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) .0 .nonce, max_priority_fee_per_gas: 0.into(), @@ -430,62 +290,19 @@ fn swap_fail_below_ed() { action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), value: U256::from(swap_balance), input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(swap_native_account.as_ref())) + .write(H256::from(target_swap_native_account().as_ref())) .build(), access_list: Default::default(), odd_y_parity: false, r: Default::default(), s: Default::default(), }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - // Assert state changes. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(::total_balance(&swap_native_account), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::Other("unable to swap funds".into())), ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Error(ExitError::Other("unable to swap funds".into())), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!(transaction_logs, vec![]); }); } @@ -494,24 +311,13 @@ fn swap_fail_below_ed() { #[test] fn swap_fail_bad_selector() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); let swap_balance = 1; - - let expected_gas_usage: u64 = 50_000; // all fee will be consumed - - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); + let expected_gas_usage: u64 = 50_000; // all gas will be consumed // Prepare EVM call. let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) .0 .nonce, max_priority_fee_per_gas: 0.into(), @@ -520,62 +326,19 @@ fn swap_fail_bad_selector() { action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), value: U256::from(swap_balance), input: EvmDataWriter::new_with_selector(111_u32) - .write(H256::from(swap_native_account.as_ref())) + .write(H256::from(target_swap_native_account().as_ref())) .build(), access_list: Default::default(), odd_y_parity: false, r: Default::default(), s: Default::default(), }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - // Assert state changes. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(::total_balance(&swap_native_account), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::Other("invalid function selector".into())), ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Error(ExitError::Other("invalid function selector".into())), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!(transaction_logs, vec![]); }); } @@ -588,76 +351,33 @@ fn swap_fail_bad_selector() { #[test] fn swap_fail_value_overflow() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); - let expected_gas_usage: u64 = 21216; // precompile gas cost is not included - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); - // Prepare EVM call. let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) .0 .nonce, max_priority_fee_per_gas: 0.into(), max_fee_per_gas: 0.into(), gas_limit: 50_000.into(), // a reasonable upper bound for tests action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::MAX, + value: U256::MAX, // use max value input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(swap_native_account.as_ref())) + .write(H256::from(target_swap_native_account().as_ref())) .build(), access_list: Default::default(), odd_y_parity: false, r: Default::default(), s: Default::default(), }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - // Assert state changes. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::OutOfFund), ); - assert_eq!(::total_balance(&swap_native_account), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Error(ExitError::OutOfFund), - extra_data: vec![], - })); }); } @@ -666,24 +386,13 @@ fn swap_fail_value_overflow() { #[test] fn swap_fail_no_arguments() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); let swap_balance = 1; - - let expected_gas_usage: u64 = 50_000; // all fee will be consumed - - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); + let expected_gas_usage: u64 = 50_000; // all gas will be consumed // Prepare EVM call. let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) .0 .nonce, max_priority_fee_per_gas: 0.into(), @@ -697,57 +406,12 @@ fn swap_fail_no_arguments() { r: Default::default(), s: Default::default(), }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - // Assert state changes. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::Other("exactly one argument is expected".into())), ); - assert_eq!(::total_balance(&swap_native_account), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Error(ExitError::Other( - "exactly one argument is expected".into(), - )), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!(transaction_logs, vec![]); }); } @@ -756,19 +420,8 @@ fn swap_fail_no_arguments() { #[test] fn swap_fail_short_argument() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); let swap_balance = 1; - - let expected_gas_usage: u64 = 50_000; // all fee will be consumed - - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); + let expected_gas_usage: u64 = 50_000; // all gas will be consumed // Prepare EVM call. let mut input = EvmDataWriter::new_with_selector(precompile::Action::Swap).build(); @@ -776,7 +429,7 @@ fn swap_fail_short_argument() { let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) .0 .nonce, max_priority_fee_per_gas: 0.into(), @@ -790,57 +443,12 @@ fn swap_fail_short_argument() { r: Default::default(), s: Default::default(), }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - // Assert state changes. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::Other("exactly one argument is expected".into())), ); - assert_eq!(::total_balance(&swap_native_account), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Error(ExitError::Other( - "exactly one argument is expected".into(), - )), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!(transaction_logs, vec![]); }); } @@ -849,29 +457,18 @@ fn swap_fail_short_argument() { #[test] fn swap_fail_trailing_junk() { new_test_ext().execute_with_ext(|_| { - let swap_native_account = AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )); let swap_balance = 1; - - let expected_gas_usage: u64 = 50_000; // all fee will be consumed - - // Check test preconditions. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(::total_balance(&swap_native_account), 0); - - // Set block number to enable events. - System::set_block_number(1); + let expected_gas_usage: u64 = 50_000; // all gas will be consumed // Prepare EVM call. let mut input = EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(swap_native_account.as_ref())) + .write(H256::from(target_swap_native_account().as_ref())) .build(); input.extend_from_slice(&hex_literal::hex!("1000")); // bad input let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&alice_evm()) + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) .0 .nonce, max_priority_fee_per_gas: 0.into(), @@ -885,54 +482,11 @@ fn swap_fail_trailing_junk() { r: Default::default(), s: Default::default(), }); - let transaction_hash = transaction.hash(); - - // Invoke the function under test. - let post_info = - pallet_ethereum::ValidatedTransaction::::apply(alice_evm(), transaction).unwrap(); - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); - // Assert state changes. - assert_eq!(::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(::total_balance(&swap_native_account), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::Other("junk at the end of input".into())), ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: alice_evm(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Error(ExitError::Other("junk at the end of input".into())), - extra_data: vec![], - })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!(transaction_logs, vec![]); }); } From 413f49b8f2b3a13c3665dfe15e74e401ab06c153 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 20:10:35 +0300 Subject: [PATCH 045/102] Use more elegant way to build precompiles at mock --- crates/pallet-evm-swap/src/mock.rs | 64 ++++++++++-------------------- 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/crates/pallet-evm-swap/src/mock.rs b/crates/pallet-evm-swap/src/mock.rs index 74b43c700..54eb0c660 100644 --- a/crates/pallet-evm-swap/src/mock.rs +++ b/crates/pallet-evm-swap/src/mock.rs @@ -1,6 +1,5 @@ use std::collections::BTreeMap; -use fp_evm::{IsPrecompileResult, PrecompileHandle}; use frame_support::{ once_cell::sync::Lazy, parameter_types, sp_io, @@ -13,21 +12,22 @@ use frame_support::{ weights::Weight, }; use pallet_ethereum::PostLogContent as EthereumPostLogContent; +use precompile_utils::precompile_set::{PrecompileAt, PrecompileSetBuilder}; use sp_core::{Get, H160, H256, U256}; use crate::{self as pallet_evm_swap, precompile}; -pub(crate) const INIT_BALANCE: u128 = 10_000_000_000_000_000; +pub const INIT_BALANCE: u128 = 10_000_000_000_000_000; // Add some tokens to test swap with full balance. -pub(crate) const BRIDGE_INIT_BALANCE: u128 = INIT_BALANCE + 100; +pub const BRIDGE_INIT_BALANCE: u128 = INIT_BALANCE + 100; -pub(crate) fn alice() -> AccountId { +pub fn alice() -> AccountId { AccountId::from(hex_literal::hex!( "1100000000000000000000000000000000000000000000000000000000000011" )) } -pub(crate) fn alice_evm() -> EvmAccountId { +pub fn alice_evm() -> EvmAccountId { EvmAccountId::from(hex_literal::hex!( "1100000000000000000000000000000000000011" )) @@ -36,9 +36,9 @@ pub(crate) fn alice_evm() -> EvmAccountId { type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; -pub(crate) type AccountId = frame_support::sp_runtime::AccountId32; -pub(crate) type EvmAccountId = H160; -pub(crate) type Balance = u128; +pub type AccountId = frame_support::sp_runtime::AccountId32; +pub type EvmAccountId = H160; +pub type Balance = u128; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -131,7 +131,7 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } -pub(crate) static GAS_PRICE: Lazy = Lazy::new(|| 1_000_000_000u128.into()); +pub static GAS_PRICE: Lazy = Lazy::new(|| 1_000_000_000u128.into()); pub struct FixedGasPrice; impl fp_evm::FeeCalculator for FixedGasPrice { @@ -141,11 +141,19 @@ impl fp_evm::FeeCalculator for FixedGasPrice { } } +pub static PRECOMPILE_ADDRESS: Lazy = Lazy::new(|| H160::from_low_u64_be(0x901)); + +pub type EvmSwapPrecompile = precompile::EvmSwap>; + +pub type Precompiles = + PrecompileSetBuilder>; + parameter_types! { pub BlockGasLimit: U256 = U256::max_value(); pub GasLimitPovSizeRatio: u64 = 0; pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); - pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet; + pub PrecompileAddress: H160 = *PRECOMPILE_ADDRESS; + pub PrecompilesValue: Precompiles = Precompiles::new(); } impl pallet_evm::Config for Test { @@ -163,8 +171,8 @@ impl pallet_evm::Config for Test { type AddressMapping = pallet_evm::IdentityAddressMapping; type Currency = EvmBalances; type RuntimeEvent = RuntimeEvent; - type PrecompilesType = MockPrecompileSet; - type PrecompilesValue = MockPrecompiles; + type PrecompilesType = Precompiles; + type PrecompilesValue = PrecompilesValue; type ChainId = (); type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; @@ -219,38 +227,6 @@ impl pallet_evm_swap::Config for Test { type WeightInfo = (); } -type EvmSwapPrecompile = precompile::EvmSwap>; - -/// 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(EvmSwapPrecompile::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, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: address == *PRECOMPILE_ADDRESS, - extra_cost: 0, - } - } -} - pub fn new_test_ext() -> sp_io::TestExternalities { // Build genesis. let config = GenesisConfig { From 031053435874c39fd9fd63be64a06979950e5d92 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 20:22:15 +0300 Subject: [PATCH 046/102] Fix typo --- crates/pallet-evm-swap/src/tests/evm_to_native.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 2388f400a..b4090c712 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -75,7 +75,7 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) // Assert state changes. - // Verify that source swap evm account balance has been decreased by swap value. + // Verify that source swap evm balance has been decreased by swap value. assert_eq!( ::total_balance(&source_swap_evm_account()), INIT_BALANCE - swap_balance, From 9c1d157397cc849d5709d1cf7590e1ff3a359008 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 20:27:04 +0300 Subject: [PATCH 047/102] Add a helper to run succeeded test for native to evm flow --- .../src/tests/native_to_evm.rs | 135 ++++++++++-------- 1 file changed, 78 insertions(+), 57 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 88ca398cb..239a909af 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -2,68 +2,89 @@ use fp_evm::{ExitReason, ExitSucceed}; use frame_support::assert_ok; use sp_core::Get; -use crate::{mock::*, *}; +use crate::{ + mock::{alice as source_swap_native_account, *}, + *, +}; -/// This test verifies that the swap call works in the happy path. -#[test] -fn swap_works() { - new_test_ext().execute_with_ext(|_| { - let swap_evm_account = EvmAccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000077" - )); - let swap_balance = 100; +/// Returns target swap evm account used in tests. +fn target_swap_evm_account() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000077" + )) +} + +/// A helper function to run succeeded test and assert state changes. +fn run_succeeded_test_and_assert(swap_balance: Balance) { + // Check test preconditions. + assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); + assert_eq!(EvmBalances::total_balance(&target_swap_evm_account()), 0); - // Check test preconditions. - assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); - assert_eq!(EvmBalances::total_balance(&swap_evm_account), 0); + // We should remember expected evm transaction hash before execution as nonce is increased + // after the execution. + let expected_evm_transaction_hash = ethereum_transfer_transaction::( + BridgePotEvm::get(), + target_swap_evm_account(), + swap_balance, + ) + .hash(); - // We should remember expected evm transaction hash before execution as nonce is increased - // after the execution. - let expected_evm_transaction_hash = ethereum_transfer_transaction::( - BridgePotEvm::get(), - swap_evm_account, - swap_balance, - ) - .hash(); + // Set block number to enable events. + System::set_block_number(1); - // Set block number to enable events. - System::set_block_number(1); + // Invoke the function under test. + assert_ok!(EvmSwap::swap( + RuntimeOrigin::signed(alice()), + target_swap_evm_account(), + swap_balance + )); - // Invoke the function under test. - assert_ok!(EvmSwap::swap( - RuntimeOrigin::signed(alice()), - swap_evm_account, - swap_balance - )); + // Assert state changes. - // Assert state changes. - assert_eq!( - Balances::total_balance(&alice()), - INIT_BALANCE - swap_balance - ); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE + swap_balance - ); - assert_eq!(EvmBalances::total_balance(&swap_evm_account), swap_balance); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE - swap_balance - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { - from: alice(), - withdrawed_amount: swap_balance, - to: swap_evm_account, - deposited_amount: swap_balance, - evm_transaction_hash: expected_evm_transaction_hash, - })); - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: BridgePotEvm::get(), - to: swap_evm_account, - transaction_hash: expected_evm_transaction_hash, - exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), - extra_data: vec![], - })); + // Verify that source swap native balance has been decreased by swap value. + assert_eq!( + ::total_balance(&source_swap_native_account()), + INIT_BALANCE - swap_balance, + ); + // Verify that bridge pot native balance has been increased by swap value. + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE + swap_balance, + ); + // Verify that target swap evm balance has been increased by swap value. + assert_eq!( + ::total_balance(&target_swap_evm_account()), + swap_balance + ); + // Verify that bridge pot evm balance has been decreased by swap value. + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE - swap_balance, + ); + // Verify that precompile balance remains the same. + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + // Verifyt that we have a corresponding evm swap event. + System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { + from: alice(), + withdrawed_amount: swap_balance, + to: target_swap_evm_account(), + deposited_amount: swap_balance, + evm_transaction_hash: expected_evm_transaction_hash, + })); + // Verify that we have a corresponding ethereum event. + System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { + from: BridgePotEvm::get(), + to: target_swap_evm_account(), + transaction_hash: expected_evm_transaction_hash, + exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), + extra_data: vec![], + })); +} + +/// This test verifies that the swap call works in the happy path. +#[test] +fn swap_works() { + new_test_ext().execute_with_ext(|_| { + run_succeeded_test_and_assert(100); }); } From a2da3d9fa18c8c5efe0651c173f6ebb3e74ee562 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 20:30:37 +0300 Subject: [PATCH 048/102] Add swap_works_kill_origin for native to evm flow --- crates/pallet-evm-swap/src/tests/native_to_evm.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 239a909af..4c09f5e72 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -15,7 +15,7 @@ fn target_swap_evm_account() -> EvmAccountId { } /// A helper function to run succeeded test and assert state changes. -fn run_succeeded_test_and_assert(swap_balance: Balance) { +fn run_succeeded_test_and_assert(swap_balance: Balance, expected_left_origin_balance: Balance) { // Check test preconditions. assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); assert_eq!(EvmBalances::total_balance(&target_swap_evm_account()), 0); @@ -44,7 +44,7 @@ fn run_succeeded_test_and_assert(swap_balance: Balance) { // Verify that source swap native balance has been decreased by swap value. assert_eq!( ::total_balance(&source_swap_native_account()), - INIT_BALANCE - swap_balance, + expected_left_origin_balance, ); // Verify that bridge pot native balance has been increased by swap value. assert_eq!( @@ -85,6 +85,15 @@ fn run_succeeded_test_and_assert(swap_balance: Balance) { #[test] fn swap_works() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(100); + run_succeeded_test_and_assert(100, INIT_BALANCE - 100); + }); +} + +/// This test verifies that swap call works as expected in case origin left balances amount +/// is less than existential deposit. The origin account should be killed. +#[test] +fn swap_works_kill_origin() { + new_test_ext().execute_with_ext(|_| { + run_succeeded_test_and_assert(INIT_BALANCE - 1, 0); }); } From b2ff08b84ebf7e17feac1290deb610cf1b86cf19 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 20:39:13 +0300 Subject: [PATCH 049/102] Add swap_keep_alive_works test --- .../src/tests/native_to_evm.rs | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 4c09f5e72..6123a73c1 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -14,10 +14,23 @@ fn target_swap_evm_account() -> EvmAccountId { )) } +/// A helper to identify call used in tests. +enum TestCall { + Swap, + SwapKeepAlive, +} + /// A helper function to run succeeded test and assert state changes. -fn run_succeeded_test_and_assert(swap_balance: Balance, expected_left_origin_balance: Balance) { +fn run_succeeded_test_and_assert( + call: TestCall, + swap_balance: Balance, + expected_left_origin_balance: Balance, +) { // Check test preconditions. - assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); + assert_eq!( + Balances::total_balance(&source_swap_native_account()), + INIT_BALANCE + ); assert_eq!(EvmBalances::total_balance(&target_swap_evm_account()), 0); // We should remember expected evm transaction hash before execution as nonce is increased @@ -33,15 +46,22 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_left_origin_bal System::set_block_number(1); // Invoke the function under test. - assert_ok!(EvmSwap::swap( - RuntimeOrigin::signed(alice()), - target_swap_evm_account(), - swap_balance - )); + assert_ok!(match call { + TestCall::Swap => EvmSwap::swap( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + swap_balance + ), + TestCall::SwapKeepAlive => EvmSwap::swap_keep_alive( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + swap_balance + ), + }); // Assert state changes. - // Verify that source swap native balance has been decreased by swap value. + // Verify that source swap native balance is equal to expected left origin balance value. assert_eq!( ::total_balance(&source_swap_native_account()), expected_left_origin_balance, @@ -65,7 +85,7 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_left_origin_bal assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); // Verifyt that we have a corresponding evm swap event. System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { - from: alice(), + from: source_swap_native_account(), withdrawed_amount: swap_balance, to: target_swap_evm_account(), deposited_amount: swap_balance, @@ -85,7 +105,7 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_left_origin_bal #[test] fn swap_works() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(100, INIT_BALANCE - 100); + run_succeeded_test_and_assert(TestCall::Swap, 100, INIT_BALANCE - 100); }); } @@ -94,6 +114,14 @@ fn swap_works() { #[test] fn swap_works_kill_origin() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(INIT_BALANCE - 1, 0); + run_succeeded_test_and_assert(TestCall::Swap, INIT_BALANCE - 1, 0); + }); +} + +/// This test verifies that `swap_keep_alive` call works in the happy path. +#[test] +fn swap_keep_alive_works() { + new_test_ext().execute_with_ext(|_| { + run_succeeded_test_and_assert(TestCall::SwapKeepAlive, 100, INIT_BALANCE - 100); }); } From 27cf98e1953f829c62cac6062253f9ec12d5b882 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 21:08:16 +0300 Subject: [PATCH 050/102] Add some swap failed tests --- .../src/tests/native_to_evm.rs | 86 ++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 6123a73c1..7077192c9 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -1,5 +1,9 @@ use fp_evm::{ExitReason, ExitSucceed}; -use frame_support::assert_ok; +use frame_support::{ + assert_noop, assert_ok, + sp_runtime::{ArithmeticError, TokenError}, + traits::fungible::Unbalanced, +}; use sp_core::Get; use crate::{ @@ -101,7 +105,7 @@ fn run_succeeded_test_and_assert( })); } -/// This test verifies that the swap call works in the happy path. +/// This test verifies that the `swap` call works in the happy path. #[test] fn swap_works() { new_test_ext().execute_with_ext(|_| { @@ -109,7 +113,7 @@ fn swap_works() { }); } -/// This test verifies that swap call works as expected in case origin left balances amount +/// This test verifies that `swap` call works as expected in case origin left balances amount /// is less than existential deposit. The origin account should be killed. #[test] fn swap_works_kill_origin() { @@ -125,3 +129,79 @@ fn swap_keep_alive_works() { run_succeeded_test_and_assert(TestCall::SwapKeepAlive, 100, INIT_BALANCE - 100); }); } + +/// This test verifies that `swap` call fails in case source account has no the sufficient balance. +#[test] +fn swap_fails_no_funds() { + new_test_ext().execute_with_ext(|_| { + let swap_balance = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_noop!( + EvmSwap::swap( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + swap_balance + ), + DispatchError::Token(TokenError::FundsUnavailable) + ); + }); +} + +/// This test verifies that `swap` call fails in case target deposit results into overflow. +#[test] +fn swap_fails_overflow() { + new_test_ext().execute_with_ext(|_| { + let swap_balance = 1; + + EvmBalances::write_balance(&target_swap_evm_account(), Balance::MAX).unwrap(); + + // Invoke the function under test. + assert_noop!( + EvmSwap::swap( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + swap_balance + ), + DispatchError::Arithmetic(ArithmeticError::Overflow) + ); + }); +} + +/// This test verifies that `swap_keep_alive` call fails in case source account has no the sufficient balance. +#[test] +fn swap_keep_alive_fails_no_funds() { + new_test_ext().execute_with_ext(|_| { + let swap_balance = INIT_BALANCE + 1; + + // Invoke the function under test. + assert_noop!( + EvmSwap::swap_keep_alive( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + swap_balance + ), + DispatchError::Token(TokenError::FundsUnavailable) + ); + }); +} + +/// This test verifies that `swap_keep_alive` call fails in case target deposit results into overflow. +#[test] +fn swap_keep_alive_fails_overflow() { + new_test_ext().execute_with_ext(|_| { + let swap_balance = 1; + + EvmBalances::write_balance(&target_swap_evm_account(), Balance::MAX).unwrap(); + + // Invoke the function under test. + assert_noop!( + EvmSwap::swap_keep_alive( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + swap_balance + ), + DispatchError::Arithmetic(ArithmeticError::Overflow) + ); + }); +} From 383802b841a241844c0b11c3d6e6c083db0fb690 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 22:39:06 +0300 Subject: [PATCH 051/102] Add swap_keep_alive_fails test --- .../src/tests/native_to_evm.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 7077192c9..a46ceaa04 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -168,6 +168,25 @@ fn swap_fails_overflow() { }); } +/// This test verifies that `swap_keep_alive` call fails in case origin left balances amount +/// is less than existential deposit. The call should prevent swap operation. +#[test] +fn swap_keep_alive_fails() { + new_test_ext().execute_with_ext(|_| { + let swap_balance = INIT_BALANCE - 1; + + // Invoke the function under test. + assert_noop!( + EvmSwap::swap_keep_alive( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + swap_balance + ), + DispatchError::Token(TokenError::NotExpendable) + ); + }); +} + /// This test verifies that `swap_keep_alive` call fails in case source account has no the sufficient balance. #[test] fn swap_keep_alive_fails_no_funds() { From f2f9afd5c53925866b2e5bdf95ed397d5c7dafe0 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 25 Feb 2025 22:42:04 +0300 Subject: [PATCH 052/102] Switch to mutate fungible interface --- crates/pallet-evm-swap/src/lib.rs | 50 ++++--------------- crates/pallet-evm-swap/src/precompile.rs | 8 +-- .../src/tests/native_to_evm.rs | 1 + 3 files changed, 15 insertions(+), 44 deletions(-) diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index 1041815ef..984d1a109 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -2,12 +2,9 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - dispatch::DispatchError, - traits::{ - fungible::{Balanced, Inspect}, - tokens::{Fortitude, Precision, Preservation, Provenance}, - }, +use frame_support::traits::{ + fungible::{Inspect, Mutate}, + tokens::{Preservation, Provenance}, }; pub use pallet::*; use sp_core::{Get, H160, U256}; @@ -61,10 +58,14 @@ pub mod pallet { type EvmAccountId: Parameter + Into; /// Native token. - type NativeToken: Inspect + Balanced; + /// + /// TODO(#1462): switch from `Mutate` to `Balanced` fungible interface. + type NativeToken: Inspect + Mutate; /// EVM token. - type EvmToken: Inspect + Balanced; + /// + /// TODO(#1462): switch from `Mutate` to `Balanced` fungible interface. + type EvmToken: Inspect + Mutate; /// The converter to determine how the balance amount should be converted from native /// to EVM token. @@ -149,12 +150,7 @@ pub mod pallet { T::EvmToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; - balanced_transfer::<_, T::NativeToken>( - &who, - &T::BridgePotNative::get(), - amount, - preservation, - )?; + T::NativeToken::transfer(&who, &T::BridgePotNative::get(), amount, preservation)?; let evm_transaction_hash = Self::execute_ethereum_transfer( T::BridgePotEvm::get().into(), @@ -230,29 +226,3 @@ pub(crate) fn ethereum_transfer_transaction( s: Default::default(), }) } - -/// A helper function to execute balanced transfer. -pub(crate) fn balanced_transfer + Balanced>( - from: &AccountId, - to: &AccountId, - amount: Token::Balance, - preservation: Preservation, -) -> Result<(), DispatchError> { - let credit = Token::withdraw( - from, - amount, - Precision::Exact, - preservation, - Fortitude::Polite, - )?; - - if let Err(credit) = Token::resolve(to, credit) { - // Here we undo the withdrawal to avoid having a dangling credit. - // - // Drop the result which will trigger the `OnDrop` of the credit in case of - // resolving back fails. - let _ = Token::resolve(from, credit); - } - - Ok(()) -} diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs index 3a9d209b9..74a433a7d 100644 --- a/crates/pallet-evm-swap/src/precompile.rs +++ b/crates/pallet-evm-swap/src/precompile.rs @@ -8,7 +8,7 @@ use frame_support::{ sp_runtime::traits::Convert, sp_std::{marker::PhantomData, prelude::*}, traits::{ - fungible::Inspect, + fungible::{Inspect, Mutate}, tokens::{Preservation, Provenance}, }, }; @@ -20,7 +20,7 @@ use precompile_utils::{ }; use sp_core::{Get, H160, H256, U256}; -use crate::{balanced_transfer, Config, EvmBalanceOf}; +use crate::{Config, EvmBalanceOf}; /// Solidity selector of the Swap log, which is the Keccak of the Log signature. pub const SELECTOR_LOG_SWAP: [u8; 32] = keccak256!("Swap(address,bytes32,uint256)"); @@ -116,7 +116,7 @@ where .into_result() .map_err(process_dispatch_error)?; - balanced_transfer::<_, EvmSwapT::EvmToken>( + EvmSwapT::EvmToken::transfer( &from, &EvmSwapT::BridgePotEvm::get(), value, @@ -124,7 +124,7 @@ where ) .map_err(process_dispatch_error)?; - balanced_transfer::<_, EvmSwapT::NativeToken>( + EvmSwapT::NativeToken::transfer( &EvmSwapT::BridgePotNative::get(), &to, estimated_swapped_balance, diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index a46ceaa04..1dcefe3c4 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -1,6 +1,7 @@ use fp_evm::{ExitReason, ExitSucceed}; use frame_support::{ assert_noop, assert_ok, + dispatch::DispatchError, sp_runtime::{ArithmeticError, TokenError}, traits::fungible::Unbalanced, }; From 8c2bda3438dbfc479b374fb170c5b60e08556973 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 11:23:47 +0300 Subject: [PATCH 053/102] Some renaming --- crates/pallet-evm-swap/src/tests/native_to_evm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 1dcefe3c4..c17a446b6 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -29,7 +29,7 @@ enum TestCall { fn run_succeeded_test_and_assert( call: TestCall, swap_balance: Balance, - expected_left_origin_balance: Balance, + source_native_expected_left_balance: Balance, ) { // Check test preconditions. assert_eq!( @@ -69,7 +69,7 @@ fn run_succeeded_test_and_assert( // Verify that source swap native balance is equal to expected left origin balance value. assert_eq!( ::total_balance(&source_swap_native_account()), - expected_left_origin_balance, + source_native_expected_left_balance, ); // Verify that bridge pot native balance has been increased by swap value. assert_eq!( From 446b73646407de02d8bd620a7bc0c6b91f2cbce6 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 11:45:51 +0300 Subject: [PATCH 054/102] Add mode details into process_dispatch_error --- crates/pallet-evm-swap/src/precompile.rs | 29 +++++++++++++++---- .../src/tests/evm_to_native.rs | 4 ++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs index 74a433a7d..52f0bcba6 100644 --- a/crates/pallet-evm-swap/src/precompile.rs +++ b/crates/pallet-evm-swap/src/precompile.rs @@ -114,7 +114,9 @@ where EvmSwapT::NativeToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result() - .map_err(process_dispatch_error)?; + .map_err(|err| { + process_dispatch_error(err, "unable to deposit into target native account") + })?; EvmSwapT::EvmToken::transfer( &from, @@ -122,7 +124,12 @@ where value, Preservation::Expendable, ) - .map_err(process_dispatch_error)?; + .map_err(|err| { + process_dispatch_error( + err, + "unable to transfer from source evm to bridge pot evm account", + ) + })?; EvmSwapT::NativeToken::transfer( &EvmSwapT::BridgePotNative::get(), @@ -130,7 +137,12 @@ where estimated_swapped_balance, Preservation::Expendable, ) - .map_err(process_dispatch_error)?; + .map_err(|err| { + process_dispatch_error( + err, + "unable to transfer from bridge pot native to target native account", + ) + })?; let logs_builder = LogsBuilder::new(handle.context().address); @@ -148,15 +160,20 @@ where } /// A helper function to process dispatch related errors. -fn process_dispatch_error(error: DispatchError) -> PrecompileFailure { +fn process_dispatch_error( + error: DispatchError, + other_error_message: &'static str, +) -> PrecompileFailure { match error { DispatchError::Token(frame_support::sp_runtime::TokenError::FundsUnavailable) => { PrecompileFailure::Error { exit_status: ExitError::OutOfFund, } } - _ => PrecompileFailure::Error { - exit_status: ExitError::Other("unable to swap funds".into()), + other_error_details => PrecompileFailure::Error { + exit_status: ExitError::Other( + format!("{other_error_message}: {other_error_details:?}").into(), + ), }, } } diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index b4090c712..5cf81328e 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -301,7 +301,9 @@ fn swap_fail_below_ed() { run_failed_test_and_assert( expected_gas_usage, transaction, - ExitReason::Error(ExitError::Other("unable to swap funds".into())), + ExitReason::Error(ExitError::Other( + "unable to deposit into target native account: Token(BelowMinimum)".into(), + )), ); }); } From d0cb02c5c55a2b1f8fbb117a7c47e349d52b6fb2 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 12:05:09 +0300 Subject: [PATCH 055/102] Redesign evm to native tests --- .../src/tests/evm_to_native.rs | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 5cf81328e..7d96577df 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -2,7 +2,7 @@ use fp_ethereum::ValidatedTransaction; use fp_evm::{ExitError, ExitReason, ExitSucceed}; use frame_support::{ dispatch::{Pays, PostDispatchInfo}, - traits::OnFinalize, + traits::{fungible::Unbalanced, OnFinalize}, }; use pallet_evm::GasWeightMapping; use precompile_utils::{EvmDataWriter, LogsBuilder}; @@ -22,12 +22,10 @@ fn target_swap_native_account() -> AccountId { /// A helper function to run succeeded test and assert state changes. fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) { - // Check test preconditions. - assert_eq!( - EvmBalances::total_balance(&source_swap_evm_account()), - INIT_BALANCE - ); - assert_eq!(Balances::total_balance(&target_swap_native_account()), 0); + let source_swap_evm_account_balance_before = + EvmBalances::total_balance(&source_swap_evm_account()); + let target_swap_native_account_balance_before = + Balances::total_balance(&target_swap_native_account()); // Set block number to enable events. System::set_block_number(1); @@ -78,7 +76,7 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) // Verify that source swap evm balance has been decreased by swap value. assert_eq!( ::total_balance(&source_swap_evm_account()), - INIT_BALANCE - swap_balance, + source_swap_evm_account_balance_before - swap_balance, ); // Verify that bridge pot evm balance has been increased by swap value. assert_eq!( @@ -88,7 +86,7 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) // Verify that target swap native balance has been increased by swap value. assert_eq!( ::total_balance(&target_swap_native_account()), - swap_balance + target_swap_native_account_balance_before + swap_balance ); // Verify that bridge pot native balance has been decreased by swap value. assert_eq!( @@ -159,12 +157,10 @@ fn run_failed_test_and_assert( transaction: pallet_ethereum::Transaction, exit_reason: ExitReason, ) { - // Check test preconditions. - assert_eq!( - EvmBalances::total_balance(&source_swap_evm_account()), - INIT_BALANCE - ); - assert_eq!(Balances::total_balance(&target_swap_native_account()), 0); + let source_swap_evm_account_balance_before = + EvmBalances::total_balance(&source_swap_evm_account()); + let target_swap_native_account_balance_before = + Balances::total_balance(&target_swap_native_account()); // Set block number to enable events. System::set_block_number(1); @@ -194,7 +190,7 @@ fn run_failed_test_and_assert( // Verify that source swap evm balance remains the same. assert_eq!( ::total_balance(&source_swap_evm_account()), - INIT_BALANCE + source_swap_evm_account_balance_before, ); // Verify that bridge pot evm balance remains the same. assert_eq!( @@ -202,7 +198,10 @@ fn run_failed_test_and_assert( BRIDGE_INIT_BALANCE, ); // Verify that target swap native balance remains the same. - assert_eq!(::total_balance(&target_swap_native_account()), 0); + assert_eq!( + ::total_balance(&target_swap_native_account()), + target_swap_native_account_balance_before + ); // Verify that bridge pot native balance remains the same. assert_eq!( Balances::total_balance(&BridgePotNative::get()), @@ -237,7 +236,7 @@ fn run_failed_test_and_assert( /// This test verifies that the swap precompile call behaves as expected when called without /// the sufficient balance. #[test] -fn swap_fail_no_funds() { +fn swap_fail_source_balance_no_funds() { new_test_ext().execute_with_ext(|_| { let swap_balance = INIT_BALANCE + 1; // more than we have let expected_gas_usage: u64 = 21216; // precompile gas cost is not included @@ -273,7 +272,7 @@ fn swap_fail_no_funds() { /// This test verifies that the swap precompile call behaves as expected when /// estimated swapped balance less or equal than target currency existential deposit. #[test] -fn swap_fail_below_ed() { +fn swap_fail_target_balance_below_ed() { new_test_ext().execute_with_ext(|_| { let swap_balance = 1; let expected_gas_usage: u64 = 50_000; // all gas will be consumed From 7d544db05ffb76d8dd206e8754f242d4446246c4 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 12:06:49 +0300 Subject: [PATCH 056/102] Add swap_fail_target_balance_overflow test --- .../src/tests/evm_to_native.rs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 7d96577df..e5b9cfcfc 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -307,6 +307,46 @@ fn swap_fail_target_balance_below_ed() { }); } +/// This test verifies that the swap precompile call behaves as expected when +/// estimated swapped balance results into target swap native account balance overflow. +#[test] +fn swap_fail_target_balance_overflow() { + new_test_ext().execute_with_ext(|_| { + let swap_balance = 1; + let expected_gas_usage: u64 = 50_000; // all gas will be consumed + + Balances::write_balance(&target_swap_native_account(), Balance::MAX).unwrap(); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::Other( + "unable to deposit into target native account: Arithmetic(Overflow)".into(), + )), + ); + }); +} + /// This test verifies that the swap precompile call behaves as expected when a bad selector is /// passed. #[test] From c47063ae04a55d9b8e820f216efbaddbd7f05a11 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 12:14:16 +0300 Subject: [PATCH 057/102] Redesign run_succeeded_test_and_assert for native to evm tests --- .../src/tests/native_to_evm.rs | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index c17a446b6..a6bba6f6c 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -29,14 +29,12 @@ enum TestCall { fn run_succeeded_test_and_assert( call: TestCall, swap_balance: Balance, - source_native_expected_left_balance: Balance, + is_origin_should_be_killed: bool, ) { - // Check test preconditions. - assert_eq!( - Balances::total_balance(&source_swap_native_account()), - INIT_BALANCE - ); - assert_eq!(EvmBalances::total_balance(&target_swap_evm_account()), 0); + let source_swap_native_account_balance_before = + Balances::total_balance(&source_swap_native_account()); + let target_swap_evm_account_balance_before = + EvmBalances::total_balance(&target_swap_evm_account()); // We should remember expected evm transaction hash before execution as nonce is increased // after the execution. @@ -66,11 +64,17 @@ fn run_succeeded_test_and_assert( // Assert state changes. - // Verify that source swap native balance is equal to expected left origin balance value. - assert_eq!( - ::total_balance(&source_swap_native_account()), - source_native_expected_left_balance, - ); + // Verify that source swap native balance either has been decreased by swap value or equal to 0 + // due to left balance becomes less than existential deposit. + if is_origin_should_be_killed { + assert_eq!(::total_balance(&source_swap_native_account()), 0,); + } else { + assert_eq!( + ::total_balance(&source_swap_native_account()), + source_swap_native_account_balance_before - swap_balance, + ); + } + // Verify that bridge pot native balance has been increased by swap value. assert_eq!( Balances::total_balance(&BridgePotNative::get()), @@ -79,7 +83,7 @@ fn run_succeeded_test_and_assert( // Verify that target swap evm balance has been increased by swap value. assert_eq!( ::total_balance(&target_swap_evm_account()), - swap_balance + target_swap_evm_account_balance_before + swap_balance ); // Verify that bridge pot evm balance has been decreased by swap value. assert_eq!( @@ -110,7 +114,7 @@ fn run_succeeded_test_and_assert( #[test] fn swap_works() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(TestCall::Swap, 100, INIT_BALANCE - 100); + run_succeeded_test_and_assert(TestCall::Swap, 100, false); }); } @@ -119,7 +123,7 @@ fn swap_works() { #[test] fn swap_works_kill_origin() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(TestCall::Swap, INIT_BALANCE - 1, 0); + run_succeeded_test_and_assert(TestCall::Swap, INIT_BALANCE - 1, true); }); } @@ -127,7 +131,7 @@ fn swap_works_kill_origin() { #[test] fn swap_keep_alive_works() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(TestCall::SwapKeepAlive, 100, INIT_BALANCE - 100); + run_succeeded_test_and_assert(TestCall::SwapKeepAlive, 100, false); }); } From 98f9deadc4395dec95510102a2b9efb81c3d6c6d Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 12:40:29 +0300 Subject: [PATCH 058/102] Prevent bridge account killing at precompile logic --- crates/pallet-evm-swap/src/precompile.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs index 52f0bcba6..d9d9e786a 100644 --- a/crates/pallet-evm-swap/src/precompile.rs +++ b/crates/pallet-evm-swap/src/precompile.rs @@ -135,7 +135,8 @@ where &EvmSwapT::BridgePotNative::get(), &to, estimated_swapped_balance, - Preservation::Expendable, + // Bridge pot native account shouldn't be killed. + Preservation::Preserve, ) .map_err(|err| { process_dispatch_error( From 3dfb0751e726559331b5de91627585c4d5a8e6e2 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 12:51:27 +0300 Subject: [PATCH 059/102] Prevent killing bridge pot evm account at evm to native flow --- crates/pallet-evm-swap/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index 984d1a109..7d0ad84d5 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -149,6 +149,9 @@ pub mod pallet { let estimated_swapped_balance = T::BalanceConverterNativeToEvm::convert(amount); T::EvmToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; + T::EvmToken::can_withdraw(&T::BridgePotEvm::get(), estimated_swapped_balance) + // Bridge pot EVM account shouldn't be killed. + .into_result(true)?; T::NativeToken::transfer(&who, &T::BridgePotNative::get(), amount, preservation)?; From 21a744baa426e5afb1ec3dec5a22e91f20eec8ed Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 13:03:51 +0300 Subject: [PATCH 060/102] Add swap_fail_bridge_evm_overflow test --- .../src/tests/evm_to_native.rs | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index e5b9cfcfc..b49a00daf 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -24,6 +24,8 @@ fn target_swap_native_account() -> AccountId { fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) { let source_swap_evm_account_balance_before = EvmBalances::total_balance(&source_swap_evm_account()); + let bridge_pot_evm_account_balance_before = EvmBalances::total_balance(&BridgePotEvm::get()); + let bridge_pot_native_account_balance_before = Balances::total_balance(&BridgePotNative::get()); let target_swap_native_account_balance_before = Balances::total_balance(&target_swap_native_account()); @@ -81,7 +83,7 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) // Verify that bridge pot evm balance has been increased by swap value. assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE + swap_balance, + bridge_pot_evm_account_balance_before + swap_balance, ); // Verify that target swap native balance has been increased by swap value. assert_eq!( @@ -91,7 +93,7 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) // Verify that bridge pot native balance has been decreased by swap value. assert_eq!( Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE - swap_balance, + bridge_pot_native_account_balance_before - swap_balance, ); // Verify that precompile balance remains the same. assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); @@ -159,6 +161,8 @@ fn run_failed_test_and_assert( ) { let source_swap_evm_account_balance_before = EvmBalances::total_balance(&source_swap_evm_account()); + let bridge_pot_evm_account_balance_before = EvmBalances::total_balance(&BridgePotEvm::get()); + let bridge_pot_native_account_balance_before = Balances::total_balance(&BridgePotNative::get()); let target_swap_native_account_balance_before = Balances::total_balance(&target_swap_native_account()); @@ -195,7 +199,7 @@ fn run_failed_test_and_assert( // Verify that bridge pot evm balance remains the same. assert_eq!( EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, + bridge_pot_evm_account_balance_before, ); // Verify that target swap native balance remains the same. assert_eq!( @@ -205,7 +209,7 @@ fn run_failed_test_and_assert( // Verify that bridge pot native balance remains the same. assert_eq!( Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, + bridge_pot_native_account_balance_before, ); // Verify that precompile balance remains the same. assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); @@ -347,6 +351,46 @@ fn swap_fail_target_balance_overflow() { }); } +/// This test verifies that the swap precompile call behaves as expected when +/// swapped balance results into bridge pot evm account balance overflow. +#[test] +fn swap_fail_bridge_evm_overflow() { + new_test_ext().execute_with_ext(|_| { + let swap_balance = 100; + let expected_gas_usage: u64 = 50_000; // all gas will be consumed + + EvmBalances::write_balance(&BridgePotEvm::get(), Balance::MAX).unwrap(); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::Other( + "unable to transfer from source evm to bridge pot evm account: Arithmetic(Overflow)".into(), + )), + ); + }); +} + /// This test verifies that the swap precompile call behaves as expected when a bad selector is /// passed. #[test] From 3cc5c6a034deaff3d3df8bdd9aa4b04ce50034aa Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 15:41:59 +0300 Subject: [PATCH 061/102] Add swap_fail_bridge_native_killed test --- crates/pallet-evm-swap/src/lib.rs | 1 + .../src/tests/evm_to_native.rs | 54 +++++++- crates/pallet-evm-swap/src/tests/mod.rs | 2 +- .../src/tests/native_to_evm.rs | 120 ++++++++++-------- 4 files changed, 115 insertions(+), 62 deletions(-) diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index 7d0ad84d5..ffe8fdc58 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -149,6 +149,7 @@ pub mod pallet { let estimated_swapped_balance = T::BalanceConverterNativeToEvm::convert(amount); T::EvmToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result()?; + T::EvmToken::can_withdraw(&T::BridgePotEvm::get(), estimated_swapped_balance) // Bridge pot EVM account shouldn't be killed. .into_result(true)?; diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index b49a00daf..d6e193b71 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -8,10 +8,14 @@ use pallet_evm::GasWeightMapping; use precompile_utils::{EvmDataWriter, LogsBuilder}; use sp_core::H256; -use crate::{ - mock::{alice_evm as source_swap_evm_account, *}, - *, -}; +use crate::{mock::*, *}; + +/// Returns source swap evm account used in tests. +fn source_swap_evm_account() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "1100000000000000000000000000000000000011" + )) +} /// Returns target swap native account used in tests. fn target_swap_native_account() -> AccountId { @@ -274,7 +278,7 @@ fn swap_fail_source_balance_no_funds() { } /// This test verifies that the swap precompile call behaves as expected when -/// estimated swapped balance less or equal than target currency existential deposit. +/// estimated swapped balance less or equal than native token existential deposit. #[test] fn swap_fail_target_balance_below_ed() { new_test_ext().execute_with_ext(|_| { @@ -391,6 +395,46 @@ fn swap_fail_bridge_evm_overflow() { }); } +/// This test verifies that the swap precompile call behaves as expected when +/// swapped balance results into bridge pot evm account balance overflow. +#[test] +fn swap_fail_bridge_native_killed() { + new_test_ext().execute_with_ext(|_| { + let swap_balance = BRIDGE_INIT_BALANCE; + let expected_gas_usage: u64 = 50_000; // all gas will be consumed + + EvmBalances::write_balance(&source_swap_evm_account(), BRIDGE_INIT_BALANCE).unwrap(); + + // Prepare EVM call. + let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: ::ChainId::get(), + nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) + .0 + .nonce, + max_priority_fee_per_gas: 0.into(), + max_fee_per_gas: 0.into(), + gas_limit: 50_000.into(), // a reasonable upper bound for tests + action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), + value: U256::from(swap_balance), + input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + access_list: Default::default(), + odd_y_parity: false, + r: Default::default(), + s: Default::default(), + }); + + run_failed_test_and_assert( + expected_gas_usage, + transaction, + ExitReason::Error(ExitError::Other( + "unable to transfer from bridge pot native to target native account: Token(NotExpendable)".into(), + )), + ); + }); +} + /// This test verifies that the swap precompile call behaves as expected when a bad selector is /// passed. #[test] diff --git a/crates/pallet-evm-swap/src/tests/mod.rs b/crates/pallet-evm-swap/src/tests/mod.rs index 835bf8279..06e381dae 100644 --- a/crates/pallet-evm-swap/src/tests/mod.rs +++ b/crates/pallet-evm-swap/src/tests/mod.rs @@ -8,7 +8,7 @@ use crate::{mock::*, *}; mod evm_to_native; mod native_to_evm; -/// This test verifies that basic genesis setup works in the happy path. +/// This test verifies that basic tests setup works in the happy path. #[test] fn basic_setup_works() { new_test_ext().execute_with_ext(|_| { diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index a6bba6f6c..8cb532fd2 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -7,10 +7,14 @@ use frame_support::{ }; use sp_core::Get; -use crate::{ - mock::{alice as source_swap_native_account, *}, - *, -}; +use crate::{mock::*, *}; + +/// Returns source swap native account used in tests. +fn source_swap_native_account() -> AccountId { + AccountId::from(hex_literal::hex!( + "1100000000000000000000000000000000000000000000000000000000000011" + )) +} /// Returns target swap evm account used in tests. fn target_swap_evm_account() -> EvmAccountId { @@ -19,7 +23,7 @@ fn target_swap_evm_account() -> EvmAccountId { )) } -/// A helper to identify call used in tests. +/// A helper enum to identify call used in tests. enum TestCall { Swap, SwapKeepAlive, @@ -33,6 +37,8 @@ fn run_succeeded_test_and_assert( ) { let source_swap_native_account_balance_before = Balances::total_balance(&source_swap_native_account()); + let bridge_pot_native_account_balance_before = Balances::total_balance(&BridgePotNative::get()); + let bridge_pot_evm_account_balance_before = EvmBalances::total_balance(&BridgePotEvm::get()); let target_swap_evm_account_balance_before = EvmBalances::total_balance(&target_swap_evm_account()); @@ -64,10 +70,10 @@ fn run_succeeded_test_and_assert( // Assert state changes. - // Verify that source swap native balance either has been decreased by swap value or equal to 0 + // Verify that source swap native balance either has been decreased by swap value or reduced to 0 // due to left balance becomes less than existential deposit. if is_origin_should_be_killed { - assert_eq!(::total_balance(&source_swap_native_account()), 0,); + assert_eq!(::total_balance(&source_swap_native_account()), 0); } else { assert_eq!( ::total_balance(&source_swap_native_account()), @@ -78,20 +84,18 @@ fn run_succeeded_test_and_assert( // Verify that bridge pot native balance has been increased by swap value. assert_eq!( Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE + swap_balance, + bridge_pot_native_account_balance_before + swap_balance, + ); + // Verify that bridge pot evm balance has been decreased by swap value. + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + bridge_pot_evm_account_balance_before - swap_balance, ); // Verify that target swap evm balance has been increased by swap value. assert_eq!( ::total_balance(&target_swap_evm_account()), target_swap_evm_account_balance_before + swap_balance ); - // Verify that bridge pot evm balance has been decreased by swap value. - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE - swap_balance, - ); - // Verify that precompile balance remains the same. - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); // Verifyt that we have a corresponding evm swap event. System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { from: source_swap_native_account(), @@ -135,97 +139,101 @@ fn swap_keep_alive_works() { }); } -/// This test verifies that `swap` call fails in case source account has no the sufficient balance. +/// This test verifies that `swap_keep_alive` call fails in case origin left balances amount +/// is less than existential deposit. The call should prevent swap operation. #[test] -fn swap_fails_no_funds() { +fn swap_keep_alive_fails_kill_origin() { new_test_ext().execute_with_ext(|_| { - let swap_balance = INIT_BALANCE + 1; - // Invoke the function under test. assert_noop!( - EvmSwap::swap( + EvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), - swap_balance + INIT_BALANCE - 1, ), - DispatchError::Token(TokenError::FundsUnavailable) + DispatchError::Token(TokenError::NotExpendable) ); }); } -/// This test verifies that `swap` call fails in case target deposit results into overflow. +/// This test verifies that both calls fail in case source account has no the sufficient balance. #[test] -fn swap_fails_overflow() { +fn swap_both_fails_source_no_funds() { new_test_ext().execute_with_ext(|_| { - let swap_balance = 1; - - EvmBalances::write_balance(&target_swap_evm_account(), Balance::MAX).unwrap(); - - // Invoke the function under test. + // Invoke the `swap` under test. assert_noop!( EvmSwap::swap( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), - swap_balance + INIT_BALANCE + 1, ), - DispatchError::Arithmetic(ArithmeticError::Overflow) + DispatchError::Token(TokenError::FundsUnavailable) ); - }); -} - -/// This test verifies that `swap_keep_alive` call fails in case origin left balances amount -/// is less than existential deposit. The call should prevent swap operation. -#[test] -fn swap_keep_alive_fails() { - new_test_ext().execute_with_ext(|_| { - let swap_balance = INIT_BALANCE - 1; - // Invoke the function under test. + // Invoke the `swap_keep_alive` under test. assert_noop!( EvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), - swap_balance + INIT_BALANCE + 1, ), - DispatchError::Token(TokenError::NotExpendable) + DispatchError::Token(TokenError::FundsUnavailable) ); }); } -/// This test verifies that `swap_keep_alive` call fails in case source account has no the sufficient balance. +/// This test verifies that both calls fail in case target deposit results into overflow. #[test] -fn swap_keep_alive_fails_no_funds() { +fn swap_both_fails_target_overflow() { new_test_ext().execute_with_ext(|_| { - let swap_balance = INIT_BALANCE + 1; + EvmBalances::write_balance(&target_swap_evm_account(), Balance::MAX).unwrap(); - // Invoke the function under test. + // Invoke the `swap` under test. + assert_noop!( + EvmSwap::swap( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + 100, + ), + DispatchError::Arithmetic(ArithmeticError::Overflow) + ); + + // Invoke the `swap_keep_alive` under test. assert_noop!( EvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), - swap_balance + 100, ), - DispatchError::Token(TokenError::FundsUnavailable) + DispatchError::Arithmetic(ArithmeticError::Overflow) ); }); } -/// This test verifies that `swap_keep_alive` call fails in case target deposit results into overflow. +/// This test verifies that both calls fail in case bridge evm account would be killed. #[test] -fn swap_keep_alive_fails_overflow() { +fn swap_both_fails_bridge_evm_killed() { new_test_ext().execute_with_ext(|_| { - let swap_balance = 1; + Balances::write_balance(&source_swap_native_account(), BRIDGE_INIT_BALANCE).unwrap(); - EvmBalances::write_balance(&target_swap_evm_account(), Balance::MAX).unwrap(); + // Invoke the `swap` under test. + assert_noop!( + EvmSwap::swap( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + BRIDGE_INIT_BALANCE, + ), + DispatchError::Token(TokenError::NotExpendable) + ); - // Invoke the function under test. + // Invoke the `swap_keep_alive` under test. assert_noop!( EvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), - swap_balance + BRIDGE_INIT_BALANCE, ), - DispatchError::Arithmetic(ArithmeticError::Overflow) + DispatchError::Token(TokenError::NotExpendable) ); }); } From 50e6d9c3198965cc4365706536d88e5aa84a6ba9 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 27 Feb 2025 16:22:51 +0300 Subject: [PATCH 062/102] Improve process_dispatch_error handler --- crates/pallet-evm-swap/src/precompile.rs | 41 ++++++++----------- .../src/tests/evm_to_native.rs | 14 ++----- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs index d9d9e786a..350ba9ddf 100644 --- a/crates/pallet-evm-swap/src/precompile.rs +++ b/crates/pallet-evm-swap/src/precompile.rs @@ -114,9 +114,7 @@ where EvmSwapT::NativeToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result() - .map_err(|err| { - process_dispatch_error(err, "unable to deposit into target native account") - })?; + .map_err(process_dispatch_error)?; EvmSwapT::EvmToken::transfer( &from, @@ -124,12 +122,7 @@ where value, Preservation::Expendable, ) - .map_err(|err| { - process_dispatch_error( - err, - "unable to transfer from source evm to bridge pot evm account", - ) - })?; + .map_err(process_dispatch_error)?; EvmSwapT::NativeToken::transfer( &EvmSwapT::BridgePotNative::get(), @@ -138,12 +131,7 @@ where // Bridge pot native account shouldn't be killed. Preservation::Preserve, ) - .map_err(|err| { - process_dispatch_error( - err, - "unable to transfer from bridge pot native to target native account", - ) - })?; + .map_err(process_dispatch_error)?; let logs_builder = LogsBuilder::new(handle.context().address); @@ -161,20 +149,27 @@ where } /// A helper function to process dispatch related errors. -fn process_dispatch_error( - error: DispatchError, - other_error_message: &'static str, -) -> PrecompileFailure { +fn process_dispatch_error(error: DispatchError) -> PrecompileFailure { match error { DispatchError::Token(frame_support::sp_runtime::TokenError::FundsUnavailable) => { PrecompileFailure::Error { exit_status: ExitError::OutOfFund, } } - other_error_details => PrecompileFailure::Error { - exit_status: ExitError::Other( - format!("{other_error_message}: {other_error_details:?}").into(), - ), + DispatchError::Token(frame_support::sp_runtime::TokenError::BelowMinimum) => { + PrecompileFailure::Error { + exit_status: ExitError::Other( + "resulted balance is less than existential deposit".into(), + ), + } + } + DispatchError::Token(frame_support::sp_runtime::TokenError::NotExpendable) => { + PrecompileFailure::Error { + exit_status: ExitError::Other("account would be killed".into()), + } + } + _ => PrecompileFailure::Error { + exit_status: ExitError::Other("unable to execute swap".into()), }, } } diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index d6e193b71..29c85f57e 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -309,7 +309,7 @@ fn swap_fail_target_balance_below_ed() { expected_gas_usage, transaction, ExitReason::Error(ExitError::Other( - "unable to deposit into target native account: Token(BelowMinimum)".into(), + "resulted balance is less than existential deposit".into(), )), ); }); @@ -348,9 +348,7 @@ fn swap_fail_target_balance_overflow() { run_failed_test_and_assert( expected_gas_usage, transaction, - ExitReason::Error(ExitError::Other( - "unable to deposit into target native account: Arithmetic(Overflow)".into(), - )), + ExitReason::Error(ExitError::Other("unable to execute swap".into())), ); }); } @@ -388,9 +386,7 @@ fn swap_fail_bridge_evm_overflow() { run_failed_test_and_assert( expected_gas_usage, transaction, - ExitReason::Error(ExitError::Other( - "unable to transfer from source evm to bridge pot evm account: Arithmetic(Overflow)".into(), - )), + ExitReason::Error(ExitError::Other("unable to execute swap".into())), ); }); } @@ -428,9 +424,7 @@ fn swap_fail_bridge_native_killed() { run_failed_test_and_assert( expected_gas_usage, transaction, - ExitReason::Error(ExitError::Other( - "unable to transfer from bridge pot native to target native account: Token(NotExpendable)".into(), - )), + ExitReason::Error(ExitError::Other("account would be killed".into())), ); }); } From 51b3e90d45f8a969c291c92a234e957869d4dd06 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 28 Feb 2025 14:56:30 +0300 Subject: [PATCH 063/102] e2e: swap: nativeToEvm implementation --- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 109 +++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 utils/e2e-tests/ts/tests/swap/nativeToEvm.ts diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts new file mode 100644 index 000000000..6a8b25fbf --- /dev/null +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -0,0 +1,109 @@ +import { expect, it, describe, assert } from "vitest"; +import { RunNodeState, runNode } from "../../lib/node"; +import * as substrate from "../../lib/substrate"; +import { beforeEachWithCleanup } from "../../lib/lifecycle"; +import { Keyring } from "@polkadot/api"; +import { Codec, IEvent } from "@polkadot/types/types"; +import sendAndWait from "../../lib/substrateSendAndAwait"; + +type EvmSwapBalancesSwappedEvent = Record< + "from" | "withdrawedAmount" | "to" | "depositedAmount" | "evmTransactionHash", + Codec +>; + +type EthereumExecutedEvent = Record< + "from" | "to" | "transactionHash" | "exitReason", + Codec +>; + +const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; + +describe("native to evm tokens swap", () => { + let node: RunNodeState; + let api: substrate.Api; + beforeEachWithCleanup(async (cleanup) => { + node = runNode({ args: ["--dev", "--tmp"] }, cleanup.push); + + await node.waitForBoot; + + api = await substrate.apiFromNodeWebSocket(node, cleanup.push); + }, 60 * 1000); + + it("success", async () => { + const keyring = new Keyring({ type: "sr25519", ss58Format: 5234 }); + const alice = keyring.addFromUri("//Alice"); + + const targetEvmAddress = "0x1100000000000000000000000000000000000011"; + const swapBalance = 1_000_000; + + const swap = api.tx["evmSwap"]?.["swap"]; + assert(swap); + + const { isCompleted, internalError, events, status, dispatchError } = + await sendAndWait(swap(targetEvmAddress, swapBalance), { + signWith: alice, + }); + + expect(isCompleted).toBe(true); + expect(status.isInBlock).toBe(true); + expect(dispatchError).toBe(undefined); + expect(internalError).toBe(undefined); + + let ewmSwapBalancesSwappedEvent; + let ethereumExecutedEvent; + + for (const item of events) { + if ( + item.event.section == "evmSwap" && + item.event.method == "BalancesSwapped" + ) { + ewmSwapBalancesSwappedEvent = item.event as unknown as IEvent< + Codec[], + EvmSwapBalancesSwappedEvent + >; + } + + if (item.event.section == "ethereum" && item.event.method == "Executed") { + ethereumExecutedEvent = item.event as unknown as IEvent< + Codec[], + EthereumExecutedEvent + >; + } + } + + assert(ewmSwapBalancesSwappedEvent); + assert(ethereumExecutedEvent); + + expect(ewmSwapBalancesSwappedEvent.data.from.toPrimitive()).toEqual( + alice.address, + ); + + expect( + ewmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive(), + ).toEqual(swapBalance); + + expect(ewmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( + targetEvmAddress, + ); + + expect( + ewmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive(), + ).toEqual(swapBalance); + + expect(ewmSwapBalancesSwappedEvent.data.evmTransactionHash).toEqual( + ethereumExecutedEvent.data.transactionHash, + ); + + expect(ethereumExecutedEvent.data.from.toPrimitive()).toEqual( + bridgePotEvmAddress, + ); + + expect(ethereumExecutedEvent.data.to.toPrimitive()).toEqual( + targetEvmAddress, + ); + + expect(ethereumExecutedEvent.data.exitReason.toPrimitive()).toEqual({ + succeed: "Stopped", + }); + }); +}); From 1b249cca73e885f463f02e4acf4a166e2d15adb7 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 28 Feb 2025 18:10:38 +0300 Subject: [PATCH 064/102] Add ewmSwap precompile abi --- utils/e2e-tests/ts/lib/abis/evmSwap.ts | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 utils/e2e-tests/ts/lib/abis/evmSwap.ts diff --git a/utils/e2e-tests/ts/lib/abis/evmSwap.ts b/utils/e2e-tests/ts/lib/abis/evmSwap.ts new file mode 100644 index 000000000..b66cf450b --- /dev/null +++ b/utils/e2e-tests/ts/lib/abis/evmSwap.ts @@ -0,0 +1,62 @@ +// /** +// * @title Evm Swap Interface +// * +// * An interface enabling swapping the funds from EVM accounts to +// * native Substrate accounts. +// * +// * Address: 0x0000000000000000000000000000000000000901 +// */ +// interface EvmSwap { +// /** +// * 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); +// } + +export default { + abi: [ + { + inputs: [ + { + internalType: "bytes32", + name: "nativeAddress", + type: "bytes32", + }, + ], + name: "swap", + outputs: [ + { + internalType: "bool", + name: "success", + type: "bool", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { indexed: true, internalType: "address", name: "to", type: "bytes32" }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Swap", + type: "event", + }, + ], +} as const; From 9ab94b512d6f3e9e7a09840bcf6343bfc3bb1f7a Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 28 Feb 2025 18:12:41 +0300 Subject: [PATCH 065/102] Implement evm to native tokens swap e2e test --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 utils/e2e-tests/ts/tests/swap/evmToNative.ts diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts new file mode 100644 index 000000000..5de7beb5d --- /dev/null +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from "vitest"; +import { RunNodeState, runNode } from "../../lib/node"; +import * as eth from "../../lib/ethViem"; +import "../../lib/expect"; +import { beforeEachWithCleanup } from "../../lib/lifecycle"; +import evmSwap from "../../lib/abis/evmSwap"; +import { decodeEventLog } from "viem"; + +const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000901"; + +describe("evm to native tokens swap", () => { + let node: RunNodeState; + let publicClient: eth.PublicClientWebSocket; + let devClients: eth.DevClientsWebSocket; + beforeEachWithCleanup(async (cleanup) => { + node = runNode({ args: ["--dev", "--tmp"] }, cleanup.push); + + await node.waitForBoot; + + publicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push); + devClients = eth.devClientsFromNodeWebSocket(node, cleanup.push); + }, 60 * 1000); + + it("success", async () => { + const [alice, _] = devClients; + + const swapBalance = 1_000_000n; + const targetNativeAccount = + "0x7700000000000000000000000000000000000000000000000000000000000077"; + + const swapTxHash = await alice.writeContract({ + abi: evmSwap.abi, + address: evmSwapPrecompileAddress, + functionName: "swap", + args: [targetNativeAccount], + value: swapBalance, + }); + + const swapTxReceipt = await publicClient.waitForTransactionReceipt({ + hash: swapTxHash, + timeout: 18_000, + }); + + expect(swapTxReceipt.status).toBe("success"); + + const log = swapTxReceipt.logs[0]!; + const event = decodeEventLog({ + abi: evmSwap.abi, + data: log.data, + topics: log.topics, + }); + + console.log(swapTxReceipt.logs); + expect(event).toEqual({ + eventName: "Swap", + args: { + from: alice.account.address, + to: targetNativeAccount, + value: swapBalance, + }, + }); + }); +}); From d14852eb5251377923960fb2bff76ec3ccc14835 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 28 Feb 2025 18:30:45 +0300 Subject: [PATCH 066/102] Remove description of the interface from abi --- utils/e2e-tests/ts/lib/abis/evmSwap.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/utils/e2e-tests/ts/lib/abis/evmSwap.ts b/utils/e2e-tests/ts/lib/abis/evmSwap.ts index b66cf450b..d4f5aa657 100644 --- a/utils/e2e-tests/ts/lib/abis/evmSwap.ts +++ b/utils/e2e-tests/ts/lib/abis/evmSwap.ts @@ -1,22 +1,3 @@ -// /** -// * @title Evm Swap Interface -// * -// * An interface enabling swapping the funds from EVM accounts to -// * native Substrate accounts. -// * -// * Address: 0x0000000000000000000000000000000000000901 -// */ -// interface EvmSwap { -// /** -// * 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); -// } - export default { abi: [ { From 24eeba5f396b83d1e8b859dd70bf2baa238fc341 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 28 Feb 2025 19:32:35 +0300 Subject: [PATCH 067/102] Integrate CallOrCreateInfo usage --- Cargo.lock | 1 + crates/pallet-evm-swap/Cargo.toml | 1 + crates/pallet-evm-swap/src/lib.rs | 22 +++++- .../src/tests/evm_to_native.rs | 75 ++++++++++--------- 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ece05105f..db91e873e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6169,6 +6169,7 @@ dependencies = [ name = "pallet-evm-swap" version = "0.1.0" dependencies = [ + "assert_matches", "ethereum", "fp-ethereum", "fp-evm", diff --git a/crates/pallet-evm-swap/Cargo.toml b/crates/pallet-evm-swap/Cargo.toml index 11c281960..b05d3b699 100644 --- a/crates/pallet-evm-swap/Cargo.toml +++ b/crates/pallet-evm-swap/Cargo.toml @@ -23,6 +23,7 @@ sp-core = { workspace = true } pallet-evm-balances = { path = "../pallet-evm-balances", features = ["default"] } pallet-evm-system = { path = "../pallet-evm-system", features = ["default"] } +assert_matches = { workspace = true } hex-literal = { workspace = true } pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index ffe8fdc58..e2490ef73 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -103,6 +103,13 @@ pub mod pallet { }, } + /// Possible error conditions during tokens swap. + #[pallet::error] + pub enum Error { + /// Ethereum transfer execution has not succeeded. + EthereumExecutionNotSucceeded, + } + #[pallet::call(weight(::WeightInfo))] impl Pallet { /// Swap balances. @@ -184,10 +191,23 @@ pub mod pallet { ethereum_transfer_transaction::(source_address, target_address, balance); let transaction_hash = transaction.hash(); - let post_info = + let (post_info, call_info) = pallet_ethereum::ValidatedTransaction::::apply(source_address, transaction) .map_err(|dispatch_error_with_post_info| dispatch_error_with_post_info.error)?; + match call_info { + fp_evm::CallOrCreateInfo::Call(execution_info) => { + match execution_info.exit_reason { + fp_evm::ExitReason::Succeed(_) => { + // We are fine. + } + _ => return Err(Error::::EthereumExecutionNotSucceeded.into()), + } + } + // We use explicitly `ethereum::TransactionAction::Call` in prepared transaction. + fp_evm::CallOrCreateInfo::Create(_) => unreachable!(), + } + debug_assert!( post_info == PostDispatchInfo { diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index 29c85f57e..ecba989fc 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -2,7 +2,7 @@ use fp_ethereum::ValidatedTransaction; use fp_evm::{ExitError, ExitReason, ExitSucceed}; use frame_support::{ dispatch::{Pays, PostDispatchInfo}, - traits::{fungible::Unbalanced, OnFinalize}, + traits::fungible::Unbalanced, }; use pallet_evm::GasWeightMapping; use precompile_utils::{EvmDataWriter, LogsBuilder}; @@ -58,12 +58,35 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) let transaction_hash = transaction.hash(); // Invoke the function under test. - let post_info = pallet_ethereum::ValidatedTransaction::::apply( + let (post_info, call_info) = pallet_ethereum::ValidatedTransaction::::apply( source_swap_evm_account(), transaction, ) .unwrap(); + match call_info { + fp_evm::CallOrCreateInfo::Call(execution_info) => { + assert_eq!( + execution_info.exit_reason, + ExitReason::Succeed(ExitSucceed::Returned) + ); + + // Verify that we have expected transaction log corresponding to swap execution. + let transaction_logs = execution_info.logs; + assert_eq!( + transaction_logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + precompile::SELECTOR_LOG_SWAP, + source_swap_evm_account(), + H256::from(target_swap_native_account().as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] + ); + } + // We use explicitly `ethereum::TransactionAction::Call` in prepared transaction. + fp_evm::CallOrCreateInfo::Create(_) => unreachable!(), + } + assert_eq!( post_info, PostDispatchInfo { @@ -109,26 +132,6 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) exit_reason: ExitReason::Succeed(ExitSucceed::Returned), extra_data: vec![], })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - // Verify that we have expected transaction log corresponding to swap execution. - let transaction_logs = pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs; - assert_eq!( - transaction_logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile::SELECTOR_LOG_SWAP, - source_swap_evm_account(), - H256::from(target_swap_native_account().as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); } /// This test verifies that the swap precompile call works in the happy path. @@ -176,12 +179,24 @@ fn run_failed_test_and_assert( let transaction_hash = transaction.hash(); // Invoke the function under test. - let post_info = pallet_ethereum::ValidatedTransaction::::apply( + let (post_info, call_info) = pallet_ethereum::ValidatedTransaction::::apply( source_swap_evm_account(), transaction, ) .unwrap(); + match call_info { + fp_evm::CallOrCreateInfo::Call(execution_info) => { + assert_eq!(execution_info.exit_reason, exit_reason); + + // Verify that we don't have a transaction log corresponding to swap execution. + let transaction_logs = execution_info.logs; + assert_eq!(transaction_logs, vec![]); + } + // We use explicitly `ethereum::TransactionAction::Call` in prepared transaction. + fp_evm::CallOrCreateInfo::Create(_) => unreachable!(), + } + assert_eq!( post_info, PostDispatchInfo { @@ -225,20 +240,6 @@ fn run_failed_test_and_assert( exit_reason, extra_data: vec![], })); - - // Finalize block to check transaction logs. - Ethereum::on_finalize(1); - - // Verify that we don't have a transaction log corresponding to swap execution. - assert_eq!( - pallet_ethereum::pallet::CurrentTransactionStatuses::::get() - .unwrap() - .first() - .cloned() - .unwrap() - .logs, - vec![] - ); } /// This test verifies that the swap precompile call behaves as expected when called without From 1ee7d0d0e7bc332178979922ac56c77e5ea14b6b Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 28 Feb 2025 19:35:53 +0300 Subject: [PATCH 068/102] Add swap_both_fails_bridge_evm_no_funds test --- .../src/tests/native_to_evm.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 8cb532fd2..8514190c4 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -237,3 +237,31 @@ fn swap_both_fails_bridge_evm_killed() { ); }); } + +/// This test verifies that both calls fail in case bridge evm account has no funds. +#[test] +fn swap_both_fails_bridge_evm_no_funds() { + new_test_ext().execute_with_ext(|_| { + EvmBalances::write_balance(&BridgePotEvm::get(), 0).unwrap(); + + // Invoke the `swap` under test. + assert_noop!( + EvmSwap::swap( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + 100, + ), + DispatchError::Token(TokenError::FundsUnavailable) + ); + + // Invoke the `swap_keep_alive` under test. + assert_noop!( + EvmSwap::swap_keep_alive( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + 100, + ), + DispatchError::Token(TokenError::FundsUnavailable) + ); + }); +} From 0bc056ffa6b4873b9f5dcb4451608ff8476b8127 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 28 Feb 2025 19:40:47 +0300 Subject: [PATCH 069/102] Add swap_both_fails_bridge_native_overflow test --- .../src/tests/native_to_evm.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 8514190c4..6bc27c29a 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -265,3 +265,31 @@ fn swap_both_fails_bridge_evm_no_funds() { ); }); } + +/// This test verifies that both calls fail in case bridge native balance results into overflow. +#[test] +fn swap_both_fails_bridge_native_overflow() { + new_test_ext().execute_with_ext(|_| { + Balances::write_balance(&BridgePotNative::get(), Balance::MAX).unwrap(); + + // Invoke the `swap` under test. + assert_noop!( + EvmSwap::swap( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + 100, + ), + DispatchError::Arithmetic(ArithmeticError::Overflow) + ); + + // Invoke the `swap_keep_alive` under test. + assert_noop!( + EvmSwap::swap_keep_alive( + RuntimeOrigin::signed(source_swap_native_account()), + target_swap_evm_account(), + 100, + ), + DispatchError::Arithmetic(ArithmeticError::Overflow) + ); + }); +} From 65bb7e88fef52a49e50c8a9d8e78ce1cc3ad4df2 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 28 Feb 2025 20:37:58 +0300 Subject: [PATCH 070/102] Check alice, bridgePotEvm, evmSwapPrecompile balances at evmToNative e2e test --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 5de7beb5d..298f6a7aa 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -7,6 +7,7 @@ import evmSwap from "../../lib/abis/evmSwap"; import { decodeEventLog } from "viem"; const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000901"; +const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; describe("evm to native tokens swap", () => { let node: RunNodeState; @@ -28,6 +29,13 @@ describe("evm to native tokens swap", () => { const targetNativeAccount = "0x7700000000000000000000000000000000000000000000000000000000000077"; + const aliceBalanceBefore = await publicClient.getBalance({ + address: alice.account.address, + }); + const bridgePotEvmAddressBalanceBefore = await publicClient.getBalance({ + address: bridgePotEvmAddress, + }); + const swapTxHash = await alice.writeContract({ abi: evmSwap.abi, address: evmSwapPrecompileAddress, @@ -59,5 +67,25 @@ describe("evm to native tokens swap", () => { value: swapBalance, }, }); + + const fee = + swapTxReceipt.cumulativeGasUsed * swapTxReceipt.effectiveGasPrice; + + const aliceBalanceAfter = await publicClient.getBalance({ + address: alice.account.address, + }); + expect(aliceBalanceAfter).toEqual(aliceBalanceBefore - swapBalance - fee); + + const bridgePotEvmAddressBalanceAfter = await publicClient.getBalance({ + address: bridgePotEvmAddress, + }); + expect(bridgePotEvmAddressBalanceAfter).toEqual( + bridgePotEvmAddressBalanceBefore + swapBalance + fee, + ); + + const evmSwapPrecompileBalance = await publicClient.getBalance({ + address: evmSwapPrecompileAddress, + }); + expect(evmSwapPrecompileBalance).toEqual(0n); }); }); From efe156f3c94c4135e3b472e18f6025a00dc6bc27 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 10:21:58 +0300 Subject: [PATCH 071/102] Add native related balances checks at evm to native e2e test --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 79 ++++++++++++++++---- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 298f6a7aa..ecc7727bb 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -5,36 +5,72 @@ import "../../lib/expect"; import { beforeEachWithCleanup } from "../../lib/lifecycle"; import evmSwap from "../../lib/abis/evmSwap"; import { decodeEventLog } from "viem"; +import * as substrate from "../../lib/substrate"; + +type SystemAccount = { + data: { + free: bigint; + }; +}; + +/// A helper function to get balance of native account. +const getNativeBalance = async ( + substrateApi: substrate.Api, + nativeAccount: string, +) => { + const systemAccount = (await substrateApi.query["system"]?.["account"]?.( + nativeAccount, + )) as unknown as SystemAccount; + + const free = systemAccount.data.free; + + // We should explicitly convert to native bigint for future math operations. + return BigInt(free); +}; const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000901"; const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; +const bridgePotNativeAccount = + "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; describe("evm to native tokens swap", () => { let node: RunNodeState; - let publicClient: eth.PublicClientWebSocket; - let devClients: eth.DevClientsWebSocket; + let ethPiblicClient: eth.PublicClientWebSocket; + let ethDevClients: eth.DevClientsWebSocket; + let substrateApi: substrate.Api; beforeEachWithCleanup(async (cleanup) => { node = runNode({ args: ["--dev", "--tmp"] }, cleanup.push); await node.waitForBoot; - publicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push); - devClients = eth.devClientsFromNodeWebSocket(node, cleanup.push); + ethPiblicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push); + ethDevClients = eth.devClientsFromNodeWebSocket(node, cleanup.push); + substrateApi = await substrate.apiFromNodeWebSocket(node, cleanup.push); }, 60 * 1000); it("success", async () => { - const [alice, _] = devClients; + const [alice, _] = ethDevClients; const swapBalance = 1_000_000n; const targetNativeAccount = "0x7700000000000000000000000000000000000000000000000000000000000077"; + const targetNativeAccountSs58 = + "hmqAEn816d1W6TxbT7Md2Zc4hq1AUXFiLEs8yXW5BCUHFx54W"; - const aliceBalanceBefore = await publicClient.getBalance({ + const aliceBalanceBefore = await ethPiblicClient.getBalance({ address: alice.account.address, }); - const bridgePotEvmAddressBalanceBefore = await publicClient.getBalance({ + const bridgePotEvmBalanceBefore = await ethPiblicClient.getBalance({ address: bridgePotEvmAddress, }); + const bridgePotNativeBalanceBefore = await getNativeBalance( + substrateApi, + bridgePotNativeAccount, + ); + const targetNativeAccountBalanceBefore = await getNativeBalance( + substrateApi, + targetNativeAccountSs58, + ); const swapTxHash = await alice.writeContract({ abi: evmSwap.abi, @@ -44,7 +80,7 @@ describe("evm to native tokens swap", () => { value: swapBalance, }); - const swapTxReceipt = await publicClient.waitForTransactionReceipt({ + const swapTxReceipt = await ethPiblicClient.waitForTransactionReceipt({ hash: swapTxHash, timeout: 18_000, }); @@ -58,7 +94,6 @@ describe("evm to native tokens swap", () => { topics: log.topics, }); - console.log(swapTxReceipt.logs); expect(event).toEqual({ eventName: "Swap", args: { @@ -71,19 +106,35 @@ describe("evm to native tokens swap", () => { const fee = swapTxReceipt.cumulativeGasUsed * swapTxReceipt.effectiveGasPrice; - const aliceBalanceAfter = await publicClient.getBalance({ + const aliceBalanceAfter = await ethPiblicClient.getBalance({ address: alice.account.address, }); expect(aliceBalanceAfter).toEqual(aliceBalanceBefore - swapBalance - fee); - const bridgePotEvmAddressBalanceAfter = await publicClient.getBalance({ + const bridgePotEvmBalanceAfter = await ethPiblicClient.getBalance({ address: bridgePotEvmAddress, }); - expect(bridgePotEvmAddressBalanceAfter).toEqual( - bridgePotEvmAddressBalanceBefore + swapBalance + fee, + expect(bridgePotEvmBalanceAfter).toEqual( + bridgePotEvmBalanceBefore + swapBalance + fee, + ); + + const bridgePotNativeBalanceAfter = await getNativeBalance( + substrateApi, + bridgePotNativeAccount, + ); + expect(bridgePotNativeBalanceAfter).toEqual( + bridgePotNativeBalanceBefore - swapBalance - fee, + ); + + const targetNativeAccountBalanceAfter = await getNativeBalance( + substrateApi, + targetNativeAccountSs58, + ); + expect(targetNativeAccountBalanceAfter).toEqual( + targetNativeAccountBalanceBefore + swapBalance, ); - const evmSwapPrecompileBalance = await publicClient.getBalance({ + const evmSwapPrecompileBalance = await ethPiblicClient.getBalance({ address: evmSwapPrecompileAddress, }); expect(evmSwapPrecompileBalance).toEqual(0n); From 256a006903d4d85052b31ae74ed67dd1c41c4475 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 10:25:56 +0300 Subject: [PATCH 072/102] Move getNativeBalance to swap utils --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 22 +----------------- utils/e2e-tests/ts/tests/swap/utils.ts | 24 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 21 deletions(-) create mode 100644 utils/e2e-tests/ts/tests/swap/utils.ts diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index ecc7727bb..03a24be72 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -6,27 +6,7 @@ import { beforeEachWithCleanup } from "../../lib/lifecycle"; import evmSwap from "../../lib/abis/evmSwap"; import { decodeEventLog } from "viem"; import * as substrate from "../../lib/substrate"; - -type SystemAccount = { - data: { - free: bigint; - }; -}; - -/// A helper function to get balance of native account. -const getNativeBalance = async ( - substrateApi: substrate.Api, - nativeAccount: string, -) => { - const systemAccount = (await substrateApi.query["system"]?.["account"]?.( - nativeAccount, - )) as unknown as SystemAccount; - - const free = systemAccount.data.free; - - // We should explicitly convert to native bigint for future math operations. - return BigInt(free); -}; +import { getNativeBalance } from "../swap/utils"; const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000901"; const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; diff --git a/utils/e2e-tests/ts/tests/swap/utils.ts b/utils/e2e-tests/ts/tests/swap/utils.ts new file mode 100644 index 000000000..2d2de63ff --- /dev/null +++ b/utils/e2e-tests/ts/tests/swap/utils.ts @@ -0,0 +1,24 @@ +//! Common swap utils. + +import * as substrate from "../../lib/substrate"; + +type SystemAccount = { + data: { + free: bigint; + }; +}; + +/// A helper function to get balance of native account. +export const getNativeBalance = async ( + substrateApi: substrate.Api, + nativeAccount: string, +) => { + const systemAccount = (await substrateApi.query["system"]?.["account"]?.( + nativeAccount, + )) as unknown as SystemAccount; + + const free = systemAccount.data.free; + + // We should explicitly convert to native bigint for future math operations. + return BigInt(free); +}; From c210dcc6e2527552e698457fe1a6181f660a3ffa Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 11:16:09 +0300 Subject: [PATCH 073/102] Add native balances checks at native to evm e2e test --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 9 +-- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 61 ++++++++++++++++---- utils/e2e-tests/ts/tests/swap/utils.ts | 4 ++ 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 03a24be72..fe48eaa0a 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -6,12 +6,13 @@ import { beforeEachWithCleanup } from "../../lib/lifecycle"; import evmSwap from "../../lib/abis/evmSwap"; import { decodeEventLog } from "viem"; import * as substrate from "../../lib/substrate"; -import { getNativeBalance } from "../swap/utils"; +import { + getNativeBalance, + bridgePotEvmAddress, + bridgePotNativeAccount, +} from "../swap/utils"; const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000901"; -const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; -const bridgePotNativeAccount = - "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; describe("evm to native tokens swap", () => { let node: RunNodeState; diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 6a8b25fbf..eb06f6059 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -5,6 +5,11 @@ import { beforeEachWithCleanup } from "../../lib/lifecycle"; import { Keyring } from "@polkadot/api"; import { Codec, IEvent } from "@polkadot/types/types"; import sendAndWait from "../../lib/substrateSendAndAwait"; +import { + getNativeBalance, + bridgePotEvmAddress, + bridgePotNativeAccount, +} from "../swap/utils"; type EvmSwapBalancesSwappedEvent = Record< "from" | "withdrawedAmount" | "to" | "depositedAmount" | "evmTransactionHash", @@ -16,7 +21,7 @@ type EthereumExecutedEvent = Record< Codec >; -const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; +type TransactionPaymentEvent = Record<"who" | "actualFee", Codec>; describe("native to evm tokens swap", () => { let node: RunNodeState; @@ -34,11 +39,17 @@ describe("native to evm tokens swap", () => { const alice = keyring.addFromUri("//Alice"); const targetEvmAddress = "0x1100000000000000000000000000000000000011"; - const swapBalance = 1_000_000; + const swapBalance = 1_000_000n; const swap = api.tx["evmSwap"]?.["swap"]; assert(swap); + const aliceBalanceBefore = await getNativeBalance(api, alice.address); + const bridgePotNativeBalanceBefore = await getNativeBalance( + api, + bridgePotNativeAccount, + ); + const { isCompleted, internalError, events, status, dispatchError } = await sendAndWait(swap(targetEvmAddress, swapBalance), { signWith: alice, @@ -51,6 +62,7 @@ describe("native to evm tokens swap", () => { let ewmSwapBalancesSwappedEvent; let ethereumExecutedEvent; + let transactionPaymentEvent; for (const item of events) { if ( @@ -69,41 +81,68 @@ describe("native to evm tokens swap", () => { EthereumExecutedEvent >; } + + if ( + item.event.section == "transactionPayment" && + item.event.method == "TransactionFeePaid" + ) { + transactionPaymentEvent = item.event as unknown as IEvent< + Codec[], + TransactionPaymentEvent + >; + } } assert(ewmSwapBalancesSwappedEvent); assert(ethereumExecutedEvent); + assert(transactionPaymentEvent); + // Events related asserts. expect(ewmSwapBalancesSwappedEvent.data.from.toPrimitive()).toEqual( alice.address, ); - expect( - ewmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive(), + BigInt( + ewmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as unknown as bigint, + ), ).toEqual(swapBalance); - expect(ewmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( targetEvmAddress, ); - expect( - ewmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive(), + BigInt( + ewmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as unknown as bigint, + ), ).toEqual(swapBalance); - expect(ewmSwapBalancesSwappedEvent.data.evmTransactionHash).toEqual( ethereumExecutedEvent.data.transactionHash, ); - expect(ethereumExecutedEvent.data.from.toPrimitive()).toEqual( bridgePotEvmAddress, ); - expect(ethereumExecutedEvent.data.to.toPrimitive()).toEqual( targetEvmAddress, ); - expect(ethereumExecutedEvent.data.exitReason.toPrimitive()).toEqual({ succeed: "Stopped", }); + + const fee = BigInt( + transactionPaymentEvent.data.actualFee.toPrimitive() as unknown as bigint, + ); + expect(transactionPaymentEvent.data.who.toPrimitive()).toEqual( + alice.address, + ); + + const aliceBalanceAfter = await getNativeBalance(api, alice.address); + expect(aliceBalanceAfter).toEqual(aliceBalanceBefore - swapBalance - fee); + + const bridgePotNativeBalanceAfter = await getNativeBalance( + api, + bridgePotNativeAccount, + ); + expect(bridgePotNativeBalanceAfter).toEqual( + bridgePotNativeBalanceBefore + swapBalance, + ); }); }); diff --git a/utils/e2e-tests/ts/tests/swap/utils.ts b/utils/e2e-tests/ts/tests/swap/utils.ts index 2d2de63ff..d939e4eef 100644 --- a/utils/e2e-tests/ts/tests/swap/utils.ts +++ b/utils/e2e-tests/ts/tests/swap/utils.ts @@ -2,6 +2,10 @@ import * as substrate from "../../lib/substrate"; +export const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; +export const bridgePotNativeAccount = + "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; + type SystemAccount = { data: { free: bigint; From 0a1572f452ce180210e931d1d3f87b4a03b29069 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 11:20:16 +0300 Subject: [PATCH 074/102] Add evm balances checks at native to evm e2e test --- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 41 ++++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index eb06f6059..5e89f2322 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -5,6 +5,7 @@ import { beforeEachWithCleanup } from "../../lib/lifecycle"; import { Keyring } from "@polkadot/api"; import { Codec, IEvent } from "@polkadot/types/types"; import sendAndWait from "../../lib/substrateSendAndAwait"; +import * as eth from "../../lib/ethViem"; import { getNativeBalance, bridgePotEvmAddress, @@ -25,13 +26,15 @@ type TransactionPaymentEvent = Record<"who" | "actualFee", Codec>; describe("native to evm tokens swap", () => { let node: RunNodeState; - let api: substrate.Api; + let substrateApi: substrate.Api; + let ethPiblicClient: eth.PublicClientWebSocket; beforeEachWithCleanup(async (cleanup) => { node = runNode({ args: ["--dev", "--tmp"] }, cleanup.push); await node.waitForBoot; - api = await substrate.apiFromNodeWebSocket(node, cleanup.push); + substrateApi = await substrate.apiFromNodeWebSocket(node, cleanup.push); + ethPiblicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push); }, 60 * 1000); it("success", async () => { @@ -41,14 +44,23 @@ describe("native to evm tokens swap", () => { const targetEvmAddress = "0x1100000000000000000000000000000000000011"; const swapBalance = 1_000_000n; - const swap = api.tx["evmSwap"]?.["swap"]; + const swap = substrateApi.tx["evmSwap"]?.["swap"]; assert(swap); - const aliceBalanceBefore = await getNativeBalance(api, alice.address); + const aliceBalanceBefore = await getNativeBalance( + substrateApi, + alice.address, + ); const bridgePotNativeBalanceBefore = await getNativeBalance( - api, + substrateApi, bridgePotNativeAccount, ); + const targetEvmBalanceBefore = await ethPiblicClient.getBalance({ + address: targetEvmAddress, + }); + const bridgePotEvmBalanceBefore = await ethPiblicClient.getBalance({ + address: bridgePotEvmAddress, + }); const { isCompleted, internalError, events, status, dispatchError } = await sendAndWait(swap(targetEvmAddress, swapBalance), { @@ -134,15 +146,30 @@ describe("native to evm tokens swap", () => { alice.address, ); - const aliceBalanceAfter = await getNativeBalance(api, alice.address); + const aliceBalanceAfter = await getNativeBalance( + substrateApi, + alice.address, + ); expect(aliceBalanceAfter).toEqual(aliceBalanceBefore - swapBalance - fee); const bridgePotNativeBalanceAfter = await getNativeBalance( - api, + substrateApi, bridgePotNativeAccount, ); expect(bridgePotNativeBalanceAfter).toEqual( bridgePotNativeBalanceBefore + swapBalance, ); + + const targetEvmBalanceAfter = await ethPiblicClient.getBalance({ + address: targetEvmAddress, + }); + expect(targetEvmBalanceAfter).toEqual(targetEvmBalanceBefore + swapBalance); + + const bridgePotEvmBalanceAfter = await ethPiblicClient.getBalance({ + address: bridgePotEvmAddress, + }); + expect(bridgePotEvmBalanceAfter).toEqual( + bridgePotEvmBalanceBefore - swapBalance, + ); }); }); From 7a003d8b2bea662d01d8287eafffab5bf47a66b5 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 11:24:18 +0300 Subject: [PATCH 075/102] Some e2e renaimings --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 20 +++++++++++--------- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 16 ++++++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index fe48eaa0a..9f17fe60f 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -33,12 +33,12 @@ describe("evm to native tokens swap", () => { const [alice, _] = ethDevClients; const swapBalance = 1_000_000n; - const targetNativeAccount = + const targetSwapNativeAccount = "0x7700000000000000000000000000000000000000000000000000000000000077"; - const targetNativeAccountSs58 = + const targetSwapNativeAccountSs58 = "hmqAEn816d1W6TxbT7Md2Zc4hq1AUXFiLEs8yXW5BCUHFx54W"; - const aliceBalanceBefore = await ethPiblicClient.getBalance({ + const sourceSwapBalanceBefore = await ethPiblicClient.getBalance({ address: alice.account.address, }); const bridgePotEvmBalanceBefore = await ethPiblicClient.getBalance({ @@ -50,14 +50,14 @@ describe("evm to native tokens swap", () => { ); const targetNativeAccountBalanceBefore = await getNativeBalance( substrateApi, - targetNativeAccountSs58, + targetSwapNativeAccountSs58, ); const swapTxHash = await alice.writeContract({ abi: evmSwap.abi, address: evmSwapPrecompileAddress, functionName: "swap", - args: [targetNativeAccount], + args: [targetSwapNativeAccount], value: swapBalance, }); @@ -79,7 +79,7 @@ describe("evm to native tokens swap", () => { eventName: "Swap", args: { from: alice.account.address, - to: targetNativeAccount, + to: targetSwapNativeAccount, value: swapBalance, }, }); @@ -87,10 +87,12 @@ describe("evm to native tokens swap", () => { const fee = swapTxReceipt.cumulativeGasUsed * swapTxReceipt.effectiveGasPrice; - const aliceBalanceAfter = await ethPiblicClient.getBalance({ + const sourceSwapBalanceAfter = await ethPiblicClient.getBalance({ address: alice.account.address, }); - expect(aliceBalanceAfter).toEqual(aliceBalanceBefore - swapBalance - fee); + expect(sourceSwapBalanceAfter).toEqual( + sourceSwapBalanceBefore - swapBalance - fee, + ); const bridgePotEvmBalanceAfter = await ethPiblicClient.getBalance({ address: bridgePotEvmAddress, @@ -109,7 +111,7 @@ describe("evm to native tokens swap", () => { const targetNativeAccountBalanceAfter = await getNativeBalance( substrateApi, - targetNativeAccountSs58, + targetSwapNativeAccountSs58, ); expect(targetNativeAccountBalanceAfter).toEqual( targetNativeAccountBalanceBefore + swapBalance, diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 5e89f2322..d53615681 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -47,7 +47,7 @@ describe("native to evm tokens swap", () => { const swap = substrateApi.tx["evmSwap"]?.["swap"]; assert(swap); - const aliceBalanceBefore = await getNativeBalance( + const sourceSwapBalanceBefore = await getNativeBalance( substrateApi, alice.address, ); @@ -55,7 +55,7 @@ describe("native to evm tokens swap", () => { substrateApi, bridgePotNativeAccount, ); - const targetEvmBalanceBefore = await ethPiblicClient.getBalance({ + const targetEvmAccountBalanceBefore = await ethPiblicClient.getBalance({ address: targetEvmAddress, }); const bridgePotEvmBalanceBefore = await ethPiblicClient.getBalance({ @@ -146,11 +146,13 @@ describe("native to evm tokens swap", () => { alice.address, ); - const aliceBalanceAfter = await getNativeBalance( + const sourceSwapBalanceAfter = await getNativeBalance( substrateApi, alice.address, ); - expect(aliceBalanceAfter).toEqual(aliceBalanceBefore - swapBalance - fee); + expect(sourceSwapBalanceAfter).toEqual( + sourceSwapBalanceBefore - swapBalance - fee, + ); const bridgePotNativeBalanceAfter = await getNativeBalance( substrateApi, @@ -160,10 +162,12 @@ describe("native to evm tokens swap", () => { bridgePotNativeBalanceBefore + swapBalance, ); - const targetEvmBalanceAfter = await ethPiblicClient.getBalance({ + const targetEvmAccountBalanceAfter = await ethPiblicClient.getBalance({ address: targetEvmAddress, }); - expect(targetEvmBalanceAfter).toEqual(targetEvmBalanceBefore + swapBalance); + expect(targetEvmAccountBalanceAfter).toEqual( + targetEvmAccountBalanceBefore + swapBalance, + ); const bridgePotEvmBalanceAfter = await ethPiblicClient.getBalance({ address: bridgePotEvmAddress, From a3c6114755d38cb551dd84acb74c3c99c4cdf3db Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 11:37:06 +0300 Subject: [PATCH 076/102] Move getNativeBalance to substrate lib --- .../ts/{tests/swap/utils.ts => lib/substrateUtils.ts} | 10 +++------- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 9 ++++----- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 10 +++++----- 3 files changed, 12 insertions(+), 17 deletions(-) rename utils/e2e-tests/ts/{tests/swap/utils.ts => lib/substrateUtils.ts} (56%) diff --git a/utils/e2e-tests/ts/tests/swap/utils.ts b/utils/e2e-tests/ts/lib/substrateUtils.ts similarity index 56% rename from utils/e2e-tests/ts/tests/swap/utils.ts rename to utils/e2e-tests/ts/lib/substrateUtils.ts index d939e4eef..dc902396b 100644 --- a/utils/e2e-tests/ts/tests/swap/utils.ts +++ b/utils/e2e-tests/ts/lib/substrateUtils.ts @@ -1,10 +1,6 @@ -//! Common swap utils. +//! Common substrate utils. -import * as substrate from "../../lib/substrate"; - -export const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; -export const bridgePotNativeAccount = - "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; +import * as substrate from "../lib/substrate"; type SystemAccount = { data: { @@ -23,6 +19,6 @@ export const getNativeBalance = async ( const free = systemAccount.data.free; - // We should explicitly convert to native bigint for future math operations. + // We should explicitly convert to native bigint for math operations. return BigInt(free); }; diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 9f17fe60f..a51c48e9c 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -6,13 +6,12 @@ import { beforeEachWithCleanup } from "../../lib/lifecycle"; import evmSwap from "../../lib/abis/evmSwap"; import { decodeEventLog } from "viem"; import * as substrate from "../../lib/substrate"; -import { - getNativeBalance, - bridgePotEvmAddress, - bridgePotNativeAccount, -} from "../swap/utils"; +import { getNativeBalance } from "../../lib/substrateUtils"; const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000901"; +const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; +const bridgePotNativeAccount = + "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; describe("evm to native tokens swap", () => { let node: RunNodeState; diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index d53615681..63a575bf4 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -6,11 +6,7 @@ import { Keyring } from "@polkadot/api"; import { Codec, IEvent } from "@polkadot/types/types"; import sendAndWait from "../../lib/substrateSendAndAwait"; import * as eth from "../../lib/ethViem"; -import { - getNativeBalance, - bridgePotEvmAddress, - bridgePotNativeAccount, -} from "../swap/utils"; +import { getNativeBalance } from "../../lib/substrateUtils"; type EvmSwapBalancesSwappedEvent = Record< "from" | "withdrawedAmount" | "to" | "depositedAmount" | "evmTransactionHash", @@ -24,6 +20,10 @@ type EthereumExecutedEvent = Record< type TransactionPaymentEvent = Record<"who" | "actualFee", Codec>; +const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; +const bridgePotNativeAccount = + "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; + describe("native to evm tokens swap", () => { let node: RunNodeState; let substrateApi: substrate.Api; From 55f249e785f6a6e196e5369aaea17e7e0244b7ea Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 12:17:38 +0300 Subject: [PATCH 077/102] Add evm swap tests at humanode-runtime --- crates/humanode-runtime/src/tests/evm_swap.rs | 240 ++++++++++++++++++ crates/humanode-runtime/src/tests/mod.rs | 1 + 2 files changed, 241 insertions(+) create mode 100644 crates/humanode-runtime/src/tests/evm_swap.rs diff --git a/crates/humanode-runtime/src/tests/evm_swap.rs b/crates/humanode-runtime/src/tests/evm_swap.rs new file mode 100644 index 000000000..30d8ddae1 --- /dev/null +++ b/crates/humanode-runtime/src/tests/evm_swap.rs @@ -0,0 +1,240 @@ +//! Tests to verify evm swap related basic operations. + +// Allow simple integer arithmetic in tests. +#![allow(clippy::arithmetic_side_effects)] + +use frame_support::{assert_ok, once_cell::sync::Lazy, traits::fungible::Inspect}; +use precompile_utils::{EvmDataWriter, LogsBuilder}; +use sp_core::H160; + +use super::*; +use crate::dev_utils::*; +use crate::opaque::SessionKeys; + +pub(crate) static PRECOMPILE_ADDRESS: Lazy = Lazy::new(|| H160::from_low_u64_be(0x901)); +pub(crate) static GAS_PRICE: Lazy = + Lazy::new(|| ::FeeCalculator::min_gas_price().0); + +const INIT_BALANCE: Balance = 10u128.pow(18 + 6); + +/// Build test externalities from the custom genesis. +/// Using this call requires manual assertions on the genesis init logic. +fn new_test_ext_with() -> sp_io::TestExternalities { + let authorities = [authority_keys("Alice")]; + let bootnodes = vec![account_id("Alice")]; + + let endowed_accounts = [account_id("Alice"), account_id("Bob")]; + let pot_accounts = vec![FeesPot::account_id()]; + + let evm_endowed_accounts = vec![evm_account_id("EvmAlice"), evm_account_id("EvmBob")]; + // Build test genesis. + let config = GenesisConfig { + balances: BalancesConfig { + balances: { + endowed_accounts + .iter() + .cloned() + .chain(pot_accounts) + .map(|k| (k, INIT_BALANCE)) + .chain([ + (TreasuryPot::account_id(), 10 * INIT_BALANCE), + ( + TokenClaimsPot::account_id(), + >::minimum_balance(), + ), + ( + NativeToEvmSwapBridgePot::account_id(), + >::minimum_balance(), + ), + ]) + .collect() + }, + }, + session: SessionConfig { + keys: authorities + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + SessionKeys { + babe: x.1.clone(), + grandpa: x.2.clone(), + im_online: x.3.clone(), + }, + ) + }) + .collect::>(), + }, + babe: BabeConfig { + authorities: vec![], + epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG), + }, + bootnodes: BootnodesConfig { + bootnodes: bootnodes.try_into().unwrap(), + }, + evm: EVMConfig { + accounts: { + let init_genesis_account = fp_evm::GenesisAccount { + balance: INIT_BALANCE.into(), + code: Default::default(), + nonce: Default::default(), + storage: Default::default(), + }; + + evm_endowed_accounts + .into_iter() + .map(|k| (k, init_genesis_account.clone())) + .chain([( + EvmToNativeSwapBridgePot::account_id(), + fp_evm::GenesisAccount { + balance: >::minimum_balance() + .into(), + code: Default::default(), + nonce: Default::default(), + storage: Default::default(), + }, + )]) + .collect() + }, + }, + ..Default::default() + }; + let storage = config.build_storage().unwrap(); + + // Make test externalities from the storage. + storage.into() +} + +/// This test verifies that bridges initialization has been applied at genesis. +#[test] +fn currencies_are_balanced() { + // Build the state from the config. + new_test_ext_with().execute_with(move || { + assert_eq!( + BalancedCurrencySwapBridgesInitializer::last_initializer_version(), + pallet_balanced_currency_swap_bridges_initializer::CURRENT_BRIDGES_INITIALIZER_VERSION + ); + assert!(BalancedCurrencySwapBridgesInitializer::is_balanced().unwrap()); + }) +} + +/// This test verifies that evm swap native call works in the happy path. +#[test] +fn evm_swap_native_call_works() { + // Build the state from the config. + new_test_ext_with().execute_with(move || { + let alice_balance_before = Balances::total_balance(&account_id("Alice")); + let bridge_pot_native_account_balance_before = + Balances::total_balance(&NativeToEvmSwapBridgePot::account_id()); + let alice_evm_balance_before = EvmBalances::total_balance(&evm_account_id("EvmAlice")); + let bridge_pot_evm_account_balance_before = + EvmBalances::total_balance(&EvmToNativeSwapBridgePot::account_id()); + let swap_balance: Balance = 1000; + + // Make swap. + assert_ok!(EvmSwap::swap( + Some(account_id("Alice")).into(), + evm_account_id("EvmAlice"), + swap_balance + )); + + // Assert state changes. + assert!(BalancedCurrencySwapBridgesInitializer::is_balanced().unwrap()); + assert_eq!( + Balances::total_balance(&account_id("Alice")), + alice_balance_before - swap_balance + ); + assert_eq!( + Balances::total_balance(&NativeToEvmSwapBridgePot::account_id()), + bridge_pot_native_account_balance_before + swap_balance + ); + assert_eq!( + EvmBalances::total_balance(&evm_account_id("EvmAlice")), + alice_evm_balance_before + swap_balance + ); + assert_eq!( + EvmBalances::total_balance(&EvmToNativeSwapBridgePot::account_id()), + bridge_pot_evm_account_balance_before - swap_balance + ); + }) +} + +/// This test verifies that the ewm swap precompile call works in the happy path. +#[test] +fn ewm_swap_precompile_call_works() { + // Build the state from the config. + new_test_ext_with().execute_with(move || { + let alice_balance_before = Balances::total_balance(&account_id("Alice")); + let bridge_pot_native_account_balance_before = + Balances::total_balance(&NativeToEvmSwapBridgePot::account_id()); + let alice_evm_balance_before = EvmBalances::total_balance(&evm_account_id("EvmAlice")); + let bridge_pot_evm_account_balance_before = + EvmBalances::total_balance(&EvmToNativeSwapBridgePot::account_id()); + let fees_pot_balance_before = Balances::total_balance(&FeesPot::account_id()); + let swap_balance: Balance = 1000; + + let expected_gas_usage: u64 = 21216 + 560; + let expected_fee: Balance = + u128::from(expected_gas_usage) * u128::try_from(*GAS_PRICE).unwrap(); + + // Invoke the function under test. + let execinfo = ::Runner::call( + evm_account_id("EvmAlice"), + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(pallet_evm_swap::precompile::Action::Swap) + .write(H256::from(account_id("Alice").as_ref())) + .build(), + swap_balance.into(), + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) + ); + assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); + assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); + assert_eq!( + execinfo.logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + pallet_evm_swap::precompile::SELECTOR_LOG_SWAP, + evm_account_id("EvmAlice"), + H256::from(account_id("Alice").as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] + ); + + // Assert state changes. + assert!(BalancedCurrencySwapBridgesInitializer::is_balanced().unwrap()); + assert_eq!( + Balances::total_balance(&FeesPot::account_id()), + fees_pot_balance_before + expected_fee + ); + assert_eq!( + Balances::total_balance(&account_id("Alice")), + alice_balance_before + swap_balance + ); + assert_eq!( + Balances::total_balance(&NativeToEvmSwapBridgePot::account_id()), + bridge_pot_native_account_balance_before - swap_balance - expected_fee + ); + assert_eq!( + EvmBalances::total_balance(&evm_account_id("EvmAlice")), + alice_evm_balance_before - swap_balance - expected_fee + ); + assert_eq!( + EvmBalances::total_balance(&EvmToNativeSwapBridgePot::account_id()), + bridge_pot_evm_account_balance_before + swap_balance + expected_fee + ); + }) +} diff --git a/crates/humanode-runtime/src/tests/mod.rs b/crates/humanode-runtime/src/tests/mod.rs index 9502ad313..5484ef9cb 100644 --- a/crates/humanode-runtime/src/tests/mod.rs +++ b/crates/humanode-runtime/src/tests/mod.rs @@ -2,6 +2,7 @@ use super::*; mod claims_and_vesting; mod currency_swap; +mod evm_swap; mod fees; mod fixed_supply; mod genesis_config; From 85782b42f8ca0d9699926f850cdaea1134c7ec3f Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 13:43:42 +0300 Subject: [PATCH 078/102] Use runner at evm to native unit tests at pallet-evm-swap --- .../src/tests/evm_to_native.rs | 662 +++++++----------- .../src/tests/native_to_evm.rs | 4 +- 2 files changed, 270 insertions(+), 396 deletions(-) diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs index ecba989fc..8559bc7dc 100644 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ b/crates/pallet-evm-swap/src/tests/evm_to_native.rs @@ -1,10 +1,6 @@ -use fp_ethereum::ValidatedTransaction; -use fp_evm::{ExitError, ExitReason, ExitSucceed}; -use frame_support::{ - dispatch::{Pays, PostDispatchInfo}, - traits::fungible::Unbalanced, -}; -use pallet_evm::GasWeightMapping; +use fp_evm::{ExitError, ExitReason}; +use frame_support::traits::fungible::Unbalanced; +use pallet_evm::Runner; use precompile_utils::{EvmDataWriter, LogsBuilder}; use sp_core::H256; @@ -12,9 +8,7 @@ use crate::{mock::*, *}; /// Returns source swap evm account used in tests. fn source_swap_evm_account() -> EvmAccountId { - EvmAccountId::from(hex_literal::hex!( - "1100000000000000000000000000000000000011" - )) + alice_evm() } /// Returns target swap native account used in tests. @@ -24,8 +18,17 @@ fn target_swap_native_account() -> AccountId { )) } +/// A utility that performs gas to fee computation. +fn gas_to_fee(gas: u64) -> Balance { + Balance::from(gas) * Balance::try_from(*GAS_PRICE).unwrap() +} + /// A helper function to run succeeded test and assert state changes. -fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) { +fn run_succeeded_test_and_assert( + swap_balance: Balance, + expected_gas_usage: u64, + expected_fee: Balance, +) { let source_swap_evm_account_balance_before = EvmBalances::total_balance(&source_swap_evm_account()); let bridge_pot_evm_account_balance_before = EvmBalances::total_balance(&BridgePotEvm::get()); @@ -33,79 +36,48 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) let target_swap_native_account_balance_before = Balances::total_balance(&target_swap_native_account()); - // Set block number to enable events. - System::set_block_number(1); - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - let transaction_hash = transaction.hash(); - // Invoke the function under test. - let (post_info, call_info) = pallet_ethereum::ValidatedTransaction::::apply( + let execinfo = ::Runner::call( source_swap_evm_account(), - transaction, + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + 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, + None, + None, + ::config(), ) .unwrap(); - - match call_info { - fp_evm::CallOrCreateInfo::Call(execution_info) => { - assert_eq!( - execution_info.exit_reason, - ExitReason::Succeed(ExitSucceed::Returned) - ); - - // Verify that we have expected transaction log corresponding to swap execution. - let transaction_logs = execution_info.logs; - assert_eq!( - transaction_logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile::SELECTOR_LOG_SWAP, - source_swap_evm_account(), - H256::from(target_swap_native_account().as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); - } - // We use explicitly `ethereum::TransactionAction::Call` in prepared transaction. - fp_evm::CallOrCreateInfo::Create(_) => unreachable!(), - } - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, + execinfo.exit_reason, + fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) + ); + assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); + assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); + assert_eq!( + execinfo.logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + precompile::SELECTOR_LOG_SWAP, + source_swap_evm_account(), + H256::from(target_swap_native_account().as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] ); // Assert state changes. - // Verify that source swap evm balance has been decreased by swap value. + // Verify that source swap evm balance has been decreased by swap value and fee. assert_eq!( ::total_balance(&source_swap_evm_account()), - source_swap_evm_account_balance_before - swap_balance, + source_swap_evm_account_balance_before - swap_balance - expected_fee, ); // Verify that bridge pot evm balance has been increased by swap value. assert_eq!( @@ -124,21 +96,16 @@ fn run_succeeded_test_and_assert(swap_balance: Balance, expected_gas_usage: u64) ); // Verify that precompile balance remains the same. assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - // Verify that we have a corresponding ethereum event about succeeded transaction. - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: source_swap_evm_account(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason: ExitReason::Succeed(ExitSucceed::Returned), - extra_data: vec![], - })); } /// This test verifies that the swap precompile call works in the happy path. #[test] fn swap_works() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(100, 21216 + 200); + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_succeeded_test_and_assert(100, expected_gas_usage, expected_fee); }); } @@ -148,7 +115,14 @@ fn swap_works() { #[test] fn swap_works_almost_full_balance() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(INIT_BALANCE - 1, 21216 + 200); + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_succeeded_test_and_assert( + INIT_BALANCE - expected_fee - 1, + expected_gas_usage, + expected_fee, + ); }); } @@ -156,15 +130,25 @@ fn swap_works_almost_full_balance() { #[test] fn swap_works_full_balance() { new_test_ext().execute_with_ext(|_| { - run_succeeded_test_and_assert(INIT_BALANCE, 21216 + 200); + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_succeeded_test_and_assert( + INIT_BALANCE - expected_fee, + expected_gas_usage, + expected_fee, + ); }); } /// A helper function to run failed test and assert state changes. fn run_failed_test_and_assert( + input: Vec, + value: U256, expected_gas_usage: u64, - transaction: pallet_ethereum::Transaction, - exit_reason: ExitReason, + expected_fee: Balance, + expected_exit_reason: fp_evm::ExitReason, + expected_exit_value: Vec, ) { let source_swap_evm_account_balance_before = EvmBalances::total_balance(&source_swap_evm_account()); @@ -173,47 +157,33 @@ fn run_failed_test_and_assert( let target_swap_native_account_balance_before = Balances::total_balance(&target_swap_native_account()); - // Set block number to enable events. - System::set_block_number(1); - - let transaction_hash = transaction.hash(); - // Invoke the function under test. - let (post_info, call_info) = pallet_ethereum::ValidatedTransaction::::apply( + let execinfo = ::Runner::call( source_swap_evm_account(), - transaction, + *PRECOMPILE_ADDRESS, + input, + value, + expected_gas_usage, // the exact amount of fee we'll be using + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), ) .unwrap(); - - match call_info { - fp_evm::CallOrCreateInfo::Call(execution_info) => { - assert_eq!(execution_info.exit_reason, exit_reason); - - // Verify that we don't have a transaction log corresponding to swap execution. - let transaction_logs = execution_info.logs; - assert_eq!(transaction_logs, vec![]); - } - // We use explicitly `ethereum::TransactionAction::Call` in prepared transaction. - fp_evm::CallOrCreateInfo::Create(_) => unreachable!(), - } - - assert_eq!( - post_info, - PostDispatchInfo { - actual_weight: Some( - ::GasWeightMapping::gas_to_weight( - expected_gas_usage, - true - ) - ), - pays_fee: Pays::No - }, - ); + assert_eq!(execinfo.exit_reason, expected_exit_reason); + assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); + assert_eq!(execinfo.value, expected_exit_value); + assert_eq!(execinfo.logs, vec![]); // Verify that source swap evm balance remains the same. assert_eq!( ::total_balance(&source_swap_evm_account()), - source_swap_evm_account_balance_before, + source_swap_evm_account_balance_before - expected_fee, ); // Verify that bridge pot evm balance remains the same. assert_eq!( @@ -232,385 +202,291 @@ fn run_failed_test_and_assert( ); // Verify that precompile balance remains the same. assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - // Verify that we have a corresponding ethereum event about failed transaction. - System::assert_has_event(RuntimeEvent::Ethereum(pallet_ethereum::Event::Executed { - from: source_swap_evm_account(), - to: *PRECOMPILE_ADDRESS, - transaction_hash, - exit_reason, - extra_data: vec![], - })); } -/// This test verifies that the swap precompile call behaves as expected when called without -/// the sufficient balance. +/// This test verifies that the swap precompile call fails when estimated swapped balance is +/// less or equal than native token existential deposit. #[test] -fn swap_fail_source_balance_no_funds() { +fn swap_fail_target_balance_below_ed() { new_test_ext().execute_with_ext(|_| { - let swap_balance = INIT_BALANCE + 1; // more than we have - let expected_gas_usage: u64 = 21216; // precompile gas cost is not included - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); run_failed_test_and_assert( - expected_gas_usage, - transaction, - ExitReason::Error(ExitError::OutOfFund), - ); - }); -} - -/// This test verifies that the swap precompile call behaves as expected when -/// estimated swapped balance less or equal than native token existential deposit. -#[test] -fn swap_fail_target_balance_below_ed() { - new_test_ext().execute_with_ext(|_| { - let swap_balance = 1; - let expected_gas_usage: u64 = 50_000; // all gas will be consumed - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + EvmDataWriter::new_with_selector(precompile::Action::Swap) .write(H256::from(target_swap_native_account().as_ref())) .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - - run_failed_test_and_assert( + U256::from(1), expected_gas_usage, - transaction, + expected_fee, ExitReason::Error(ExitError::Other( "resulted balance is less than existential deposit".into(), )), + EvmDataWriter::new().build(), ); }); } -/// This test verifies that the swap precompile call behaves as expected when -/// estimated swapped balance results into target swap native account balance overflow. +/// This test verifies that the swap precompile call fails when estimated swapped balance results +/// into target swap native account balance overflow. #[test] fn swap_fail_target_balance_overflow() { new_test_ext().execute_with_ext(|_| { - let swap_balance = 1; - let expected_gas_usage: u64 = 50_000; // all gas will be consumed + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); Balances::write_balance(&target_swap_native_account(), Balance::MAX).unwrap(); - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + run_failed_test_and_assert( + EvmDataWriter::new_with_selector(precompile::Action::Swap) .write(H256::from(target_swap_native_account().as_ref())) .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - - run_failed_test_and_assert( + U256::from(1), expected_gas_usage, - transaction, + expected_fee, ExitReason::Error(ExitError::Other("unable to execute swap".into())), + EvmDataWriter::new().build(), ); }); } -/// This test verifies that the swap precompile call behaves as expected when -/// swapped balance results into bridge pot evm account balance overflow. +/// This test verifies that the swap precompile call fails when swapped balance results into +/// bridge pot evm account balance overflow. #[test] fn swap_fail_bridge_evm_overflow() { new_test_ext().execute_with_ext(|_| { - let swap_balance = 100; - let expected_gas_usage: u64 = 50_000; // all gas will be consumed + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); EvmBalances::write_balance(&BridgePotEvm::get(), Balance::MAX).unwrap(); - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + run_failed_test_and_assert( + EvmDataWriter::new_with_selector(precompile::Action::Swap) .write(H256::from(target_swap_native_account().as_ref())) .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - - run_failed_test_and_assert( + U256::from(100), expected_gas_usage, - transaction, + expected_fee, ExitReason::Error(ExitError::Other("unable to execute swap".into())), + EvmDataWriter::new().build(), ); }); } -/// This test verifies that the swap precompile call behaves as expected when -/// swapped balance results into bridge pot evm account balance overflow. +/// This test verifies that the swap precompile call fails when swap results into killing bridge +/// pot native account. #[test] fn swap_fail_bridge_native_killed() { new_test_ext().execute_with_ext(|_| { - let swap_balance = BRIDGE_INIT_BALANCE; - let expected_gas_usage: u64 = 50_000; // all gas will be consumed - - EvmBalances::write_balance(&source_swap_evm_account(), BRIDGE_INIT_BALANCE).unwrap(); - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + EvmBalances::write_balance( + &source_swap_evm_account(), + INIT_BALANCE + BRIDGE_INIT_BALANCE, + ) + .unwrap(); run_failed_test_and_assert( + EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(BRIDGE_INIT_BALANCE), expected_gas_usage, - transaction, + expected_fee, ExitReason::Error(ExitError::Other("account would be killed".into())), + EvmDataWriter::new().build(), ); }); } -/// This test verifies that the swap precompile call behaves as expected when a bad selector is -/// passed. +/// This test verifies that the swap precompile call fails when a bad selector is passed. #[test] fn swap_fail_bad_selector() { new_test_ext().execute_with_ext(|_| { - let swap_balance = 1; - let expected_gas_usage: u64 = 50_000; // all gas will be consumed - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(111_u32) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); run_failed_test_and_assert( - expected_gas_usage, - transaction, - ExitReason::Error(ExitError::Other("invalid function selector".into())), - ); - }); -} - -/// 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. -#[test] -fn swap_fail_value_overflow() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 21216; // precompile gas cost is not included - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::MAX, // use max value - input: EvmDataWriter::new_with_selector(precompile::Action::Swap) + EvmDataWriter::new_with_selector(111_u32) .write(H256::from(target_swap_native_account().as_ref())) .build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - - run_failed_test_and_assert( + U256::from(1), expected_gas_usage, - transaction, - ExitReason::Error(ExitError::OutOfFund), + expected_fee, + ExitReason::Error(ExitError::Other("invalid function selector".into())), + EvmDataWriter::new().build(), ); }); } -/// This test verifies that the swap precompile call behaves as expected when the call has no +/// This test verifies that the swap precompile call fails when the call has no /// arguments. #[test] fn swap_fail_no_arguments() { new_test_ext().execute_with_ext(|_| { - let swap_balance = 1; - let expected_gas_usage: u64 = 50_000; // all gas will be consumed - - // Prepare EVM call. - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input: EvmDataWriter::new_with_selector(precompile::Action::Swap).build(), - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); run_failed_test_and_assert( + EvmDataWriter::new_with_selector(precompile::Action::Swap).build(), + U256::from(1), expected_gas_usage, - transaction, + expected_fee, ExitReason::Error(ExitError::Other("exactly one argument is expected".into())), + EvmDataWriter::new().build(), ); }); } -/// This test verifies that the swap precompile call behaves as expected when the call has -/// an incomplete argument. +/// This test verifies that the swap precompile call fails the call has an incomplete argument. #[test] fn swap_fail_short_argument() { new_test_ext().execute_with_ext(|_| { - let swap_balance = 1; - let expected_gas_usage: u64 = 50_000; // all gas will be consumed + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); - // Prepare EVM call. let mut input = EvmDataWriter::new_with_selector(precompile::Action::Swap).build(); input.extend_from_slice(&hex_literal::hex!("1000")); // bad input - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input, - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - run_failed_test_and_assert( + input, + U256::from(1), expected_gas_usage, - transaction, + expected_fee, ExitReason::Error(ExitError::Other("exactly one argument is expected".into())), + EvmDataWriter::new().build(), ); }); } -/// This test verifies that the swap precompile call behaves as expected when the call has -/// extra data after the end of the first argument. +/// This test verifies that the swap precompile call fails when the call has extra data after +/// the end of the first argument. #[test] fn swap_fail_trailing_junk() { new_test_ext().execute_with_ext(|_| { - let swap_balance = 1; - let expected_gas_usage: u64 = 50_000; // all gas will be consumed + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); - // Prepare EVM call. let mut input = EvmDataWriter::new_with_selector(precompile::Action::Swap) .write(H256::from(target_swap_native_account().as_ref())) .build(); input.extend_from_slice(&hex_literal::hex!("1000")); // bad input - let transaction = pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { - chain_id: ::ChainId::get(), - nonce: pallet_evm::Pallet::::account_basic(&source_swap_evm_account()) - .0 - .nonce, - max_priority_fee_per_gas: 0.into(), - max_fee_per_gas: 0.into(), - gas_limit: 50_000.into(), // a reasonable upper bound for tests - action: ethereum::TransactionAction::Call(*PRECOMPILE_ADDRESS), - value: U256::from(swap_balance), - input, - access_list: Default::default(), - odd_y_parity: false, - r: Default::default(), - s: Default::default(), - }); - run_failed_test_and_assert( + input, + U256::from(1), expected_gas_usage, - transaction, + expected_fee, ExitReason::Error(ExitError::Other("junk at the end of input".into())), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails when called without the sufficient balance. +#[test] +fn runner_fail_source_balance_no_funds() { + new_test_ext().execute_with_ext(|_| { + let storage_root = frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1); + + // Invoke the function under test. + let execerr = ::Runner::call( + source_swap_evm_account(), + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(INIT_BALANCE + 1), + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), + ) + .unwrap_err(); + assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); + assert_eq!( + storage_root, + frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1), + "storage changed" + ); + + // Assert that interested balances have not been changed. + assert_eq!( + ::total_balance(&source_swap_evm_account()), + INIT_BALANCE, + ); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&target_swap_native_account()), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + }); +} + +/// This test verifies that the swap precompile call fails 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. +#[test] +fn runner_fail_value_overflow() { + new_test_ext().execute_with_ext(|_| { + let storage_root = frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1); + + // Invoke the function under test. + let execerr = ::Runner::call( + source_swap_evm_account(), + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(precompile::Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::MAX, + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), + ) + .unwrap_err(); + assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); + assert_eq!( + storage_root, + frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1), + "storage changed" + ); + + // Assert that interested balances have not been changed. + assert_eq!( + ::total_balance(&source_swap_evm_account()), + INIT_BALANCE, + ); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&target_swap_native_account()), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); }); } diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests/native_to_evm.rs index 6bc27c29a..f2118c998 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests/native_to_evm.rs @@ -11,9 +11,7 @@ use crate::{mock::*, *}; /// Returns source swap native account used in tests. fn source_swap_native_account() -> AccountId { - AccountId::from(hex_literal::hex!( - "1100000000000000000000000000000000000000000000000000000000000011" - )) + alice() } /// Returns target swap evm account used in tests. From 4ffef3329a547a7b3980a31bf1f0c480e06f61d6 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 3 Mar 2025 13:52:27 +0300 Subject: [PATCH 079/102] Edit balance type at fee calculation at humanode-runtime tests --- crates/humanode-runtime/src/tests/evm_swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/humanode-runtime/src/tests/evm_swap.rs b/crates/humanode-runtime/src/tests/evm_swap.rs index 30d8ddae1..5ffb36cbd 100644 --- a/crates/humanode-runtime/src/tests/evm_swap.rs +++ b/crates/humanode-runtime/src/tests/evm_swap.rs @@ -176,7 +176,7 @@ fn ewm_swap_precompile_call_works() { let expected_gas_usage: u64 = 21216 + 560; let expected_fee: Balance = - u128::from(expected_gas_usage) * u128::try_from(*GAS_PRICE).unwrap(); + Balance::from(expected_gas_usage) * Balance::try_from(*GAS_PRICE).unwrap(); // Invoke the function under test. let execinfo = ::Runner::call( From c938fdbbf33744142e317dc02cba6a9d1273623c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 4 Mar 2025 09:57:29 +0300 Subject: [PATCH 080/102] Add EVM_SWAP to precompiles_constants --- crates/humanode-runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 7a97db8c5..f798fc241 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -800,6 +800,7 @@ frame_support::parameter_types! { frontier_precompiles::hash(precompiles_constants::EVM_ACCOUNTS_MAPPING), frontier_precompiles::hash(precompiles_constants::NATIVE_CURRENCY), frontier_precompiles::hash(precompiles_constants::CURRENCY_SWAP), + frontier_precompiles::hash(precompiles_constants::EVM_SWAP), ]; } From be29fd9b2f7e4befe153c841cca98c657b3dff1f Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 4 Mar 2025 15:57:24 +0300 Subject: [PATCH 081/102] Deprecate precompile-currency-swap usage at humanode-runtime --- Cargo.lock | 1 - crates/humanode-runtime/Cargo.toml | 2 - .../src/frontier_precompiles.rs | 17 +--- crates/humanode-runtime/src/lib.rs | 1 - .../src/tests/currency_swap.rs | 83 ------------------- crates/humanode-runtime/src/tests/evm_swap.rs | 2 +- crates/humanode-runtime/src/tests/mod.rs | 1 - utils/e2e-tests/ts/tests/swap/evmToNative.ts | 2 +- 8 files changed, 4 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db91e873e..e385af121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3830,7 +3830,6 @@ dependencies = [ "parity-scale-codec", "precompile-bioauth", "precompile-bls12381", - "precompile-currency-swap", "precompile-evm-accounts-mapping", "precompile-native-currency", "precompile-utils", diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index 804f58316..b76a8d06b 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -38,7 +38,6 @@ pallet-token-claims = { path = "../pallet-token-claims", default-features = fals pallet-vesting = { path = "../pallet-vesting", default-features = false } precompile-bioauth = { path = "../precompile-bioauth", default-features = false } precompile-bls12381 = { path = "../precompile-bls12381", default-features = false } -precompile-currency-swap = { path = "../precompile-currency-swap", default-features = false } precompile-evm-accounts-mapping = { path = "../precompile-evm-accounts-mapping", default-features = false } precompile-native-currency = { path = "../precompile-native-currency", default-features = false } precompile-utils = { path = "../precompile-utils", default-features = false } @@ -204,7 +203,6 @@ std = [ "pallet-vesting/std", "precompile-bioauth/std", "precompile-bls12381/std", - "precompile-currency-swap/std", "precompile-evm-accounts-mapping/std", "precompile-native-currency/std", "precompile-utils/std", diff --git a/crates/humanode-runtime/src/frontier_precompiles.rs b/crates/humanode-runtime/src/frontier_precompiles.rs index c1f486cd3..36158ccfe 100644 --- a/crates/humanode-runtime/src/frontier_precompiles.rs +++ b/crates/humanode-runtime/src/frontier_precompiles.rs @@ -13,14 +13,13 @@ use precompile_bls12381::{ Bls12381G1Add, Bls12381G1Mul, Bls12381G1MultiExp, Bls12381G2Add, Bls12381G2Mul, Bls12381G2MultiExp, Bls12381MapG1, Bls12381MapG2, Bls12381Pairing, }; -use precompile_currency_swap::CurrencySwap; use precompile_evm_accounts_mapping::EvmAccountsMapping; use precompile_native_currency::NativeCurrency; use precompile_utils::EvmData; use sp_core::{H160, U256}; use sp_std::marker::PhantomData; -use crate::{currency_swap, AccountId, ConstU64, EvmAccountId}; +use crate::ConstU64; /// A set of constant values used to indicate precompiles. pub mod precompiles_constants { @@ -75,10 +74,8 @@ pub mod precompiles_constants { pub const EVM_ACCOUNTS_MAPPING: u64 = 2049; /// `NativeCurrency` precompile constant. pub const NATIVE_CURRENCY: u64 = 2050; - /// `CurrencySwap` precompile constant. - pub const CURRENCY_SWAP: u64 = 2304; /// `EvmSwap` precompile constant. - pub const EVM_SWAP: u64 = 2305; + pub const EVM_SWAP: u64 = 2304; } use precompiles_constants::*; @@ -120,7 +117,6 @@ where BIOAUTH, EVM_ACCOUNTS_MAPPING, NATIVE_CURRENCY, - CURRENCY_SWAP, EVM_SWAP, ] .into_iter() @@ -180,15 +176,6 @@ where a if a == hash(NATIVE_CURRENCY) => { Some(NativeCurrency::>::execute(handle)) } - a if a == hash(CURRENCY_SWAP) => { - Some(CurrencySwap::< - currency_swap::EvmToNativeOneToOne, - EvmAccountId, - AccountId, - // TODO(#697): implement proper dynamic gas cost estimation. - ConstU64<200>, - >::execute(handle)) - } a if a == hash(EVM_SWAP) => Some(EvmSwap::< R, // TODO(#697): implement proper dynamic gas cost estimation. diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index f798fc241..05477e57d 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -799,7 +799,6 @@ frame_support::parameter_types! { frontier_precompiles::hash(precompiles_constants::BIOAUTH), frontier_precompiles::hash(precompiles_constants::EVM_ACCOUNTS_MAPPING), frontier_precompiles::hash(precompiles_constants::NATIVE_CURRENCY), - frontier_precompiles::hash(precompiles_constants::CURRENCY_SWAP), frontier_precompiles::hash(precompiles_constants::EVM_SWAP), ]; } diff --git a/crates/humanode-runtime/src/tests/currency_swap.rs b/crates/humanode-runtime/src/tests/currency_swap.rs index fc615e770..fbbf581db 100644 --- a/crates/humanode-runtime/src/tests/currency_swap.rs +++ b/crates/humanode-runtime/src/tests/currency_swap.rs @@ -164,86 +164,3 @@ fn currency_swap_native_call_works() { ); }) } - -/// This test verifies that the swap precompile call works in the happy path. -#[test] -fn currency_swap_precompile_call_works() { - // Build the state from the config. - new_test_ext_with().execute_with(move || { - let alice_balance_before = Balances::total_balance(&account_id("Alice")); - let native_to_evm_swap_bridge_pot_before = - Balances::total_balance(&NativeToEvmSwapBridgePot::account_id()); - let alice_evm_balance_before = EvmBalances::total_balance(&evm_account_id("EvmAlice")); - let evm_to_native_swap_bridge_pot_before = - EvmBalances::total_balance(&EvmToNativeSwapBridgePot::account_id()); - let fees_pot_balance_before = Balances::total_balance(&FeesPot::account_id()); - let swap_balance: Balance = 1000; - - // Prepare EVM call. - let input = EvmDataWriter::new_with_selector(precompile_currency_swap::Action::Swap) - .write(H256::from(account_id("Alice").as_ref())) - .build(); - - let expected_gas_usage: u64 = 21216 + 560; - let expected_fee: Balance = - u128::from(expected_gas_usage) * u128::try_from(*GAS_PRICE).unwrap(); - - // Invoke the function under test. - let config = ::config(); - let execinfo = ::Runner::call( - evm_account_id("EvmAlice"), - *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, - None, - None, - config, - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); - assert_eq!( - execinfo.logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile_currency_swap::SELECTOR_LOG_SWAP, - evm_account_id("EvmAlice"), - H256::from(account_id("Alice").as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); - - // Assert state changes. - assert!(BalancedCurrencySwapBridgesInitializer::is_balanced().unwrap()); - assert_eq!( - Balances::total_balance(&FeesPot::account_id()), - fees_pot_balance_before + expected_fee - ); - assert_eq!( - Balances::total_balance(&account_id("Alice")), - alice_balance_before + swap_balance - ); - assert_eq!( - Balances::total_balance(&NativeToEvmSwapBridgePot::account_id()), - native_to_evm_swap_bridge_pot_before - swap_balance - expected_fee - ); - assert_eq!( - EvmBalances::total_balance(&evm_account_id("EvmAlice")), - alice_evm_balance_before - swap_balance - expected_fee - ); - assert_eq!( - EvmBalances::total_balance(&EvmToNativeSwapBridgePot::account_id()), - evm_to_native_swap_bridge_pot_before + swap_balance + expected_fee - ); - }) -} diff --git a/crates/humanode-runtime/src/tests/evm_swap.rs b/crates/humanode-runtime/src/tests/evm_swap.rs index 5ffb36cbd..cdc983f92 100644 --- a/crates/humanode-runtime/src/tests/evm_swap.rs +++ b/crates/humanode-runtime/src/tests/evm_swap.rs @@ -11,7 +11,7 @@ use super::*; use crate::dev_utils::*; use crate::opaque::SessionKeys; -pub(crate) static PRECOMPILE_ADDRESS: Lazy = Lazy::new(|| H160::from_low_u64_be(0x901)); +pub(crate) static PRECOMPILE_ADDRESS: Lazy = Lazy::new(|| H160::from_low_u64_be(0x900)); pub(crate) static GAS_PRICE: Lazy = Lazy::new(|| ::FeeCalculator::min_gas_price().0); diff --git a/crates/humanode-runtime/src/tests/mod.rs b/crates/humanode-runtime/src/tests/mod.rs index 5484ef9cb..f53e136ff 100644 --- a/crates/humanode-runtime/src/tests/mod.rs +++ b/crates/humanode-runtime/src/tests/mod.rs @@ -1,7 +1,6 @@ use super::*; mod claims_and_vesting; -mod currency_swap; mod evm_swap; mod fees; mod fixed_supply; diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index a51c48e9c..ed6139b57 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -8,7 +8,7 @@ import { decodeEventLog } from "viem"; import * as substrate from "../../lib/substrate"; import { getNativeBalance } from "../../lib/substrateUtils"; -const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000901"; +const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000900"; const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; const bridgePotNativeAccount = "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; From e7fbf1a796895af650f84b0b50226f814213bc96 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 10:25:57 +0300 Subject: [PATCH 082/102] Introduce precompile-evm-swap --- Cargo.lock | 22 ++ crates/precompile-evm-swap/Cargo.toml | 47 +++ crates/precompile-evm-swap/EvmSwap.sol | 22 ++ crates/precompile-evm-swap/src/lib.rs | 215 ++++++++++ crates/precompile-evm-swap/src/mock.rs | 275 +++++++++++++ crates/precompile-evm-swap/src/tests.rs | 497 ++++++++++++++++++++++++ 6 files changed, 1078 insertions(+) create mode 100644 crates/precompile-evm-swap/Cargo.toml create mode 100644 crates/precompile-evm-swap/EvmSwap.sol create mode 100644 crates/precompile-evm-swap/src/lib.rs create mode 100644 crates/precompile-evm-swap/src/mock.rs create mode 100644 crates/precompile-evm-swap/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index e385af121..651ca70bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6880,6 +6880,28 @@ dependencies = [ "sp-std", ] +[[package]] +name = "precompile-evm-swap" +version = "0.1.0" +dependencies = [ + "fp-evm", + "frame-support", + "frame-system", + "hex-literal", + "mockall", + "num_enum 0.7.3", + "pallet-balances", + "pallet-evm", + "pallet-evm-balances", + "pallet-evm-system", + "pallet-timestamp", + "parity-scale-codec", + "precompile-utils", + "primitives-currency-swap", + "scale-info", + "sp-core", +] + [[package]] name = "precompile-native-currency" version = "0.1.0" diff --git a/crates/precompile-evm-swap/Cargo.toml b/crates/precompile-evm-swap/Cargo.toml new file mode 100644 index 000000000..487e3f45f --- /dev/null +++ b/crates/precompile-evm-swap/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "precompile-evm-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 = { workspace = true, features = ["derive"] } +fp-evm = { workspace = true } +frame-support = { workspace = true } +num_enum = { workspace = true } +pallet-evm = { workspace = true } +scale-info = { workspace = true, features = ["derive"] } +sp-core = { workspace = true } + +[dev-dependencies] +pallet-evm-balances = { path = "../pallet-evm-balances", features = ["default"] } +pallet-evm-system = { path = "../pallet-evm-system", features = ["default"] } + +frame-system = { workspace = true } +hex-literal = { workspace = true } +mockall = { workspace = true } +pallet-balances = { workspace = true, features = ["default"] } +pallet-evm = { workspace = true } +pallet-timestamp = { workspace = true, features = ["default"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "fp-evm/std", + "frame-support/std", + "frame-system/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", + "sp-core/std", +] diff --git a/crates/precompile-evm-swap/EvmSwap.sol b/crates/precompile-evm-swap/EvmSwap.sol new file mode 100644 index 000000000..6f9b638fa --- /dev/null +++ b/crates/precompile-evm-swap/EvmSwap.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title Evm Swap Interface + * + * An interface enabling swapping the funds from EVM accounts to + * native Substrate accounts. + * + * Address: 0x0000000000000000000000000000000000000900 + */ +interface EvmSwap { + /** + * 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-evm-swap/src/lib.rs b/crates/precompile-evm-swap/src/lib.rs new file mode 100644 index 000000000..c83663a2b --- /dev/null +++ b/crates/precompile-evm-swap/src/lib.rs @@ -0,0 +1,215 @@ +//! A precompile to swap EVM tokens with native chain tokens using fungible interfaces. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + dispatch::DispatchError, + sp_runtime::traits::Convert, + sp_std::{marker::PhantomData, prelude::*}, + traits::{ + fungible::{Inspect, Mutate}, + tokens::{Preservation, Provenance}, + }, +}; +use pallet_evm::{ + ExitError, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, +}; +use precompile_utils::{ + keccak256, succeed, EvmDataWriter, EvmResult, LogExt, LogsBuilder, PrecompileHandleExt, +}; +use sp_core::{Get, H160, H256, U256}; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +/// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::NativeToken`] type. +pub type NativeBalanceOf = + <::NativeToken as Inspect<::AccountId>>::Balance; + +/// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::EvmToken`] type. +pub type EvmBalanceOf = + <::EvmToken as Inspect<::EvmAccountId>>::Balance; + +/// The config for the swap logic. +pub trait Config { + /// The native user account identifier type. + type AccountId: From<[u8; 32]>; + + /// The EVM user account identifier type. + type EvmAccountId: From; + + /// Native token. + /// + /// TODO(#1462): switch from `Mutate` to `Balanced` fungible interface. + type NativeToken: Inspect + Mutate; + + /// EVM token. + /// + /// TODO(#1462): switch from `Mutate` to `Balanced` fungible interface. + type EvmToken: Inspect + Mutate; + + /// The converter to determine how the balance amount should be converted from EVM + /// to native token. + type BalanceConverterEvmToNative: Convert, NativeBalanceOf>; + + /// The bridge pot native account. + type BridgePotNative: Get; + + /// The bridge pot EVM account. + type BridgePotEvm: Get; +} + +/// Solidity selector of the Swap log, which is the Keccak of the Log signature. +pub const SELECTOR_LOG_SWAP: [u8; 32] = keccak256!("Swap(address,bytes32,uint256)"); + +/// 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 EVM swap interface. +pub struct EvmSwap(PhantomData<(ConfigT, GasCost)>) +where + ConfigT: Config, + EvmBalanceOf: TryFrom, + ConfigT::EvmAccountId: From, + ConfigT::AccountId: From<[u8; 32]>, + GasCost: Get; + +impl Precompile for EvmSwap +where + ConfigT: Config, + EvmBalanceOf: TryFrom, + ConfigT::EvmAccountId: From, + ConfigT::AccountId: From<[u8; 32]>, + 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), + } + } +} + +impl EvmSwap +where + ConfigT: Config, + EvmBalanceOf: TryFrom, + ConfigT::EvmAccountId: From, + ConfigT::AccountId: From<[u8; 32]>, + 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 { + address, + apparent_value: value, + .. + } = handle.context(); + + let value_u256 = *value; + let value: EvmBalanceOf = + (*value).try_into().map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("value is out of bounds".into()), + })?; + + input + .expect_arguments(1) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("exactly one argument is expected".into()), + })?; + + let to_h256: H256 = input.read()?; + let to: [u8; 32] = to_h256.into(); + let to: ConfigT::AccountId = 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()), + }); + } + + // 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: ConfigT::EvmAccountId = (*address).into(); + + let estimated_swapped_balance = ConfigT::BalanceConverterEvmToNative::convert(value); + + ConfigT::NativeToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) + .into_result() + .map_err(process_dispatch_error)?; + + ConfigT::EvmToken::transfer( + &from, + &ConfigT::BridgePotEvm::get(), + value, + Preservation::Expendable, + ) + .map_err(process_dispatch_error)?; + + ConfigT::NativeToken::transfer( + &ConfigT::BridgePotNative::get(), + &to, + estimated_swapped_balance, + // Bridge pot native account shouldn't be killed. + Preservation::Preserve, + ) + .map_err(process_dispatch_error)?; + + let logs_builder = LogsBuilder::new(handle.context().address); + + logs_builder + .log3( + SELECTOR_LOG_SWAP, + handle.context().caller, + to_h256, + EvmDataWriter::new().write(value_u256).build(), + ) + .record(handle)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } +} + +/// A helper function to process dispatch related errors. +fn process_dispatch_error(error: DispatchError) -> PrecompileFailure { + match error { + DispatchError::Token(frame_support::sp_runtime::TokenError::FundsUnavailable) => { + PrecompileFailure::Error { + exit_status: ExitError::OutOfFund, + } + } + DispatchError::Token(frame_support::sp_runtime::TokenError::BelowMinimum) => { + PrecompileFailure::Error { + exit_status: ExitError::Other( + "resulted balance is less than existential deposit".into(), + ), + } + } + DispatchError::Token(frame_support::sp_runtime::TokenError::NotExpendable) => { + PrecompileFailure::Error { + exit_status: ExitError::Other("account would be killed".into()), + } + } + _ => PrecompileFailure::Error { + exit_status: ExitError::Other("unable to execute swap".into()), + }, + } +} diff --git a/crates/precompile-evm-swap/src/mock.rs b/crates/precompile-evm-swap/src/mock.rs new file mode 100644 index 000000000..5aab44e81 --- /dev/null +++ b/crates/precompile-evm-swap/src/mock.rs @@ -0,0 +1,275 @@ +use std::collections::BTreeMap; + +use frame_support::{ + once_cell::sync::Lazy, + parameter_types, sp_io, + sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, Identity, IdentityLookup}, + BuildStorage, + }, + traits::{ConstU128, ConstU32, ConstU64}, + weights::Weight, +}; +use precompile_utils::precompile_set::{PrecompileAt, PrecompileSetBuilder}; +use sp_core::{Get, H160, H256, U256}; + +use crate::{Config, EvmSwap}; + +pub const INIT_BALANCE: u128 = 10_000_000_000_000_000; +// Add some tokens to test swap with full balance. +pub const BRIDGE_INIT_BALANCE: u128 = INIT_BALANCE + 100; + +pub fn alice() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "1100000000000000000000000000000000000011" + )) +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub type AccountId = frame_support::sp_runtime::AccountId32; +pub type EvmAccountId = H160; +pub type Balance = u128; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub struct Test + where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + 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 DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<2>; // 2 because we test the account kills via 1 balance + type AccountStore = System; + type MaxLocks = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxReserves = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; + 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 = ConstU128<1>; + type AccountStore = EvmSystem; + type DustRemoval = (); +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +pub 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 + (*GAS_PRICE, Weight::from_parts(7u64, 0)) + } +} + +pub static PRECOMPILE_ADDRESS: Lazy = Lazy::new(|| H160::from_low_u64_be(0x900)); + +pub struct EvmSwapConfig; + +impl Config for EvmSwapConfig { + type AccountId = AccountId; + type EvmAccountId = EvmAccountId; + type NativeToken = Balances; + type EvmToken = EvmBalances; + type BalanceConverterEvmToNative = Identity; + type BridgePotNative = BridgePotNative; + type BridgePotEvm = BridgePotEvm; +} + +pub type EvmSwapPrecompile = EvmSwap>; + +pub type Precompiles = + PrecompileSetBuilder>; + +parameter_types! { + pub BlockGasLimit: U256 = U256::max_value(); + pub GasLimitPovSizeRatio: u64 = 0; + pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); + pub PrecompileAddress: H160 = *PRECOMPILE_ADDRESS; + pub PrecompilesValue: Precompiles = Precompiles::new(); +} + +impl pallet_evm::Config for Test { + 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, + >; + type WithdrawOrigin = pallet_evm::EnsureAddressNever< + ::AccountId, + >; + type AddressMapping = pallet_evm::IdentityAddressMapping; + type Currency = EvmBalances; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = Precompiles; + type PrecompilesValue = PrecompilesValue; + type ChainId = (); + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = (); + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type Timestamp = Timestamp; + type WeightInfo = (); +} + +pub struct BridgePotNative; + +impl Get for BridgePotNative { + fn get() -> AccountId { + AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )) + } +} + +pub struct BridgePotEvm; + +impl Get for BridgePotEvm { + fn get() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )) + } +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + // Build genesis. + let config = GenesisConfig { + balances: BalancesConfig { + balances: vec![(BridgePotNative::get(), BRIDGE_INIT_BALANCE)], + }, + evm: EVMConfig { + accounts: { + let mut map = BTreeMap::new(); + map.insert( + BridgePotEvm::get(), + fp_evm::GenesisAccount { + balance: BRIDGE_INIT_BALANCE.into(), + code: Default::default(), + nonce: Default::default(), + storage: Default::default(), + }, + ); + map.insert( + alice(), + fp_evm::GenesisAccount { + balance: INIT_BALANCE.into(), + code: Default::default(), + nonce: Default::default(), + storage: Default::default(), + }, + ); + map + }, + }, + ..Default::default() + }; + let storage = config.build_storage().unwrap(); + + // Make test externalities from the storage. + storage.into() +} + +pub fn runtime_lock() -> std::sync::MutexGuard<'static, ()> { + static MOCK_RUNTIME_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + // Ignore the poisoning for the tests that panic. + // We only care about concurrency here, not about the poisoning. + match MOCK_RUNTIME_MUTEX.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + } +} + +pub trait TestExternalitiesExt { + fn execute_with_ext(&mut self, execute: E) -> R + where + E: for<'e> FnOnce(&'e ()) -> R; +} + +impl TestExternalitiesExt for frame_support::sp_io::TestExternalities { + fn execute_with_ext(&mut self, execute: E) -> R + where + E: for<'e> FnOnce(&'e ()) -> R, + { + let guard = runtime_lock(); + let result = self.execute_with(|| execute(&guard)); + drop(guard); + result + } +} diff --git a/crates/precompile-evm-swap/src/tests.rs b/crates/precompile-evm-swap/src/tests.rs new file mode 100644 index 000000000..3ab6679f8 --- /dev/null +++ b/crates/precompile-evm-swap/src/tests.rs @@ -0,0 +1,497 @@ +// Allow simple integer arithmetic in tests. +#![allow(clippy::arithmetic_side_effects)] + +use fp_evm::{ExitError, ExitReason}; +use frame_support::traits::fungible::Unbalanced; +use pallet_evm::Runner; +use precompile_utils::{EvmDataWriter, LogsBuilder}; +use sp_core::H256; + +use crate::{mock::*, *}; + +/// Returns source swap evm account used in tests. +fn source_swap_evm_account() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "1100000000000000000000000000000000000011" + )) +} + +/// Returns target swap native account used in tests. +fn target_swap_native_account() -> AccountId { + AccountId::from(hex_literal::hex!( + "7700000000000000000000000000000000000000000000000000000000000077" + )) +} + +/// A utility that performs gas to fee computation. +fn gas_to_fee(gas: u64) -> Balance { + Balance::from(gas) * Balance::try_from(*GAS_PRICE).unwrap() +} + +/// A helper function to run succeeded test and assert state changes. +fn run_succeeded_test_and_assert( + swap_balance: Balance, + expected_gas_usage: u64, + expected_fee: Balance, +) { + let source_swap_evm_account_balance_before = + EvmBalances::total_balance(&source_swap_evm_account()); + let bridge_pot_evm_account_balance_before = EvmBalances::total_balance(&BridgePotEvm::get()); + let bridge_pot_native_account_balance_before = Balances::total_balance(&BridgePotNative::get()); + let target_swap_native_account_balance_before = + Balances::total_balance(&target_swap_native_account()); + + // Invoke the function under test. + let execinfo = ::Runner::call( + source_swap_evm_account(), + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + 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, + None, + None, + ::config(), + ) + .unwrap(); + assert_eq!( + execinfo.exit_reason, + fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) + ); + assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); + assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); + assert_eq!( + execinfo.logs, + vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( + SELECTOR_LOG_SWAP, + source_swap_evm_account(), + H256::from(target_swap_native_account().as_ref()), + EvmDataWriter::new().write(swap_balance).build(), + )] + ); + + // Assert state changes. + + // Verify that source swap evm balance has been decreased by swap value and fee. + assert_eq!( + ::total_balance(&source_swap_evm_account()), + source_swap_evm_account_balance_before - swap_balance - expected_fee, + ); + // Verify that bridge pot evm balance has been increased by swap value. + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + bridge_pot_evm_account_balance_before + swap_balance, + ); + // Verify that target swap native balance has been increased by swap value. + assert_eq!( + ::total_balance(&target_swap_native_account()), + target_swap_native_account_balance_before + swap_balance + ); + // Verify that bridge pot native balance has been decreased by swap value. + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + bridge_pot_native_account_balance_before - swap_balance, + ); + // Verify that precompile balance remains the same. + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); +} + +/// This test verifies that the swap precompile call works in the happy path. +#[test] +fn swap_works() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_succeeded_test_and_assert(100, expected_gas_usage, expected_fee); + }); +} + +/// 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 expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_succeeded_test_and_assert( + INIT_BALANCE - expected_fee - 1, + expected_gas_usage, + expected_fee, + ); + }); +} + +/// This test verifies that the swap precompile call works when we transfer the full account balance. +#[test] +fn swap_works_full_balance() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 21216 + 200; + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_succeeded_test_and_assert( + INIT_BALANCE - expected_fee, + expected_gas_usage, + expected_fee, + ); + }); +} + +/// A helper function to run failed test and assert state changes. +fn run_failed_test_and_assert( + input: Vec, + value: U256, + expected_gas_usage: u64, + expected_fee: Balance, + expected_exit_reason: fp_evm::ExitReason, + expected_exit_value: Vec, +) { + let source_swap_evm_account_balance_before = + EvmBalances::total_balance(&source_swap_evm_account()); + let bridge_pot_evm_account_balance_before = EvmBalances::total_balance(&BridgePotEvm::get()); + let bridge_pot_native_account_balance_before = Balances::total_balance(&BridgePotNative::get()); + let target_swap_native_account_balance_before = + Balances::total_balance(&target_swap_native_account()); + + // Invoke the function under test. + let execinfo = ::Runner::call( + source_swap_evm_account(), + *PRECOMPILE_ADDRESS, + input, + value, + expected_gas_usage, // the exact amount of fee we'll be using + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), + ) + .unwrap(); + assert_eq!(execinfo.exit_reason, expected_exit_reason); + assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); + assert_eq!(execinfo.value, expected_exit_value); + assert_eq!(execinfo.logs, vec![]); + + // Verify that source swap evm balance remains the same. + assert_eq!( + ::total_balance(&source_swap_evm_account()), + source_swap_evm_account_balance_before - expected_fee, + ); + // Verify that bridge pot evm balance remains the same. + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + bridge_pot_evm_account_balance_before, + ); + // Verify that target swap native balance remains the same. + assert_eq!( + ::total_balance(&target_swap_native_account()), + target_swap_native_account_balance_before + ); + // Verify that bridge pot native balance remains the same. + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + bridge_pot_native_account_balance_before, + ); + // Verify that precompile balance remains the same. + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); +} + +/// This test verifies that the swap precompile call fails when estimated swapped balance is +/// less or equal than native token existential deposit. +#[test] +fn swap_fail_target_balance_below_ed() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_failed_test_and_assert( + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(1), + expected_gas_usage, + expected_fee, + ExitReason::Error(ExitError::Other( + "resulted balance is less than existential deposit".into(), + )), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails when estimated swapped balance results +/// into target swap native account balance overflow. +#[test] +fn swap_fail_target_balance_overflow() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + Balances::write_balance(&target_swap_native_account(), Balance::MAX).unwrap(); + + run_failed_test_and_assert( + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(1), + expected_gas_usage, + expected_fee, + ExitReason::Error(ExitError::Other("unable to execute swap".into())), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails when swapped balance results into +/// bridge pot evm account balance overflow. +#[test] +fn swap_fail_bridge_evm_overflow() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + EvmBalances::write_balance(&BridgePotEvm::get(), Balance::MAX).unwrap(); + + run_failed_test_and_assert( + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(100), + expected_gas_usage, + expected_fee, + ExitReason::Error(ExitError::Other("unable to execute swap".into())), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails when swap results into killing bridge +/// pot native account. +#[test] +fn swap_fail_bridge_native_killed() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + EvmBalances::write_balance( + &source_swap_evm_account(), + INIT_BALANCE + BRIDGE_INIT_BALANCE, + ) + .unwrap(); + + run_failed_test_and_assert( + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(BRIDGE_INIT_BALANCE), + expected_gas_usage, + expected_fee, + ExitReason::Error(ExitError::Other("account would be killed".into())), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails when a bad selector is passed. +#[test] +fn swap_fail_bad_selector() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_failed_test_and_assert( + EvmDataWriter::new_with_selector(111_u32) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(1), + expected_gas_usage, + expected_fee, + ExitReason::Error(ExitError::Other("invalid function selector".into())), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails when the call has no +/// arguments. +#[test] +fn swap_fail_no_arguments() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + run_failed_test_and_assert( + EvmDataWriter::new_with_selector(Action::Swap).build(), + U256::from(1), + expected_gas_usage, + expected_fee, + ExitReason::Error(ExitError::Other("exactly one argument is expected".into())), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails the call has an incomplete argument. +#[test] +fn swap_fail_short_argument() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + let mut input = EvmDataWriter::new_with_selector(Action::Swap).build(); + input.extend_from_slice(&hex_literal::hex!("1000")); // bad input + + run_failed_test_and_assert( + input, + U256::from(1), + expected_gas_usage, + expected_fee, + ExitReason::Error(ExitError::Other("exactly one argument is expected".into())), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails when the call has extra data after +/// the end of the first argument. +#[test] +fn swap_fail_trailing_junk() { + new_test_ext().execute_with_ext(|_| { + let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed + let expected_fee: Balance = gas_to_fee(expected_gas_usage); + + let mut input = EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(); + input.extend_from_slice(&hex_literal::hex!("1000")); // bad input + + run_failed_test_and_assert( + input, + U256::from(1), + expected_gas_usage, + expected_fee, + ExitReason::Error(ExitError::Other("junk at the end of input".into())), + EvmDataWriter::new().build(), + ); + }); +} + +/// This test verifies that the swap precompile call fails when called without the sufficient balance. +#[test] +fn runner_fail_source_balance_no_funds() { + new_test_ext().execute_with_ext(|_| { + let storage_root = frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1); + + // Invoke the function under test. + let execerr = ::Runner::call( + source_swap_evm_account(), + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(INIT_BALANCE + 1), + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), + ) + .unwrap_err(); + assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); + assert_eq!( + storage_root, + frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1), + "storage changed" + ); + + // Assert that interested balances have not been changed. + assert_eq!( + ::total_balance(&source_swap_evm_account()), + INIT_BALANCE, + ); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&target_swap_native_account()), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + }); +} + +/// This test verifies that the swap precompile call fails 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. +#[test] +fn runner_fail_value_overflow() { + new_test_ext().execute_with_ext(|_| { + let storage_root = frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1); + + // Invoke the function under test. + let execerr = ::Runner::call( + source_swap_evm_account(), + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::MAX, + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), + ) + .unwrap_err(); + assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); + assert_eq!( + storage_root, + frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1), + "storage changed" + ); + + // Assert that interested balances have not been changed. + assert_eq!( + ::total_balance(&source_swap_evm_account()), + INIT_BALANCE, + ); + assert_eq!( + EvmBalances::total_balance(&BridgePotEvm::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(::total_balance(&target_swap_native_account()), 0); + assert_eq!( + Balances::total_balance(&BridgePotNative::get()), + BRIDGE_INIT_BALANCE, + ); + assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); + }); +} From 41d63af98ae97be4fa9612ffe29fb9907a702a4b Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 10:26:34 +0300 Subject: [PATCH 083/102] Remove precompile-currency-swap crate --- crates/precompile-currency-swap/Cargo.toml | 47 - .../precompile-currency-swap/CurrencySwap.sol | 22 - crates/precompile-currency-swap/src/lib.rs | 184 --- crates/precompile-currency-swap/src/mock.rs | 256 ---- crates/precompile-currency-swap/src/tests.rs | 1058 ----------------- 5 files changed, 1567 deletions(-) delete mode 100644 crates/precompile-currency-swap/Cargo.toml delete mode 100644 crates/precompile-currency-swap/CurrencySwap.sol delete mode 100644 crates/precompile-currency-swap/src/lib.rs delete mode 100644 crates/precompile-currency-swap/src/mock.rs delete mode 100644 crates/precompile-currency-swap/src/tests.rs diff --git a/crates/precompile-currency-swap/Cargo.toml b/crates/precompile-currency-swap/Cargo.toml deleted file mode 100644 index a81ea63bf..000000000 --- a/crates/precompile-currency-swap/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[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 = { workspace = true, features = ["derive"] } -fp-evm = { workspace = true } -frame-support = { workspace = true } -num_enum = { workspace = true } -pallet-evm = { workspace = true } -scale-info = { workspace = true, features = ["derive"] } -sp-core = { workspace = true } - -[dev-dependencies] -pallet-evm-balances = { path = "../pallet-evm-balances", features = ["default"] } -pallet-evm-system = { path = "../pallet-evm-system", features = ["default"] } - -frame-system = { workspace = true } -hex-literal = { workspace = true } -mockall = { workspace = true } -pallet-balances = { workspace = true, features = ["default"] } -pallet-evm = { workspace = true } -pallet-timestamp = { workspace = true, features = ["default"] } - -[features] -default = ["std"] -std = [ - "codec/std", - "fp-evm/std", - "frame-support/std", - "frame-system/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", - "sp-core/std", -] diff --git a/crates/precompile-currency-swap/CurrencySwap.sol b/crates/precompile-currency-swap/CurrencySwap.sol deleted file mode 100644 index cc8a9cb40..000000000 --- a/crates/precompile-currency-swap/CurrencySwap.sol +++ /dev/null @@ -1,22 +0,0 @@ -// 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 deleted file mode 100644 index ecb6ab715..000000000 --- a/crates/precompile-currency-swap/src/lib.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! A precompile to swap EVM tokens with native chain tokens. - -#![cfg_attr(not(feature = "std"), no_std)] - -use frame_support::{ - sp_runtime, - sp_std::{marker::PhantomData, prelude::*}, - traits::{ - fungible::Inspect, - tokens::{currency::Currency, Provenance}, - }, -}; -use pallet_evm::{ - ExitError, ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, - PrecompileResult, -}; -use precompile_utils::{ - keccak256, succeed, EvmDataWriter, EvmResult, LogExt, LogsBuilder, PrecompileHandleExt, -}; -use sp_core::{Get, H160, H256, U256}; - -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod tests; - -/// Solidity selector of the Swap log, which is the Keccak of the Log signature. -pub const SELECTOR_LOG_SWAP: [u8; 32] = keccak256!("Swap(address,bytes32,uint256)"); - -/// 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)>, -) -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<[u8; 32]>, - CurrencySwapT: primitives_currency_swap::CurrencySwap, - FromBalanceFor: TryFrom, - 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 the [`Currency::Balance`] of -/// the [`primitives_currency_swap::CurrencySwap::From`] type. -type FromBalanceFor = - as Currency>::Balance; - -/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::From`] type. -type FromCurrencyFor = - >::From; - -impl - CurrencySwap -where - AccountIdFrom: From, - AccountIdTo: From<[u8; 32]>, - CurrencySwapT: primitives_currency_swap::CurrencySwap, - 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 { - address, - apparent_value: value, - .. - } = handle.context(); - - // 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_u256 = *value; - let value: FromBalanceFor = - (*value).try_into().map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("value is out of bounds".into()), - })?; - - input - .expect_arguments(1) - .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("exactly one argument is expected".into()), - })?; - - let to_h256: H256 = input.read()?; - let to: [u8; 32] = to_h256.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 estimated_swapped_balance = CurrencySwapT::estimate_swapped_balance(value); - CurrencySwapT::To::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) - .into_result() - .map_err(|error| match error { - sp_runtime::DispatchError::Token(sp_runtime::TokenError::BelowMinimum) => { - PrecompileFailure::Error { - exit_status: ExitError::OutOfFund, - } - } - _ => PrecompileFailure::Error { - exit_status: ExitError::Other("unable to deposit funds".into()), - }, - })?; - - let imbalance = CurrencySwapT::From::withdraw( - &from, - value, - frame_support::traits::WithdrawReasons::TRANSFER, - frame_support::traits::ExistenceRequirement::AllowDeath, - ) - .map_err(|error| match error { - sp_runtime::DispatchError::Token(sp_runtime::TokenError::FundsUnavailable) => { - PrecompileFailure::Error { - exit_status: ExitError::OutOfFund, - } - } - _ => PrecompileFailure::Error { - exit_status: ExitError::Other("unable to withdraw funds".into()), - }, - })?; - - let imbalance = CurrencySwapT::swap(imbalance).map_err(|error| { - // Here we undo the withdrawal to avoid having a dangling imbalance. - CurrencySwapT::From::resolve_creating(&from, error.incoming_imbalance); - PrecompileFailure::Revert { - exit_status: ExitRevert::Reverted, - output: "unable to swap the currency".into(), - } - })?; - - CurrencySwapT::To::resolve_creating(&to, imbalance); - - let logs_builder = LogsBuilder::new(handle.context().address); - - logs_builder - .log3( - SELECTOR_LOG_SWAP, - handle.context().caller, - to_h256, - EvmDataWriter::new().write(value_u256).build(), - ) - .record(handle)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } -} diff --git a/crates/precompile-currency-swap/src/mock.rs b/crates/precompile-currency-swap/src/mock.rs deleted file mode 100644 index 32c5242d1..000000000 --- a/crates/precompile-currency-swap/src/mock.rs +++ /dev/null @@ -1,256 +0,0 @@ -//! The mock for the precompile. - -// Allow simple integer arithmetic in tests. -#![allow(clippy::arithmetic_side_effects)] - -use fp_evm::{IsPrecompileResult, PrecompileHandle}; -use frame_support::{ - once_cell::sync::Lazy, - sp_io, - sp_runtime::{ - self, - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, DispatchError, - }, - traits::{ConstU16, ConstU32, ConstU64}, - weights::Weight, -}; -use frame_system as system; -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 = sp_runtime::AccountId32; -pub(crate) type EvmAccountId = H160; -pub(crate) type Balance = u128; - -// Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( - pub struct Test - where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system, - Timestamp: pallet_timestamp, - Balances: pallet_balances, - EvmSystem: pallet_evm_system, - EvmBalances: pallet_evm_balances, - EVM: pallet_evm, - } -); - -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 = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<1>; - type OnSetCode = (); - type MaxConsumers = 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 = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ConstU128<2>; // 2 because we test the account kills via 1 balance - type AccountStore = System; - type MaxLocks = (); - type HoldIdentifier = (); - type FreezeIdentifier = (); - type MaxReserves = (); - type MaxHolds = ConstU32<0>; - type MaxFreezes = ConstU32<0>; - 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 = 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 - (*GAS_PRICE, Weight::from_parts(7u64, 0)) - } -} - -frame_support::parameter_types! { - pub BlockGasLimit: U256 = U256::max_value(); - pub GasLimitPovSizeRatio: u64 = 0; - pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); - pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet; -} - -impl pallet_evm::Config for Test { - 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, - >; - type WithdrawOrigin = pallet_evm::EnsureAddressNever< - ::AccountId, - >; - type AddressMapping = pallet_evm::IdentityAddressMapping; - type Currency = EvmBalances; - 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 = (); - type GasLimitPovSizeRatio = GasLimitPovSizeRatio; - type Timestamp = Timestamp; - type WeightInfo = (); -} - -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, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: address == *PRECOMPILE_ADDRESS, - extra_cost: 0, - } - } -} - -mock! { - #[derive(Debug)] - pub CurrencySwap {} - impl primitives_currency_swap::CurrencySwap for CurrencySwap { - type From = EvmBalances; - type To = Balances; - type Error = DispatchError; - - fn swap( - imbalance: primitives_currency_swap::FromNegativeImbalanceFor, - ) -> Result< - primitives_currency_swap::ToNegativeImbalanceFor, - primitives_currency_swap::ErrorFor, - >; - - fn estimate_swapped_balance( - balance: primitives_currency_swap::FromBalanceFor, - ) -> primitives_currency_swap::ToBalanceFor; - } -} - -pub fn new_test_ext() -> sp_io::TestExternalities { - let genesis_config = GenesisConfig::default(); - new_test_ext_with(genesis_config) -} - -// 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 deleted file mode 100644 index ff6f10b6c..000000000 --- a/crates/precompile-currency-swap/src/tests.rs +++ /dev/null @@ -1,1058 +0,0 @@ -#![allow(clippy::arithmetic_side_effects)] // not a problem in tests - -use mockall::predicate; -use pallet_evm::Runner; -use precompile_utils::EvmDataWriter; - -use crate::{mock::*, *}; - -/// 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_evm = H160::from(hex_literal::hex!( - "1000000000000000000000000000000000000001" - )); - let alice = AccountId::from(hex_literal::hex!( - "1000000000000000000000000000000000000000000000000000000000000001" - )); - 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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - 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(), - 50_000, // a reasonable upper bound for tests - Some(*GAS_PRICE), - Some(*GAS_PRICE), - None, - Vec::new(), - true, - true, - None, - None, - config, - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); - assert_eq!( - execinfo.logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - SELECTOR_LOG_SWAP, - alice_evm, - H256::from(alice.as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - swap_balance - expected_fee - ); - assert_eq!( - >::total_balance(&alice), - swap_balance - ); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// 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_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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - 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, - None, - None, - config, - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); - assert_eq!( - execinfo.logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - SELECTOR_LOG_SWAP, - alice_evm, - H256::from(alice.as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - swap_balance - expected_fee - ); - assert_eq!(>::total_balance(&alice_evm), 1); - assert_eq!( - >::total_balance(&alice), - swap_balance - ); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that the swap precompile call behaves as expected when called without -/// the sufficient balance. -/// The fee is not consumed, and neither is the value. -#[test] -fn swap_fail_no_funds() { - 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_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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx.expect().never(); - 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, - None, - None, - 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!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that the swap precompile call behaves as expected when -/// estimated swapped balance less or equal than target currency existential deposit. -/// All fee (up to specified max fee limit!) will be consumed, but not the value. -#[test] -fn swap_fail_below_ed() { - 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 = 50_123; // all fee will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - let alice_evm_balance = 100 * 10u128.pow(18); - let swap_balance = 10 * 10u128.pow(18); - - // Prepare the test state. - EvmBalances::make_free_balance_be(&alice_evm, alice_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(1_u128); - 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 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, - None, - None, - config, - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Error(fp_evm::ExitError::OutOfFund) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, Vec::::new()); - assert_eq!(execinfo.logs, Vec::new()); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - expected_fee - ); - assert_eq!(>::total_balance(&alice), 0); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that the swap precompile call behaves as expected when the currency swap -/// implementation fails. -/// 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() { - 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_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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - let swap_ctx = MockCurrencySwap::swap_context(); - swap_ctx - .expect() - .once() - .with(predicate::eq( - >::NegativeImbalance::new(swap_balance), - )) - .return_once(move |incoming_imbalance| { - Err(primitives_currency_swap::Error { - cause: sp_runtime::DispatchError::Other("test"), - incoming_imbalance, - }) - }); - - // 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(), - 50_000, // a reasonable upper bound for tests - Some(*GAS_PRICE), - Some(*GAS_PRICE), - None, - Vec::new(), - true, - true, - None, - None, - config, - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Revert(ExitRevert::Reverted) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, "unable to swap the currency".as_bytes()); - assert_eq!(execinfo.logs, Vec::new()); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - expected_fee - ); - assert_eq!(>::total_balance(&alice), 0); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that the swap precompile call works when we transfer the full account balance. -#[test] -fn swap_works_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_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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx - .expect() - .once() - .with(predicate::eq(swap_balance)) - .return_const(swap_balance); - 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, - None, - None, - config, - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); - assert_eq!( - execinfo.logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - SELECTOR_LOG_SWAP, - alice_evm, - H256::from(alice.as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - swap_balance - expected_fee - ); - assert_eq!(>::total_balance(&alice_evm), 0); - assert_eq!( - >::total_balance(&alice), - swap_balance - ); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} - -/// This test verifies that the swap precompile call behaves as expected when a bad selector is -/// passed. -/// 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(|_| { - let alice_evm = H160::from(hex_literal::hex!( - "1000000000000000000000000000000000000001" - )); - let alice = AccountId::from(hex_literal::hex!( - "1000000000000000000000000000000000000000000000000000000000000001" - )); - 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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx.expect().never(); - 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, - None, - None, - config, - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Error(fp_evm::ExitError::Other("invalid function selector".into())) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, Vec::::new()); - assert_eq!(execinfo.logs, Vec::new()); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - expected_fee - ); - assert_eq!(>::total_balance(&alice), 0); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - 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_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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx.expect().never(); - 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, - None, - None, - 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!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - 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_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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx.expect().never(); - 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, - None, - None, - 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.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, Vec::::new()); - assert_eq!(execinfo.logs, Vec::new()); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - expected_fee - ); - assert_eq!(>::total_balance(&alice), 0); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - 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_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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx.expect().never(); - 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, - None, - None, - 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.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, Vec::::new()); - assert_eq!(execinfo.logs, Vec::new()); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - expected_fee - ); - assert_eq!(>::total_balance(&alice), 0); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - 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_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_evm_balance); - - // Check test preconditions. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - ); - assert_eq!(>::total_balance(&alice), 0); - - // Set block number to enable events. - System::set_block_number(1); - - // Set mock expectations. - let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context(); - estimate_swapped_balance_ctx.expect().never(); - 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, - None, - None, - 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.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, Vec::::new()); - assert_eq!(execinfo.logs, Vec::new()); - - // Assert state changes. - assert_eq!( - >::total_balance(&alice_evm), - alice_evm_balance - expected_fee - ); - assert_eq!(>::total_balance(&alice), 0); - assert_eq!( - >::total_balance(&PRECOMPILE_ADDRESS), - 0 - ); - - // Assert mock invocations. - estimate_swapped_balance_ctx.checkpoint(); - swap_ctx.checkpoint(); - }); -} From d334f3f111226c6a1aa9bcfb283e0a78068d4404 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 10:46:16 +0300 Subject: [PATCH 084/102] Move precompile logic to own crate --- Cargo.lock | 24 +- crates/humanode-runtime/Cargo.toml | 2 + crates/humanode-runtime/src/currency_swap.rs | 12 + .../src/frontier_precompiles.rs | 9 +- crates/humanode-runtime/src/lib.rs | 1 - crates/humanode-runtime/src/tests/evm_swap.rs | 4 +- crates/pallet-evm-swap/Cargo.toml | 3 - crates/pallet-evm-swap/src/lib.rs | 10 +- crates/pallet-evm-swap/src/mock.rs | 33 +- crates/pallet-evm-swap/src/precompile.rs | 175 ------- .../src/{tests/native_to_evm.rs => tests.rs} | 3 + .../src/tests/evm_to_native.rs | 492 ------------------ crates/pallet-evm-swap/src/tests/mod.rs | 27 - crates/precompile-evm-swap/src/mock.rs | 46 +- 14 files changed, 51 insertions(+), 790 deletions(-) delete mode 100644 crates/pallet-evm-swap/src/precompile.rs rename crates/pallet-evm-swap/src/{tests/native_to_evm.rs => tests.rs} (99%) delete mode 100644 crates/pallet-evm-swap/src/tests/evm_to_native.rs delete mode 100644 crates/pallet-evm-swap/src/tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 651ca70bc..322d17ca3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3831,6 +3831,7 @@ dependencies = [ "precompile-bioauth", "precompile-bls12381", "precompile-evm-accounts-mapping", + "precompile-evm-swap", "precompile-native-currency", "precompile-utils", "primitives-auth-ticket", @@ -6183,7 +6184,6 @@ dependencies = [ "pallet-evm-system", "pallet-timestamp", "parity-scale-codec", - "precompile-utils", "scale-info", "sp-core", ] @@ -6839,28 +6839,6 @@ dependencies = [ "pallet-evm-test-vector-support", ] -[[package]] -name = "precompile-currency-swap" -version = "0.1.0" -dependencies = [ - "fp-evm", - "frame-support", - "frame-system", - "hex-literal", - "mockall", - "num_enum 0.7.3", - "pallet-balances", - "pallet-evm", - "pallet-evm-balances", - "pallet-evm-system", - "pallet-timestamp", - "parity-scale-codec", - "precompile-utils", - "primitives-currency-swap", - "scale-info", - "sp-core", -] - [[package]] name = "precompile-evm-accounts-mapping" version = "0.1.0" diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index b76a8d06b..f27163bfb 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -39,6 +39,7 @@ pallet-vesting = { path = "../pallet-vesting", default-features = false } precompile-bioauth = { path = "../precompile-bioauth", default-features = false } precompile-bls12381 = { path = "../precompile-bls12381", default-features = false } precompile-evm-accounts-mapping = { path = "../precompile-evm-accounts-mapping", default-features = false } +precompile-evm-swap = { path = "../precompile-evm-swap", default-features = false } precompile-native-currency = { path = "../precompile-native-currency", default-features = false } precompile-utils = { path = "../precompile-utils", default-features = false } primitives-auth-ticket = { path = "../primitives-auth-ticket", default-features = false } @@ -204,6 +205,7 @@ std = [ "precompile-bioauth/std", "precompile-bls12381/std", "precompile-evm-accounts-mapping/std", + "precompile-evm-swap/std", "precompile-native-currency/std", "precompile-utils/std", "primitives-auth-ticket/std", diff --git a/crates/humanode-runtime/src/currency_swap.rs b/crates/humanode-runtime/src/currency_swap.rs index d69fdef9a..c2025e2e0 100644 --- a/crates/humanode-runtime/src/currency_swap.rs +++ b/crates/humanode-runtime/src/currency_swap.rs @@ -1,4 +1,5 @@ use bridge_pot_currency_swap::ExistenceRequired; +use precompile_evm_swap::Config; use sp_runtime::traits::Identity; use crate::{ @@ -11,6 +12,17 @@ parameter_types! { pub NativeToEvmSwapBridgePotAccountId: AccountId = NativeToEvmSwapBridgePot::account_id(); pub EvmToNativeSwapBridgePotAccountId: EvmAccountId = EvmToNativeSwapBridgePot::account_id(); } +pub struct PrecompileConfig; + +impl Config for PrecompileConfig { + type AccountId = AccountId; + type EvmAccountId = EvmAccountId; + type NativeToken = Balances; + type EvmToken = EvmBalances; + type BalanceConverterEvmToNative = Identity; + type BridgePotNative = NativeToEvmSwapBridgePotAccountId; + type BridgePotEvm = EvmToNativeSwapBridgePotAccountId; +} pub type NativeToEvmOneToOne = bridge_pot_currency_swap::CurrencySwap; diff --git a/crates/humanode-runtime/src/frontier_precompiles.rs b/crates/humanode-runtime/src/frontier_precompiles.rs index 36158ccfe..18c687734 100644 --- a/crates/humanode-runtime/src/frontier_precompiles.rs +++ b/crates/humanode-runtime/src/frontier_precompiles.rs @@ -7,19 +7,19 @@ use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_modexp::Modexp; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; -use pallet_evm_swap::precompile::EvmSwap; use precompile_bioauth::Bioauth; use precompile_bls12381::{ Bls12381G1Add, Bls12381G1Mul, Bls12381G1MultiExp, Bls12381G2Add, Bls12381G2Mul, Bls12381G2MultiExp, Bls12381MapG1, Bls12381MapG2, Bls12381Pairing, }; use precompile_evm_accounts_mapping::EvmAccountsMapping; +use precompile_evm_swap::EvmSwap; use precompile_native_currency::NativeCurrency; use precompile_utils::EvmData; use sp_core::{H160, U256}; use sp_std::marker::PhantomData; -use crate::ConstU64; +use crate::{currency_swap, ConstU64}; /// A set of constant values used to indicate precompiles. pub mod precompiles_constants { @@ -132,14 +132,11 @@ where R: pallet_evm_accounts_mapping::Config, R: pallet_evm_balances::Config, R: pallet_erc20_support::Config, - R: pallet_evm_swap::Config, ::AccountId: From, <::Currency as Currency< ::AccountId, >>::Balance: Into + TryFrom, ::Allowance: TryFrom + EvmData, - pallet_evm_swap::EvmBalanceOf: TryFrom, - ::EvmAccountId: From, ::AccountId: From<[u8; 32]>, R::ValidatorPublicKey: for<'a> TryFrom<&'a [u8]> + Eq, { @@ -177,7 +174,7 @@ where Some(NativeCurrency::>::execute(handle)) } a if a == hash(EVM_SWAP) => Some(EvmSwap::< - R, + currency_swap::PrecompileConfig, // TODO(#697): implement proper dynamic gas cost estimation. ConstU64<200>, >::execute(handle)), diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 05477e57d..4f012d806 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -641,7 +641,6 @@ impl pallet_evm_swap::Config for Runtime { type NativeToken = Balances; type EvmToken = EvmBalances; type BalanceConverterNativeToEvm = Identity; - type BalanceConverterEvmToNative = Identity; type BridgePotNative = NativeToEvmSwapBridgePotAccountId; type BridgePotEvm = EvmToNativeSwapBridgePotAccountId; type WeightInfo = (); diff --git a/crates/humanode-runtime/src/tests/evm_swap.rs b/crates/humanode-runtime/src/tests/evm_swap.rs index cdc983f92..4d35a529b 100644 --- a/crates/humanode-runtime/src/tests/evm_swap.rs +++ b/crates/humanode-runtime/src/tests/evm_swap.rs @@ -182,7 +182,7 @@ fn ewm_swap_precompile_call_works() { let execinfo = ::Runner::call( evm_account_id("EvmAlice"), *PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(pallet_evm_swap::precompile::Action::Swap) + EvmDataWriter::new_with_selector(precompile_evm_swap::Action::Swap) .write(H256::from(account_id("Alice").as_ref())) .build(), swap_balance.into(), @@ -207,7 +207,7 @@ fn ewm_swap_precompile_call_works() { assert_eq!( execinfo.logs, vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - pallet_evm_swap::precompile::SELECTOR_LOG_SWAP, + precompile_evm_swap::SELECTOR_LOG_SWAP, evm_account_id("EvmAlice"), H256::from(account_id("Alice").as_ref()), EvmDataWriter::new().write(swap_balance).build(), diff --git a/crates/pallet-evm-swap/Cargo.toml b/crates/pallet-evm-swap/Cargo.toml index b05d3b699..965f9e24e 100644 --- a/crates/pallet-evm-swap/Cargo.toml +++ b/crates/pallet-evm-swap/Cargo.toml @@ -5,8 +5,6 @@ edition = "2021" publish = false [dependencies] -precompile-utils = { path = "../precompile-utils", default-features = false } - codec = { workspace = true, features = ["derive"] } ethereum = { workspace = true } fp-ethereum = { workspace = true } @@ -50,7 +48,6 @@ std = [ "pallet-ethereum/std", "pallet-evm/std", "pallet-timestamp/std", - "precompile-utils/std", "scale-info/std", "sp-core/std", ] diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-evm-swap/src/lib.rs index e2490ef73..56223b757 100644 --- a/crates/pallet-evm-swap/src/lib.rs +++ b/crates/pallet-evm-swap/src/lib.rs @@ -10,7 +10,6 @@ pub use pallet::*; use sp_core::{Get, H160, U256}; pub use weights::*; -pub mod precompile; pub mod weights; #[cfg(test)] @@ -20,12 +19,11 @@ mod mock; mod tests; /// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::NativeToken`] type. -pub type NativeBalanceOf = +type NativeBalanceOf = <::NativeToken as Inspect<::AccountId>>::Balance; /// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::EvmToken`] type. -pub type EvmBalanceOf = - <::EvmToken as Inspect<::EvmAccountId>>::Balance; +type EvmBalanceOf = <::EvmToken as Inspect<::EvmAccountId>>::Balance; // We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to // fix them at their end. @@ -71,10 +69,6 @@ pub mod pallet { /// to EVM token. type BalanceConverterNativeToEvm: Convert, EvmBalanceOf>; - /// The converter to determine how the balance amount should be converted from EVM - /// to native token. - type BalanceConverterEvmToNative: Convert, NativeBalanceOf>; - /// The bridge pot native account. type BridgePotNative: Get; diff --git a/crates/pallet-evm-swap/src/mock.rs b/crates/pallet-evm-swap/src/mock.rs index 54eb0c660..b1333cf9e 100644 --- a/crates/pallet-evm-swap/src/mock.rs +++ b/crates/pallet-evm-swap/src/mock.rs @@ -12,10 +12,9 @@ use frame_support::{ weights::Weight, }; use pallet_ethereum::PostLogContent as EthereumPostLogContent; -use precompile_utils::precompile_set::{PrecompileAt, PrecompileSetBuilder}; use sp_core::{Get, H160, H256, U256}; -use crate::{self as pallet_evm_swap, precompile}; +use crate::{self as pallet_evm_swap}; pub const INIT_BALANCE: u128 = 10_000_000_000_000_000; // Add some tokens to test swap with full balance. @@ -27,12 +26,6 @@ pub fn alice() -> AccountId { )) } -pub fn alice_evm() -> EvmAccountId { - EvmAccountId::from(hex_literal::hex!( - "1100000000000000000000000000000000000011" - )) -} - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -132,7 +125,6 @@ impl pallet_timestamp::Config for Test { } pub 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) { @@ -141,19 +133,10 @@ impl fp_evm::FeeCalculator for FixedGasPrice { } } -pub static PRECOMPILE_ADDRESS: Lazy = Lazy::new(|| H160::from_low_u64_be(0x901)); - -pub type EvmSwapPrecompile = precompile::EvmSwap>; - -pub type Precompiles = - PrecompileSetBuilder>; - parameter_types! { pub BlockGasLimit: U256 = U256::max_value(); pub GasLimitPovSizeRatio: u64 = 0; pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); - pub PrecompileAddress: H160 = *PRECOMPILE_ADDRESS; - pub PrecompilesValue: Precompiles = Precompiles::new(); } impl pallet_evm::Config for Test { @@ -171,8 +154,8 @@ impl pallet_evm::Config for Test { type AddressMapping = pallet_evm::IdentityAddressMapping; type Currency = EvmBalances; type RuntimeEvent = RuntimeEvent; - type PrecompilesType = Precompiles; - type PrecompilesValue = PrecompilesValue; + type PrecompilesType = (); + type PrecompilesValue = (); type ChainId = (); type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; @@ -221,7 +204,6 @@ impl pallet_evm_swap::Config for Test { type NativeToken = Balances; type EvmToken = EvmBalances; type BalanceConverterNativeToEvm = Identity; - type BalanceConverterEvmToNative = Identity; type BridgePotNative = BridgePotNative; type BridgePotEvm = BridgePotEvm; type WeightInfo = (); @@ -248,15 +230,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { storage: Default::default(), }, ); - map.insert( - alice_evm(), - fp_evm::GenesisAccount { - balance: INIT_BALANCE.into(), - code: Default::default(), - nonce: Default::default(), - storage: Default::default(), - }, - ); map }, }, diff --git a/crates/pallet-evm-swap/src/precompile.rs b/crates/pallet-evm-swap/src/precompile.rs deleted file mode 100644 index 350ba9ddf..000000000 --- a/crates/pallet-evm-swap/src/precompile.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! A precompile to swap EVM tokens with native chain tokens using `Balanced` and `Inspect` -//! fungible interfaces. - -#![cfg_attr(not(feature = "std"), no_std)] - -use frame_support::{ - dispatch::DispatchError, - sp_runtime::traits::Convert, - sp_std::{marker::PhantomData, prelude::*}, - traits::{ - fungible::{Inspect, Mutate}, - tokens::{Preservation, Provenance}, - }, -}; -use pallet_evm::{ - ExitError, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, -}; -use precompile_utils::{ - keccak256, succeed, EvmDataWriter, EvmResult, LogExt, LogsBuilder, PrecompileHandleExt, -}; -use sp_core::{Get, H160, H256, U256}; - -use crate::{Config, EvmBalanceOf}; - -/// Solidity selector of the Swap log, which is the Keccak of the Log signature. -pub const SELECTOR_LOG_SWAP: [u8; 32] = keccak256!("Swap(address,bytes32,uint256)"); - -/// 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 EVM swap interface. -pub struct EvmSwap(PhantomData<(EvmSwapT, GasCost)>) -where - EvmSwapT: Config, - EvmBalanceOf: TryFrom, - ::EvmAccountId: From, - ::AccountId: From<[u8; 32]>, - GasCost: Get; - -impl Precompile for EvmSwap -where - EvmSwapT: Config, - EvmBalanceOf: TryFrom, - ::EvmAccountId: From, - ::AccountId: From<[u8; 32]>, - 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), - } - } -} - -impl EvmSwap -where - EvmSwapT: Config, - EvmBalanceOf: TryFrom, - ::EvmAccountId: From, - ::AccountId: From<[u8; 32]>, - 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 { - address, - apparent_value: value, - .. - } = handle.context(); - - let value_u256 = *value; - let value: EvmBalanceOf = - (*value).try_into().map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("value is out of bounds".into()), - })?; - - input - .expect_arguments(1) - .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("exactly one argument is expected".into()), - })?; - - let to_h256: H256 = input.read()?; - let to: [u8; 32] = to_h256.into(); - let to: EvmSwapT::AccountId = 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()), - }); - } - - // 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: EvmSwapT::EvmAccountId = (*address).into(); - - let estimated_swapped_balance = EvmSwapT::BalanceConverterEvmToNative::convert(value); - - EvmSwapT::NativeToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) - .into_result() - .map_err(process_dispatch_error)?; - - EvmSwapT::EvmToken::transfer( - &from, - &EvmSwapT::BridgePotEvm::get(), - value, - Preservation::Expendable, - ) - .map_err(process_dispatch_error)?; - - EvmSwapT::NativeToken::transfer( - &EvmSwapT::BridgePotNative::get(), - &to, - estimated_swapped_balance, - // Bridge pot native account shouldn't be killed. - Preservation::Preserve, - ) - .map_err(process_dispatch_error)?; - - let logs_builder = LogsBuilder::new(handle.context().address); - - logs_builder - .log3( - SELECTOR_LOG_SWAP, - handle.context().caller, - to_h256, - EvmDataWriter::new().write(value_u256).build(), - ) - .record(handle)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } -} - -/// A helper function to process dispatch related errors. -fn process_dispatch_error(error: DispatchError) -> PrecompileFailure { - match error { - DispatchError::Token(frame_support::sp_runtime::TokenError::FundsUnavailable) => { - PrecompileFailure::Error { - exit_status: ExitError::OutOfFund, - } - } - DispatchError::Token(frame_support::sp_runtime::TokenError::BelowMinimum) => { - PrecompileFailure::Error { - exit_status: ExitError::Other( - "resulted balance is less than existential deposit".into(), - ), - } - } - DispatchError::Token(frame_support::sp_runtime::TokenError::NotExpendable) => { - PrecompileFailure::Error { - exit_status: ExitError::Other("account would be killed".into()), - } - } - _ => PrecompileFailure::Error { - exit_status: ExitError::Other("unable to execute swap".into()), - }, - } -} diff --git a/crates/pallet-evm-swap/src/tests/native_to_evm.rs b/crates/pallet-evm-swap/src/tests.rs similarity index 99% rename from crates/pallet-evm-swap/src/tests/native_to_evm.rs rename to crates/pallet-evm-swap/src/tests.rs index f2118c998..3b7f7854d 100644 --- a/crates/pallet-evm-swap/src/tests/native_to_evm.rs +++ b/crates/pallet-evm-swap/src/tests.rs @@ -1,3 +1,6 @@ +// Allow simple integer arithmetic in tests. +#![allow(clippy::arithmetic_side_effects)] + use fp_evm::{ExitReason, ExitSucceed}; use frame_support::{ assert_noop, assert_ok, diff --git a/crates/pallet-evm-swap/src/tests/evm_to_native.rs b/crates/pallet-evm-swap/src/tests/evm_to_native.rs deleted file mode 100644 index 8559bc7dc..000000000 --- a/crates/pallet-evm-swap/src/tests/evm_to_native.rs +++ /dev/null @@ -1,492 +0,0 @@ -use fp_evm::{ExitError, ExitReason}; -use frame_support::traits::fungible::Unbalanced; -use pallet_evm::Runner; -use precompile_utils::{EvmDataWriter, LogsBuilder}; -use sp_core::H256; - -use crate::{mock::*, *}; - -/// Returns source swap evm account used in tests. -fn source_swap_evm_account() -> EvmAccountId { - alice_evm() -} - -/// Returns target swap native account used in tests. -fn target_swap_native_account() -> AccountId { - AccountId::from(hex_literal::hex!( - "7700000000000000000000000000000000000000000000000000000000000077" - )) -} - -/// A utility that performs gas to fee computation. -fn gas_to_fee(gas: u64) -> Balance { - Balance::from(gas) * Balance::try_from(*GAS_PRICE).unwrap() -} - -/// A helper function to run succeeded test and assert state changes. -fn run_succeeded_test_and_assert( - swap_balance: Balance, - expected_gas_usage: u64, - expected_fee: Balance, -) { - let source_swap_evm_account_balance_before = - EvmBalances::total_balance(&source_swap_evm_account()); - let bridge_pot_evm_account_balance_before = EvmBalances::total_balance(&BridgePotEvm::get()); - let bridge_pot_native_account_balance_before = Balances::total_balance(&BridgePotNative::get()); - let target_swap_native_account_balance_before = - Balances::total_balance(&target_swap_native_account()); - - // Invoke the function under test. - let execinfo = ::Runner::call( - source_swap_evm_account(), - *PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - 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, - None, - None, - ::config(), - ) - .unwrap(); - assert_eq!( - execinfo.exit_reason, - fp_evm::ExitReason::Succeed(fp_evm::ExitSucceed::Returned) - ); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, EvmDataWriter::new().write(true).build()); - assert_eq!( - execinfo.logs, - vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile::SELECTOR_LOG_SWAP, - source_swap_evm_account(), - H256::from(target_swap_native_account().as_ref()), - EvmDataWriter::new().write(swap_balance).build(), - )] - ); - - // Assert state changes. - - // Verify that source swap evm balance has been decreased by swap value and fee. - assert_eq!( - ::total_balance(&source_swap_evm_account()), - source_swap_evm_account_balance_before - swap_balance - expected_fee, - ); - // Verify that bridge pot evm balance has been increased by swap value. - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - bridge_pot_evm_account_balance_before + swap_balance, - ); - // Verify that target swap native balance has been increased by swap value. - assert_eq!( - ::total_balance(&target_swap_native_account()), - target_swap_native_account_balance_before + swap_balance - ); - // Verify that bridge pot native balance has been decreased by swap value. - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - bridge_pot_native_account_balance_before - swap_balance, - ); - // Verify that precompile balance remains the same. - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); -} - -/// This test verifies that the swap precompile call works in the happy path. -#[test] -fn swap_works() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 21216 + 200; - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - run_succeeded_test_and_assert(100, expected_gas_usage, expected_fee); - }); -} - -/// 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 expected_gas_usage: u64 = 21216 + 200; - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - run_succeeded_test_and_assert( - INIT_BALANCE - expected_fee - 1, - expected_gas_usage, - expected_fee, - ); - }); -} - -/// This test verifies that the swap precompile call works when we transfer the full account balance. -#[test] -fn swap_works_full_balance() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 21216 + 200; - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - run_succeeded_test_and_assert( - INIT_BALANCE - expected_fee, - expected_gas_usage, - expected_fee, - ); - }); -} - -/// A helper function to run failed test and assert state changes. -fn run_failed_test_and_assert( - input: Vec, - value: U256, - expected_gas_usage: u64, - expected_fee: Balance, - expected_exit_reason: fp_evm::ExitReason, - expected_exit_value: Vec, -) { - let source_swap_evm_account_balance_before = - EvmBalances::total_balance(&source_swap_evm_account()); - let bridge_pot_evm_account_balance_before = EvmBalances::total_balance(&BridgePotEvm::get()); - let bridge_pot_native_account_balance_before = Balances::total_balance(&BridgePotNative::get()); - let target_swap_native_account_balance_before = - Balances::total_balance(&target_swap_native_account()); - - // Invoke the function under test. - let execinfo = ::Runner::call( - source_swap_evm_account(), - *PRECOMPILE_ADDRESS, - input, - value, - expected_gas_usage, // the exact amount of fee we'll be using - Some(*GAS_PRICE), - Some(*GAS_PRICE), - None, - Vec::new(), - true, - true, - None, - None, - ::config(), - ) - .unwrap(); - assert_eq!(execinfo.exit_reason, expected_exit_reason); - assert_eq!(execinfo.used_gas.standard, expected_gas_usage.into()); - assert_eq!(execinfo.value, expected_exit_value); - assert_eq!(execinfo.logs, vec![]); - - // Verify that source swap evm balance remains the same. - assert_eq!( - ::total_balance(&source_swap_evm_account()), - source_swap_evm_account_balance_before - expected_fee, - ); - // Verify that bridge pot evm balance remains the same. - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - bridge_pot_evm_account_balance_before, - ); - // Verify that target swap native balance remains the same. - assert_eq!( - ::total_balance(&target_swap_native_account()), - target_swap_native_account_balance_before - ); - // Verify that bridge pot native balance remains the same. - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - bridge_pot_native_account_balance_before, - ); - // Verify that precompile balance remains the same. - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); -} - -/// This test verifies that the swap precompile call fails when estimated swapped balance is -/// less or equal than native token existential deposit. -#[test] -fn swap_fail_target_balance_below_ed() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - run_failed_test_and_assert( - EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::from(1), - expected_gas_usage, - expected_fee, - ExitReason::Error(ExitError::Other( - "resulted balance is less than existential deposit".into(), - )), - EvmDataWriter::new().build(), - ); - }); -} - -/// This test verifies that the swap precompile call fails when estimated swapped balance results -/// into target swap native account balance overflow. -#[test] -fn swap_fail_target_balance_overflow() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - Balances::write_balance(&target_swap_native_account(), Balance::MAX).unwrap(); - - run_failed_test_and_assert( - EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::from(1), - expected_gas_usage, - expected_fee, - ExitReason::Error(ExitError::Other("unable to execute swap".into())), - EvmDataWriter::new().build(), - ); - }); -} - -/// This test verifies that the swap precompile call fails when swapped balance results into -/// bridge pot evm account balance overflow. -#[test] -fn swap_fail_bridge_evm_overflow() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - EvmBalances::write_balance(&BridgePotEvm::get(), Balance::MAX).unwrap(); - - run_failed_test_and_assert( - EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::from(100), - expected_gas_usage, - expected_fee, - ExitReason::Error(ExitError::Other("unable to execute swap".into())), - EvmDataWriter::new().build(), - ); - }); -} - -/// This test verifies that the swap precompile call fails when swap results into killing bridge -/// pot native account. -#[test] -fn swap_fail_bridge_native_killed() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - EvmBalances::write_balance( - &source_swap_evm_account(), - INIT_BALANCE + BRIDGE_INIT_BALANCE, - ) - .unwrap(); - - run_failed_test_and_assert( - EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::from(BRIDGE_INIT_BALANCE), - expected_gas_usage, - expected_fee, - ExitReason::Error(ExitError::Other("account would be killed".into())), - EvmDataWriter::new().build(), - ); - }); -} - -/// This test verifies that the swap precompile call fails when a bad selector is passed. -#[test] -fn swap_fail_bad_selector() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - run_failed_test_and_assert( - EvmDataWriter::new_with_selector(111_u32) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::from(1), - expected_gas_usage, - expected_fee, - ExitReason::Error(ExitError::Other("invalid function selector".into())), - EvmDataWriter::new().build(), - ); - }); -} - -/// This test verifies that the swap precompile call fails when the call has no -/// arguments. -#[test] -fn swap_fail_no_arguments() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - run_failed_test_and_assert( - EvmDataWriter::new_with_selector(precompile::Action::Swap).build(), - U256::from(1), - expected_gas_usage, - expected_fee, - ExitReason::Error(ExitError::Other("exactly one argument is expected".into())), - EvmDataWriter::new().build(), - ); - }); -} - -/// This test verifies that the swap precompile call fails the call has an incomplete argument. -#[test] -fn swap_fail_short_argument() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - let mut input = EvmDataWriter::new_with_selector(precompile::Action::Swap).build(); - input.extend_from_slice(&hex_literal::hex!("1000")); // bad input - - run_failed_test_and_assert( - input, - U256::from(1), - expected_gas_usage, - expected_fee, - ExitReason::Error(ExitError::Other("exactly one argument is expected".into())), - EvmDataWriter::new().build(), - ); - }); -} - -/// This test verifies that the swap precompile call fails when the call has extra data after -/// the end of the first argument. -#[test] -fn swap_fail_trailing_junk() { - new_test_ext().execute_with_ext(|_| { - let expected_gas_usage: u64 = 50_000; // all passed gas will be consumed - let expected_fee: Balance = gas_to_fee(expected_gas_usage); - - let mut input = EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(); - input.extend_from_slice(&hex_literal::hex!("1000")); // bad input - - run_failed_test_and_assert( - input, - U256::from(1), - expected_gas_usage, - expected_fee, - ExitReason::Error(ExitError::Other("junk at the end of input".into())), - EvmDataWriter::new().build(), - ); - }); -} - -/// This test verifies that the swap precompile call fails when called without the sufficient balance. -#[test] -fn runner_fail_source_balance_no_funds() { - new_test_ext().execute_with_ext(|_| { - let storage_root = frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1); - - // Invoke the function under test. - let execerr = ::Runner::call( - source_swap_evm_account(), - *PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::from(INIT_BALANCE + 1), - 50_000, // a reasonable upper bound for tests - Some(*GAS_PRICE), - Some(*GAS_PRICE), - None, - Vec::new(), - true, - true, - None, - None, - ::config(), - ) - .unwrap_err(); - assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); - assert_eq!( - storage_root, - frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1), - "storage changed" - ); - - // Assert that interested balances have not been changed. - assert_eq!( - ::total_balance(&source_swap_evm_account()), - INIT_BALANCE, - ); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(::total_balance(&target_swap_native_account()), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - }); -} - -/// This test verifies that the swap precompile call fails 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. -#[test] -fn runner_fail_value_overflow() { - new_test_ext().execute_with_ext(|_| { - let storage_root = frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1); - - // Invoke the function under test. - let execerr = ::Runner::call( - source_swap_evm_account(), - *PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(precompile::Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::MAX, - 50_000, // a reasonable upper bound for tests - Some(*GAS_PRICE), - Some(*GAS_PRICE), - None, - Vec::new(), - true, - true, - None, - None, - ::config(), - ) - .unwrap_err(); - assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); - assert_eq!( - storage_root, - frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1), - "storage changed" - ); - - // Assert that interested balances have not been changed. - assert_eq!( - ::total_balance(&source_swap_evm_account()), - INIT_BALANCE, - ); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(::total_balance(&target_swap_native_account()), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - }); -} diff --git a/crates/pallet-evm-swap/src/tests/mod.rs b/crates/pallet-evm-swap/src/tests/mod.rs deleted file mode 100644 index 06e381dae..000000000 --- a/crates/pallet-evm-swap/src/tests/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Allow simple integer arithmetic in tests. -#![allow(clippy::arithmetic_side_effects)] - -use sp_core::Get; - -use crate::{mock::*, *}; - -mod evm_to_native; -mod native_to_evm; - -/// This test verifies that basic tests setup works in the happy path. -#[test] -fn basic_setup_works() { - new_test_ext().execute_with_ext(|_| { - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE - ); - assert_eq!(Balances::total_balance(&alice()), INIT_BALANCE); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE - ); - assert_eq!(EvmBalances::total_balance(&alice_evm()), INIT_BALANCE); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); - }); -} diff --git a/crates/precompile-evm-swap/src/mock.rs b/crates/precompile-evm-swap/src/mock.rs index 5aab44e81..888e45435 100644 --- a/crates/precompile-evm-swap/src/mock.rs +++ b/crates/precompile-evm-swap/src/mock.rs @@ -134,9 +134,29 @@ impl fp_evm::FeeCalculator for FixedGasPrice { pub static PRECOMPILE_ADDRESS: Lazy = Lazy::new(|| H160::from_low_u64_be(0x900)); -pub struct EvmSwapConfig; +pub struct BridgePotNative; + +impl Get for BridgePotNative { + fn get() -> AccountId { + AccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000000000000000000000000000001" + )) + } +} + +pub struct BridgePotEvm; + +impl Get for BridgePotEvm { + fn get() -> EvmAccountId { + EvmAccountId::from(hex_literal::hex!( + "1000000000000000000000000000000000000001" + )) + } +} + +pub struct PrecompileConfig; -impl Config for EvmSwapConfig { +impl Config for PrecompileConfig { type AccountId = AccountId; type EvmAccountId = EvmAccountId; type NativeToken = Balances; @@ -146,7 +166,7 @@ impl Config for EvmSwapConfig { type BridgePotEvm = BridgePotEvm; } -pub type EvmSwapPrecompile = EvmSwap>; +pub type EvmSwapPrecompile = EvmSwap>; pub type Precompiles = PrecompileSetBuilder>; @@ -187,26 +207,6 @@ impl pallet_evm::Config for Test { type WeightInfo = (); } -pub struct BridgePotNative; - -impl Get for BridgePotNative { - fn get() -> AccountId { - AccountId::from(hex_literal::hex!( - "1000000000000000000000000000000000000000000000000000000000000001" - )) - } -} - -pub struct BridgePotEvm; - -impl Get for BridgePotEvm { - fn get() -> EvmAccountId { - EvmAccountId::from(hex_literal::hex!( - "1000000000000000000000000000000000000001" - )) - } -} - pub fn new_test_ext() -> sp_io::TestExternalities { // Build genesis. let config = GenesisConfig { From d4e4460e136cbb7a086d5d12402f99e44c6fe976 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 10:48:08 +0300 Subject: [PATCH 085/102] Rename currency_swap mod to evm_swap at humanode-runtime --- .../src/{currency_swap.rs => evm_swap.rs} | 4 ++-- crates/humanode-runtime/src/frontier_precompiles.rs | 4 ++-- crates/humanode-runtime/src/lib.rs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) rename crates/humanode-runtime/src/{currency_swap.rs => evm_swap.rs} (96%) diff --git a/crates/humanode-runtime/src/currency_swap.rs b/crates/humanode-runtime/src/evm_swap.rs similarity index 96% rename from crates/humanode-runtime/src/currency_swap.rs rename to crates/humanode-runtime/src/evm_swap.rs index c2025e2e0..11ec55c0d 100644 --- a/crates/humanode-runtime/src/currency_swap.rs +++ b/crates/humanode-runtime/src/evm_swap.rs @@ -1,5 +1,5 @@ use bridge_pot_currency_swap::ExistenceRequired; -use precompile_evm_swap::Config; +use precompile_evm_swap::Config as PrecompileConfigT; use sp_runtime::traits::Identity; use crate::{ @@ -14,7 +14,7 @@ parameter_types! { } pub struct PrecompileConfig; -impl Config for PrecompileConfig { +impl PrecompileConfigT for PrecompileConfig { type AccountId = AccountId; type EvmAccountId = EvmAccountId; type NativeToken = Balances; diff --git a/crates/humanode-runtime/src/frontier_precompiles.rs b/crates/humanode-runtime/src/frontier_precompiles.rs index 18c687734..e6a359ced 100644 --- a/crates/humanode-runtime/src/frontier_precompiles.rs +++ b/crates/humanode-runtime/src/frontier_precompiles.rs @@ -19,7 +19,7 @@ use precompile_utils::EvmData; use sp_core::{H160, U256}; use sp_std::marker::PhantomData; -use crate::{currency_swap, ConstU64}; +use crate::{evm_swap, ConstU64}; /// A set of constant values used to indicate precompiles. pub mod precompiles_constants { @@ -174,7 +174,7 @@ where Some(NativeCurrency::>::execute(handle)) } a if a == hash(EVM_SWAP) => Some(EvmSwap::< - currency_swap::PrecompileConfig, + evm_swap::PrecompileConfig, // TODO(#697): implement proper dynamic gas cost estimation. ConstU64<200>, >::execute(handle)), diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 4f012d806..4fa9e64fb 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -85,12 +85,12 @@ use frontier_precompiles::{precompiles_constants, FrontierPrecompiles}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub mod constants; -mod currency_swap; mod deauthentication_reason; #[cfg(test)] mod dev_utils; mod display_moment; pub mod eth_sig; +mod evm_swap; mod find_author; mod fixed_supply; pub mod robonode; @@ -625,13 +625,13 @@ impl pallet_evm_balances::Config for Runtime { type Balance = Balance; type ExistentialDeposit = ConstU128<1>; type AccountStore = EvmSystem; - type DustRemoval = currency_swap::TreasuryPotProxy; + type DustRemoval = evm_swap::TreasuryPotProxy; } impl pallet_currency_swap::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AccountIdTo = EvmAccountId; - type CurrencySwap = currency_swap::NativeToEvmOneToOne; + type CurrencySwap = evm_swap::NativeToEvmOneToOne; type WeightInfo = (); } @@ -672,7 +672,7 @@ impl pallet_evm::Config for Runtime { type ChainId = EthereumChainId; type BlockGasLimit = BlockGasLimit; type OnChargeTransaction = - fixed_supply::EvmTransactionCharger; + fixed_supply::EvmTransactionCharger; type OnCreate = (); type FindAuthor = find_author::FindAuthorTruncated< find_author::FindAuthorFromSession, From 1a2aa98c46048e7626bc33c0a376653953707ccc Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 10:50:05 +0300 Subject: [PATCH 086/102] Fix typos --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 16 +++++------ utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 28 ++++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index ed6139b57..480a16005 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -15,7 +15,7 @@ const bridgePotNativeAccount = describe("evm to native tokens swap", () => { let node: RunNodeState; - let ethPiblicClient: eth.PublicClientWebSocket; + let ethPublicClient: eth.PublicClientWebSocket; let ethDevClients: eth.DevClientsWebSocket; let substrateApi: substrate.Api; beforeEachWithCleanup(async (cleanup) => { @@ -23,7 +23,7 @@ describe("evm to native tokens swap", () => { await node.waitForBoot; - ethPiblicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push); + ethPublicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push); ethDevClients = eth.devClientsFromNodeWebSocket(node, cleanup.push); substrateApi = await substrate.apiFromNodeWebSocket(node, cleanup.push); }, 60 * 1000); @@ -37,10 +37,10 @@ describe("evm to native tokens swap", () => { const targetSwapNativeAccountSs58 = "hmqAEn816d1W6TxbT7Md2Zc4hq1AUXFiLEs8yXW5BCUHFx54W"; - const sourceSwapBalanceBefore = await ethPiblicClient.getBalance({ + const sourceSwapBalanceBefore = await ethPublicClient.getBalance({ address: alice.account.address, }); - const bridgePotEvmBalanceBefore = await ethPiblicClient.getBalance({ + const bridgePotEvmBalanceBefore = await ethPublicClient.getBalance({ address: bridgePotEvmAddress, }); const bridgePotNativeBalanceBefore = await getNativeBalance( @@ -60,7 +60,7 @@ describe("evm to native tokens swap", () => { value: swapBalance, }); - const swapTxReceipt = await ethPiblicClient.waitForTransactionReceipt({ + const swapTxReceipt = await ethPublicClient.waitForTransactionReceipt({ hash: swapTxHash, timeout: 18_000, }); @@ -86,14 +86,14 @@ describe("evm to native tokens swap", () => { const fee = swapTxReceipt.cumulativeGasUsed * swapTxReceipt.effectiveGasPrice; - const sourceSwapBalanceAfter = await ethPiblicClient.getBalance({ + const sourceSwapBalanceAfter = await ethPublicClient.getBalance({ address: alice.account.address, }); expect(sourceSwapBalanceAfter).toEqual( sourceSwapBalanceBefore - swapBalance - fee, ); - const bridgePotEvmBalanceAfter = await ethPiblicClient.getBalance({ + const bridgePotEvmBalanceAfter = await ethPublicClient.getBalance({ address: bridgePotEvmAddress, }); expect(bridgePotEvmBalanceAfter).toEqual( @@ -116,7 +116,7 @@ describe("evm to native tokens swap", () => { targetNativeAccountBalanceBefore + swapBalance, ); - const evmSwapPrecompileBalance = await ethPiblicClient.getBalance({ + const evmSwapPrecompileBalance = await ethPublicClient.getBalance({ address: evmSwapPrecompileAddress, }); expect(evmSwapPrecompileBalance).toEqual(0n); diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 63a575bf4..08c407ee1 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -27,14 +27,14 @@ const bridgePotNativeAccount = describe("native to evm tokens swap", () => { let node: RunNodeState; let substrateApi: substrate.Api; - let ethPiblicClient: eth.PublicClientWebSocket; + let ethPublicClient: eth.PublicClientWebSocket; beforeEachWithCleanup(async (cleanup) => { node = runNode({ args: ["--dev", "--tmp"] }, cleanup.push); await node.waitForBoot; substrateApi = await substrate.apiFromNodeWebSocket(node, cleanup.push); - ethPiblicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push); + ethPublicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push); }, 60 * 1000); it("success", async () => { @@ -55,10 +55,10 @@ describe("native to evm tokens swap", () => { substrateApi, bridgePotNativeAccount, ); - const targetEvmAccountBalanceBefore = await ethPiblicClient.getBalance({ + const targetEvmAccountBalanceBefore = await ethPublicClient.getBalance({ address: targetEvmAddress, }); - const bridgePotEvmBalanceBefore = await ethPiblicClient.getBalance({ + const bridgePotEvmBalanceBefore = await ethPublicClient.getBalance({ address: bridgePotEvmAddress, }); @@ -72,7 +72,7 @@ describe("native to evm tokens swap", () => { expect(dispatchError).toBe(undefined); expect(internalError).toBe(undefined); - let ewmSwapBalancesSwappedEvent; + let evmSwapBalancesSwappedEvent; let ethereumExecutedEvent; let transactionPaymentEvent; @@ -81,7 +81,7 @@ describe("native to evm tokens swap", () => { item.event.section == "evmSwap" && item.event.method == "BalancesSwapped" ) { - ewmSwapBalancesSwappedEvent = item.event as unknown as IEvent< + evmSwapBalancesSwappedEvent = item.event as unknown as IEvent< Codec[], EvmSwapBalancesSwappedEvent >; @@ -105,28 +105,28 @@ describe("native to evm tokens swap", () => { } } - assert(ewmSwapBalancesSwappedEvent); + assert(evmSwapBalancesSwappedEvent); assert(ethereumExecutedEvent); assert(transactionPaymentEvent); // Events related asserts. - expect(ewmSwapBalancesSwappedEvent.data.from.toPrimitive()).toEqual( + expect(evmSwapBalancesSwappedEvent.data.from.toPrimitive()).toEqual( alice.address, ); expect( BigInt( - ewmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as unknown as bigint, + evmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as unknown as bigint, ), ).toEqual(swapBalance); - expect(ewmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( + expect(evmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( targetEvmAddress, ); expect( BigInt( - ewmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as unknown as bigint, + evmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as unknown as bigint, ), ).toEqual(swapBalance); - expect(ewmSwapBalancesSwappedEvent.data.evmTransactionHash).toEqual( + expect(evmSwapBalancesSwappedEvent.data.evmTransactionHash).toEqual( ethereumExecutedEvent.data.transactionHash, ); expect(ethereumExecutedEvent.data.from.toPrimitive()).toEqual( @@ -162,14 +162,14 @@ describe("native to evm tokens swap", () => { bridgePotNativeBalanceBefore + swapBalance, ); - const targetEvmAccountBalanceAfter = await ethPiblicClient.getBalance({ + const targetEvmAccountBalanceAfter = await ethPublicClient.getBalance({ address: targetEvmAddress, }); expect(targetEvmAccountBalanceAfter).toEqual( targetEvmAccountBalanceBefore + swapBalance, ); - const bridgePotEvmBalanceAfter = await ethPiblicClient.getBalance({ + const bridgePotEvmBalanceAfter = await ethPublicClient.getBalance({ address: bridgePotEvmAddress, }); expect(bridgePotEvmBalanceAfter).toEqual( From 2408617defac3d4edaa2601da7883e24218e07c8 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 10:52:48 +0300 Subject: [PATCH 087/102] Fix machete --- Cargo.lock | 1 - crates/precompile-evm-swap/Cargo.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 322d17ca3..ac42bab18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6875,7 +6875,6 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "precompile-utils", - "primitives-currency-swap", "scale-info", "sp-core", ] diff --git a/crates/precompile-evm-swap/Cargo.toml b/crates/precompile-evm-swap/Cargo.toml index 487e3f45f..840cbbae0 100644 --- a/crates/precompile-evm-swap/Cargo.toml +++ b/crates/precompile-evm-swap/Cargo.toml @@ -6,7 +6,6 @@ publish = false [dependencies] precompile-utils = { path = "../precompile-utils", default-features = false } -primitives-currency-swap = { path = "../primitives-currency-swap", default-features = false } codec = { workspace = true, features = ["derive"] } fp-evm = { workspace = true } @@ -41,7 +40,6 @@ std = [ "pallet-evm/std", "pallet-timestamp/std", "precompile-utils/std", - "primitives-currency-swap/std", "scale-info/std", "sp-core/std", ] From 66d43e642c937d1c93159a58f6ea0c7ddbccc114 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 10:53:13 +0300 Subject: [PATCH 088/102] Fix features snapshot --- utils/checks/snapshots/features.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/checks/snapshots/features.yaml b/utils/checks/snapshots/features.yaml index c6f230008..1ad3694d3 100644 --- a/utils/checks/snapshots/features.yaml +++ b/utils/checks/snapshots/features.yaml @@ -2160,11 +2160,11 @@ features: - default - std -- name: precompile-currency-swap 0.1.0 +- name: precompile-evm-accounts-mapping 0.1.0 features: - default - std -- name: precompile-evm-accounts-mapping 0.1.0 +- name: precompile-evm-swap 0.1.0 features: - default - std From 2c0d205295e22af50934a79ed820f4aee9ad4125 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 10:55:44 +0300 Subject: [PATCH 089/102] Minor improvments --- crates/humanode-runtime/src/evm_swap.rs | 1 + crates/humanode-runtime/src/frontier_precompiles.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/humanode-runtime/src/evm_swap.rs b/crates/humanode-runtime/src/evm_swap.rs index 11ec55c0d..14c6f33f0 100644 --- a/crates/humanode-runtime/src/evm_swap.rs +++ b/crates/humanode-runtime/src/evm_swap.rs @@ -12,6 +12,7 @@ parameter_types! { pub NativeToEvmSwapBridgePotAccountId: AccountId = NativeToEvmSwapBridgePot::account_id(); pub EvmToNativeSwapBridgePotAccountId: EvmAccountId = EvmToNativeSwapBridgePot::account_id(); } + pub struct PrecompileConfig; impl PrecompileConfigT for PrecompileConfig { diff --git a/crates/humanode-runtime/src/frontier_precompiles.rs b/crates/humanode-runtime/src/frontier_precompiles.rs index e6a359ced..2a4acffaf 100644 --- a/crates/humanode-runtime/src/frontier_precompiles.rs +++ b/crates/humanode-runtime/src/frontier_precompiles.rs @@ -137,7 +137,6 @@ where ::AccountId, >>::Balance: Into + TryFrom, ::Allowance: TryFrom + EvmData, - ::AccountId: From<[u8; 32]>, R::ValidatorPublicKey: for<'a> TryFrom<&'a [u8]> + Eq, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { From b71246bc0ee068efe9d36b9776bf6c29e1e2f629 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 11:21:50 +0300 Subject: [PATCH 090/102] Add event description to swap precompile interface --- crates/precompile-evm-swap/EvmSwap.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/precompile-evm-swap/EvmSwap.sol b/crates/precompile-evm-swap/EvmSwap.sol index 6f9b638fa..da18a3843 100644 --- a/crates/precompile-evm-swap/EvmSwap.sol +++ b/crates/precompile-evm-swap/EvmSwap.sol @@ -19,4 +19,14 @@ interface EvmSwap { * @return success Whether or not the swap was successful. */ function swap(bytes32 nativeAddress) external payable returns (bool success); + + /** + * Event emitted when a transfer has been performed. + * Selector: 69d31d1d87c1206beee49fbab11570a7f001121cf21fbb234d5b0a2473fa5c58 + * + * @param from The EVM account id the tokens withdrawed from. + * @param to The Native account id the tokens deposited to. + * @param value The amount of tokens swapped. + */ + event Swap(address indexed from, bytes32 indexed to, uint256 value); } From 25462568a63c50b922b212ee3206bd9dc7fb07ae Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 12:00:51 +0300 Subject: [PATCH 091/102] Rename precompile and pallet crates --- Cargo.lock | 52 +++++++++---------- crates/humanode-runtime/Cargo.toml | 12 ++--- crates/humanode-runtime/src/evm_swap.rs | 6 +-- .../src/frontier_precompiles.rs | 12 ++--- crates/humanode-runtime/src/lib.rs | 6 +-- crates/humanode-runtime/src/tests/evm_swap.rs | 14 ++--- .../Cargo.toml | 2 +- .../src/lib.rs | 0 .../src/mock.rs | 6 +-- .../src/tests.rs | 28 +++++----- .../src/weights.rs | 0 .../Cargo.toml | 2 +- .../EvmToNativeSwap.sol} | 4 +- .../src/lib.rs | 12 ++--- .../src/mock.rs | 6 +-- .../src/tests.rs | 0 utils/checks/snapshots/features.yaml | 10 ++-- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 7 +-- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 20 +++---- 19 files changed, 100 insertions(+), 99 deletions(-) rename crates/{pallet-evm-swap => pallet-native-to-evm-swap}/Cargo.toml (97%) rename crates/{pallet-evm-swap => pallet-native-to-evm-swap}/src/lib.rs (100%) rename crates/{pallet-evm-swap => pallet-native-to-evm-swap}/src/mock.rs (98%) rename crates/{pallet-evm-swap => pallet-native-to-evm-swap}/src/tests.rs (93%) rename crates/{pallet-evm-swap => pallet-native-to-evm-swap}/src/weights.rs (100%) rename crates/{precompile-evm-swap => precompile-evm-to-native-swap}/Cargo.toml (96%) rename crates/{precompile-evm-swap/EvmSwap.sol => precompile-evm-to-native-swap/EvmToNativeSwap.sol} (92%) rename crates/{precompile-evm-swap => precompile-evm-to-native-swap}/src/lib.rs (94%) rename crates/{precompile-evm-swap => precompile-evm-to-native-swap}/src/mock.rs (97%) rename crates/{precompile-evm-swap => precompile-evm-to-native-swap}/src/tests.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index ac42bab18..afcf35e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3811,13 +3811,13 @@ dependencies = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", - "pallet-evm-swap", "pallet-evm-system", "pallet-grandpa", "pallet-humanode-offences", "pallet-humanode-session", "pallet-im-online", "pallet-multisig", + "pallet-native-to-evm-swap", "pallet-pot", "pallet-session", "pallet-sudo", @@ -3831,7 +3831,7 @@ dependencies = [ "precompile-bioauth", "precompile-bls12381", "precompile-evm-accounts-mapping", - "precompile-evm-swap", + "precompile-evm-to-native-swap", "precompile-native-currency", "precompile-utils", "primitives-auth-ticket", @@ -6165,29 +6165,6 @@ dependencies = [ "sp-io", ] -[[package]] -name = "pallet-evm-swap" -version = "0.1.0" -dependencies = [ - "assert_matches", - "ethereum", - "fp-ethereum", - "fp-evm", - "frame-support", - "frame-system", - "hex-literal", - "num_enum 0.7.3", - "pallet-balances", - "pallet-ethereum", - "pallet-evm", - "pallet-evm-balances", - "pallet-evm-system", - "pallet-timestamp", - "parity-scale-codec", - "scale-info", - "sp-core", -] - [[package]] name = "pallet-evm-system" version = "0.1.0" @@ -6313,6 +6290,29 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-native-to-evm-swap" +version = "0.1.0" +dependencies = [ + "assert_matches", + "ethereum", + "fp-ethereum", + "fp-evm", + "frame-support", + "frame-system", + "hex-literal", + "num_enum 0.7.3", + "pallet-balances", + "pallet-ethereum", + "pallet-evm", + "pallet-evm-balances", + "pallet-evm-system", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", +] + [[package]] name = "pallet-pot" version = "0.1.0" @@ -6859,7 +6859,7 @@ dependencies = [ ] [[package]] -name = "precompile-evm-swap" +name = "precompile-evm-to-native-swap" version = "0.1.0" dependencies = [ "fp-evm", diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index f27163bfb..528727410 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -29,17 +29,17 @@ pallet-erc20-support = { path = "../pallet-erc20-support", default-features = fa pallet-ethereum-chain-id = { path = "../pallet-ethereum-chain-id", default-features = false } pallet-evm-accounts-mapping = { path = "../pallet-evm-accounts-mapping", default-features = false } pallet-evm-balances = { path = "../pallet-evm-balances", default-features = false } -pallet-evm-swap = { path = "../pallet-evm-swap", default-features = false } pallet-evm-system = { path = "../pallet-evm-system", default-features = false } pallet-humanode-offences = { path = "../pallet-humanode-offences", default-features = false } pallet-humanode-session = { path = "../pallet-humanode-session", default-features = false } +pallet-native-to-evm-swap = { path = "../pallet-native-to-evm-swap", default-features = false } pallet-pot = { path = "../pallet-pot", default-features = false } pallet-token-claims = { path = "../pallet-token-claims", default-features = false } pallet-vesting = { path = "../pallet-vesting", default-features = false } precompile-bioauth = { path = "../precompile-bioauth", default-features = false } precompile-bls12381 = { path = "../precompile-bls12381", default-features = false } precompile-evm-accounts-mapping = { path = "../precompile-evm-accounts-mapping", default-features = false } -precompile-evm-swap = { path = "../precompile-evm-swap", default-features = false } +precompile-evm-to-native-swap = { path = "../precompile-evm-to-native-swap", default-features = false } precompile-native-currency = { path = "../precompile-native-currency", default-features = false } precompile-utils = { path = "../precompile-utils", default-features = false } primitives-auth-ticket = { path = "../primitives-auth-ticket", default-features = false } @@ -127,12 +127,12 @@ runtime-benchmarks = [ "pallet-currency-swap/runtime-benchmarks", "pallet-ethereum/runtime-benchmarks", "pallet-evm-accounts-mapping/runtime-benchmarks", - "pallet-evm-swap/runtime-benchmarks", "pallet-evm/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-humanode-session/runtime-benchmarks", "pallet-im-online/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-native-to-evm-swap/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-token-claims/runtime-benchmarks", @@ -186,13 +186,13 @@ std = [ "pallet-evm-precompile-simple/std", "pallet-evm/std", "pallet-evm-balances/std", - "pallet-evm-swap/std", "pallet-evm-system/std", "pallet-grandpa/std", "pallet-humanode-offences/std", "pallet-humanode-session/std", "pallet-im-online/std", "pallet-multisig/std", + "pallet-native-to-evm-swap/std", "pallet-pot/std", "pallet-session/std", "pallet-sudo/std", @@ -205,7 +205,7 @@ std = [ "precompile-bioauth/std", "precompile-bls12381/std", "precompile-evm-accounts-mapping/std", - "precompile-evm-swap/std", + "precompile-evm-to-native-swap/std", "precompile-native-currency/std", "precompile-utils/std", "primitives-auth-ticket/std", @@ -258,13 +258,13 @@ try-runtime = [ "pallet-evm-accounts-mapping/try-runtime", "pallet-evm/try-runtime", "pallet-evm-balances/try-runtime", - "pallet-evm-swap/try-runtime", "pallet-evm-system/try-runtime", "pallet-grandpa/try-runtime", "pallet-humanode-offences/try-runtime", "pallet-humanode-session/try-runtime", "pallet-im-online/try-runtime", "pallet-multisig/try-runtime", + "pallet-native-to-evm-swap/try-runtime", "pallet-pot/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", diff --git a/crates/humanode-runtime/src/evm_swap.rs b/crates/humanode-runtime/src/evm_swap.rs index 14c6f33f0..fd1a17a79 100644 --- a/crates/humanode-runtime/src/evm_swap.rs +++ b/crates/humanode-runtime/src/evm_swap.rs @@ -1,5 +1,5 @@ use bridge_pot_currency_swap::ExistenceRequired; -use precompile_evm_swap::Config as PrecompileConfigT; +use precompile_evm_to_native_swap::Config as PrecompileConfig; use sp_runtime::traits::Identity; use crate::{ @@ -13,9 +13,9 @@ parameter_types! { pub EvmToNativeSwapBridgePotAccountId: EvmAccountId = EvmToNativeSwapBridgePot::account_id(); } -pub struct PrecompileConfig; +pub struct EvmToNativeSwapConfig; -impl PrecompileConfigT for PrecompileConfig { +impl PrecompileConfig for EvmToNativeSwapConfig { type AccountId = AccountId; type EvmAccountId = EvmAccountId; type NativeToken = Balances; diff --git a/crates/humanode-runtime/src/frontier_precompiles.rs b/crates/humanode-runtime/src/frontier_precompiles.rs index 2a4acffaf..3482dbfe2 100644 --- a/crates/humanode-runtime/src/frontier_precompiles.rs +++ b/crates/humanode-runtime/src/frontier_precompiles.rs @@ -13,7 +13,7 @@ use precompile_bls12381::{ Bls12381G2MultiExp, Bls12381MapG1, Bls12381MapG2, Bls12381Pairing, }; use precompile_evm_accounts_mapping::EvmAccountsMapping; -use precompile_evm_swap::EvmSwap; +use precompile_evm_to_native_swap::EvmToNativeSwap; use precompile_native_currency::NativeCurrency; use precompile_utils::EvmData; use sp_core::{H160, U256}; @@ -74,8 +74,8 @@ pub mod precompiles_constants { pub const EVM_ACCOUNTS_MAPPING: u64 = 2049; /// `NativeCurrency` precompile constant. pub const NATIVE_CURRENCY: u64 = 2050; - /// `EvmSwap` precompile constant. - pub const EVM_SWAP: u64 = 2304; + /// `EvmToNativeSwap` precompile constant. + pub const EVM_TO_NATIVE_SWAP: u64 = 2304; } use precompiles_constants::*; @@ -117,7 +117,7 @@ where BIOAUTH, EVM_ACCOUNTS_MAPPING, NATIVE_CURRENCY, - EVM_SWAP, + EVM_TO_NATIVE_SWAP, ] .into_iter() .map(hash) @@ -172,8 +172,8 @@ where a if a == hash(NATIVE_CURRENCY) => { Some(NativeCurrency::>::execute(handle)) } - a if a == hash(EVM_SWAP) => Some(EvmSwap::< - evm_swap::PrecompileConfig, + a if a == hash(EVM_TO_NATIVE_SWAP) => Some(EvmToNativeSwap::< + evm_swap::EvmToNativeSwapConfig, // TODO(#697): implement proper dynamic gas cost estimation. ConstU64<200>, >::execute(handle)), diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 4fa9e64fb..6b1af25fa 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -635,7 +635,7 @@ impl pallet_currency_swap::Config for Runtime { type WeightInfo = (); } -impl pallet_evm_swap::Config for Runtime { +impl pallet_native_to_evm_swap::Config for Runtime { type RuntimeEvent = RuntimeEvent; type EvmAccountId = EvmAccountId; type NativeToken = Balances; @@ -798,7 +798,7 @@ frame_support::parameter_types! { frontier_precompiles::hash(precompiles_constants::BIOAUTH), frontier_precompiles::hash(precompiles_constants::EVM_ACCOUNTS_MAPPING), frontier_precompiles::hash(precompiles_constants::NATIVE_CURRENCY), - frontier_precompiles::hash(precompiles_constants::EVM_SWAP), + frontier_precompiles::hash(precompiles_constants::EVM_TO_NATIVE_SWAP), ]; } @@ -853,7 +853,7 @@ construct_runtime!( EvmBalancesErc20Support: pallet_erc20_support = 37, DummyPrecompilesCode: pallet_dummy_precompiles_code = 38, HumanodeOffences: pallet_humanode_offences = 39, - EvmSwap: pallet_evm_swap = 40, + NativeToEvmSwap: pallet_native_to_evm_swap = 40, } ); diff --git a/crates/humanode-runtime/src/tests/evm_swap.rs b/crates/humanode-runtime/src/tests/evm_swap.rs index 4d35a529b..57b52daf2 100644 --- a/crates/humanode-runtime/src/tests/evm_swap.rs +++ b/crates/humanode-runtime/src/tests/evm_swap.rs @@ -119,9 +119,9 @@ fn currencies_are_balanced() { }) } -/// This test verifies that evm swap native call works in the happy path. +/// This test verifies that native to evm swap call works in the happy path. #[test] -fn evm_swap_native_call_works() { +fn native_to_evm_swap_call_works() { // Build the state from the config. new_test_ext_with().execute_with(move || { let alice_balance_before = Balances::total_balance(&account_id("Alice")); @@ -133,7 +133,7 @@ fn evm_swap_native_call_works() { let swap_balance: Balance = 1000; // Make swap. - assert_ok!(EvmSwap::swap( + assert_ok!(NativeToEvmSwap::swap( Some(account_id("Alice")).into(), evm_account_id("EvmAlice"), swap_balance @@ -160,9 +160,9 @@ fn evm_swap_native_call_works() { }) } -/// This test verifies that the ewm swap precompile call works in the happy path. +/// This test verifies that the ewm to native swap precompile call works in the happy path. #[test] -fn ewm_swap_precompile_call_works() { +fn ewm_to_native_precompile_call_works() { // Build the state from the config. new_test_ext_with().execute_with(move || { let alice_balance_before = Balances::total_balance(&account_id("Alice")); @@ -182,7 +182,7 @@ fn ewm_swap_precompile_call_works() { let execinfo = ::Runner::call( evm_account_id("EvmAlice"), *PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(precompile_evm_swap::Action::Swap) + EvmDataWriter::new_with_selector(precompile_evm_to_native_swap::Action::Swap) .write(H256::from(account_id("Alice").as_ref())) .build(), swap_balance.into(), @@ -207,7 +207,7 @@ fn ewm_swap_precompile_call_works() { assert_eq!( execinfo.logs, vec![LogsBuilder::new(*PRECOMPILE_ADDRESS).log3( - precompile_evm_swap::SELECTOR_LOG_SWAP, + precompile_evm_to_native_swap::SELECTOR_LOG_SWAP, evm_account_id("EvmAlice"), H256::from(account_id("Alice").as_ref()), EvmDataWriter::new().write(swap_balance).build(), diff --git a/crates/pallet-evm-swap/Cargo.toml b/crates/pallet-native-to-evm-swap/Cargo.toml similarity index 97% rename from crates/pallet-evm-swap/Cargo.toml rename to crates/pallet-native-to-evm-swap/Cargo.toml index 965f9e24e..349fe2f48 100644 --- a/crates/pallet-evm-swap/Cargo.toml +++ b/crates/pallet-native-to-evm-swap/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-evm-swap" +name = "pallet-native-to-evm-swap" version = "0.1.0" edition = "2021" publish = false diff --git a/crates/pallet-evm-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs similarity index 100% rename from crates/pallet-evm-swap/src/lib.rs rename to crates/pallet-native-to-evm-swap/src/lib.rs diff --git a/crates/pallet-evm-swap/src/mock.rs b/crates/pallet-native-to-evm-swap/src/mock.rs similarity index 98% rename from crates/pallet-evm-swap/src/mock.rs rename to crates/pallet-native-to-evm-swap/src/mock.rs index b1333cf9e..2b755dfd7 100644 --- a/crates/pallet-evm-swap/src/mock.rs +++ b/crates/pallet-native-to-evm-swap/src/mock.rs @@ -14,7 +14,7 @@ use frame_support::{ use pallet_ethereum::PostLogContent as EthereumPostLogContent; use sp_core::{Get, H160, H256, U256}; -use crate::{self as pallet_evm_swap}; +use crate::{self as pallet_native_to_evm_swap}; pub const INIT_BALANCE: u128 = 10_000_000_000_000_000; // Add some tokens to test swap with full balance. @@ -48,7 +48,7 @@ frame_support::construct_runtime!( EvmBalances: pallet_evm_balances, EVM: pallet_evm, Ethereum: pallet_ethereum, - EvmSwap: pallet_evm_swap, + NativeToEvmSwap: pallet_native_to_evm_swap, } ); @@ -198,7 +198,7 @@ impl Get for BridgePotEvm { } } -impl pallet_evm_swap::Config for Test { +impl pallet_native_to_evm_swap::Config for Test { type RuntimeEvent = RuntimeEvent; type EvmAccountId = EvmAccountId; type NativeToken = Balances; diff --git a/crates/pallet-evm-swap/src/tests.rs b/crates/pallet-native-to-evm-swap/src/tests.rs similarity index 93% rename from crates/pallet-evm-swap/src/tests.rs rename to crates/pallet-native-to-evm-swap/src/tests.rs index 3b7f7854d..a9685a85f 100644 --- a/crates/pallet-evm-swap/src/tests.rs +++ b/crates/pallet-native-to-evm-swap/src/tests.rs @@ -57,12 +57,12 @@ fn run_succeeded_test_and_assert( // Invoke the function under test. assert_ok!(match call { - TestCall::Swap => EvmSwap::swap( + TestCall::Swap => NativeToEvmSwap::swap( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), swap_balance ), - TestCall::SwapKeepAlive => EvmSwap::swap_keep_alive( + TestCall::SwapKeepAlive => NativeToEvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), swap_balance @@ -98,7 +98,7 @@ fn run_succeeded_test_and_assert( target_swap_evm_account_balance_before + swap_balance ); // Verifyt that we have a corresponding evm swap event. - System::assert_has_event(RuntimeEvent::EvmSwap(Event::BalancesSwapped { + System::assert_has_event(RuntimeEvent::NativeToEvmSwap(Event::BalancesSwapped { from: source_swap_native_account(), withdrawed_amount: swap_balance, to: target_swap_evm_account(), @@ -147,7 +147,7 @@ fn swap_keep_alive_fails_kill_origin() { new_test_ext().execute_with_ext(|_| { // Invoke the function under test. assert_noop!( - EvmSwap::swap_keep_alive( + NativeToEvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), INIT_BALANCE - 1, @@ -163,7 +163,7 @@ fn swap_both_fails_source_no_funds() { new_test_ext().execute_with_ext(|_| { // Invoke the `swap` under test. assert_noop!( - EvmSwap::swap( + NativeToEvmSwap::swap( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), INIT_BALANCE + 1, @@ -173,7 +173,7 @@ fn swap_both_fails_source_no_funds() { // Invoke the `swap_keep_alive` under test. assert_noop!( - EvmSwap::swap_keep_alive( + NativeToEvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), INIT_BALANCE + 1, @@ -191,7 +191,7 @@ fn swap_both_fails_target_overflow() { // Invoke the `swap` under test. assert_noop!( - EvmSwap::swap( + NativeToEvmSwap::swap( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), 100, @@ -201,7 +201,7 @@ fn swap_both_fails_target_overflow() { // Invoke the `swap_keep_alive` under test. assert_noop!( - EvmSwap::swap_keep_alive( + NativeToEvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), 100, @@ -219,7 +219,7 @@ fn swap_both_fails_bridge_evm_killed() { // Invoke the `swap` under test. assert_noop!( - EvmSwap::swap( + NativeToEvmSwap::swap( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), BRIDGE_INIT_BALANCE, @@ -229,7 +229,7 @@ fn swap_both_fails_bridge_evm_killed() { // Invoke the `swap_keep_alive` under test. assert_noop!( - EvmSwap::swap_keep_alive( + NativeToEvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), BRIDGE_INIT_BALANCE, @@ -247,7 +247,7 @@ fn swap_both_fails_bridge_evm_no_funds() { // Invoke the `swap` under test. assert_noop!( - EvmSwap::swap( + NativeToEvmSwap::swap( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), 100, @@ -257,7 +257,7 @@ fn swap_both_fails_bridge_evm_no_funds() { // Invoke the `swap_keep_alive` under test. assert_noop!( - EvmSwap::swap_keep_alive( + NativeToEvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), 100, @@ -275,7 +275,7 @@ fn swap_both_fails_bridge_native_overflow() { // Invoke the `swap` under test. assert_noop!( - EvmSwap::swap( + NativeToEvmSwap::swap( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), 100, @@ -285,7 +285,7 @@ fn swap_both_fails_bridge_native_overflow() { // Invoke the `swap_keep_alive` under test. assert_noop!( - EvmSwap::swap_keep_alive( + NativeToEvmSwap::swap_keep_alive( RuntimeOrigin::signed(source_swap_native_account()), target_swap_evm_account(), 100, diff --git a/crates/pallet-evm-swap/src/weights.rs b/crates/pallet-native-to-evm-swap/src/weights.rs similarity index 100% rename from crates/pallet-evm-swap/src/weights.rs rename to crates/pallet-native-to-evm-swap/src/weights.rs diff --git a/crates/precompile-evm-swap/Cargo.toml b/crates/precompile-evm-to-native-swap/Cargo.toml similarity index 96% rename from crates/precompile-evm-swap/Cargo.toml rename to crates/precompile-evm-to-native-swap/Cargo.toml index 840cbbae0..8d3d75254 100644 --- a/crates/precompile-evm-swap/Cargo.toml +++ b/crates/precompile-evm-to-native-swap/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "precompile-evm-swap" +name = "precompile-evm-to-native-swap" version = "0.1.0" edition = "2021" publish = false diff --git a/crates/precompile-evm-swap/EvmSwap.sol b/crates/precompile-evm-to-native-swap/EvmToNativeSwap.sol similarity index 92% rename from crates/precompile-evm-swap/EvmSwap.sol rename to crates/precompile-evm-to-native-swap/EvmToNativeSwap.sol index da18a3843..11a704d6e 100644 --- a/crates/precompile-evm-swap/EvmSwap.sol +++ b/crates/precompile-evm-to-native-swap/EvmToNativeSwap.sol @@ -3,14 +3,14 @@ pragma solidity >=0.7.0 <0.9.0; /** - * @title Evm Swap Interface + * @title Evm to Native tokens Swap Interface * * An interface enabling swapping the funds from EVM accounts to * native Substrate accounts. * * Address: 0x0000000000000000000000000000000000000900 */ -interface EvmSwap { +interface EvmToNativeSwap { /** * Transfer the funds from an EVM account to native substrate account. * Selector: 76467cbd diff --git a/crates/precompile-evm-swap/src/lib.rs b/crates/precompile-evm-to-native-swap/src/lib.rs similarity index 94% rename from crates/precompile-evm-swap/src/lib.rs rename to crates/precompile-evm-to-native-swap/src/lib.rs index c83663a2b..9e9c82c29 100644 --- a/crates/precompile-evm-swap/src/lib.rs +++ b/crates/precompile-evm-to-native-swap/src/lib.rs @@ -1,4 +1,4 @@ -//! A precompile to swap EVM tokens with native chain tokens using fungible interfaces. +//! A precompile to swap EVM tokens to native chain tokens using fungible interfaces. #![cfg_attr(not(feature = "std"), no_std)] @@ -73,8 +73,8 @@ pub enum Action { Swap = "swap(bytes32)", } -/// Exposes the EVM swap interface. -pub struct EvmSwap(PhantomData<(ConfigT, GasCost)>) +/// Exposes the swap interface. +pub struct EvmToNativeSwap(PhantomData<(ConfigT, GasCost)>) where ConfigT: Config, EvmBalanceOf: TryFrom, @@ -82,7 +82,7 @@ where ConfigT::AccountId: From<[u8; 32]>, GasCost: Get; -impl Precompile for EvmSwap +impl Precompile for EvmToNativeSwap where ConfigT: Config, EvmBalanceOf: TryFrom, @@ -105,7 +105,7 @@ where } } -impl EvmSwap +impl EvmToNativeSwap where ConfigT: Config, EvmBalanceOf: TryFrom, @@ -113,7 +113,7 @@ where ConfigT::AccountId: From<[u8; 32]>, GasCost: Get, { - /// Swap EVM tokens to native tokens. + /// Swap EVM tokens to native chain tokens. fn swap(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; diff --git a/crates/precompile-evm-swap/src/mock.rs b/crates/precompile-evm-to-native-swap/src/mock.rs similarity index 97% rename from crates/precompile-evm-swap/src/mock.rs rename to crates/precompile-evm-to-native-swap/src/mock.rs index 888e45435..aaf37c79f 100644 --- a/crates/precompile-evm-swap/src/mock.rs +++ b/crates/precompile-evm-to-native-swap/src/mock.rs @@ -14,7 +14,7 @@ use frame_support::{ use precompile_utils::precompile_set::{PrecompileAt, PrecompileSetBuilder}; use sp_core::{Get, H160, H256, U256}; -use crate::{Config, EvmSwap}; +use crate::{Config, EvmToNativeSwap}; pub const INIT_BALANCE: u128 = 10_000_000_000_000_000; // Add some tokens to test swap with full balance. @@ -166,10 +166,10 @@ impl Config for PrecompileConfig { type BridgePotEvm = BridgePotEvm; } -pub type EvmSwapPrecompile = EvmSwap>; +pub type EvmToNativeSwapPrecompile = EvmToNativeSwap>; pub type Precompiles = - PrecompileSetBuilder>; + PrecompileSetBuilder>; parameter_types! { pub BlockGasLimit: U256 = U256::max_value(); diff --git a/crates/precompile-evm-swap/src/tests.rs b/crates/precompile-evm-to-native-swap/src/tests.rs similarity index 100% rename from crates/precompile-evm-swap/src/tests.rs rename to crates/precompile-evm-to-native-swap/src/tests.rs diff --git a/utils/checks/snapshots/features.yaml b/utils/checks/snapshots/features.yaml index 1ad3694d3..6139346c5 100644 --- a/utils/checks/snapshots/features.yaml +++ b/utils/checks/snapshots/features.yaml @@ -1984,10 +1984,6 @@ - name: pallet-evm-precompile-simple 2.0.0-dev features: - std -- name: pallet-evm-swap 0.1.0 - features: - - default - - std - name: pallet-evm-system 0.1.0 features: - default @@ -2012,6 +2008,10 @@ - name: pallet-multisig 4.0.0-dev features: - std +- name: pallet-native-to-evm-swap 0.1.0 + features: + - default + - std - name: pallet-pot 0.1.0 features: - default @@ -2164,7 +2164,7 @@ features: - default - std -- name: precompile-evm-swap 0.1.0 +- name: precompile-evm-to-native-swap 0.1.0 features: - default - std diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 480a16005..e942329c2 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -8,7 +8,8 @@ import { decodeEventLog } from "viem"; import * as substrate from "../../lib/substrate"; import { getNativeBalance } from "../../lib/substrateUtils"; -const evmSwapPrecompileAddress = "0x0000000000000000000000000000000000000900"; +const evmToNativeSwapPrecompileAddress = + "0x0000000000000000000000000000000000000900"; const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; const bridgePotNativeAccount = "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; @@ -54,7 +55,7 @@ describe("evm to native tokens swap", () => { const swapTxHash = await alice.writeContract({ abi: evmSwap.abi, - address: evmSwapPrecompileAddress, + address: evmToNativeSwapPrecompileAddress, functionName: "swap", args: [targetSwapNativeAccount], value: swapBalance, @@ -117,7 +118,7 @@ describe("evm to native tokens swap", () => { ); const evmSwapPrecompileBalance = await ethPublicClient.getBalance({ - address: evmSwapPrecompileAddress, + address: evmToNativeSwapPrecompileAddress, }); expect(evmSwapPrecompileBalance).toEqual(0n); }); diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 08c407ee1..0c3b4187b 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -44,7 +44,7 @@ describe("native to evm tokens swap", () => { const targetEvmAddress = "0x1100000000000000000000000000000000000011"; const swapBalance = 1_000_000n; - const swap = substrateApi.tx["evmSwap"]?.["swap"]; + const swap = substrateApi.tx["nativeToEvmSwap"]?.["swap"]; assert(swap); const sourceSwapBalanceBefore = await getNativeBalance( @@ -72,16 +72,16 @@ describe("native to evm tokens swap", () => { expect(dispatchError).toBe(undefined); expect(internalError).toBe(undefined); - let evmSwapBalancesSwappedEvent; + let nativeToEvmSwapBalancesSwappedEvent; let ethereumExecutedEvent; let transactionPaymentEvent; for (const item of events) { if ( - item.event.section == "evmSwap" && + item.event.section == "nativeToEvmSwap" && item.event.method == "BalancesSwapped" ) { - evmSwapBalancesSwappedEvent = item.event as unknown as IEvent< + nativeToEvmSwapBalancesSwappedEvent = item.event as unknown as IEvent< Codec[], EvmSwapBalancesSwappedEvent >; @@ -105,28 +105,28 @@ describe("native to evm tokens swap", () => { } } - assert(evmSwapBalancesSwappedEvent); + assert(nativeToEvmSwapBalancesSwappedEvent); assert(ethereumExecutedEvent); assert(transactionPaymentEvent); // Events related asserts. - expect(evmSwapBalancesSwappedEvent.data.from.toPrimitive()).toEqual( + expect(nativeToEvmSwapBalancesSwappedEvent.data.from.toPrimitive()).toEqual( alice.address, ); expect( BigInt( - evmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as unknown as bigint, + nativeToEvmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as unknown as bigint, ), ).toEqual(swapBalance); - expect(evmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( + expect(nativeToEvmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( targetEvmAddress, ); expect( BigInt( - evmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as unknown as bigint, + nativeToEvmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as unknown as bigint, ), ).toEqual(swapBalance); - expect(evmSwapBalancesSwappedEvent.data.evmTransactionHash).toEqual( + expect(nativeToEvmSwapBalancesSwappedEvent.data.evmTransactionHash).toEqual( ethereumExecutedEvent.data.transactionHash, ); expect(ethereumExecutedEvent.data.from.toPrimitive()).toEqual( From 3a84067521f81b1f21c79dd913dd7e592223bd59 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 12:45:22 +0300 Subject: [PATCH 092/102] Fix typos --- crates/humanode-runtime/src/tests/evm_swap.rs | 2 +- crates/pallet-native-to-evm-swap/src/lib.rs | 19 +++--- crates/pallet-native-to-evm-swap/src/tests.rs | 2 +- .../pallet-native-to-evm-swap/src/weights.rs | 4 +- .../precompile-evm-to-native-swap/src/lib.rs | 60 +++++++++---------- .../src/tests.rs | 2 +- 6 files changed, 45 insertions(+), 44 deletions(-) diff --git a/crates/humanode-runtime/src/tests/evm_swap.rs b/crates/humanode-runtime/src/tests/evm_swap.rs index 57b52daf2..d4b88b82d 100644 --- a/crates/humanode-runtime/src/tests/evm_swap.rs +++ b/crates/humanode-runtime/src/tests/evm_swap.rs @@ -160,7 +160,7 @@ fn native_to_evm_swap_call_works() { }) } -/// This test verifies that the ewm to native swap precompile call works in the happy path. +/// This test verifies that ewm to native swap precompile call works in the happy path. #[test] fn ewm_to_native_precompile_call_works() { // Build the state from the config. diff --git a/crates/pallet-native-to-evm-swap/src/lib.rs b/crates/pallet-native-to-evm-swap/src/lib.rs index 56223b757..21d0bf414 100644 --- a/crates/pallet-native-to-evm-swap/src/lib.rs +++ b/crates/pallet-native-to-evm-swap/src/lib.rs @@ -1,4 +1,4 @@ -//! A substrate pallet containing the EVM swap integration. +//! A substrate pallet containing native to EVM swap tokens integration. #![cfg_attr(not(feature = "std"), no_std)] @@ -160,7 +160,7 @@ pub mod pallet { let evm_transaction_hash = Self::execute_ethereum_transfer( T::BridgePotEvm::get().into(), to.clone().into(), - estimated_swapped_balance.unique_saturated_into(), + estimated_swapped_balance, )?; Self::deposit_event(Event::BalancesSwapped { @@ -175,14 +175,17 @@ pub mod pallet { } /// Execute ethereum transfer from source address to target EVM address with provided - /// balance to be sent. + /// balance value to be sent. fn execute_ethereum_transfer( source_address: H160, target_address: H160, - balance: u128, + value: EvmBalanceOf, ) -> Result { - let transaction = - ethereum_transfer_transaction::(source_address, target_address, balance); + let transaction = ethereum_transfer_transaction::( + source_address, + target_address, + value.unique_saturated_into(), + ); let transaction_hash = transaction.hash(); let (post_info, call_info) = @@ -225,7 +228,7 @@ pub mod pallet { pub(crate) fn ethereum_transfer_transaction( source_address: H160, target_address: H160, - balance: u128, + value: u128, ) -> pallet_ethereum::Transaction { pallet_ethereum::Transaction::EIP1559(ethereum::EIP1559Transaction { chain_id: T::ChainId::get(), @@ -236,7 +239,7 @@ pub(crate) fn ethereum_transfer_transaction( max_fee_per_gas: 0.into(), gas_limit: T::config().gas_transaction_call.into(), action: ethereum::TransactionAction::Call(target_address), - value: U256::from(balance), + value: U256::from(value), input: Default::default(), access_list: Default::default(), odd_y_parity: false, diff --git a/crates/pallet-native-to-evm-swap/src/tests.rs b/crates/pallet-native-to-evm-swap/src/tests.rs index a9685a85f..eae4a22b4 100644 --- a/crates/pallet-native-to-evm-swap/src/tests.rs +++ b/crates/pallet-native-to-evm-swap/src/tests.rs @@ -97,7 +97,7 @@ fn run_succeeded_test_and_assert( ::total_balance(&target_swap_evm_account()), target_swap_evm_account_balance_before + swap_balance ); - // Verifyt that we have a corresponding evm swap event. + // Verifyt that we have a corresponding native to evm swap event. System::assert_has_event(RuntimeEvent::NativeToEvmSwap(Event::BalancesSwapped { from: source_swap_native_account(), withdrawed_amount: swap_balance, diff --git a/crates/pallet-native-to-evm-swap/src/weights.rs b/crates/pallet-native-to-evm-swap/src/weights.rs index da08f451d..02d1fe381 100644 --- a/crates/pallet-native-to-evm-swap/src/weights.rs +++ b/crates/pallet-native-to-evm-swap/src/weights.rs @@ -1,8 +1,8 @@ -//! Weights definition for pallet-native-to-evm-currency-swap. +//! Weights definition for pallet-native-to-evm-swap. use frame_support::weights::Weight; -/// Weight functions needed for pallet-currency-swap. +/// Weight functions needed for pallet-native-to-evm-swap. pub trait WeightInfo { /// A function to calculate required weights for swap call. fn swap() -> Weight; diff --git a/crates/precompile-evm-to-native-swap/src/lib.rs b/crates/precompile-evm-to-native-swap/src/lib.rs index 9e9c82c29..d9417ff39 100644 --- a/crates/precompile-evm-to-native-swap/src/lib.rs +++ b/crates/precompile-evm-to-native-swap/src/lib.rs @@ -26,14 +26,13 @@ mod mock; mod tests; /// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::NativeToken`] type. -pub type NativeBalanceOf = +type NativeBalanceOf = <::NativeToken as Inspect<::AccountId>>::Balance; /// Utility alias for easy access to the [`Inspect::Balance`] of the [`Config::EvmToken`] type. -pub type EvmBalanceOf = - <::EvmToken as Inspect<::EvmAccountId>>::Balance; +type EvmBalanceOf = <::EvmToken as Inspect<::EvmAccountId>>::Balance; -/// The config for the swap logic. +/// The config for EVM to native swap logic. pub trait Config { /// The native user account identifier type. type AccountId: From<[u8; 32]>; @@ -74,20 +73,20 @@ pub enum Action { } /// Exposes the swap interface. -pub struct EvmToNativeSwap(PhantomData<(ConfigT, GasCost)>) +pub struct EvmToNativeSwap(PhantomData<(C, GasCost)>) where - ConfigT: Config, - EvmBalanceOf: TryFrom, - ConfigT::EvmAccountId: From, - ConfigT::AccountId: From<[u8; 32]>, + C: Config, + EvmBalanceOf: TryFrom, + C::EvmAccountId: From, + C::AccountId: From<[u8; 32]>, GasCost: Get; -impl Precompile for EvmToNativeSwap +impl Precompile for EvmToNativeSwap where - ConfigT: Config, - EvmBalanceOf: TryFrom, - ConfigT::EvmAccountId: From, - ConfigT::AccountId: From<[u8; 32]>, + C: Config, + EvmBalanceOf: TryFrom, + C::EvmAccountId: From, + C::AccountId: From<[u8; 32]>, GasCost: Get, { fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { @@ -105,12 +104,12 @@ where } } -impl EvmToNativeSwap +impl EvmToNativeSwap where - ConfigT: Config, - EvmBalanceOf: TryFrom, - ConfigT::EvmAccountId: From, - ConfigT::AccountId: From<[u8; 32]>, + C: Config, + EvmBalanceOf: TryFrom, + C::EvmAccountId: From, + C::AccountId: From<[u8; 32]>, GasCost: Get, { /// Swap EVM tokens to native chain tokens. @@ -124,10 +123,9 @@ where } = handle.context(); let value_u256 = *value; - let value: EvmBalanceOf = - (*value).try_into().map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("value is out of bounds".into()), - })?; + let value: EvmBalanceOf = (*value).try_into().map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("value is out of bounds".into()), + })?; input .expect_arguments(1) @@ -137,7 +135,7 @@ where let to_h256: H256 = input.read()?; let to: [u8; 32] = to_h256.into(); - let to: ConfigT::AccountId = to.into(); + let to: C::AccountId = to.into(); let junk_data = input.read_till_end()?; if !junk_data.is_empty() { @@ -148,24 +146,24 @@ where // 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: ConfigT::EvmAccountId = (*address).into(); + let from: C::EvmAccountId = (*address).into(); - let estimated_swapped_balance = ConfigT::BalanceConverterEvmToNative::convert(value); + let estimated_swapped_balance = C::BalanceConverterEvmToNative::convert(value); - ConfigT::NativeToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) + C::NativeToken::can_deposit(&to, estimated_swapped_balance, Provenance::Extant) .into_result() .map_err(process_dispatch_error)?; - ConfigT::EvmToken::transfer( + C::EvmToken::transfer( &from, - &ConfigT::BridgePotEvm::get(), + &C::BridgePotEvm::get(), value, Preservation::Expendable, ) .map_err(process_dispatch_error)?; - ConfigT::NativeToken::transfer( - &ConfigT::BridgePotNative::get(), + C::NativeToken::transfer( + &C::BridgePotNative::get(), &to, estimated_swapped_balance, // Bridge pot native account shouldn't be killed. diff --git a/crates/precompile-evm-to-native-swap/src/tests.rs b/crates/precompile-evm-to-native-swap/src/tests.rs index 3ab6679f8..173ae86ab 100644 --- a/crates/precompile-evm-to-native-swap/src/tests.rs +++ b/crates/precompile-evm-to-native-swap/src/tests.rs @@ -185,7 +185,7 @@ fn run_failed_test_and_assert( assert_eq!(execinfo.value, expected_exit_value); assert_eq!(execinfo.logs, vec![]); - // Verify that source swap evm balance remains the same. + // Verify that source swap evm balance is reduced just by spent fee. assert_eq!( ::total_balance(&source_swap_evm_account()), source_swap_evm_account_balance_before - expected_fee, From 5ca3164e2bb646124edcb8ae048a11f5bfc349d8 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 5 Mar 2025 13:26:30 +0300 Subject: [PATCH 093/102] Add feesPot balance check at e2e tests --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 14 ++++++++++++++ utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index e942329c2..7621b78f4 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -13,6 +13,8 @@ const evmToNativeSwapPrecompileAddress = const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; const bridgePotNativeAccount = "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; +const feesPotNativeAccount = + "hmpwhPbL5XJTYPWXPMkacfqGhJ3eoQRPLKphajpvcot5Q5zkk"; describe("evm to native tokens swap", () => { let node: RunNodeState; @@ -52,6 +54,10 @@ describe("evm to native tokens swap", () => { substrateApi, targetSwapNativeAccountSs58, ); + const feesPotNativeAccountBalanceBefore = await getNativeBalance( + substrateApi, + feesPotNativeAccount, + ); const swapTxHash = await alice.writeContract({ abi: evmSwap.abi, @@ -117,6 +123,14 @@ describe("evm to native tokens swap", () => { targetNativeAccountBalanceBefore + swapBalance, ); + const feesPotNativeAccountBalanceAfter = await getNativeBalance( + substrateApi, + feesPotNativeAccount, + ); + expect(feesPotNativeAccountBalanceAfter).toEqual( + feesPotNativeAccountBalanceBefore + fee, + ); + const evmSwapPrecompileBalance = await ethPublicClient.getBalance({ address: evmToNativeSwapPrecompileAddress, }); diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 0c3b4187b..5e436852e 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -23,6 +23,8 @@ type TransactionPaymentEvent = Record<"who" | "actualFee", Codec>; const bridgePotEvmAddress = "0x6d6f646c686d63732f656e310000000000000000"; const bridgePotNativeAccount = "hmpwhPbL5XJM1pYFVL6wRPkUP5gHQyvC6R5jMkziwnGTQ6hFr"; +const feesPotNativeAccount = + "hmpwhPbL5XJTYPWXPMkacfqGhJ3eoQRPLKphajpvcot5Q5zkk"; describe("native to evm tokens swap", () => { let node: RunNodeState; @@ -61,6 +63,10 @@ describe("native to evm tokens swap", () => { const bridgePotEvmBalanceBefore = await ethPublicClient.getBalance({ address: bridgePotEvmAddress, }); + const feesPotNativeAccountBalanceBefore = await getNativeBalance( + substrateApi, + feesPotNativeAccount, + ); const { isCompleted, internalError, events, status, dispatchError } = await sendAndWait(swap(targetEvmAddress, swapBalance), { @@ -175,5 +181,13 @@ describe("native to evm tokens swap", () => { expect(bridgePotEvmBalanceAfter).toEqual( bridgePotEvmBalanceBefore - swapBalance, ); + + const feesPotNativeAccountBalanceAfter = await getNativeBalance( + substrateApi, + feesPotNativeAccount, + ); + expect(feesPotNativeAccountBalanceAfter).toEqual( + feesPotNativeAccountBalanceBefore + fee, + ); }); }); From 154e03c575f79e2797b3134d92f2d56cf8782182 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 7 Mar 2025 14:03:36 +0300 Subject: [PATCH 094/102] Pass more gas for failed tests in precompile --- crates/precompile-evm-to-native-swap/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/precompile-evm-to-native-swap/src/tests.rs b/crates/precompile-evm-to-native-swap/src/tests.rs index 173ae86ab..d2eb9ddad 100644 --- a/crates/precompile-evm-to-native-swap/src/tests.rs +++ b/crates/precompile-evm-to-native-swap/src/tests.rs @@ -168,7 +168,7 @@ fn run_failed_test_and_assert( *PRECOMPILE_ADDRESS, input, value, - expected_gas_usage, // the exact amount of fee we'll be using + 50_000, // a reasonable upper bound for tests Some(*GAS_PRICE), Some(*GAS_PRICE), None, From 6a56dbbe13bb994f1e3b8d73df8a1372f942bfaa Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 7 Mar 2025 14:31:50 +0300 Subject: [PATCH 095/102] Minor improvements for e2e tests --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 33 +++++++++--------- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 36 +++++++++----------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 7621b78f4..9c5abeff4 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -40,7 +40,7 @@ describe("evm to native tokens swap", () => { const targetSwapNativeAccountSs58 = "hmqAEn816d1W6TxbT7Md2Zc4hq1AUXFiLEs8yXW5BCUHFx54W"; - const sourceSwapBalanceBefore = await ethPublicClient.getBalance({ + const sourceSwapEvmBalanceBefore = await ethPublicClient.getBalance({ address: alice.account.address, }); const bridgePotEvmBalanceBefore = await ethPublicClient.getBalance({ @@ -50,11 +50,11 @@ describe("evm to native tokens swap", () => { substrateApi, bridgePotNativeAccount, ); - const targetNativeAccountBalanceBefore = await getNativeBalance( + const targetSwapNativeBalanceBefore = await getNativeBalance( substrateApi, targetSwapNativeAccountSs58, ); - const feesPotNativeAccountBalanceBefore = await getNativeBalance( + const feesPotNativeBalanceBefore = await getNativeBalance( substrateApi, feesPotNativeAccount, ); @@ -74,11 +74,14 @@ describe("evm to native tokens swap", () => { expect(swapTxReceipt.status).toBe("success"); - const log = swapTxReceipt.logs[0]!; + const logs = swapTxReceipt.logs; + expect(logs.length).toEqual(1); + + const swapLog = logs[0]!; const event = decodeEventLog({ abi: evmSwap.abi, - data: log.data, - topics: log.topics, + data: swapLog.data, + topics: swapLog.topics, }); expect(event).toEqual({ @@ -93,11 +96,11 @@ describe("evm to native tokens swap", () => { const fee = swapTxReceipt.cumulativeGasUsed * swapTxReceipt.effectiveGasPrice; - const sourceSwapBalanceAfter = await ethPublicClient.getBalance({ + const sourceSwapEvmBalanceAfter = await ethPublicClient.getBalance({ address: alice.account.address, }); - expect(sourceSwapBalanceAfter).toEqual( - sourceSwapBalanceBefore - swapBalance - fee, + expect(sourceSwapEvmBalanceAfter).toEqual( + sourceSwapEvmBalanceBefore - swapBalance - fee, ); const bridgePotEvmBalanceAfter = await ethPublicClient.getBalance({ @@ -115,21 +118,19 @@ describe("evm to native tokens swap", () => { bridgePotNativeBalanceBefore - swapBalance - fee, ); - const targetNativeAccountBalanceAfter = await getNativeBalance( + const targetSwapNativeBalanceAfter = await getNativeBalance( substrateApi, targetSwapNativeAccountSs58, ); - expect(targetNativeAccountBalanceAfter).toEqual( - targetNativeAccountBalanceBefore + swapBalance, + expect(targetSwapNativeBalanceAfter).toEqual( + targetSwapNativeBalanceBefore + swapBalance, ); - const feesPotNativeAccountBalanceAfter = await getNativeBalance( + const feesPotNativeBalanceAfter = await getNativeBalance( substrateApi, feesPotNativeAccount, ); - expect(feesPotNativeAccountBalanceAfter).toEqual( - feesPotNativeAccountBalanceBefore + fee, - ); + expect(feesPotNativeBalanceAfter).toEqual(feesPotNativeBalanceBefore + fee); const evmSwapPrecompileBalance = await ethPublicClient.getBalance({ address: evmToNativeSwapPrecompileAddress, diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 5e436852e..4fedcedfe 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -43,13 +43,13 @@ describe("native to evm tokens swap", () => { const keyring = new Keyring({ type: "sr25519", ss58Format: 5234 }); const alice = keyring.addFromUri("//Alice"); - const targetEvmAddress = "0x1100000000000000000000000000000000000011"; + const targetSwapEvmAddress = "0x1100000000000000000000000000000000000011"; const swapBalance = 1_000_000n; const swap = substrateApi.tx["nativeToEvmSwap"]?.["swap"]; assert(swap); - const sourceSwapBalanceBefore = await getNativeBalance( + const sourceSwapNativeBalanceBefore = await getNativeBalance( substrateApi, alice.address, ); @@ -57,19 +57,19 @@ describe("native to evm tokens swap", () => { substrateApi, bridgePotNativeAccount, ); - const targetEvmAccountBalanceBefore = await ethPublicClient.getBalance({ - address: targetEvmAddress, + const targetSwapEvmBalanceBefore = await ethPublicClient.getBalance({ + address: targetSwapEvmAddress, }); const bridgePotEvmBalanceBefore = await ethPublicClient.getBalance({ address: bridgePotEvmAddress, }); - const feesPotNativeAccountBalanceBefore = await getNativeBalance( + const feesPotNativeBalanceBefore = await getNativeBalance( substrateApi, feesPotNativeAccount, ); const { isCompleted, internalError, events, status, dispatchError } = - await sendAndWait(swap(targetEvmAddress, swapBalance), { + await sendAndWait(swap(targetSwapEvmAddress, swapBalance), { signWith: alice, }); @@ -125,7 +125,7 @@ describe("native to evm tokens swap", () => { ), ).toEqual(swapBalance); expect(nativeToEvmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( - targetEvmAddress, + targetSwapEvmAddress, ); expect( BigInt( @@ -139,7 +139,7 @@ describe("native to evm tokens swap", () => { bridgePotEvmAddress, ); expect(ethereumExecutedEvent.data.to.toPrimitive()).toEqual( - targetEvmAddress, + targetSwapEvmAddress, ); expect(ethereumExecutedEvent.data.exitReason.toPrimitive()).toEqual({ succeed: "Stopped", @@ -152,12 +152,12 @@ describe("native to evm tokens swap", () => { alice.address, ); - const sourceSwapBalanceAfter = await getNativeBalance( + const sourceSwapNativeBalanceAfter = await getNativeBalance( substrateApi, alice.address, ); - expect(sourceSwapBalanceAfter).toEqual( - sourceSwapBalanceBefore - swapBalance - fee, + expect(sourceSwapNativeBalanceAfter).toEqual( + sourceSwapNativeBalanceBefore - swapBalance - fee, ); const bridgePotNativeBalanceAfter = await getNativeBalance( @@ -168,11 +168,11 @@ describe("native to evm tokens swap", () => { bridgePotNativeBalanceBefore + swapBalance, ); - const targetEvmAccountBalanceAfter = await ethPublicClient.getBalance({ - address: targetEvmAddress, + const targetSwapEvmBalanceAfter = await ethPublicClient.getBalance({ + address: targetSwapEvmAddress, }); - expect(targetEvmAccountBalanceAfter).toEqual( - targetEvmAccountBalanceBefore + swapBalance, + expect(targetSwapEvmBalanceAfter).toEqual( + targetSwapEvmBalanceBefore + swapBalance, ); const bridgePotEvmBalanceAfter = await ethPublicClient.getBalance({ @@ -182,12 +182,10 @@ describe("native to evm tokens swap", () => { bridgePotEvmBalanceBefore - swapBalance, ); - const feesPotNativeAccountBalanceAfter = await getNativeBalance( + const feesPotNativeBalanceAfter = await getNativeBalance( substrateApi, feesPotNativeAccount, ); - expect(feesPotNativeAccountBalanceAfter).toEqual( - feesPotNativeAccountBalanceBefore + fee, - ); + expect(feesPotNativeBalanceAfter).toEqual(feesPotNativeBalanceBefore + fee); }); }); From f0be229355a2d4d0613b1e4721d216016a93670d Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 13 Mar 2025 10:14:18 +0300 Subject: [PATCH 096/102] Use fully qualified path for precompile_evm_to_native_swap --- crates/humanode-runtime/src/evm_swap.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/humanode-runtime/src/evm_swap.rs b/crates/humanode-runtime/src/evm_swap.rs index fd1a17a79..46672ba5c 100644 --- a/crates/humanode-runtime/src/evm_swap.rs +++ b/crates/humanode-runtime/src/evm_swap.rs @@ -1,5 +1,4 @@ use bridge_pot_currency_swap::ExistenceRequired; -use precompile_evm_to_native_swap::Config as PrecompileConfig; use sp_runtime::traits::Identity; use crate::{ @@ -15,7 +14,7 @@ parameter_types! { pub struct EvmToNativeSwapConfig; -impl PrecompileConfig for EvmToNativeSwapConfig { +impl precompile_evm_to_native_swap::Config for EvmToNativeSwapConfig { type AccountId = AccountId; type EvmAccountId = EvmAccountId; type NativeToken = Balances; From c9456c3610aad63aea94b125d8b7679d209aa353 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 13 Mar 2025 19:46:57 +0300 Subject: [PATCH 097/102] Add ethereum execution checks for nativeToEvm e2e test --- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 4fedcedfe..31cbb178f 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -115,6 +115,19 @@ describe("native to evm tokens swap", () => { assert(ethereumExecutedEvent); assert(transactionPaymentEvent); + // Ethereum execution checks. + const executedEvmTransaction = await ethPublicClient.getTransaction({ + hash: ethereumExecutedEvent.data.transactionHash.toPrimitive() as `0x${string}`, + }); + expect(executedEvmTransaction.from).toEqual(bridgePotEvmAddress); + expect(executedEvmTransaction.to).toEqual(targetSwapEvmAddress); + expect(executedEvmTransaction.value).toEqual(swapBalance); + expect(executedEvmTransaction.gasPrice).toEqual(0n); + expect(executedEvmTransaction.maxFeePerGas).toEqual(0n); + expect(executedEvmTransaction.maxPriorityFeePerGas).toEqual(0n); + expect(executedEvmTransaction.gas).toEqual(21000n); + expect(executedEvmTransaction.input).toEqual("0x"); + // Events related asserts. expect(nativeToEvmSwapBalancesSwappedEvent.data.from.toPrimitive()).toEqual( alice.address, From 8b7fc35364f46381c67129755ae7733d006b8dcb Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 13 Mar 2025 19:49:50 +0300 Subject: [PATCH 098/102] Add some comment --- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 31cbb178f..a717b274d 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -158,6 +158,7 @@ describe("native to evm tokens swap", () => { succeed: "Stopped", }); + // Balances changes related checks. const fee = BigInt( transactionPaymentEvent.data.actualFee.toPrimitive() as unknown as bigint, ); From f68e07485d5b93eb2a2b29d388e3801873ae3ea5 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Fri, 14 Mar 2025 09:52:42 +0300 Subject: [PATCH 099/102] Improve the way of checking expected events for nativeToEvm e2e test --- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 80 +++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index a717b274d..70420405d 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -78,42 +78,50 @@ describe("native to evm tokens swap", () => { expect(dispatchError).toBe(undefined); expect(internalError).toBe(undefined); - let nativeToEvmSwapBalancesSwappedEvent; - let ethereumExecutedEvent; - let transactionPaymentEvent; - - for (const item of events) { - if ( - item.event.section == "nativeToEvmSwap" && - item.event.method == "BalancesSwapped" - ) { - nativeToEvmSwapBalancesSwappedEvent = item.event as unknown as IEvent< - Codec[], - EvmSwapBalancesSwappedEvent - >; - } - - if (item.event.section == "ethereum" && item.event.method == "Executed") { - ethereumExecutedEvent = item.event as unknown as IEvent< - Codec[], - EthereumExecutedEvent - >; - } - - if ( - item.event.section == "transactionPayment" && - item.event.method == "TransactionFeePaid" - ) { - transactionPaymentEvent = item.event as unknown as IEvent< - Codec[], - TransactionPaymentEvent - >; - } - } - - assert(nativeToEvmSwapBalancesSwappedEvent); - assert(ethereumExecutedEvent); - assert(transactionPaymentEvent); + const expectedEvents = [ + // Executed fee withdraw from source swap native account. + ["balances", "Withdraw"], + // Executed swap balance transfer from source swap to bridge pot native account. + ["balances", "Transfer"], + // Executed new target swap EVM account creation in accounts records. + ["evmSystem", "NewAccount"], + // Executed EVM balances explicitly related event that an account is created with some free balance. + ["evmBalances", "Endowed"], + // Executed swap balance transfer from bridge EVM to target swap address. + ["evmBalances", "Transfer"], + // Executed ethereum transaction. + ["ethereum", "Executed"], + // Executed native to EVM swap. + ["nativeToEvmSwap", "BalancesSwapped"], + // Executed fee deposit to fees pot native account. + ["balances", "Deposit"], + // Executed pot explicitly related event that some balance is deposited. + ["feesPot", "Deposit"], + // Executed transaction payment event. + ["transactionPayment", "TransactionFeePaid"], + // Executed extrinsic success event. + ["system", "ExtrinsicSuccess"], + ] as const; + + expectedEvents.forEach((value, idx) => { + const [section, method] = value; + expect(events[idx]).toBeDefined(); + expect(events[idx]?.event?.section).toBe(section); + expect(events[idx]?.event?.method).toBe(method); + }); + + expect(events).toHaveLength(expectedEvents.length); + + const ethereumExecutedEvent = events[5]?.event as unknown as IEvent< + Codec[], + EthereumExecutedEvent + >; + const nativeToEvmSwapBalancesSwappedEvent = events[6] + ?.event as unknown as IEvent; + const transactionPaymentEvent = events[9]?.event as unknown as IEvent< + Codec[], + TransactionPaymentEvent + >; // Ethereum execution checks. const executedEvmTransaction = await ethPublicClient.getTransaction({ From 13d59b9026ca34516a114c0c7ae7f3edd3e46023 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 24 Mar 2025 17:11:09 +0300 Subject: [PATCH 100/102] Use assert_noop at precompile tests where fee is not spent --- .../src/tests.rs | 137 +++++++----------- 1 file changed, 51 insertions(+), 86 deletions(-) diff --git a/crates/precompile-evm-to-native-swap/src/tests.rs b/crates/precompile-evm-to-native-swap/src/tests.rs index d2eb9ddad..a33ff1d76 100644 --- a/crates/precompile-evm-to-native-swap/src/tests.rs +++ b/crates/precompile-evm-to-native-swap/src/tests.rs @@ -2,7 +2,7 @@ #![allow(clippy::arithmetic_side_effects)] use fp_evm::{ExitError, ExitReason}; -use frame_support::traits::fungible::Unbalanced; +use frame_support::{assert_noop, traits::fungible::Unbalanced}; use pallet_evm::Runner; use precompile_utils::{EvmDataWriter, LogsBuilder}; use sp_core::H256; @@ -393,50 +393,32 @@ fn swap_fail_trailing_junk() { #[test] fn runner_fail_source_balance_no_funds() { new_test_ext().execute_with_ext(|_| { - let storage_root = frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1); - - // Invoke the function under test. - let execerr = ::Runner::call( - source_swap_evm_account(), - *PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::from(INIT_BALANCE + 1), - 50_000, // a reasonable upper bound for tests - Some(*GAS_PRICE), - Some(*GAS_PRICE), - None, - Vec::new(), - true, - true, - None, - None, - ::config(), - ) - .unwrap_err(); - assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); - assert_eq!( - storage_root, - frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1), - "storage changed" - ); - - // Assert that interested balances have not been changed. - assert_eq!( - ::total_balance(&source_swap_evm_account()), - INIT_BALANCE, - ); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(::total_balance(&target_swap_native_account()), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, + assert_noop!( + Err::<(), DispatchError>( + ::Runner::call( + source_swap_evm_account(), + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::from(INIT_BALANCE + 1), + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), + ) + .unwrap_err() + .error + .into() + ), + pallet_evm::Error::::BalanceLow ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); }); } @@ -449,49 +431,32 @@ fn runner_fail_source_balance_no_funds() { #[test] fn runner_fail_value_overflow() { new_test_ext().execute_with_ext(|_| { - let storage_root = frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1); - // Invoke the function under test. - let execerr = ::Runner::call( - source_swap_evm_account(), - *PRECOMPILE_ADDRESS, - EvmDataWriter::new_with_selector(Action::Swap) - .write(H256::from(target_swap_native_account().as_ref())) - .build(), - U256::MAX, - 50_000, // a reasonable upper bound for tests - Some(*GAS_PRICE), - Some(*GAS_PRICE), - None, - Vec::new(), - true, - true, - None, - None, - ::config(), - ) - .unwrap_err(); - assert!(matches!(execerr.error, pallet_evm::Error::BalanceLow)); - assert_eq!( - storage_root, - frame_support::storage_root(frame_support::sp_runtime::StateVersion::V1), - "storage changed" - ); - - // Assert that interested balances have not been changed. - assert_eq!( - ::total_balance(&source_swap_evm_account()), - INIT_BALANCE, - ); - assert_eq!( - EvmBalances::total_balance(&BridgePotEvm::get()), - BRIDGE_INIT_BALANCE, - ); - assert_eq!(::total_balance(&target_swap_native_account()), 0); - assert_eq!( - Balances::total_balance(&BridgePotNative::get()), - BRIDGE_INIT_BALANCE, + assert_noop!( + Err::<(), DispatchError>( + ::Runner::call( + source_swap_evm_account(), + *PRECOMPILE_ADDRESS, + EvmDataWriter::new_with_selector(Action::Swap) + .write(H256::from(target_swap_native_account().as_ref())) + .build(), + U256::MAX, + 50_000, // a reasonable upper bound for tests + Some(*GAS_PRICE), + Some(*GAS_PRICE), + None, + Vec::new(), + true, + true, + None, + None, + ::config(), + ) + .unwrap_err() + .error + .into() + ), + pallet_evm::Error::::BalanceLow ); - assert_eq!(EvmBalances::total_balance(&*PRECOMPILE_ADDRESS), 0); }); } From 38b15ed040409217e0c06164927a24ef11aa89d2 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 24 Mar 2025 17:19:48 +0300 Subject: [PATCH 101/102] Explicitly convert to number from primitive --- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 70420405d..5511fb74c 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -142,7 +142,7 @@ describe("native to evm tokens swap", () => { ); expect( BigInt( - nativeToEvmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as unknown as bigint, + nativeToEvmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as number, ), ).toEqual(swapBalance); expect(nativeToEvmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( @@ -150,7 +150,7 @@ describe("native to evm tokens swap", () => { ); expect( BigInt( - nativeToEvmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as unknown as bigint, + nativeToEvmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as number, ), ).toEqual(swapBalance); expect(nativeToEvmSwapBalancesSwappedEvent.data.evmTransactionHash).toEqual( @@ -168,7 +168,7 @@ describe("native to evm tokens swap", () => { // Balances changes related checks. const fee = BigInt( - transactionPaymentEvent.data.actualFee.toPrimitive() as unknown as bigint, + transactionPaymentEvent.data.actualFee.toPrimitive() as number, ); expect(transactionPaymentEvent.data.who.toPrimitive()).toEqual( alice.address, From 01643bd82e5405181e50074c48e74641dd256314 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 24 Mar 2025 19:28:22 +0300 Subject: [PATCH 102/102] Use as string | number for primitive --- utils/e2e-tests/ts/tests/swap/nativeToEvm.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts index 5511fb74c..eb175f020 100644 --- a/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts +++ b/utils/e2e-tests/ts/tests/swap/nativeToEvm.ts @@ -142,7 +142,9 @@ describe("native to evm tokens swap", () => { ); expect( BigInt( - nativeToEvmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as number, + nativeToEvmSwapBalancesSwappedEvent.data.withdrawedAmount.toPrimitive() as + | string + | number, ), ).toEqual(swapBalance); expect(nativeToEvmSwapBalancesSwappedEvent.data.to.toPrimitive()).toEqual( @@ -150,7 +152,9 @@ describe("native to evm tokens swap", () => { ); expect( BigInt( - nativeToEvmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as number, + nativeToEvmSwapBalancesSwappedEvent.data.depositedAmount.toPrimitive() as + | string + | number, ), ).toEqual(swapBalance); expect(nativeToEvmSwapBalancesSwappedEvent.data.evmTransactionHash).toEqual( @@ -168,7 +172,7 @@ describe("native to evm tokens swap", () => { // Balances changes related checks. const fee = BigInt( - transactionPaymentEvent.data.actualFee.toPrimitive() as number, + transactionPaymentEvent.data.actualFee.toPrimitive() as string | number, ); expect(transactionPaymentEvent.data.who.toPrimitive()).toEqual( alice.address,