diff --git a/Cargo.lock b/Cargo.lock index 36d44bfd40..9c9dec411f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5108,6 +5108,7 @@ dependencies = [ "frame-support", "frame-system", "hex", + "hex-literal", "impl-trait-for-tuples", "log", "pallet-balances", diff --git a/frame/evm/Cargo.toml b/frame/evm/Cargo.toml index 03b429fedc..b3d4ac5302 100644 --- a/frame/evm/Cargo.toml +++ b/frame/evm/Cargo.toml @@ -15,6 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] environmental = { workspace = true, optional = true } evm = { workspace = true, features = ["with-codec"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +hex-literal = { version = "0.3.4" } impl-trait-for-tuples = "0.2.2" log = { workspace = true } rlp = { workspace = true } diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index f46d0e4e8e..2100a29ce7 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -76,7 +76,8 @@ use frame_support::{ }; use frame_system::RawOrigin; use impl_trait_for_tuples::impl_for_tuples; -use sp_core::{Hasher, H160, H256, U256}; +use scale_info::TypeInfo; +use sp_core::{Decode, Encode, Hasher, H160, H256, U256}; use sp_runtime::{ traits::{BadOrigin, Saturating, UniqueSaturatedInto, Zero}, AccountId32, DispatchErrorWithPostInfo, @@ -512,6 +513,10 @@ pub mod pallet { #[pallet::storage] pub type AccountCodes = StorageMap<_, Blake2_128Concat, H160, Vec, ValueQuery>; + #[pallet::storage] + pub type AccountCodesMetadata = + StorageMap<_, Blake2_128Concat, H160, CodeMetadata, OptionQuery>; + #[pallet::storage] pub type AccountStorages = StorageDoubleMap<_, Blake2_128Concat, H160, Blake2_128Concat, H256, H256, ValueQuery>; @@ -525,6 +530,21 @@ pub type BalanceOf = type NegativeImbalanceOf = ::AccountId>>::NegativeImbalance; +#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode, TypeInfo)] +pub struct CodeMetadata { + pub size: u64, + pub hash: H256, +} + +impl CodeMetadata { + fn from_code(code: &[u8]) -> Self { + let size = code.len() as u64; + let hash = H256::from(sp_io::hashing::keccak_256(code)); + + Self { size, hash } + } +} + pub trait EnsureAddressOrigin { /// Success return type. type Success; @@ -720,6 +740,7 @@ impl Pallet { } >::remove(address); + >::remove(address); #[allow(deprecated)] let _ = >::remove_prefix(address, None); } @@ -735,9 +756,40 @@ impl Pallet { let _ = frame_system::Pallet::::inc_sufficients(&account_id); } + // Update metadata. + let meta = CodeMetadata::from_code(&code); + >::insert(address, meta); + >::insert(address, code); } + /// Get the account metadata (hash and size) from storage if it exists, + /// or compute it from code and store it if it doesn't exist. + pub fn account_code_metadata(address: H160) -> CodeMetadata { + if let Some(meta) = >::get(address) { + return meta; + } + + let code = >::get(address); + + // If code is empty we return precomputed hash for empty code. + // We don't store it as this address could get code deployed in the future. + if code.is_empty() { + const EMPTY_CODE_HASH: [u8; 32] = hex_literal::hex!( + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ); + return CodeMetadata { + size: 0, + hash: EMPTY_CODE_HASH.into(), + }; + } + + let meta = CodeMetadata::from_code(&code); + + >::insert(address, meta); + meta + } + /// Get the account basic in EVM format. pub fn account_basic(address: &H160) -> (Account, frame_support::weights::Weight) { let account_id = T::AddressMapping::into_account_id(*address); diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 2c9f66c45f..f21a8b1fbd 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -854,6 +854,14 @@ where self.substate .recursive_is_cold(&|a: &Accessed| a.accessed_storage.contains(&(address, key))) } + + fn code_size(&self, address: H160) -> U256 { + U256::from(>::account_code_metadata(address).size) + } + + fn code_hash(&self, address: H160) -> H256 { + >::account_code_metadata(address).hash + } } #[cfg(feature = "forbid-evm-reentrancy")] diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 80170483a8..bb09d665db 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -651,3 +651,40 @@ fn eip3607_transaction_from_precompile() { .is_ok()); }); } + +#[test] +fn metadata_code_gets_cached() { + new_test_ext().execute_with(|| { + let address = H160::repeat_byte(0xaa); + + crate::Pallet::::create_account(address, b"Exemple".to_vec()); + + let metadata = crate::Pallet::::account_code_metadata(address); + assert_eq!(metadata.size, 7); + assert_eq!( + metadata.hash, + hex_literal::hex!("e8396a990fe08f2402e64a00647e41dadf360ba078a59ba79f55e876e67ed4bc") + .into() + ); + + let metadata2 = >::get(&address).expect("to have metadata set"); + assert_eq!(metadata, metadata2); + }); +} + +#[test] +fn metadata_empty_dont_code_gets_cached() { + new_test_ext().execute_with(|| { + let address = H160::repeat_byte(0xaa); + + let metadata = crate::Pallet::::account_code_metadata(address); + assert_eq!(metadata.size, 0); + assert_eq!( + metadata.hash, + hex_literal::hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + .into() + ); + + assert!(>::get(&address).is_none()); + }); +}