diff --git a/Cargo.lock b/Cargo.lock index 45a2a54c93..a2de75f6c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2137,9 +2137,9 @@ dependencies = [ [[package]] name = "evm" -version = "0.41.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "767f43e9630cc36cf8ff2777cbb0121b055f0d1fd6eaaa13b46a1808f0d0e7e9" +checksum = "8d4b86ab0845a40c27ce2a54e01f4c1aa356bd2658028ae87c570e469a2286d6" dependencies = [ "auto_impl", "environmental", @@ -2158,9 +2158,9 @@ dependencies = [ [[package]] name = "evm-core" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da6cedc5cedb4208e59467106db0d1f50db01b920920589f8e672c02fdc04f" +checksum = "67c39818f9bc4cbd39fce2ed63c2707c3685b15e877796f730a1ff72a49dc094" dependencies = [ "parity-scale-codec", "primitive-types", @@ -2170,9 +2170,9 @@ dependencies = [ [[package]] name = "evm-gasometer" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dc0eb591abc5cd7b05bef6a036c2bb6c66ab6c5e0c5ce94bfe377ab670b1fd7" +checksum = "f8c114afd9f616fac0cfa7cbd0af139bef11b9e1901781d8a1620657ba49f37e" dependencies = [ "environmental", "evm-core", @@ -2182,9 +2182,9 @@ dependencies = [ [[package]] name = "evm-runtime" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84bbe09b64ae13a29514048c1bb6fda6374ac0b4f6a1f15a443348ab88ef42cd" +checksum = "56a7f1d44af271868a63f66e724c2da74f661dbada2a1d5da87aa51231c218f3" dependencies = [ "auto_impl", "environmental", @@ -6036,26 +6036,6 @@ dependencies = [ "sp-io", ] -[[package]] -name = "pallet-evm-precompile-storage-cleaner" -version = "0.1.0" -dependencies = [ - "fp-evm", - "frame-support", - "frame-system", - "pallet-balances", - "pallet-evm", - "pallet-timestamp", - "pallet-utility", - "parity-scale-codec", - "precompile-utils", - "rlp", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", -] - [[package]] name = "pallet-evm-test-vector-support" version = "1.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 26cb89b6dd..f86361c64a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ members = [ "frame/evm/precompile/bls12381", "frame/evm/precompile/dispatch", "frame/evm/precompile/curve25519", - "frame/evm/precompile/storage-cleaner", "frame/evm-chain-id", "frame/hotfix-sufficients", "client/api", @@ -56,7 +55,7 @@ derive_more = "0.99" environmental = { version = "1.1.4", default-features = false } ethereum = { version = "0.15.0", default-features = false } ethereum-types = { version = "0.14.1", default-features = false } -evm = { version = "0.41.1", default-features = false } +evm = { version = "0.42.0", default-features = false } futures = "0.3.31" hash-db = { version = "0.16.0", default-features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/frame/evm/precompile/dispatch/src/mock.rs b/frame/evm/precompile/dispatch/src/mock.rs index 98d21c8978..c1a61cf72d 100644 --- a/frame/evm/precompile/dispatch/src/mock.rs +++ b/frame/evm/precompile/dispatch/src/mock.rs @@ -133,7 +133,6 @@ impl FindAuthor for FindAuthorTruncated { parameter_types! { pub BlockGasLimit: U256 = U256::max_value(); pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); - pub SuicideQuickClearLimit: u32 = 0; } impl pallet_evm::Config for Test { type AccountProvider = pallet_evm::FrameSystemAccountProvider; @@ -157,7 +156,6 @@ impl pallet_evm::Config for Test { type OnChargeTransaction = (); type OnCreate = (); type FindAuthor = FindAuthorTruncated; - type SuicideQuickClearLimit = SuicideQuickClearLimit; type GasLimitPovSizeRatio = (); type GasLimitStorageGrowthRatio = (); type Timestamp = Timestamp; diff --git a/frame/evm/precompile/storage-cleaner/Cargo.toml b/frame/evm/precompile/storage-cleaner/Cargo.toml deleted file mode 100644 index 7f940c740b..0000000000 --- a/frame/evm/precompile/storage-cleaner/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "pallet-evm-precompile-storage-cleaner" -version = "0.1.0" -license = "Apache-2.0" -description = "Storage cleaner precompile to clean storage of a suicided contracts" -authors = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } - -[dependencies] -scale-codec = { package = "parity-scale-codec", workspace = true } -# Substrate -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-core = { workspace = true } -sp-runtime = { workspace = true } -# Frontier -fp-evm = { workspace = true } -pallet-evm = { workspace = true } -precompile-utils = { workspace = true } - -[dev-dependencies] -scale-info = { workspace = true } -# Substrate -frame-system = { workspace = true, features = ["default"] } -pallet-balances = { workspace = true, features = ["default", "insecure_zero_ed"] } -pallet-timestamp = { workspace = true, features = ["default"] } -pallet-utility = { workspace = true, features = ["default"] } -rlp = { workspace = true } -sp-core = { workspace = true, features = ["default"] } -sp-io = { workspace = true, features = ["default"] } -sp-runtime = { workspace = true, features = ["default"] } - -# Frontier -precompile-utils = { workspace = true, features = ["std", "testing"] } - -[features] -default = ["std"] -std = [ - "scale-codec/std", - # Substrate - "frame-support/std", - "frame-system/std", - "sp-runtime/std", - "sp-core/std", - # Frontier - "fp-evm/std", - "pallet-evm/std", - "precompile-utils/std", -] diff --git a/frame/evm/precompile/storage-cleaner/src/lib.rs b/frame/evm/precompile/storage-cleaner/src/lib.rs index 07a3dc6194..e69de29bb2 100644 --- a/frame/evm/precompile/storage-cleaner/src/lib.rs +++ b/frame/evm/precompile/storage-cleaner/src/lib.rs @@ -1,212 +0,0 @@ -// This file is part of Frontier. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Storage cleaner precompile. This precompile is used to clean the storage entries of smart contract that -//! has been marked as suicided (self-destructed). - -#![cfg_attr(not(feature = "std"), no_std)] -extern crate alloc; - -use alloc::vec::Vec; -use core::marker::PhantomData; -use fp_evm::{ - AccountProvider, PrecompileFailure, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE, -}; -use pallet_evm::AddressMapping; -use precompile_utils::{prelude::*, EvmResult}; -use sp_core::H160; -use sp_runtime::traits::ConstU32; - -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - -pub const ARRAY_LIMIT: u32 = 1_000; -type GetArrayLimit = ConstU32; -// Storage key for suicided contracts: Blake2_128(16) + Key (H160(20)) -pub const SUICIDED_STORAGE_KEY: u64 = 36; - -#[derive(Debug, Clone)] -pub struct StorageCleanerPrecompile(PhantomData); - -#[precompile_utils::precompile] -impl StorageCleanerPrecompile -where - Runtime: pallet_evm::Config, -{ - /// Clear Storage entries of smart contracts that has been marked as suicided (self-destructed). It takes a list of - /// addresses and a limit as input. The limit is used to prevent the function from consuming too much gas. The - /// maximum number of storage entries that can be removed is limit - 1. - #[precompile::public("clearSuicidedStorage(address[],uint64)")] - fn clear_suicided_storage( - handle: &mut impl PrecompileHandle, - addresses: BoundedVec, - limit: u64, - ) -> EvmResult { - let addresses: Vec<_> = addresses.into(); - let nb_addresses = addresses.len() as u64; - if limit == 0 { - return Err(revert("Limit should be greater than zero")); - } - - Self::record_max_cost(handle, nb_addresses, limit)?; - let result = Self::clear_suicided_storage_inner(addresses, limit - 1)?; - Self::refund_cost(handle, result, nb_addresses, limit); - - Ok(()) - } - - /// This function iterates over the addresses, checks if each address is marked as suicided, and then deletes the storage - /// entries associated with that address. If there are no remaining entries, we clear the suicided contract by calling the - /// `clear_suicided_contract` function. - fn clear_suicided_storage_inner( - addresses: Vec
, - limit: u64, - ) -> Result { - let mut deleted_entries = 0u64; - let mut deleted_contracts = 0u64; - - for Address(address) in addresses { - if !pallet_evm::Pallet::::is_account_suicided(&address) { - return Err(revert(alloc::format!("NotSuicided: {}", address))); - } - - let deleted = pallet_evm::AccountStorages::::drain_prefix(address) - .take((limit.saturating_sub(deleted_entries)) as usize) - .count(); - deleted_entries = deleted_entries.saturating_add(deleted as u64); - - // Check if the storage of this contract has been completely removed - if pallet_evm::AccountStorages::::iter_key_prefix(address) - .next() - .is_none() - { - Self::clear_suicided_contract(address); - deleted_contracts = deleted_contracts.saturating_add(1); - } - - if deleted_entries >= limit { - break; - } - } - - Ok(RemovalResult { - deleted_entries, - deleted_contracts, - }) - } - - /// Record the maximum cost (Worst case Scenario) of the clear_suicided_storage function. - fn record_max_cost( - handle: &mut impl PrecompileHandle, - nb_addresses: u64, - limit: u64, - ) -> EvmResult { - let read_cost = RuntimeHelper::::db_read_gas_cost(); - let write_cost = RuntimeHelper::::db_write_gas_cost(); - let ref_time = 0u64 - // EVM:: Suicided (reads = nb_addresses) - .saturating_add(read_cost.saturating_mul(nb_addresses)) - // EVM:: Suicided (writes = nb_addresses) - .saturating_add(write_cost.saturating_mul(nb_addresses)) - // System: AccountInfo (reads = nb_addresses) for decrementing sufficients - .saturating_add(read_cost.saturating_mul(nb_addresses)) - // System: AccountInfo (writes = nb_addresses) for decrementing sufficients - .saturating_add(write_cost.saturating_mul(nb_addresses)) - // EVM: AccountStorage (reads = limit) - .saturating_add(read_cost.saturating_mul(limit)) - // EVM: AccountStorage (writes = limit) - .saturating_add(write_cost.saturating_mul(limit)); - - let proof_size = 0u64 - // Proof: EVM::Suicided (SUICIDED_STORAGE_KEY) * nb_addresses - .saturating_add(SUICIDED_STORAGE_KEY.saturating_mul(nb_addresses)) - // Proof: EVM::AccountStorage (ACCOUNT_BASIC_PROOF_SIZE) * limit - .saturating_add(ACCOUNT_STORAGE_PROOF_SIZE.saturating_mul(limit)) - // Proof: System::AccountInfo (ACCOUNT_BASIC_PROOF_SIZE) * nb_addresses - .saturating_add(ACCOUNT_BASIC_PROOF_SIZE.saturating_mul(nb_addresses)); - - handle.record_external_cost(Some(ref_time), Some(proof_size), None)?; - Ok(()) - } - - /// Refund the additional cost recorded for the clear_suicided_storage function. - fn refund_cost( - handle: &mut impl PrecompileHandle, - result: RemovalResult, - nb_addresses: u64, - limit: u64, - ) { - let read_cost = RuntimeHelper::::db_read_gas_cost(); - let write_cost = RuntimeHelper::::db_write_gas_cost(); - - let extra_entries = limit.saturating_sub(result.deleted_entries); - let extra_contracts = nb_addresses.saturating_sub(result.deleted_contracts); - - let mut ref_time = 0u64; - let mut proof_size = 0u64; - - // Refund the cost of the remaining entries - if extra_entries > 0 { - ref_time = ref_time - // EVM:: AccountStorage (reads = extra_entries) - .saturating_add(read_cost.saturating_mul(extra_entries)) - // EVM:: AccountStorage (writes = extra_entries) - .saturating_add(write_cost.saturating_mul(extra_entries)); - proof_size = proof_size - // Proof: EVM::AccountStorage (ACCOUNT_BASIC_PROOF_SIZE) * extra_entries - .saturating_add(ACCOUNT_STORAGE_PROOF_SIZE.saturating_mul(extra_entries)); - } - - // Refund the cost of the remaining contracts - if extra_contracts > 0 { - ref_time = ref_time - // EVM:: Suicided (reads = extra_contracts) - .saturating_add(read_cost.saturating_mul(extra_contracts)) - // EVM:: Suicided (writes = extra_contracts) - .saturating_add(write_cost.saturating_mul(extra_contracts)) - // System: AccountInfo (reads = extra_contracts) for decrementing sufficients - .saturating_add(read_cost.saturating_mul(extra_contracts)) - // System: AccountInfo (writes = extra_contracts) for decrementing sufficients - .saturating_add(write_cost.saturating_mul(extra_contracts)); - proof_size = proof_size - // Proof: EVM::Suicided (SUICIDED_STORAGE_KEY) * extra_contracts - .saturating_add(SUICIDED_STORAGE_KEY.saturating_mul(extra_contracts)) - // Proof: System::AccountInfo (ACCOUNT_BASIC_PROOF_SIZE) * extra_contracts - .saturating_add(ACCOUNT_BASIC_PROOF_SIZE.saturating_mul(extra_contracts)); - } - - handle.refund_external_cost(Some(ref_time), Some(proof_size)); - } - - /// Clears the storage of a suicided contract. - /// - /// This function will remove the given address from the list of suicided contracts - /// and decrement the sufficients of the account associated with the address. - fn clear_suicided_contract(address: H160) { - pallet_evm::Suicided::::remove(address); - - let account_id = Runtime::AddressMapping::into_account_id(address); - Runtime::AccountProvider::remove_account(&account_id); - } -} - -struct RemovalResult { - pub deleted_entries: u64, - pub deleted_contracts: u64, -} diff --git a/frame/evm/precompile/storage-cleaner/src/mock.rs b/frame/evm/precompile/storage-cleaner/src/mock.rs deleted file mode 100644 index a63da6e9c0..0000000000 --- a/frame/evm/precompile/storage-cleaner/src/mock.rs +++ /dev/null @@ -1,183 +0,0 @@ -// This file is part of Frontier. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test mock for unit tests and benchmarking - -use crate::{StorageCleanerPrecompile, StorageCleanerPrecompileCall}; -use frame_support::{parameter_types, weights::Weight}; -use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, IdentityAddressMapping}; -use precompile_utils::{precompile_set::*, testing::*}; -use sp_core::{ConstU32, H256, U256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; - -pub type AccountId = MockAccount; -pub type Balance = u128; - -frame_support::construct_runtime! { - pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage}, - EVM: pallet_evm::{Pallet, Call, Storage, Config, Event}, - } -} - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 0)); -} - -impl frame_system::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type RuntimeTask = RuntimeTask; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Block = frame_system::mocking::MockBlock; - type BlockHashCount = BlockHashCount; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; - type MultiBlockMigrator = (); - type PreInherents = (); - type PostInherents = (); - type PostTransactions = (); - type SingleBlockMigrations = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 0; -} - -impl pallet_balances::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type Balance = Balance; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type ReserveIdentifier = (); - type RuntimeHoldReason = (); - type FreezeIdentifier = (); - type MaxLocks = (); - type MaxReserves = (); - type MaxFreezes = (); - type RuntimeFreezeReason = (); -} - -parameter_types! { - pub const MinimumPeriod: u64 = 1000; -} -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -pub type Precompiles = - PrecompileSetBuilder, StorageCleanerPrecompile>,)>; - -pub type PCall = StorageCleanerPrecompileCall; - -const BLOCK_GAS_LIMIT: u64 = 15_000_000; -const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; -const MAX_STORAGE_GROWTH: u64 = 400 * 1024; - -parameter_types! { - pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); - pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); - pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); - pub PrecompilesValue: Precompiles = Precompiles::new(); - pub SuicideQuickClearLimit: u32 = 0; -} - -impl pallet_evm::Config for Runtime { - type AccountProvider = pallet_evm::FrameSystemAccountProvider; - type FeeCalculator = (); - type GasWeightMapping = pallet_evm::FixedGasWeightMapping; - type WeightPerGas = WeightPerGas; - type CallOrigin = EnsureAddressRoot; - type WithdrawOrigin = EnsureAddressNever; - type AddressMapping = IdentityAddressMapping; - type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - type Runner = pallet_evm::runner::stack::Runner; - type PrecompilesType = Precompiles; - type PrecompilesValue = PrecompilesValue; - type ChainId = (); - type OnChargeTransaction = (); - type BlockGasLimit = BlockGasLimit; - type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; - type FindAuthor = (); - type OnCreate = (); - type GasLimitPovSizeRatio = GasLimitPovSizeRatio; - type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; - type Timestamp = Timestamp; - type WeightInfo = (); - type SuicideQuickClearLimit = SuicideQuickClearLimit; -} - -/// Build test externalities, prepopulated with data for testing the precompile. -#[derive(Default)] -pub(crate) struct ExtBuilder { - balances: Vec<(AccountId, Balance)>, -} - -impl ExtBuilder { - pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { - self.balances = balances; - self - } - - pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: self.balances, - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| { - System::set_block_number(1); - }); - ext - } -} diff --git a/frame/evm/precompile/storage-cleaner/src/tests.rs b/frame/evm/precompile/storage-cleaner/src/tests.rs deleted file mode 100644 index facf88d6c3..0000000000 --- a/frame/evm/precompile/storage-cleaner/src/tests.rs +++ /dev/null @@ -1,336 +0,0 @@ -// This file is part of Frontier. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::mock::{ExtBuilder, PCall, Precompiles, PrecompilesValue, Runtime}; -use pallet_evm::AddressMapping; -use precompile_utils::{solidity::codec::Address, testing::*}; -use rlp::RlpStream; -use sp_core::{keccak_256, H160, H256}; - -// Helper function that calculates the contract address -pub fn contract_address(sender: H160, nonce: u64) -> H160 { - let mut rlp = RlpStream::new_list(2); - rlp.append(&sender); - rlp.append(&nonce); - - H160::from_slice(&keccak_256(&rlp.out())[12..]) -} - -fn precompiles() -> Precompiles { - PrecompilesValue::get() -} - -// Helper function that creates an account. Returns the address of the account -fn mock_account(nonce: u64) -> H160 { - let address = contract_address(Alice.into(), nonce); - let account_id = ::AddressMapping::into_account_id(address); - let _ = frame_system::Pallet::::inc_sufficients(&account_id); - address -} - -// Helper function that creates storage entries for a contract -fn mock_entries(address: H160, num_entries: u32) { - for i in 0..num_entries { - pallet_evm::AccountStorages::::insert( - address, - H256::from_low_u64_be(i as u64), - H256::from_low_u64_be(i as u64), - ); - } -} - -// Helper function that creates contracts with storage entries. Returns contract addresses -fn mock_contracts(entries: impl IntoIterator) -> Vec
{ - entries - .into_iter() - .enumerate() - .map(|(i, j)| { - let address = mock_account(i as u64); - mock_entries(address, j); - Address(address) - }) - .collect() -} - -#[test] -fn test_clear_suicided_contract_succesfull() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10000000000000000000)]) - .build() - .execute_with(|| { - let suicided_address = mock_contracts([10])[0].0; - pallet_evm::Suicided::::insert(suicided_address, ()); - - precompiles() - .prepare_test( - Alice, - Precompile1, - PCall::clear_suicided_storage { - addresses: vec![suicided_address.into()].into(), - limit: u64::MAX, - }, - ) - .execute_returns(()); - - assert_eq!( - pallet_evm::AccountStorages::::iter_prefix(suicided_address).count(), - 0 - ); - assert!(!pallet_evm::Suicided::::contains_key( - suicided_address - )); - }) -} - -// Test that the precompile fails if the contract is not suicided -#[test] -fn test_clear_suicided_contract_failed() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10000000000000000000)]) - .build() - .execute_with(|| { - let addresses = mock_contracts([10]); - let non_suicided_address = addresses[0].0; - - // Ensure that the contract is not suicided - assert!(!pallet_evm::Suicided::::contains_key( - non_suicided_address - )); - - precompiles() - .prepare_test( - Alice, - Precompile1, - PCall::clear_suicided_storage { - addresses: vec![non_suicided_address.into()].into(), - limit: u64::MAX, - }, - ) - .execute_reverts(|output| { - output == format!("NotSuicided: {}", non_suicided_address).as_bytes() - }); - - assert_eq!( - pallet_evm::AccountStorages::::iter_prefix(non_suicided_address).count(), - 10 - ); - }) -} - -// Test that the precompile can handle an empty input -#[test] -fn test_clear_suicided_empty_input() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10000000000000000000)]) - .build() - .execute_with(|| { - let addresses = mock_contracts([10]); - // Add contract to the suicided contracts - pallet_evm::Suicided::::insert(addresses[0].0, ()); - - precompiles() - .prepare_test( - Alice, - Precompile1, - PCall::clear_suicided_storage { - addresses: vec![].into(), - limit: u64::MAX, - }, - ) - .execute_returns(()); - - assert_eq!( - pallet_evm::AccountStorages::::iter_prefix(addresses[0].0).count(), - 10 - ); - assert!(pallet_evm::Suicided::::contains_key( - addresses[0].0 - )); - }) -} - -// Test with multiple suicided contracts ensuring that the precompile can handle multiple addresses at once. -#[test] -fn test_clear_suicided_contract_multiple_addresses() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10000000000000000000)]) - .build() - .execute_with(|| { - let addresses = mock_contracts([10, 20, 30]); - - for address in &addresses { - pallet_evm::Suicided::::insert(address.0, ()); - } - - precompiles() - .prepare_test( - Alice, - Precompile1, - PCall::clear_suicided_storage { - addresses: addresses.clone().into(), - limit: u64::MAX, - }, - ) - .execute_returns(()); - - for Address(address) in addresses { - assert_eq!( - pallet_evm::AccountStorages::::iter_prefix(address).count(), - 0 - ); - assert!(!pallet_evm::Suicided::::contains_key(address)); - } - }) -} - -// Test a combination of Suicided and non-suicided contracts -#[test] -fn test_clear_suicided_mixed_suicided_and_non_suicided() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10000000000000000000)]) - .build() - .execute_with(|| { - let addresses = mock_contracts([10, 20, 30, 10]); - - // Add contract to the suicided contracts - (0..3).for_each(|i| { - pallet_evm::Suicided::::insert(addresses[i].0, ()); - }); - - precompiles() - .prepare_test( - Alice, - Precompile1, - PCall::clear_suicided_storage { - addresses: addresses.clone().into(), - limit: u64::MAX, - }, - ) - .execute_reverts(|output| { - output == format!("NotSuicided: {}", addresses[3].0).as_bytes() - }); - }) -} - -// Test that the precompile can handle suicided contracts that have no storage entries -#[test] -fn test_clear_suicided_no_storage_entries() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10000000000000000000)]) - .build() - .execute_with(|| { - let num_entries = [0, 500, 0, 400, 100]; - let addresses = mock_contracts(num_entries); - - for Address(address) in &addresses { - pallet_evm::Suicided::::insert(address, ()); - } - - precompiles() - .prepare_test( - Alice, - Precompile1, - PCall::clear_suicided_storage { - addresses: addresses.clone().into(), - limit: u64::MAX, - }, - ) - .execute_returns(()); - - for Address(address) in addresses { - assert_eq!( - pallet_evm::AccountStorages::::iter_prefix(address).count(), - 0 - ); - assert!(!pallet_evm::Suicided::::contains_key(address)); - } - }) -} - -// Test that the precompile deletes entries up to the limit -#[test] -fn test_clear_suicided_contract_limit_works() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10000000000000000000)]) - .build() - .execute_with(|| { - let addresses = mock_contracts([3, 4]); - // Add contract to the suicided contracts - for Address(address) in &addresses { - pallet_evm::Suicided::::insert(address, ()); - } - - precompiles() - .prepare_test( - Alice, - Precompile1, - PCall::clear_suicided_storage { - addresses: addresses.clone().into(), - limit: 5, - }, - ) - .execute_returns(()); - - assert_eq!( - pallet_evm::AccountStorages::::iter_prefix(addresses[0].0).count(), - 0 - ); - assert!(!pallet_evm::Suicided::::contains_key( - addresses[0].0 - )); - - assert_eq!( - pallet_evm::AccountStorages::::iter_prefix(addresses[1].0).count(), - 3 - ); - - assert!(pallet_evm::Suicided::::contains_key( - addresses[1].0 - )); - }) -} - -#[test] -fn test_clear_suicided_contract_limit_respected() { - ExtBuilder::default() - .with_balances(vec![(Alice.into(), 10000000000000000000)]) - .build() - .execute_with(|| { - let suicided_address = mock_contracts([5])[0].0; - // Add contract to the suicided contracts - pallet_evm::Suicided::::insert(suicided_address, ()); - - precompiles() - .prepare_test( - Alice, - Precompile1, - PCall::clear_suicided_storage { - addresses: vec![suicided_address.into()].into(), - limit: 5, - }, - ) - .execute_returns(()); - - assert_eq!( - pallet_evm::AccountStorages::::iter_prefix(suicided_address).count(), - 1 - ); - assert!(pallet_evm::Suicided::::contains_key( - suicided_address - )); - }) -} diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index b3689c4f81..88f8802ccb 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -79,7 +79,7 @@ use scale_info::TypeInfo; // Substrate use frame_support::{ dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo}, - storage::{child::KillStorageResult, KeyPrefixIterator}, + storage::KeyPrefixIterator, traits::{ fungible::{Balanced, Credit, Debt}, tokens::{ @@ -192,9 +192,6 @@ pub mod pallet { /// Gas limit Pov size ratio. type GasLimitPovSizeRatio: Get; - /// Define the quick clear limit of storage clearing when a contract suicides. Set to 0 to disable it. - type SuicideQuickClearLimit: Get; - /// Gas limit storage growth ratio. type GasLimitStorageGrowthRatio: Get; @@ -207,7 +204,7 @@ pub mod pallet { /// EVM config used in the module. fn config() -> &'static EvmConfig { - &SHANGHAI_CONFIG + &CANCUN_CONFIG } } @@ -236,7 +233,6 @@ pub mod pallet { pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); - pub SuicideQuickClearLimit: u32 = 0; } #[register_default_impl(TestDefaultConfig)] @@ -258,7 +254,6 @@ pub mod pallet { type FindAuthor = FindAuthorTruncated; type GasLimitPovSizeRatio = GasLimitPovSizeRatio; type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; - type SuicideQuickClearLimit = SuicideQuickClearLimit; type WeightInfo = (); } @@ -676,9 +671,6 @@ pub mod pallet { #[pallet::storage] pub type AccountStorages = StorageDoubleMap<_, Blake2_128Concat, H160, Blake2_128Concat, H256, H256, ValueQuery>; - - #[pallet::storage] - pub type Suicided = StorageMap<_, Blake2_128Concat, H160, (), OptionQuery>; } /// Utility alias for easy access to the [`AccountProvider::AccountId`] type from a given config. @@ -906,7 +898,7 @@ where } } -static SHANGHAI_CONFIG: EvmConfig = EvmConfig::shanghai(); +static CANCUN_CONFIG: EvmConfig = EvmConfig::cancun(); impl Pallet { /// Check whether an account is empty. @@ -916,10 +908,6 @@ impl Pallet { account.nonce == U256::zero() && account.balance == U256::zero() && code_len == 0 } - /// Check whether an account is a suicided contract - pub fn is_account_suicided(address: &H160) -> bool { - >::contains_key(address) - } pub fn iter_account_storages(address: &H160) -> KeyPrefixIterator { >::iter_key_prefix(address) @@ -935,44 +923,17 @@ impl Pallet { /// Remove an account. pub fn remove_account(address: &H160) { if >::contains_key(address) { - // Remember to call `dec_sufficients` when clearing Suicided. - >::insert(address, ()); - - // In theory, we can always have pre-EIP161 contracts, so we - // make sure the account nonce is at least one. let account_id = T::AddressMapping::into_account_id(*address); - T::AccountProvider::inc_account_nonce(&account_id); + T::AccountProvider::remove_account(&account_id); } >::remove(address); >::remove(address); - - if T::SuicideQuickClearLimit::get() > 0 { - #[allow(deprecated)] - let res = >::remove_prefix(address, Some(T::SuicideQuickClearLimit::get())); - - match res { - KillStorageResult::AllRemoved(_) => { - >::remove(address); - - let account_id = T::AddressMapping::into_account_id(*address); - T::AccountProvider::remove_account(&account_id); - } - KillStorageResult::SomeRemaining(_) => (), - } - } + let _ = >::clear_prefix(address, u32::MAX, None); } /// Create an account. pub fn create_account(address: H160, code: Vec) { - if >::contains_key(address) { - // This branch should never trigger, because when Suicided - // contains an address, then its nonce will be at least one, - // which causes CreateCollision error in EVM, but we add it - // here for safeguard. - return; - } - if code.is_empty() { return; } diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 616a80efed..b2bd34ee20 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -623,6 +623,7 @@ where struct SubstrateStackSubstate<'config> { metadata: StackSubstateMetadata<'config>, deletes: BTreeSet, + creates: BTreeSet, logs: Vec, parent: Option>>, } @@ -641,6 +642,7 @@ impl<'config> SubstrateStackSubstate<'config> { metadata: self.metadata.spit_child(gas_limit, is_static), parent: None, deletes: BTreeSet::new(), + creates: BTreeSet::new(), logs: Vec::new(), }; mem::swap(&mut entering, self); @@ -657,7 +659,7 @@ impl<'config> SubstrateStackSubstate<'config> { self.metadata.swallow_commit(exited.metadata)?; self.logs.append(&mut exited.logs); self.deletes.append(&mut exited.deletes); - + self.creates.append(&mut exited.creates); sp_io::storage::commit_transaction(); Ok(()) } @@ -692,10 +694,26 @@ impl<'config> SubstrateStackSubstate<'config> { false } + pub fn created(&self, address: H160) -> bool { + if self.creates.contains(&address) { + return true; + } + + if let Some(parent) = self.parent.as_ref() { + return parent.created(address); + } + + false + } + pub fn set_deleted(&mut self, address: H160) { self.deletes.insert(address); } + pub fn set_created(&mut self, address: H160) { + self.creates.insert(address); + } + pub fn log(&mut self, address: H160, topics: Vec, data: Vec) { self.logs.push(Log { address, @@ -728,6 +746,7 @@ pub struct SubstrateStackState<'vicinity, 'config, T> { vicinity: &'vicinity Vicinity, substate: SubstrateStackSubstate<'config>, original_storage: BTreeMap<(H160, H256), H256>, + transient_storage: BTreeMap<(H160, H256), H256>, recorded: Recorded, weight_info: Option, storage_meter: Option, @@ -748,11 +767,13 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { substate: SubstrateStackSubstate { metadata, deletes: BTreeSet::new(), + creates: BTreeSet::new(), logs: Vec::new(), parent: None, }, _marker: PhantomData, original_storage: BTreeMap::new(), + transient_storage: BTreeMap::new(), recorded: Default::default(), weight_info, storage_meter, @@ -847,6 +868,13 @@ where >::get(address, index) } + fn transient_storage(&self, address: H160, index: H256) -> H256 { + self.transient_storage + .get(&(address, index)) + .copied() + .unwrap_or_default() + } + fn original_storage(&self, address: H160, index: H256) -> Option { Some( self.original_storage @@ -894,6 +922,10 @@ where self.substate.deleted(address) } + fn created(&self, address: H160) -> bool { + self.substate.created(address) + } + fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> { let account_id = T::AddressMapping::into_account_id(address); T::AccountProvider::inc_account_nonce(&account_id); @@ -933,6 +965,10 @@ where } } + fn set_transient_storage(&mut self, address: H160, key: H256, value: H256) { + self.transient_storage.insert((address, key), value); + } + fn reset_storage(&mut self, address: H160) { #[allow(deprecated)] let _ = >::remove_prefix(address, None); @@ -946,6 +982,10 @@ where self.substate.set_deleted(address) } + fn set_created(&mut self, address: H160) { + self.substate.set_created(address); + } + fn set_code(&mut self, address: H160, code: Vec) { log::debug!( target: "evm", diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 07cd52131b..77949c608c 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -1246,7 +1246,7 @@ fn handle_sufficient_reference() { assert_eq!(account_2.sufficients, 1); EVM::remove_account(&addr_2); let account_2 = frame_system::Account::::get(substrate_addr_2); - assert_eq!(account_2.sufficients, 1); + assert_eq!(account_2.sufficients, 0); }); } diff --git a/precompiles/src/testing/handle.rs b/precompiles/src/testing/handle.rs index da6c2e286b..d596230900 100644 --- a/precompiles/src/testing/handle.rs +++ b/precompiles/src/testing/handle.rs @@ -117,7 +117,7 @@ impl PrecompileHandle for MockHandle { if self .record_cost(crate::evm::costs::call_cost( context.apparent_value, - &evm::Config::london(), + &evm::Config::cancun(), )) .is_err() { diff --git a/precompiles/tests-external/lib.rs b/precompiles/tests-external/lib.rs index fd5a18e4a5..28b8e9057d 100644 --- a/precompiles/tests-external/lib.rs +++ b/precompiles/tests-external/lib.rs @@ -226,7 +226,6 @@ parameter_types! { let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64(); block_gas_limit.saturating_div(MAX_POV_SIZE) }; - pub SuicideQuickClearLimit: u32 = 0; } impl pallet_evm::Config for Runtime { @@ -249,7 +248,6 @@ impl pallet_evm::Config for Runtime { type OnCreate = (); type FindAuthor = (); type GasLimitPovSizeRatio = GasLimitPovSizeRatio; - type SuicideQuickClearLimit = SuicideQuickClearLimit; type GasLimitStorageGrowthRatio = (); type Timestamp = Timestamp; type WeightInfo = pallet_evm::weights::SubstrateWeight; diff --git a/primitives/evm/src/validation.rs b/primitives/evm/src/validation.rs index 4359dcaef1..b69172e795 100644 --- a/primitives/evm/src/validation.rs +++ b/primitives/evm/src/validation.rs @@ -263,7 +263,7 @@ mod tests { UnknownError, } - static SHANGHAI_CONFIG: evm::Config = evm::Config::shanghai(); + static CANCUN_CONFIG: evm::Config = evm::Config::cancun(); impl From for TestError { fn from(e: TransactionValidationError) -> Self { @@ -337,7 +337,7 @@ mod tests { } = input; CheckEvmTransaction::::new( CheckEvmTransactionConfig { - evm_config: &SHANGHAI_CONFIG, + evm_config: &CANCUN_CONFIG, block_gas_limit: blockchain_gas_limit, base_fee: blockchain_base_fee, chain_id: blockchain_chain_id, diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index c7cac3d0a5..5c437d4e7e 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -347,7 +347,6 @@ parameter_types! { pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0); - pub SuicideQuickClearLimit: u32 = 0; } impl pallet_evm::Config for Runtime { @@ -370,7 +369,6 @@ impl pallet_evm::Config for Runtime { type OnCreate = (); type FindAuthor = FindAuthorTruncated; type GasLimitPovSizeRatio = GasLimitPovSizeRatio; - type SuicideQuickClearLimit = SuicideQuickClearLimit; type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; type Timestamp = Timestamp; type WeightInfo = pallet_evm::weights::SubstrateWeight; diff --git a/ts-tests/contracts/ECRecoverTests.sol b/ts-tests/contracts/ECRecoverTests.sol index fa7e1cb52a..0e70945ad5 100644 --- a/ts-tests/contracts/ECRecoverTests.sol +++ b/ts-tests/contracts/ECRecoverTests.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.2; +pragma solidity >=0.8.2 <0.9.0; contract ECRecoverTests { function ecrecover(bytes memory input) public returns(bytes memory) { diff --git a/ts-tests/contracts/ExplicitRevertReason.sol b/ts-tests/contracts/ExplicitRevertReason.sol index 5691f55581..e7a8c64cf8 100644 --- a/ts-tests/contracts/ExplicitRevertReason.sol +++ b/ts-tests/contracts/ExplicitRevertReason.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.2; +pragma solidity >=0.8.2 <0.9.0; contract ExplicitRevertReason { function max10(uint256 a) public returns (uint256) { diff --git a/ts-tests/contracts/Test.sol b/ts-tests/contracts/Test.sol index 959fe88bd0..cec4645667 100644 --- a/ts-tests/contracts/Test.sol +++ b/ts-tests/contracts/Test.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.2; +pragma solidity >=0.8.2 <0.9.0; contract Test { function multiply(uint a) public pure returns(uint d) { diff --git a/ts-tests/contracts/eip1153.sol b/ts-tests/contracts/eip1153.sol new file mode 100644 index 0000000000..92ec078f21 --- /dev/null +++ b/ts-tests/contracts/eip1153.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +contract ReentrancyProtected { + // A constant key for the reentrancy guard stored in Transient Storage. + // This acts as a unique identifier for the reentrancy lock. + bytes32 constant REENTRANCY_GUARD = keccak256("REENTRANCY_GUARD"); + + // Modifier to prevent reentrant calls. + // It checks if the reentrancy guard is set (indicating an ongoing execution) + // and sets the guard before proceeding with the function execution. + // After the function executes, it resets the guard to allow future calls. + modifier nonReentrant() { + // Ensure the guard is not set (i.e., no ongoing execution). + require(tload(REENTRANCY_GUARD) == 0, "Reentrant call detected."); + + // Set the guard to block reentrant calls. + tstore(REENTRANCY_GUARD, 1); + + _; // Execute the function body. + + // Reset the guard after execution to allow future calls. + tstore(REENTRANCY_GUARD, 0); + } + + // Uses inline assembly to access the Transient Storage's tstore operation. + function tstore(bytes32 location, uint value) private { + assembly { + tstore(location, value) + } + } + + // Uses inline assembly to access the Transient Storage's tload operation. + // Returns the value stored at the given location. + function tload(bytes32 location) private returns (uint value) { + assembly { + value := tload(location) + } + } + + function nonReentrantMethod() public nonReentrant() { + (bool success, bytes memory result) = msg.sender.call(""); + if (!success) { + assembly { + revert(add(32, result), mload(result)) + } + } + } + + function test() external { + this.nonReentrantMethod(); + } + + receive() external payable { + this.nonReentrantMethod(); + } +} \ No newline at end of file diff --git a/ts-tests/tests/test-contract-storage.ts b/ts-tests/tests/test-contract-storage.ts index 66ff3f6ab7..d4212c3b9e 100644 --- a/ts-tests/tests/test-contract-storage.ts +++ b/ts-tests/tests/test-contract-storage.ts @@ -117,11 +117,11 @@ describeWithFrontier("Frontier RPC (Contract)", (context) => { const baseCost = 24029; // going from unset storage to some value (original = 0) - expect(tx1.gasUsed - baseCost).to.be.eq(20000); + expect(tx1.gasUsed - baseCost).to.be.eq(19993); // in London config, setting back the same value have cost of warm read - expect(tx2.gasUsed - baseCost).to.be.eq(100); + expect(tx2.gasUsed - baseCost).to.be.eq(93); // - the original storage didn't change in the current transaction // - the original storage is not zero (otherwise tx1) - expect(tx3.gasUsed - baseCost).to.be.eq(2900); + expect(tx3.gasUsed - baseCost).to.be.eq(2893); }); }); diff --git a/ts-tests/tests/test-eip1153.ts b/ts-tests/tests/test-eip1153.ts new file mode 100644 index 0000000000..8105d159f3 --- /dev/null +++ b/ts-tests/tests/test-eip1153.ts @@ -0,0 +1,54 @@ +import { expect, use as chaiUse } from "chai"; +import chaiAsPromised from "chai-as-promised"; +import { AbiItem } from "web3-utils"; + +import ReentrancyProtected from "../build/contracts/ReentrancyProtected.json"; +import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY } from "./config"; +import { createAndFinalizeBlock, customRequest, describeWithFrontier } from "./util"; + +chaiUse(chaiAsPromised); + +describeWithFrontier("Frontier RPC (EIP-1153)", (context) => { + const TEST_CONTRACT_BYTECODE = ReentrancyProtected.bytecode; + const TEST_CONTRACT_ABI = ReentrancyProtected.abi as AbiItem[]; + let contract_address: string = null; + + // Those test are ordered. In general this should be avoided, but due to the time it takes + // to spin up a frontier node, it saves a lot of time. + + before("create the contract", async function () { + this.timeout(15000); + const tx = await context.web3.eth.accounts.signTransaction( + { + from: GENESIS_ACCOUNT, + data: TEST_CONTRACT_BYTECODE, + value: "0x00", + gasPrice: "0x3B9ACA00", + gas: "0x100000", + }, + GENESIS_ACCOUNT_PRIVATE_KEY + ); + await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); + await createAndFinalizeBlock(context.web3); + + const receipt = await context.web3.eth.getTransactionReceipt(tx.transactionHash); + contract_address = receipt.contractAddress; + }); + + it("should detect reentrant call and revert", async function () { + const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, contract_address, { + from: GENESIS_ACCOUNT, + gasPrice: "0x3B9ACA00", + }); + + try { + await contract.methods.test().call(); + } catch (error) { + return expect(error.message).to.be.eq( + "Returned error: VM Exception while processing transaction: revert Reentrant call detected." + ); + } + + expect.fail("Expected the contract call to fail"); + }); +}); diff --git a/ts-tests/tests/test-execute.ts b/ts-tests/tests/test-execute.ts index afbf731588..17a4656c28 100644 --- a/ts-tests/tests/test-execute.ts +++ b/ts-tests/tests/test-execute.ts @@ -158,7 +158,7 @@ describeWithFrontier("Frontier RPC (RPC execution)", (context) => { }, ]); - expect(result.result).to.be.equal("0x30464"); + expect(result.result).to.be.equal("0x2e4b4"); }); step("should estimateGas with gas limit up to 10x block gas limit", async function () { @@ -170,7 +170,7 @@ describeWithFrontier("Frontier RPC (RPC execution)", (context) => { }, ]); - expect(result.result).to.be.equal("0x30464"); + expect(result.result).to.be.equal("0x2e4b4"); }); step("shouldn't estimateGas with gas limit up higher than 10x block gas limit", async function () { diff --git a/ts-tests/tests/test-gas.ts b/ts-tests/tests/test-gas.ts index 7da474b7da..6c68729557 100644 --- a/ts-tests/tests/test-gas.ts +++ b/ts-tests/tests/test-gas.ts @@ -55,7 +55,7 @@ describeWithFrontier("Frontier RPC (Gas)", (context) => { it("eth_estimateGas for contract creation", async function () { // The value returned as an estimation by the evm with estimate mode ON. - let oneOffEstimation = 196701; + let oneOffEstimation = 189139; let binarySearchEstimation = binarySearch(oneOffEstimation); // Sanity check expect a variance of 10%. expect(estimationVariance(binarySearchEstimation, oneOffEstimation)).to.be.lessThan(1); @@ -97,7 +97,7 @@ describeWithFrontier("Frontier RPC (Gas)", (context) => { it("eth_estimateGas should handle AccessList alias", async function () { // The value returned as an estimation by the evm with estimate mode ON. // 4300 == 1900 for one key and 2400 for one storage. - let oneOffEstimation = 196701 + 4300; + let oneOffEstimation = 189139 + 4300; let binarySearchEstimation = binarySearch(oneOffEstimation); // Sanity check expect a variance of 10%. expect(estimationVariance(binarySearchEstimation, oneOffEstimation)).to.be.lessThan(1); @@ -124,12 +124,12 @@ describeWithFrontier("Frontier RPC (Gas)", (context) => { data: Test.bytecode, gasPrice: "0x0", }); - expect(result).to.equal(197732); + expect(result).to.equal(189620); result = await context.web3.eth.estimateGas({ from: GENESIS_ACCOUNT, data: Test.bytecode, }); - expect(result).to.equal(197732); + expect(result).to.equal(189620); }); it("eth_estimateGas should ignore nonce", async function () { @@ -138,7 +138,7 @@ describeWithFrontier("Frontier RPC (Gas)", (context) => { data: Test.bytecode, nonce: 42, // Arbitrary nonce value }); - expect(result).to.equal(197732); + expect(result).to.equal(189620); }); it("tx gas limit below ETH_BLOCK_GAS_LIMIT", async function () { @@ -192,15 +192,15 @@ describeWithFrontier("Frontier RPC (Gas limit Weightv2 ref time)", (context) => const STORAGE_LOOP_CONTRACT_ABI = StorageLoop.abi as AbiItem[]; // First call to contract storageLoop method - const FIRST_CALL = 752_450; + const FIRST_CALL = 611_438; // Rest of calls - const CALL_COST = 735_350; + const CALL_COST = 594_338; // Block gas limit const BLOCK_GAS_LIMIT = ETH_BLOCK_GAS_LIMIT - FIRST_CALL; // Number of calls per block - const CALLS_PER_BLOCK = Math.floor(BLOCK_GAS_LIMIT / CALL_COST) + 1; + const CALLS_PER_BLOCK = Math.floor(BLOCK_GAS_LIMIT / CALL_COST) + 1; // +1 to count first call // Available space left after all calls - const REMNANT = Math.floor(ETH_BLOCK_GAS_LIMIT - (CALL_COST * (CALLS_PER_BLOCK - 1) + FIRST_CALL)); + const REMNANT = Math.floor(BLOCK_GAS_LIMIT - CALL_COST * (CALLS_PER_BLOCK - 1)); // Number of transfers per available space left const TRANSFERS_PER_BLOCK = Math.floor(REMNANT / 21_000); @@ -237,7 +237,7 @@ describeWithFrontier("Frontier RPC (Gas limit Weightv2 ref time)", (context) => to: contract.options.address, data: data.encodeABI(), gasPrice: "0x3B9ACA00", - gas: "0x100000", + gas: `0x${(FIRST_CALL + 5000).toString(16)}`, nonce, }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -247,7 +247,7 @@ describeWithFrontier("Frontier RPC (Gas limit Weightv2 ref time)", (context) => } // because we are using Math.floor for everything, at the end there is room for an additional // transfer. - for (var i = 0; i < TRANSFERS_PER_BLOCK + 1; i++) { + for (var i = 0; i < TRANSFERS_PER_BLOCK; i++) { const tx = await context.web3.eth.accounts.signTransaction( { from: GENESIS_ACCOUNT, @@ -259,14 +259,14 @@ describeWithFrontier("Frontier RPC (Gas limit Weightv2 ref time)", (context) => }, GENESIS_ACCOUNT_PRIVATE_KEY ); - let r = await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); + await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); nonce++; } await createAndFinalizeBlock(context.web3); let latest = await context.web3.eth.getBlock("latest"); - expect(latest.transactions.length).to.be.eq(CALLS_PER_BLOCK + TRANSFERS_PER_BLOCK + 1); + expect(latest.transactions.length).to.be.eq(CALLS_PER_BLOCK + TRANSFERS_PER_BLOCK); expect(latest.gasUsed).to.be.lessThanOrEqual(ETH_BLOCK_GAS_LIMIT); expect(ETH_BLOCK_GAS_LIMIT - latest.gasUsed).to.be.lessThan(21_000); }); @@ -276,20 +276,20 @@ describeWithFrontier("Frontier RPC (Gas limit Weightv2 pov size)", (context) => const STORAGE_LOOP_CONTRACT_BYTECODE = StorageLoop.bytecode; const STORAGE_LOOP_CONTRACT_ABI = StorageLoop.abi as AbiItem[]; + // Big transfer + const CONTRACT_TRANSFER_EFFECTIVE_GAS = 109_116; // First call to contract storageLoop method - const FIRST_CALL = 752_450; + const FIRST_CALL = 611_438; // Rest of calls - const CALL_COST = 735_350; + const CALL_COST = 594_338; // Block gas limit - const BLOCK_GAS_LIMIT = ETH_BLOCK_GAS_LIMIT - FIRST_CALL; + const BLOCK_GAS_LIMIT = ETH_BLOCK_GAS_LIMIT - (FIRST_CALL + CONTRACT_TRANSFER_EFFECTIVE_GAS); // Number of calls per block - const CALLS_PER_BLOCK = Math.floor(BLOCK_GAS_LIMIT / CALL_COST) + 1; + const CALLS_PER_BLOCK = Math.floor(BLOCK_GAS_LIMIT / CALL_COST) + 1; // +1 to count first call // Available space left after all calls - const REMNANT = Math.floor(ETH_BLOCK_GAS_LIMIT - (CALL_COST * (CALLS_PER_BLOCK - 1) + FIRST_CALL)); - // Big transfer - const CONTRACT_TRANSFER_EFFECTIVE_GAS = 100_520; + const REMNANT = Math.floor(BLOCK_GAS_LIMIT - CALL_COST * (CALLS_PER_BLOCK - 1)); // Number of transfers per available space left - const TRANSFERS_PER_BLOCK = Math.floor((REMNANT - CONTRACT_TRANSFER_EFFECTIVE_GAS) / 21_000); + const TRANSFERS_PER_BLOCK = Math.floor(REMNANT / 21_000) + 1; // +1 to count big transfer let contractAddress; before("create the contract", async function () { @@ -377,7 +377,7 @@ describeWithFrontier("Frontier RPC (Gas limit Weightv2 pov size)", (context) => }, GENESIS_ACCOUNT_PRIVATE_KEY ); - let r = await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); + await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); nonce++; } @@ -420,6 +420,6 @@ describeWithFrontier("Frontier RPC (Invalid opcode estimate gas)", (context) => }); // The actual estimated value is irrelevant for this test purposes, we just want to verify that // the binary search is not interrupted when an InvalidCode is returned by the evm. - expect(estimate).to.equal(85703); + expect(estimate).to.equal(85699); }); }); diff --git a/ts-tests/truffle-config.js b/ts-tests/truffle-config.js index 3a2b265974..16e922a0f7 100644 --- a/ts-tests/truffle-config.js +++ b/ts-tests/truffle-config.js @@ -85,7 +85,7 @@ module.exports = { // Configure your compilers compilers: { solc: { - version: "0.8.2", // Fetch exact version from solc-bin (default: truffle's version) + version: "0.8.25", // Fetch exact version from solc-bin (default: truffle's version) docker: true, // Use "0.5.1" you've installed locally with docker (default: false) // settings: { // See the solidity docs for advice about optimization and evmVersion // optimizer: {