From 4adfa04dd361826c50c2f71b69372e5dfdd236a2 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 7 Apr 2025 13:13:58 +0300 Subject: [PATCH 01/15] Introduce PrecompilesSet to config --- crates/pallet-evm-system/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/pallet-evm-system/src/lib.rs b/crates/pallet-evm-system/src/lib.rs index 172e651c5..b26b14779 100644 --- a/crates/pallet-evm-system/src/lib.rs +++ b/crates/pallet-evm-system/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; -use frame_support::traits::StoredMap; +use frame_support::traits::{StoredMap, TypedGet}; use scale_info::TypeInfo; use sp_runtime::{ traits::{One, Zero}, @@ -34,7 +34,7 @@ pub struct AccountInfo { pub mod pallet { use frame_support::pallet_prelude::*; use sp_runtime::traits::{AtLeast32Bit, MaybeDisplay}; - use sp_std::fmt::Debug; + use sp_std::{collections::btree_set::BTreeSet, fmt::Debug}; use super::*; @@ -72,6 +72,9 @@ pub mod pallet { /// pallet does regardless). type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; + /// A set of precompiles. + type PrecompilesSet: TypedGet::AccountId>>; + /// Handler for when a new account has just been created. type OnNewAccount: OnNewAccount<::AccountId>; @@ -159,7 +162,7 @@ impl Pallet { /// Create an account. pub fn create_account(who: &::AccountId) -> AccountCreationOutcome { - if Self::account_exists(who) { + if Self::account_exists(who) || T::PrecompilesSet::get().contains(who) { return AccountCreationOutcome::AlreadyExists; } @@ -196,7 +199,7 @@ impl StoredMap<::AccountId, ::AccountData> Account::::mutate(k, |a| a.data = data); } (None, true) => { - if nonce != ::Index::zero() { + if nonce != ::Index::zero() || T::PrecompilesSet::get().contains(k) { Account::::mutate(k, |a| a.data = Default::default()); } else { Account::::remove(k); From 13859237ee1c028fca80e95d61503e37441cce0c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 7 Apr 2025 13:14:24 +0300 Subject: [PATCH 02/15] Add PrecompilesSet data for mock env --- Cargo.lock | 1 + crates/pallet-evm-system/Cargo.toml | 1 + crates/pallet-evm-system/src/mock.rs | 16 ++++++++++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f453a2ca1..0a6b1d73b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6174,6 +6174,7 @@ dependencies = [ "fp-evm", "frame-support", "frame-system", + "hex-literal", "mockall", "parity-scale-codec", "scale-info", diff --git a/crates/pallet-evm-system/Cargo.toml b/crates/pallet-evm-system/Cargo.toml index 2e00f444f..5f3cbaa05 100644 --- a/crates/pallet-evm-system/Cargo.toml +++ b/crates/pallet-evm-system/Cargo.toml @@ -14,6 +14,7 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] +hex-literal = { workspace = true } mockall = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } diff --git a/crates/pallet-evm-system/src/mock.rs b/crates/pallet-evm-system/src/mock.rs index 8beb62295..ec2d282c7 100644 --- a/crates/pallet-evm-system/src/mock.rs +++ b/crates/pallet-evm-system/src/mock.rs @@ -1,4 +1,7 @@ -use frame_support::traits::{ConstU32, ConstU64}; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; use mockall::mock; use sp_core::{H160, H256}; use sp_runtime::{ @@ -6,7 +9,7 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; -use sp_std::{boxed::Box, prelude::*}; +use sp_std::{boxed::Box, collections::btree_set::BTreeSet, prelude::*}; use crate::{self as pallet_evm_system, *}; @@ -69,11 +72,20 @@ impl frame_system::Config for Test { type MaxConsumers = ConstU32<16>; } +pub const PRECOMPILE: H160 = H160(hex_literal::hex!( + "7000000000000000000000000000000000000007" +)); + +parameter_types! { + pub PrecompilesSet: BTreeSet = BTreeSet::from([PRECOMPILE]); +} + impl pallet_evm_system::Config for Test { type RuntimeEvent = RuntimeEvent; type AccountId = H160; type Index = u64; type AccountData = u64; + type PrecompilesSet = PrecompilesSet; type OnNewAccount = MockDummyOnNewAccount; type OnKilledAccount = MockDummyOnKilledAccount; } From ca904a8f91ed7766750a53471a4e0408c91add55 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 7 Apr 2025 13:28:54 +0300 Subject: [PATCH 03/15] Add tests --- crates/pallet-evm-system/src/tests.rs | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/crates/pallet-evm-system/src/tests.rs b/crates/pallet-evm-system/src/tests.rs index c8d6c51fd..8ef30860b 100644 --- a/crates/pallet-evm-system/src/tests.rs +++ b/crates/pallet-evm-system/src/tests.rs @@ -67,6 +67,27 @@ fn create_account_already_exists() { }); } +/// This test verifies that trying creating an precompiled account works as expected. +#[test] +fn create_precompiled_account_already_exists() { + new_test_ext().execute_with_ext(|_| { + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + assert_storage_noop!(assert_eq!( + EvmSystem::create_account(&PRECOMPILE), + AccountCreationOutcome::AlreadyExists + )); + + // Assert that there is no a corresponding `NewAccount` event. + assert!(System::events().iter().all(|record| record.event + != RuntimeEvent::EvmSystem(Event::NewAccount { + account: PRECOMPILE, + }))); + }); +} + /// This test verifies that incrementing account nonce works in the happy path /// in case a new account should be created. #[test] @@ -296,6 +317,43 @@ fn try_mutate_exists_account_retained() { }); } +/// This test verifies that `try_mutate_exists` works as expected in case data was providing +/// and returned data is `None`, account is precompiled. As a result, the account has been retained. +#[test] +fn try_mutate_exists_precompiled_account_retained() { + new_test_ext().execute_with_ext(|_| { + // Prepare test data. + let nonce = 0; + let data = 100; + + let account_info = AccountInfo { nonce, data }; + >::insert(PRECOMPILE, account_info); + + // Check test preconditions. + assert!(EvmSystem::account_exists(&PRECOMPILE)); + + // Set block number to enable events. + System::set_block_number(1); + + // Invoke the function under test. + EvmSystem::try_mutate_exists(&PRECOMPILE, |maybe_data| -> Result<(), DispatchError> { + *maybe_data = None; + Ok(()) + }) + .unwrap(); + + // Assert state changes. + assert!(EvmSystem::account_exists(&PRECOMPILE)); + assert_eq!(>::get(PRECOMPILE), AccountInfo::default()); + + // Assert that there is no a corresponding `KilledAccount` event. + assert!(System::events().iter().all(|record| record.event + != RuntimeEvent::EvmSystem(Event::KilledAccount { + account: PRECOMPILE, + }))); + }); +} + /// This test verifies that `try_mutate_exists` works as expected in case data wasn't providing /// and returned data is `None`. As a result, the account hasn't been created. #[test] From 599da73d41dd39ed67ebd5bb4367aa90d464b4b1 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 8 Apr 2025 17:28:47 +0300 Subject: [PATCH 04/15] Introduce own PrecompilesSet trait --- crates/pallet-evm-system/src/lib.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/pallet-evm-system/src/lib.rs b/crates/pallet-evm-system/src/lib.rs index b26b14779..f48ceb4fc 100644 --- a/crates/pallet-evm-system/src/lib.rs +++ b/crates/pallet-evm-system/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; -use frame_support::traits::{StoredMap, TypedGet}; +use frame_support::traits::StoredMap; use scale_info::TypeInfo; use sp_runtime::{ traits::{One, Zero}, @@ -27,6 +27,14 @@ pub struct AccountInfo { pub data: AccountData, } +/// A set of precompiles. +/// +/// Checks if the provided account is in the precompile set. +pub trait PrecompilesSet { + /// Check if the given account is a precompile. + fn is_precompile(account_id: &AccountId) -> bool; +} + // 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)] @@ -34,7 +42,7 @@ pub struct AccountInfo { pub mod pallet { use frame_support::pallet_prelude::*; use sp_runtime::traits::{AtLeast32Bit, MaybeDisplay}; - use sp_std::{collections::btree_set::BTreeSet, fmt::Debug}; + use sp_std::fmt::Debug; use super::*; @@ -73,7 +81,7 @@ pub mod pallet { type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; /// A set of precompiles. - type PrecompilesSet: TypedGet::AccountId>>; + type PrecompilesSet: PrecompilesSet<::AccountId>; /// Handler for when a new account has just been created. type OnNewAccount: OnNewAccount<::AccountId>; @@ -162,7 +170,7 @@ impl Pallet { /// Create an account. pub fn create_account(who: &::AccountId) -> AccountCreationOutcome { - if Self::account_exists(who) || T::PrecompilesSet::get().contains(who) { + if Self::account_exists(who) || T::PrecompilesSet::is_precompile(who) { return AccountCreationOutcome::AlreadyExists; } @@ -199,7 +207,7 @@ impl StoredMap<::AccountId, ::AccountData> Account::::mutate(k, |a| a.data = data); } (None, true) => { - if nonce != ::Index::zero() || T::PrecompilesSet::get().contains(k) { + if nonce != ::Index::zero() || T::PrecompilesSet::is_precompile(k) { Account::::mutate(k, |a| a.data = Default::default()); } else { Account::::remove(k); From 1fb7572636472b97471cbbcbce09efdd010fcb0a Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 8 Apr 2025 17:40:52 +0300 Subject: [PATCH 05/15] Adapt mock with introduced trait --- crates/pallet-evm-system/src/mock.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/pallet-evm-system/src/mock.rs b/crates/pallet-evm-system/src/mock.rs index ec2d282c7..8df71e0e5 100644 --- a/crates/pallet-evm-system/src/mock.rs +++ b/crates/pallet-evm-system/src/mock.rs @@ -1,7 +1,4 @@ -use frame_support::{ - parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::traits::{ConstU32, ConstU64}; use mockall::mock; use sp_core::{H160, H256}; use sp_runtime::{ @@ -9,7 +6,7 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; -use sp_std::{boxed::Box, collections::btree_set::BTreeSet, prelude::*}; +use sp_std::{boxed::Box, prelude::*}; use crate::{self as pallet_evm_system, *}; @@ -72,12 +69,13 @@ impl frame_system::Config for Test { type MaxConsumers = ConstU32<16>; } -pub const PRECOMPILE: H160 = H160(hex_literal::hex!( - "7000000000000000000000000000000000000007" -)); +mock! { + #[derive(Debug)] + pub PrecompilesSet {} -parameter_types! { - pub PrecompilesSet: BTreeSet = BTreeSet::from([PRECOMPILE]); + impl PrecompilesSet for PrecompilesSet { + pub fn is_precompile(who: &H160) -> bool; + } } impl pallet_evm_system::Config for Test { @@ -85,7 +83,7 @@ impl pallet_evm_system::Config for Test { type AccountId = H160; type Index = u64; type AccountData = u64; - type PrecompilesSet = PrecompilesSet; + type PrecompilesSet = MockPrecompilesSet; type OnNewAccount = MockDummyOnNewAccount; type OnKilledAccount = MockDummyOnKilledAccount; } From 5057eca78cd7177f7de59d122183803d0f98ce13 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 8 Apr 2025 17:41:33 +0300 Subject: [PATCH 06/15] Properly use mock expectations in tests --- crates/pallet-evm-system/src/tests.rs | 57 +++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/crates/pallet-evm-system/src/tests.rs b/crates/pallet-evm-system/src/tests.rs index 8ef30860b..c6a014796 100644 --- a/crates/pallet-evm-system/src/tests.rs +++ b/crates/pallet-evm-system/src/tests.rs @@ -22,6 +22,13 @@ fn create_account_created() { System::set_block_number(1); // Set mock expectations. + let is_precompile_ctx = MockPrecompilesSet::is_precompile_context(); + is_precompile_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(false); + let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); on_new_account_ctx .expect() @@ -46,6 +53,7 @@ fn create_account_created() { })); // Assert mock invocations. + is_precompile_ctx.checkpoint(); on_new_account_ctx.checkpoint(); }); } @@ -71,20 +79,33 @@ fn create_account_already_exists() { #[test] fn create_precompiled_account_already_exists() { new_test_ext().execute_with_ext(|_| { + let precompile = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + + // Set mock expectations. + let is_precompile_ctx = MockPrecompilesSet::is_precompile_context(); + is_precompile_ctx + .expect() + .once() + .with(predicate::eq(precompile)) + .return_const(true); + // Set block number to enable events. System::set_block_number(1); // Invoke the function under test. assert_storage_noop!(assert_eq!( - EvmSystem::create_account(&PRECOMPILE), + EvmSystem::create_account(&precompile), AccountCreationOutcome::AlreadyExists )); // Assert that there is no a corresponding `NewAccount` event. assert!(System::events().iter().all(|record| record.event != RuntimeEvent::EvmSystem(Event::NewAccount { - account: PRECOMPILE, + account: precompile, }))); + + // Assert mock invocations. + is_precompile_ctx.checkpoint(); }); } @@ -254,6 +275,13 @@ fn try_mutate_exists_account_removed() { assert!(EvmSystem::account_exists(&account_id)); // Set mock expectations. + let is_precompile_ctx = MockPrecompilesSet::is_precompile_context(); + is_precompile_ctx + .expect() + .once() + .with(predicate::eq(account_id)) + .return_const(false); + let on_killed_account_ctx = MockDummyOnKilledAccount::on_killed_account_context(); on_killed_account_ctx .expect() @@ -278,6 +306,7 @@ fn try_mutate_exists_account_removed() { })); // Assert mock invocations. + is_precompile_ctx.checkpoint(); on_killed_account_ctx.checkpoint(); }); } @@ -323,34 +352,46 @@ fn try_mutate_exists_account_retained() { fn try_mutate_exists_precompiled_account_retained() { new_test_ext().execute_with_ext(|_| { // Prepare test data. + let precompile = H160::from_str("1000000000000000000000000000000000000001").unwrap(); let nonce = 0; let data = 100; let account_info = AccountInfo { nonce, data }; - >::insert(PRECOMPILE, account_info); + >::insert(precompile, account_info); // Check test preconditions. - assert!(EvmSystem::account_exists(&PRECOMPILE)); + assert!(EvmSystem::account_exists(&precompile)); + + // Set mock expectations. + let is_precompile_ctx = MockPrecompilesSet::is_precompile_context(); + is_precompile_ctx + .expect() + .once() + .with(predicate::eq(precompile)) + .return_const(true); // Set block number to enable events. System::set_block_number(1); // Invoke the function under test. - EvmSystem::try_mutate_exists(&PRECOMPILE, |maybe_data| -> Result<(), DispatchError> { + EvmSystem::try_mutate_exists(&precompile, |maybe_data| -> Result<(), DispatchError> { *maybe_data = None; Ok(()) }) .unwrap(); // Assert state changes. - assert!(EvmSystem::account_exists(&PRECOMPILE)); - assert_eq!(>::get(PRECOMPILE), AccountInfo::default()); + assert!(EvmSystem::account_exists(&precompile)); + assert_eq!(>::get(precompile), AccountInfo::default()); // Assert that there is no a corresponding `KilledAccount` event. assert!(System::events().iter().all(|record| record.event != RuntimeEvent::EvmSystem(Event::KilledAccount { - account: PRECOMPILE, + account: precompile, }))); + + // Assert mock invocations. + is_precompile_ctx.checkpoint(); }); } From fc9a6bb428d49b844c5a1e8eca6ee4c273879563 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 8 Apr 2025 18:00:07 +0300 Subject: [PATCH 07/15] Some renaming --- crates/pallet-evm-system/src/lib.rs | 14 ++++++-------- crates/pallet-evm-system/src/mock.rs | 6 +++--- crates/pallet-evm-system/src/tests.rs | 8 ++++---- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/pallet-evm-system/src/lib.rs b/crates/pallet-evm-system/src/lib.rs index f48ceb4fc..baf4fa48c 100644 --- a/crates/pallet-evm-system/src/lib.rs +++ b/crates/pallet-evm-system/src/lib.rs @@ -27,10 +27,8 @@ pub struct AccountInfo { pub data: AccountData, } -/// A set of precompiles. -/// -/// Checks if the provided account is in the precompile set. -pub trait PrecompilesSet { +/// A trait that allows checking whether a given account is a precompile or not. +pub trait IsPrecompile { /// Check if the given account is a precompile. fn is_precompile(account_id: &AccountId) -> bool; } @@ -80,8 +78,8 @@ pub mod pallet { /// pallet does regardless). type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; - /// A set of precompiles. - type PrecompilesSet: PrecompilesSet<::AccountId>; + /// Checks whether a given account is a precompile or not. + type IsPrecompile: IsPrecompile<::AccountId>; /// Handler for when a new account has just been created. type OnNewAccount: OnNewAccount<::AccountId>; @@ -170,7 +168,7 @@ impl Pallet { /// Create an account. pub fn create_account(who: &::AccountId) -> AccountCreationOutcome { - if Self::account_exists(who) || T::PrecompilesSet::is_precompile(who) { + if Self::account_exists(who) || T::IsPrecompile::is_precompile(who) { return AccountCreationOutcome::AlreadyExists; } @@ -207,7 +205,7 @@ impl StoredMap<::AccountId, ::AccountData> Account::::mutate(k, |a| a.data = data); } (None, true) => { - if nonce != ::Index::zero() || T::PrecompilesSet::is_precompile(k) { + if nonce != ::Index::zero() || T::IsPrecompile::is_precompile(k) { Account::::mutate(k, |a| a.data = Default::default()); } else { Account::::remove(k); diff --git a/crates/pallet-evm-system/src/mock.rs b/crates/pallet-evm-system/src/mock.rs index 8df71e0e5..f39ee285b 100644 --- a/crates/pallet-evm-system/src/mock.rs +++ b/crates/pallet-evm-system/src/mock.rs @@ -71,9 +71,9 @@ impl frame_system::Config for Test { mock! { #[derive(Debug)] - pub PrecompilesSet {} + pub IsPrecompile {} - impl PrecompilesSet for PrecompilesSet { + impl IsPrecompile for IsPrecompile { pub fn is_precompile(who: &H160) -> bool; } } @@ -83,7 +83,7 @@ impl pallet_evm_system::Config for Test { type AccountId = H160; type Index = u64; type AccountData = u64; - type PrecompilesSet = MockPrecompilesSet; + type IsPrecompile = MockIsPrecompile; type OnNewAccount = MockDummyOnNewAccount; type OnKilledAccount = MockDummyOnKilledAccount; } diff --git a/crates/pallet-evm-system/src/tests.rs b/crates/pallet-evm-system/src/tests.rs index c6a014796..611fbd306 100644 --- a/crates/pallet-evm-system/src/tests.rs +++ b/crates/pallet-evm-system/src/tests.rs @@ -22,7 +22,7 @@ fn create_account_created() { System::set_block_number(1); // Set mock expectations. - let is_precompile_ctx = MockPrecompilesSet::is_precompile_context(); + let is_precompile_ctx = MockIsPrecompile::is_precompile_context(); is_precompile_ctx .expect() .once() @@ -82,7 +82,7 @@ fn create_precompiled_account_already_exists() { let precompile = H160::from_str("1000000000000000000000000000000000000001").unwrap(); // Set mock expectations. - let is_precompile_ctx = MockPrecompilesSet::is_precompile_context(); + let is_precompile_ctx = MockIsPrecompile::is_precompile_context(); is_precompile_ctx .expect() .once() @@ -275,7 +275,7 @@ fn try_mutate_exists_account_removed() { assert!(EvmSystem::account_exists(&account_id)); // Set mock expectations. - let is_precompile_ctx = MockPrecompilesSet::is_precompile_context(); + let is_precompile_ctx = MockIsPrecompile::is_precompile_context(); is_precompile_ctx .expect() .once() @@ -363,7 +363,7 @@ fn try_mutate_exists_precompiled_account_retained() { assert!(EvmSystem::account_exists(&precompile)); // Set mock expectations. - let is_precompile_ctx = MockPrecompilesSet::is_precompile_context(); + let is_precompile_ctx = MockIsPrecompile::is_precompile_context(); is_precompile_ctx .expect() .once() From e2dbef28aaa97b35011e2506c6ea17b30463d246 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 8 Apr 2025 18:01:15 +0300 Subject: [PATCH 08/15] Remove unused deps --- Cargo.lock | 1 - crates/pallet-evm-system/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a6b1d73b..f453a2ca1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6174,7 +6174,6 @@ dependencies = [ "fp-evm", "frame-support", "frame-system", - "hex-literal", "mockall", "parity-scale-codec", "scale-info", diff --git a/crates/pallet-evm-system/Cargo.toml b/crates/pallet-evm-system/Cargo.toml index 5f3cbaa05..2e00f444f 100644 --- a/crates/pallet-evm-system/Cargo.toml +++ b/crates/pallet-evm-system/Cargo.toml @@ -14,7 +14,6 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] -hex-literal = { workspace = true } mockall = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } From 68a3d932c0b36cb8dd15e8b0bd143b3e2bddcc76 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 9 Apr 2025 18:11:57 +0300 Subject: [PATCH 09/15] Add default implementation for IsPrecompile --- crates/pallet-evm-system/src/lib.rs | 18 ++++++++++++------ crates/precompile-native-currency/src/mock.rs | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/pallet-evm-system/src/lib.rs b/crates/pallet-evm-system/src/lib.rs index baf4fa48c..99cd4066e 100644 --- a/crates/pallet-evm-system/src/lib.rs +++ b/crates/pallet-evm-system/src/lib.rs @@ -27,12 +27,6 @@ pub struct AccountInfo { pub data: AccountData, } -/// A trait that allows checking whether a given account is a precompile or not. -pub trait IsPrecompile { - /// Check if the given account is a precompile. - fn is_precompile(account_id: &AccountId) -> bool; -} - // 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)] @@ -242,6 +236,18 @@ impl fp_evm::AccountProvider for Pallet { } } +/// A trait that allows checking whether a given account is a precompile or not. +pub trait IsPrecompile { + /// Check if the given account is a precompile. + fn is_precompile(account_id: &AccountId) -> bool; +} + +impl IsPrecompile for () { + fn is_precompile(_account_id: &AccountId) -> bool { + false + } +} + /// Interface to handle account creation. pub trait OnNewAccount { /// A new account `who` has been registered. diff --git a/crates/precompile-native-currency/src/mock.rs b/crates/precompile-native-currency/src/mock.rs index 103556e00..37548bfd7 100644 --- a/crates/precompile-native-currency/src/mock.rs +++ b/crates/precompile-native-currency/src/mock.rs @@ -104,6 +104,7 @@ impl pallet_evm_system::Config for Test { type AccountId = EvmAccountId; type Index = u64; type AccountData = pallet_evm_balances::AccountData; + type IsPrecompile = (); type OnNewAccount = (); type OnKilledAccount = (); } From 03c235e8ebfc0a5b37866b6d329de62f57a7657a Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 9 Apr 2025 18:14:47 +0300 Subject: [PATCH 10/15] Fix mock for other pallets --- crates/pallet-currency-swap/src/mock.rs | 1 + crates/pallet-dummy-precompiles-code/src/mock/v0.rs | 1 + crates/pallet-dummy-precompiles-code/src/mock/v1.rs | 1 + crates/pallet-evm-balances/src/mock.rs | 1 + crates/pallet-native-to-evm-swap/src/mock.rs | 1 + crates/precompile-evm-to-native-swap/src/mock.rs | 1 + 6 files changed, 6 insertions(+) diff --git a/crates/pallet-currency-swap/src/mock.rs b/crates/pallet-currency-swap/src/mock.rs index e76cf0a2e..8b09e2e64 100644 --- a/crates/pallet-currency-swap/src/mock.rs +++ b/crates/pallet-currency-swap/src/mock.rs @@ -89,6 +89,7 @@ impl pallet_evm_system::Config for Test { type AccountId = EvmAccountId; type Index = u64; type AccountData = pallet_evm_balances::AccountData; + type IsPrecompile = (); type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/crates/pallet-dummy-precompiles-code/src/mock/v0.rs b/crates/pallet-dummy-precompiles-code/src/mock/v0.rs index 7563ef317..73a664f8a 100644 --- a/crates/pallet-dummy-precompiles-code/src/mock/v0.rs +++ b/crates/pallet-dummy-precompiles-code/src/mock/v0.rs @@ -94,6 +94,7 @@ impl pallet_evm_system::Config for Test { type AccountId = EvmAccountId; type Index = u64; type AccountData = pallet_evm_balances::AccountData; + type IsPrecompile = (); type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/crates/pallet-dummy-precompiles-code/src/mock/v1.rs b/crates/pallet-dummy-precompiles-code/src/mock/v1.rs index 4f4e069b7..66910e020 100644 --- a/crates/pallet-dummy-precompiles-code/src/mock/v1.rs +++ b/crates/pallet-dummy-precompiles-code/src/mock/v1.rs @@ -100,6 +100,7 @@ impl pallet_evm_system::Config for Test { type AccountId = EvmAccountId; type Index = u64; type AccountData = pallet_evm_balances::AccountData; + type IsPrecompile = (); type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/crates/pallet-evm-balances/src/mock.rs b/crates/pallet-evm-balances/src/mock.rs index efe1e8e3f..74b2498cc 100644 --- a/crates/pallet-evm-balances/src/mock.rs +++ b/crates/pallet-evm-balances/src/mock.rs @@ -92,6 +92,7 @@ impl pallet_evm_system::Config for Test { type AccountId = u64; type Index = u64; type AccountData = AccountData; + type IsPrecompile = (); type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/crates/pallet-native-to-evm-swap/src/mock.rs b/crates/pallet-native-to-evm-swap/src/mock.rs index 2b755dfd7..6cbc7e5a4 100644 --- a/crates/pallet-native-to-evm-swap/src/mock.rs +++ b/crates/pallet-native-to-evm-swap/src/mock.rs @@ -100,6 +100,7 @@ impl pallet_evm_system::Config for Test { type AccountId = EvmAccountId; type Index = u64; type AccountData = pallet_evm_balances::AccountData; + type IsPrecompile = (); type OnNewAccount = (); type OnKilledAccount = (); } diff --git a/crates/precompile-evm-to-native-swap/src/mock.rs b/crates/precompile-evm-to-native-swap/src/mock.rs index aaf37c79f..c561fb484 100644 --- a/crates/precompile-evm-to-native-swap/src/mock.rs +++ b/crates/precompile-evm-to-native-swap/src/mock.rs @@ -98,6 +98,7 @@ impl pallet_evm_system::Config for Test { type AccountId = EvmAccountId; type Index = u64; type AccountData = pallet_evm_balances::AccountData; + type IsPrecompile = (); type OnNewAccount = (); type OnKilledAccount = (); } From ce0a444c5c691deb68d200020f9f97573a61ef9c Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 9 Apr 2025 18:16:56 +0300 Subject: [PATCH 11/15] Integrate IsPrecompile usage at runtime --- crates/humanode-runtime/src/frontier_precompiles.rs | 8 +++++++- crates/humanode-runtime/src/lib.rs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/humanode-runtime/src/frontier_precompiles.rs b/crates/humanode-runtime/src/frontier_precompiles.rs index f22f85ba3..1daa3f030 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::{evm_swap, ConstU64}; +use crate::{evm_swap, ConstU64, EvmAccountId}; /// A set of constant values used to indicate precompiles. pub mod precompiles_constants { @@ -187,6 +187,12 @@ where } } +impl pallet_evm_system::IsPrecompile for FrontierPrecompiles { + fn is_precompile(account_id: &EvmAccountId) -> bool { + Self::used_addresses().contains(account_id) + } +} + pub struct FrontierPrecompilesAddresses(PhantomData); impl sp_core::TypedGet for FrontierPrecompilesAddresses { diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 6e9e561f7..1f64c6413 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -615,6 +615,7 @@ impl pallet_evm_system::Config for Runtime { type AccountId = EvmAccountId; type Index = Index; type AccountData = pallet_evm_balances::AccountData; + type IsPrecompile = FrontierPrecompiles; type OnNewAccount = (); type OnKilledAccount = (); } From c8bb38157e7c5f9651a194cc4c3d2db8e5c9bcc7 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 10 Apr 2025 11:46:32 +0300 Subject: [PATCH 12/15] Prevent remove for precompile --- crates/pallet-evm-system/src/lib.rs | 2 +- crates/pallet-evm-system/src/tests.rs | 42 --------------------------- 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/crates/pallet-evm-system/src/lib.rs b/crates/pallet-evm-system/src/lib.rs index 99cd4066e..f0ef06cfb 100644 --- a/crates/pallet-evm-system/src/lib.rs +++ b/crates/pallet-evm-system/src/lib.rs @@ -162,7 +162,7 @@ impl Pallet { /// Create an account. pub fn create_account(who: &::AccountId) -> AccountCreationOutcome { - if Self::account_exists(who) || T::IsPrecompile::is_precompile(who) { + if Self::account_exists(who) { return AccountCreationOutcome::AlreadyExists; } diff --git a/crates/pallet-evm-system/src/tests.rs b/crates/pallet-evm-system/src/tests.rs index 611fbd306..f9e378a78 100644 --- a/crates/pallet-evm-system/src/tests.rs +++ b/crates/pallet-evm-system/src/tests.rs @@ -22,13 +22,6 @@ fn create_account_created() { System::set_block_number(1); // Set mock expectations. - let is_precompile_ctx = MockIsPrecompile::is_precompile_context(); - is_precompile_ctx - .expect() - .once() - .with(predicate::eq(account_id)) - .return_const(false); - let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context(); on_new_account_ctx .expect() @@ -53,7 +46,6 @@ fn create_account_created() { })); // Assert mock invocations. - is_precompile_ctx.checkpoint(); on_new_account_ctx.checkpoint(); }); } @@ -75,40 +67,6 @@ fn create_account_already_exists() { }); } -/// This test verifies that trying creating an precompiled account works as expected. -#[test] -fn create_precompiled_account_already_exists() { - new_test_ext().execute_with_ext(|_| { - let precompile = H160::from_str("1000000000000000000000000000000000000001").unwrap(); - - // Set mock expectations. - let is_precompile_ctx = MockIsPrecompile::is_precompile_context(); - is_precompile_ctx - .expect() - .once() - .with(predicate::eq(precompile)) - .return_const(true); - - // Set block number to enable events. - System::set_block_number(1); - - // Invoke the function under test. - assert_storage_noop!(assert_eq!( - EvmSystem::create_account(&precompile), - AccountCreationOutcome::AlreadyExists - )); - - // Assert that there is no a corresponding `NewAccount` event. - assert!(System::events().iter().all(|record| record.event - != RuntimeEvent::EvmSystem(Event::NewAccount { - account: precompile, - }))); - - // Assert mock invocations. - is_precompile_ctx.checkpoint(); - }); -} - /// This test verifies that incrementing account nonce works in the happy path /// in case a new account should be created. #[test] From 14ed75803a0a1a2047bc203fc5267ada46f9bdd9 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 10 Apr 2025 13:24:27 +0300 Subject: [PATCH 13/15] Add events checks at evmToNative e2e test --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 9c5abeff4..429bddde8 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"; import * as substrate from "../../lib/substrate"; import { getNativeBalance } from "../../lib/substrateUtils"; +import { AnyJson } from "@polkadot/types-codec/types"; const evmToNativeSwapPrecompileAddress = "0x0000000000000000000000000000000000000900"; @@ -16,6 +17,14 @@ const bridgePotNativeAccount = const feesPotNativeAccount = "hmpwhPbL5XJTYPWXPMkacfqGhJ3eoQRPLKphajpvcot5Q5zkk"; +type Event = { + event: { + method: string; + section: string; + data: AnyJson; + }; +}; + describe("evm to native tokens swap", () => { let node: RunNodeState; let ethPublicClient: eth.PublicClientWebSocket; @@ -136,5 +145,32 @@ describe("evm to native tokens swap", () => { address: evmToNativeSwapPrecompileAddress, }); expect(evmSwapPrecompileBalance).toEqual(0n); + + const blockNumber = swapTxReceipt.blockNumber; + const blockHash = await substrateApi.rpc.chain.getBlockHash(blockNumber); + const substrateApiAt = await substrateApi.at(blockHash); + const events = (await substrateApiAt.query["system"]?.[ + "events" + ]?.()) as unknown as Event[]; + + events.forEach((item) => { + const section = item.event.section; + const method = item.event.method; + const data = JSON.stringify(item.event.data); + + console.log(section); + console.log(method); + console.log(data); + expect([section, method, data]).not.toEqual([ + "evmSystem", + "NewAccount", + JSON.stringify([evmToNativeSwapPrecompileAddress]), + ]); + expect([section, method, data]).not.toEqual([ + "evmSystem", + "KilledAccount", + JSON.stringify([evmToNativeSwapPrecompileAddress]), + ]); + }); }); }); From 08b20508ac907e6380cadc6a214af9e9362b5685 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 10 Apr 2025 13:29:03 +0300 Subject: [PATCH 14/15] Move a helper function for getting events to substrateUtils --- utils/e2e-tests/ts/lib/substrateUtils.ts | 23 ++++++++++++++++++++ utils/e2e-tests/ts/tests/swap/evmToNative.ts | 21 ++---------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/utils/e2e-tests/ts/lib/substrateUtils.ts b/utils/e2e-tests/ts/lib/substrateUtils.ts index dc902396b..a6fcda382 100644 --- a/utils/e2e-tests/ts/lib/substrateUtils.ts +++ b/utils/e2e-tests/ts/lib/substrateUtils.ts @@ -1,6 +1,7 @@ //! Common substrate utils. import * as substrate from "../lib/substrate"; +import { AnyJson } from "@polkadot/types-codec/types"; type SystemAccount = { data: { @@ -22,3 +23,25 @@ export const getNativeBalance = async ( // We should explicitly convert to native bigint for math operations. return BigInt(free); }; + +type Event = { + event: { + method: string; + section: string; + data: AnyJson; + }; +}; + +/// A helper function to get events at specified block. +export const getEvents = async ( + substrateApi: substrate.Api, + blockNumber: bigint, +) => { + const blockHash = await substrateApi.rpc.chain.getBlockHash(blockNumber); + const substrateApiAt = await substrateApi.at(blockHash); + const events = (await substrateApiAt.query["system"]?.[ + "events" + ]?.()) as unknown as Event[]; + + return events; +}; diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 429bddde8..71286569a 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -6,8 +6,7 @@ import { beforeEachWithCleanup } from "../../lib/lifecycle"; import evmSwap from "../../lib/abis/evmSwap"; import { decodeEventLog } from "viem"; import * as substrate from "../../lib/substrate"; -import { getNativeBalance } from "../../lib/substrateUtils"; -import { AnyJson } from "@polkadot/types-codec/types"; +import { getEvents, getNativeBalance } from "../../lib/substrateUtils"; const evmToNativeSwapPrecompileAddress = "0x0000000000000000000000000000000000000900"; @@ -17,14 +16,6 @@ const bridgePotNativeAccount = const feesPotNativeAccount = "hmpwhPbL5XJTYPWXPMkacfqGhJ3eoQRPLKphajpvcot5Q5zkk"; -type Event = { - event: { - method: string; - section: string; - data: AnyJson; - }; -}; - describe("evm to native tokens swap", () => { let node: RunNodeState; let ethPublicClient: eth.PublicClientWebSocket; @@ -146,21 +137,13 @@ describe("evm to native tokens swap", () => { }); expect(evmSwapPrecompileBalance).toEqual(0n); - const blockNumber = swapTxReceipt.blockNumber; - const blockHash = await substrateApi.rpc.chain.getBlockHash(blockNumber); - const substrateApiAt = await substrateApi.at(blockHash); - const events = (await substrateApiAt.query["system"]?.[ - "events" - ]?.()) as unknown as Event[]; + const events = await getEvents(substrateApi, swapTxReceipt.blockNumber); events.forEach((item) => { const section = item.event.section; const method = item.event.method; const data = JSON.stringify(item.event.data); - console.log(section); - console.log(method); - console.log(data); expect([section, method, data]).not.toEqual([ "evmSystem", "NewAccount", From 15ee1a1df78ab6109a5ed7736599b4b67a2112d8 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 10 Apr 2025 16:18:43 +0300 Subject: [PATCH 15/15] Minor improvement --- utils/e2e-tests/ts/tests/swap/evmToNative.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/utils/e2e-tests/ts/tests/swap/evmToNative.ts b/utils/e2e-tests/ts/tests/swap/evmToNative.ts index 71286569a..8927caead 100644 --- a/utils/e2e-tests/ts/tests/swap/evmToNative.ts +++ b/utils/e2e-tests/ts/tests/swap/evmToNative.ts @@ -137,9 +137,12 @@ describe("evm to native tokens swap", () => { }); expect(evmSwapPrecompileBalance).toEqual(0n); - const events = await getEvents(substrateApi, swapTxReceipt.blockNumber); + const substrateEvents = await getEvents( + substrateApi, + swapTxReceipt.blockNumber, + ); - events.forEach((item) => { + substrateEvents.forEach((item) => { const section = item.event.section; const method = item.event.method; const data = JSON.stringify(item.event.data);