From 7d26af25e34a93c17aaadb3a0c0e3cf032a6a4d6 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 17 Apr 2025 13:13:00 +0200 Subject: [PATCH 1/7] EVM/Execution: use access lists in the EVM handler The access lists will be properly implemented in the next commit. This commit was made in order to readapt all the unit test and reduce noise for the core implementation. --- .../kernel_latest/ethereum/src/access_list.rs | 5 ++ .../evm_execution/src/fa_bridge/mod.rs | 2 + .../evm_execution/src/fa_bridge/test_utils.rs | 5 ++ .../evm_execution/src/handler.rs | 38 ++++++++++++++ .../kernel_latest/evm_execution/src/lib.rs | 51 ++++++++++++++++++- .../src/precompiles/fa_bridge.rs | 3 ++ .../evm_execution/src/precompiles/mod.rs | 2 + etherlink/kernel_latest/kernel/src/apply.rs | 1 + .../kernel_latest/kernel/src/simulation.rs | 4 ++ 9 files changed, 110 insertions(+), 1 deletion(-) diff --git a/etherlink/kernel_latest/ethereum/src/access_list.rs b/etherlink/kernel_latest/ethereum/src/access_list.rs index 79c72e28edbe..d5e88ffa1a01 100644 --- a/etherlink/kernel_latest/ethereum/src/access_list.rs +++ b/etherlink/kernel_latest/ethereum/src/access_list.rs @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2022-2023 TriliTech +// SPDX-FileCopyrightText: 2025 Functori // // SPDX-License-Identifier: MIT @@ -46,3 +47,7 @@ impl Decodable for AccessListItem { } pub type AccessList = Vec; + +pub fn empty_access_list() -> AccessList { + vec![] +} diff --git a/etherlink/kernel_latest/evm_execution/src/fa_bridge/mod.rs b/etherlink/kernel_latest/evm_execution/src/fa_bridge/mod.rs index 8edc3d92c402..44bf2a554ede 100644 --- a/etherlink/kernel_latest/evm_execution/src/fa_bridge/mod.rs +++ b/etherlink/kernel_latest/evm_execution/src/fa_bridge/mod.rs @@ -53,6 +53,7 @@ use primitive_types::H256; use primitive_types::{H160, U256}; use rlp::Decodable; use rlp::Rlp; +use tezos_ethereum::access_list::empty_access_list; use tezos_ethereum::block::BlockConstants; use tezos_ethereum::Log; use tezos_evm_logging::{ @@ -221,6 +222,7 @@ pub fn queue_fa_deposit<'a, Host: Runtime>( precompiles, block.base_fee_per_gas(), tracer_input, + empty_access_list(), ); handler.begin_initial_transaction( diff --git a/etherlink/kernel_latest/evm_execution/src/fa_bridge/test_utils.rs b/etherlink/kernel_latest/evm_execution/src/fa_bridge/test_utils.rs index 9222cf70fdea..063823eb2a00 100644 --- a/etherlink/kernel_latest/evm_execution/src/fa_bridge/test_utils.rs +++ b/etherlink/kernel_latest/evm_execution/src/fa_bridge/test_utils.rs @@ -10,6 +10,7 @@ use num_bigint::BigInt; use primitive_types::{H160, H256, U256}; use tezos_data_encoding::enc::BinWriter; use tezos_ethereum::{ + access_list::empty_access_list, block::{BlockConstants, BlockFees}, Log, }; @@ -99,6 +100,7 @@ pub fn deploy_mock_wrapper( U256::zero(), false, None, + empty_access_list(), ) .expect("Failed to deploy") .unwrap() @@ -143,6 +145,7 @@ pub fn deploy_reentrancy_tester( U256::zero(), false, None, + empty_access_list(), ) .expect("Failed to deploy") .unwrap() @@ -189,6 +192,7 @@ pub fn run_fa_deposit( &precompiles, U256::from(21000), None, + empty_access_list(), ); handler @@ -439,6 +443,7 @@ pub fn fa_bridge_precompile_call_withdraw( &precompiles, U256::from(21000), None, + empty_access_list(), ); handler diff --git a/etherlink/kernel_latest/evm_execution/src/handler.rs b/etherlink/kernel_latest/evm_execution/src/handler.rs index b90dd2b2491d..27d955aac6cd 100644 --- a/etherlink/kernel_latest/evm_execution/src/handler.rs +++ b/etherlink/kernel_latest/evm_execution/src/handler.rs @@ -44,6 +44,7 @@ use std::cmp::min; use std::collections::{BTreeSet, HashMap}; use std::fmt::Debug; use tezos_data_encoding::enc::{BinResult, BinWriter}; +use tezos_ethereum::access_list::AccessList; use tezos_ethereum::block::BlockConstants; use tezos_evm_logging::{log, Level::*}; use tezos_evm_runtime::runtime::Runtime; @@ -445,6 +446,8 @@ pub struct EvmHandler<'a, Host: Runtime> { pub created_contracts: BTreeSet, /// Reentrancy guard prevents circular calls to impure precompiles reentrancy_guard: ReentrancyGuard, + /// Access lists as specified by EIP-2930. + access_list: AccessList, } impl<'a, Host: Runtime> EvmHandler<'a, Host> { @@ -459,6 +462,7 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { precompiles: &'a dyn PrecompileSet, effective_gas_price: U256, tracer: Option, + access_list: AccessList, ) -> Self { Self { host, @@ -480,6 +484,7 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { WITHDRAWAL_ADDRESS, FA_BRIDGE_PRECOMPILE_ADDRESS, ]), + access_list, } } @@ -3128,6 +3133,7 @@ mod test { use std::cmp::Ordering; use std::str::FromStr; use std::vec; + use tezos_ethereum::access_list::empty_access_list; use tezos_ethereum::block::BlockFees; use tezos_evm_runtime::runtime::MockKernelHost; @@ -3233,6 +3239,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let result = handler @@ -3267,6 +3274,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let code_hash: H256 = CODE_HASH_DEFAULT; @@ -3308,6 +3316,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let code_hash: H256 = CODE_HASH_DEFAULT; @@ -3354,6 +3363,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(213_u64); @@ -3423,6 +3433,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(213_u64); @@ -3524,6 +3535,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let input_value = U256::from(1025_u32); // transaction depth for contract below is callarg - 1 @@ -3624,6 +3636,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(312); @@ -3703,6 +3716,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let value = U256::zero(); @@ -3767,6 +3781,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let value = U256::zero(); @@ -3840,6 +3855,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(117); @@ -3910,6 +3926,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(210_u64); @@ -3979,6 +3996,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(210_u64); @@ -4057,6 +4075,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let hash_of_unavailable_block = handler.block_hash(U256::zero()); @@ -4084,6 +4103,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(210_u64); @@ -4143,6 +4163,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(210_u64); @@ -4236,6 +4257,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); // { (SELFDESTRUCT 0x10) } @@ -4291,6 +4313,7 @@ mod test { &precompiles, U256::one(), None, + empty_access_list(), ); set_balance(&mut handler, &caller, U256::from(10000)); @@ -4340,6 +4363,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address_1 = H160::from_low_u64_be(210_u64); @@ -4423,6 +4447,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let hash = handler.code_hash(H160::from_low_u64_le(1)); @@ -4451,6 +4476,7 @@ mod test { &precompiles, U256::one(), None, + empty_access_list(), ); set_balance(&mut handler, &caller, U256::from(1000000000)); @@ -4493,6 +4519,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address_1 = H160::from_low_u64_be(210_u64); @@ -4581,6 +4608,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address_1 = H160::from_low_u64_be(210_u64); @@ -4687,6 +4715,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let target_destruct = @@ -4751,6 +4780,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let contrac_addr = @@ -4827,6 +4857,7 @@ mod test { &precompiles, U256::from(21000), None, + empty_access_list(), ); handler @@ -4899,6 +4930,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let address = H160::from_low_u64_be(210_u64); @@ -4980,6 +5012,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); let initial_code = [1; 49153]; // MAX_INIT_CODE_SIZE + 1 @@ -5035,6 +5068,7 @@ mod test { &precompiles, U256::one(), None, + empty_access_list(), ); let _ = handler.begin_initial_transaction( @@ -5083,6 +5117,7 @@ mod test { &precompiles, U256::from(21000), None, + empty_access_list(), ); let address1 = H160::from_low_u64_be(210_u64); @@ -5163,6 +5198,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); // SUICIDE would charge 25,000 gas when the destination is non-existent, @@ -5234,6 +5270,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); // CALL would charge 25,000 gas when the destination is non-existent, @@ -5316,6 +5353,7 @@ mod test { &precompiles, gas_price, None, + empty_access_list(), ); set_balance(&mut handler, &caller, U256::from(100_000_u32)); diff --git a/etherlink/kernel_latest/evm_execution/src/lib.rs b/etherlink/kernel_latest/evm_execution/src/lib.rs index 585da7a96ae4..78c7f08e9ae0 100755 --- a/etherlink/kernel_latest/evm_execution/src/lib.rs +++ b/etherlink/kernel_latest/evm_execution/src/lib.rs @@ -17,7 +17,7 @@ use evm::executor::stack::PrecompileFailure; use handler::{EvmHandler, ExecutionOutcome, ExecutionResult}; use host::{path::RefPath, runtime::RuntimeError}; use primitive_types::{H160, H256, U256}; -use tezos_ethereum::block::BlockConstants; +use tezos_ethereum::{access_list::AccessList, block::BlockConstants}; use tezos_evm_logging::{log, Level::*}; use tezos_evm_runtime::runtime::Runtime; use tezos_smart_rollup_storage::StorageError; @@ -278,6 +278,9 @@ pub fn run_transaction<'a, Host>( value: U256, pay_for_gas: bool, tracer: Option, + // TODO: Implement EIP_2930. + // See: https://eips.ethereum.org/EIPS/eip-2930. + access_list: AccessList, ) -> Result, EthereumError> where Host: Runtime, @@ -300,6 +303,7 @@ where precompiles, effective_gas_price, tracer, + access_list, ); let call_data_for_tracing = if tracer.is_some() { @@ -478,6 +482,7 @@ mod test { use primitive_types::{H160, H256}; use std::str::FromStr; use std::vec; + use tezos_ethereum::access_list::empty_access_list; use tezos_ethereum::block::BlockFees; use tezos_ethereum::tx_common::EthereumTransactionCommon; use tezos_evm_runtime::runtime::MockKernelHost; @@ -653,6 +658,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); let expected_result = Ok(Some(ExecutionOutcome { @@ -715,6 +721,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); let expected_result = Ok(Some(ExecutionOutcome { @@ -771,6 +778,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); let expected_result = Ok(Some(ExecutionOutcome { @@ -824,6 +832,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); let new_address = @@ -859,6 +868,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); assert!(result2.is_ok(), "execution should have succeeded"); let result = result2.unwrap(); @@ -890,6 +900,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); assert!(result3.is_ok(), "execution should have succeeded"); let result = result3.unwrap(); @@ -920,6 +931,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); assert!(result2.is_ok(), "execution should have succeeded"); let result = result2.unwrap(); @@ -977,6 +989,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); assert!(result.is_ok()); @@ -1027,6 +1040,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); let expected_result = Ok(Some(ExecutionOutcome { @@ -1077,6 +1091,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000; // base cost @@ -1220,6 +1235,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000 // base cost @@ -1280,6 +1296,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000 // base cost @@ -1339,6 +1356,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); // Assert @@ -1393,6 +1411,7 @@ mod test { U256::from(100), true, None, + empty_access_list(), ); let expected_result = Ok(Some(ExecutionOutcome { @@ -1450,6 +1469,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000 // base cost @@ -1507,6 +1527,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); // Assert @@ -1619,6 +1640,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); // Assert @@ -1697,6 +1719,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000 // base cost @@ -1787,6 +1810,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000 // base cost @@ -1899,6 +1923,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let log_record1 = Log { @@ -2008,6 +2033,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let log_record1 = Log { @@ -2107,6 +2133,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000 // base cost + 7624; // execution gas cost (taken at face value from tests) @@ -2221,6 +2248,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_result = Ok(Some(ExecutionOutcome { @@ -2316,6 +2344,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000 // base cost @@ -2398,6 +2427,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let expected_gas = 21000 // base cost @@ -2476,6 +2506,7 @@ mod test { U256::from(100), true, None, + empty_access_list(), ); let expected_result = Err(EthereumError::EthereumAccountError( @@ -2525,6 +2556,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); let result = unwrap_outcome!(result); @@ -2583,6 +2615,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); let result = unwrap_outcome!(&result, false); @@ -2655,6 +2688,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); // Assert @@ -2724,6 +2758,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); // Assert @@ -2786,6 +2821,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ) } @@ -2888,6 +2924,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let result = unwrap_outcome!(&result, false); @@ -2968,6 +3005,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let result_init = unwrap_outcome!(&result_init, true); @@ -3075,6 +3113,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let result_init = unwrap_outcome!(&result_init, true); @@ -3153,6 +3192,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let result = unwrap_outcome!(&result, false); @@ -3198,6 +3238,7 @@ mod test { transaction_value, true, None, + empty_access_list(), ); let result = unwrap_outcome!(result); @@ -3251,6 +3292,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); let path = account_path(&caller).unwrap(); @@ -3338,6 +3380,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ); // Get info on contract that should not be created @@ -3460,6 +3503,7 @@ mod test { U256::zero(), false, None, + empty_access_list(), ); unwrap_outcome!(result, true); @@ -3543,6 +3587,7 @@ mod test { U256::zero(), false, None, + empty_access_list(), ); unwrap_outcome!(result, false); @@ -3580,6 +3625,7 @@ mod test { U256::zero(), false, None, + empty_access_list(), ); let internal_address_nonce = @@ -3658,6 +3704,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ) .unwrap() .unwrap(); @@ -3746,6 +3793,7 @@ mod test { U256::zero(), true, None, + empty_access_list(), ) .unwrap() .unwrap(); @@ -3804,6 +3852,7 @@ mod test { U256::zero(), false, None, + empty_access_list(), ); // The origin address is empty but when you start a transaction the nonce is bump diff --git a/etherlink/kernel_latest/evm_execution/src/precompiles/fa_bridge.rs b/etherlink/kernel_latest/evm_execution/src/precompiles/fa_bridge.rs index 5c9e1960fd17..f69efaa2d535 100644 --- a/etherlink/kernel_latest/evm_execution/src/precompiles/fa_bridge.rs +++ b/etherlink/kernel_latest/evm_execution/src/precompiles/fa_bridge.rs @@ -236,6 +236,7 @@ mod tests { use evm::ExitError; use primitive_types::{H160, U256}; use tezos_data_encoding::enc::BinWriter; + use tezos_ethereum::access_list::empty_access_list; use tezos_evm_runtime::runtime::MockKernelHost; use crate::{ @@ -282,6 +283,7 @@ mod tests { &precompiles, U256::from(21000), None, + empty_access_list(), ); if disable_reentrancy_guard { @@ -408,6 +410,7 @@ mod tests { &precompiles, U256::from(21000), None, + empty_access_list(), ); handler diff --git a/etherlink/kernel_latest/evm_execution/src/precompiles/mod.rs b/etherlink/kernel_latest/evm_execution/src/precompiles/mod.rs index 9a985fcecb51..669ad1bc16cf 100644 --- a/etherlink/kernel_latest/evm_execution/src/precompiles/mod.rs +++ b/etherlink/kernel_latest/evm_execution/src/precompiles/mod.rs @@ -299,6 +299,7 @@ mod test_helpers { use evm::Transfer; use host::runtime::Runtime; use primitive_types::{H160, U256}; + use tezos_ethereum::access_list::empty_access_list; use tezos_ethereum::block::BlockConstants; use tezos_ethereum::block::BlockFees; use tezos_evm_runtime::runtime::MockKernelHost; @@ -377,6 +378,7 @@ mod test_helpers { &precompiles, gas_price, None, + empty_access_list(), ); let value = transfer.map(|t| t.value); diff --git a/etherlink/kernel_latest/kernel/src/apply.rs b/etherlink/kernel_latest/kernel/src/apply.rs index cf8285bdb239..a91cf6c6520c 100644 --- a/etherlink/kernel_latest/kernel/src/apply.rs +++ b/etherlink/kernel_latest/kernel/src/apply.rs @@ -345,6 +345,7 @@ fn apply_ethereum_transaction_common( value, true, tracer_input, + transaction.access_list.clone(), ) { Ok(outcome) => outcome, Err(err) => { diff --git a/etherlink/kernel_latest/kernel/src/simulation.rs b/etherlink/kernel_latest/kernel/src/simulation.rs index 064eead08e81..0bfea78cb651 100644 --- a/etherlink/kernel_latest/kernel/src/simulation.rs +++ b/etherlink/kernel_latest/kernel/src/simulation.rs @@ -33,6 +33,7 @@ use evm_execution::{ use evm_execution::{run_transaction, EthereumError}; use primitive_types::{H160, U256}; use rlp::{Decodable, DecoderError, Encodable, Rlp}; +use tezos_ethereum::access_list::empty_access_list; use tezos_ethereum::block::{BlockConstants, BlockFees}; use tezos_ethereum::rlp_helpers::{ append_option_u64_le, check_list, decode_field, decode_option, decode_option_u64_le, @@ -485,6 +486,8 @@ impl Evaluation { self.value.unwrap_or_default(), false, tracer_input, + // TODO: Replace this by the decoded access lists if any. + empty_access_list(), ) { Ok(Some(outcome)) if !self.with_da_fees => { let result: SimulationResult = @@ -782,6 +785,7 @@ mod tests { transaction_value, false, None, + empty_access_list(), ); assert!(outcome.is_ok(), "contract should have been created"); let outcome = outcome.unwrap(); -- GitLab From 90e1cb9ef64af239ee824135e49bcce663c2f411 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 17 Apr 2025 13:52:35 +0200 Subject: [PATCH 2/7] EVM/Execution: implement access lists (EIP-2930) --- .../evm_execution/src/handler.rs | 162 +++++++++++++----- .../kernel_latest/evm_execution/src/lib.rs | 2 - 2 files changed, 118 insertions(+), 46 deletions(-) diff --git a/etherlink/kernel_latest/evm_execution/src/handler.rs b/etherlink/kernel_latest/evm_execution/src/handler.rs index 27d955aac6cd..9a176aa9405f 100644 --- a/etherlink/kernel_latest/evm_execution/src/handler.rs +++ b/etherlink/kernel_latest/evm_execution/src/handler.rs @@ -33,7 +33,7 @@ use alloc::borrow::Cow; use alloc::rc::Rc; use core::convert::Infallible; use evm::executor::stack::Log; -use evm::gasometer::{GasCost, Gasometer, MemoryCost}; +use evm::gasometer::{GasCost, Gasometer, MemoryCost, TransactionCost}; use evm::{ CallScheme, Capture, Config, Context, CreateScheme, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed, Handler, Opcode, Resolve, Stack, Transfer, @@ -44,7 +44,7 @@ use std::cmp::min; use std::collections::{BTreeSet, HashMap}; use std::fmt::Debug; use tezos_data_encoding::enc::{BinResult, BinWriter}; -use tezos_ethereum::access_list::AccessList; +use tezos_ethereum::access_list::{AccessList, AccessListItem}; use tezos_ethereum::block::BlockConstants; use tezos_evm_logging::{log, Level::*}; use tezos_evm_runtime::runtime::Runtime; @@ -446,7 +446,7 @@ pub struct EvmHandler<'a, Host: Runtime> { pub created_contracts: BTreeSet, /// Reentrancy guard prevents circular calls to impure precompiles reentrancy_guard: ReentrancyGuard, - /// Access lists as specified by EIP-2930. + /// Access list as specified by EIP-2930. access_list: AccessList, } @@ -488,6 +488,47 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { } } + pub fn preheat_with_accesslist(&mut self) -> Result<(), EthereumError> { + // EIP-3651 + if self.config.warm_coinbase_address { + self.get_contract(self.block_coinbase()); + if self.mark_address_as_hot(self.block_coinbase()).is_err() { + return Err(EthereumError::InconsistentState(Cow::from(format!( + "Failed to mark coinbase address {} as hot", + self.block_coinbase(), + )))); + } + } + + for AccessListItem { + address, + storage_keys, + } in self.access_list.clone() + { + // Pre-heat contract code + self.get_contract(address); + if self.mark_address_as_hot(address).is_err() { + return Err(EthereumError::InconsistentState(Cow::from(format!( + "Failed to mark access list address {} as hot", + address + )))); + } + + for index in storage_keys { + // Pre-heat storage slots + self.storage(address, index); + if self.mark_storage_as_hot(address, index).is_err() { + return Err(EthereumError::InconsistentState(Cow::from(format!( + "Failed to mark access list storage slot at address {} and index {} as hot", + address, index + )))); + } + } + } + + Ok(()) + } + /// Get the total amount of gas used for the duration of the current /// transaction. pub fn gas_used(&self) -> u64 { @@ -568,6 +609,24 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { .unwrap_or(Ok(())) } + /// Record the intrinsic gas cost of a transaction + pub fn record_transaction( + &mut self, + transaction_cost: TransactionCost, + ) -> Result<(), ExitError> { + let Some(layer) = self.transaction_data.last_mut() else { + return Err(ExitError::Other(Cow::from( + "Recording cost, but there is no transaction in progress", + ))); + }; + + layer + .gasometer + .as_mut() + .map(|gasometer| gasometer.record_transaction(transaction_cost)) + .unwrap_or(Ok(())) + } + /// Record code deposit. Pay per byte for a CREATE operation pub fn record_deposit(&mut self, len: usize) -> Result<(), ExitError> { let Some(layer) = self.transaction_data.last_mut() else { @@ -654,12 +713,15 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { (init_code.len() as u64).div_ceil(32) } - fn record_init_code_cost(&mut self, init_code: &[u8]) -> Result<(), ExitError> { + fn init_code_cost(&self, init_code: &[u8]) -> u64 { // As per EIP-3860: // > We define init_code_cost to equal INITCODE_WORD_COST * get_word_size(init_code). // where INITCODE_WORD_COST is 2. - let init_code_cost = 2 * self.get_word_size(init_code); - self.record_cost(init_code_cost) + 2 * self.get_word_size(init_code) + } + + fn record_init_code_cost(&mut self, init_code: &[u8]) -> Result<(), ExitError> { + self.record_cost(self.init_code_cost(init_code)) } /// Mark a location in durable storage as _hot_ for the purpose of calculating @@ -747,6 +809,17 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { self.created_contracts.contains(address) } + fn access_list_address_len(&self) -> usize { + self.access_list.len() + } + + fn access_list_storage_len(&self) -> usize { + self.access_list + .iter() + .map(|AccessListItem { storage_keys, .. }| storage_keys.len()) + .sum() + } + /// Record the base fee part of the transaction cost. We need the SputnikVM /// error code in case this goes wrong, so that's what we return. fn record_base_gas_cost( @@ -754,24 +827,38 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { is_create: bool, data: &[u8], ) -> Result<(), ExitError> { - let base_cost = if is_create { - self.config.gas_transaction_create + let mut zero_data_len = 0; + let mut non_zero_data_len = 0; + let access_list_address_len = self.access_list_address_len(); + let access_list_storage_len = self.access_list_storage_len(); + + data.iter().for_each(|datum| { + if *datum == 0_u8 { + zero_data_len += 1; + } else { + non_zero_data_len += 1; + } + }); + + let transaction_cost = if is_create { + let initcode_cost = self.init_code_cost(data); + TransactionCost::Create { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + initcode_cost, + } } else { - self.config.gas_transaction_call + TransactionCost::Call { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + } }; - let data_cost: u64 = data - .iter() - .map(|datum| { - if *datum == 0_u8 { - self.config.gas_transaction_zero_data - } else { - self.config.gas_transaction_non_zero_data - } - }) - .sum(); - - self.record_cost(base_cost + data_cost) + self.record_transaction(transaction_cost) } /// Add withdrawals to the current transaction layer @@ -1473,20 +1560,6 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { ))); } - if let Err(err) = self.record_base_gas_cost(true, &input) { - return self.end_initial_transaction(Ok(( - ExitReason::Error(err), - None, - vec![], - ))); - } - - if self.mark_address_as_hot(address).is_err() { - return Err(EthereumError::InconsistentState(Cow::from( - "Failed to mark callee address as hot", - ))); - } - // We check that the maximum allowed init code size as specified by EIP-3860 // can not be reached. if let Some(max_initcode_size) = self.config.max_initcode_size { @@ -1499,16 +1572,20 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { } } - if let Err(err) = self.record_init_code_cost(&input) { - log!(self.host, Debug, "{:?}: Cannot record init code cost.", err); - + if let Err(err) = self.record_base_gas_cost(true, &input) { return self.end_initial_transaction(Ok(( - ExitReason::Error(ExitError::OutOfGas), + ExitReason::Error(err), None, vec![], ))); } + if self.mark_address_as_hot(address).is_err() { + return Err(EthereumError::InconsistentState(Cow::from( + "Failed to mark callee address as hot", + ))); + } + let result = self.execute_create(caller, value.unwrap_or_default(), input, address); @@ -1668,6 +1745,8 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { AccessRecord::default(), )); + self.preheat_with_accesslist()?; + self.evm_account_storage .begin_transaction(self.host) .map_err(EthereumError::from) @@ -2698,11 +2777,6 @@ impl Handler for EvmHandler<'_, Host> { } fn is_cold(&mut self, address: H160, index: Option) -> Result { - // EIP-3651 - if self.config.warm_coinbase_address && address == self.block_coinbase() { - return Ok(false); - } - match index { Some(index) => { let is_cold = self.is_storage_hot(address, index).map(|x| !x); diff --git a/etherlink/kernel_latest/evm_execution/src/lib.rs b/etherlink/kernel_latest/evm_execution/src/lib.rs index 78c7f08e9ae0..75624b0d26ce 100755 --- a/etherlink/kernel_latest/evm_execution/src/lib.rs +++ b/etherlink/kernel_latest/evm_execution/src/lib.rs @@ -278,8 +278,6 @@ pub fn run_transaction<'a, Host>( value: U256, pay_for_gas: bool, tracer: Option, - // TODO: Implement EIP_2930. - // See: https://eips.ethereum.org/EIPS/eip-2930. access_list: AccessList, ) -> Result, EthereumError> where -- GitLab From 163c5f01ca2fdf4aa50bcf9e35b2dd9154cf7d65 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 17 Apr 2025 13:13:27 +0200 Subject: [PATCH 3/7] EVM/Evaluation: enable multi access-lists decoding in transactions --- etherlink/kernel_latest/Cargo.lock | 1 + etherlink/kernel_latest/Cargo.toml | 1 + etherlink/kernel_latest/ethereum/Cargo.toml | 6 +++++ .../kernel_latest/ethereum/src/access_list.rs | 2 ++ .../kernel_latest/evm_evaluation/Cargo.toml | 4 +-- .../evm_evaluation/src/models/mod.rs | 4 ++- .../evm_evaluation/src/runner.rs | 27 +++++++++++++------ 7 files changed, 34 insertions(+), 11 deletions(-) diff --git a/etherlink/kernel_latest/Cargo.lock b/etherlink/kernel_latest/Cargo.lock index d2ff15fb7805..c7ed4deaa0cc 100644 --- a/etherlink/kernel_latest/Cargo.lock +++ b/etherlink/kernel_latest/Cargo.lock @@ -2313,6 +2313,7 @@ dependencies = [ "libsecp256k1", "primitive-types", "rlp", + "serde", "sha3", "tezos-smart-rollup-encoding", "tezos_crypto_rs", diff --git a/etherlink/kernel_latest/Cargo.toml b/etherlink/kernel_latest/Cargo.toml index e61e3a3e8937..06cfe6b38e04 100644 --- a/etherlink/kernel_latest/Cargo.toml +++ b/etherlink/kernel_latest/Cargo.toml @@ -44,6 +44,7 @@ tezos_data_encoding = { version = "0.6", path = "../../sdk/rust/encoding" } const-decoder = { version = "0.3.0" } rlp = "0.5.2" nom = { version = "7.1", default-features = false } +serde = { version = "1.0", features = ["derive", "rc"] } # ethereum VM evm = { path = "../sputnikvm", default-features = false } diff --git a/etherlink/kernel_latest/ethereum/Cargo.toml b/etherlink/kernel_latest/ethereum/Cargo.toml index b20e5f4845c8..ee2bdc90fa8f 100644 --- a/etherlink/kernel_latest/ethereum/Cargo.toml +++ b/etherlink/kernel_latest/ethereum/Cargo.toml @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: 2023 Nomadic Labs # SPDX-FileCopyrightText: 2023 Marigold +# SPDX-FileCopyrightText: 2025 Functori # # SPDX-License-Identifier: MIT @@ -29,6 +30,11 @@ libsecp256k1.workspace = true tezos-smart-rollup-encoding.workspace = true +# Useful to keep the same types between the execution and evaluation +serde = { workspace = true, optional = true } + [features] default = [] benchmark = [] +serde = ["dep:serde"] +evaluation = ["serde"] diff --git a/etherlink/kernel_latest/ethereum/src/access_list.rs b/etherlink/kernel_latest/ethereum/src/access_list.rs index d5e88ffa1a01..012605ab3057 100644 --- a/etherlink/kernel_latest/ethereum/src/access_list.rs +++ b/etherlink/kernel_latest/ethereum/src/access_list.rs @@ -12,6 +12,8 @@ use crate::rlp_helpers::{decode_field, decode_list, next}; /// which are being accessed during a contract invocation. /// For more information see ``. #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "evaluation", derive(serde::Deserialize))] +#[cfg_attr(feature = "evaluation", serde(rename_all = "camelCase"))] pub struct AccessListItem { /// Address of the contract invoked during execution pub address: H160, diff --git a/etherlink/kernel_latest/evm_evaluation/Cargo.toml b/etherlink/kernel_latest/evm_evaluation/Cargo.toml index 0b066f78c0a1..3c21cfc9f53b 100644 --- a/etherlink/kernel_latest/evm_evaluation/Cargo.toml +++ b/etherlink/kernel_latest/evm_evaluation/Cargo.toml @@ -12,19 +12,19 @@ license = "MIT" thiserror.workspace = true evm-execution = { workspace = true, features = ["debug"] } -tezos_ethereum.workspace = true +tezos_ethereum = { workspace = true, features = ["evaluation"] } tezos-evm-logging.workspace = true tezos-evm-runtime.workspace = true tezos-smart-rollup-mock.workspace = true tezos-smart-rollup-host.workspace = true tezos-smart-rollup-core.workspace = true +serde.workspace = true hex = { version = "0.4.3", features = ["serde"] } hex-literal.workspace = true bytes = "1.5" walkdir = "2.4" -serde = { version = "1.0", features = ["derive", "rc"] } serde_json = { version = "1.0", features = ["preserve_order"] } serde_yaml = "0.9.25" structopt = "0.3.26" diff --git a/etherlink/kernel_latest/evm_evaluation/src/models/mod.rs b/etherlink/kernel_latest/evm_evaluation/src/models/mod.rs index e19df831d032..bce856616f92 100644 --- a/etherlink/kernel_latest/evm_evaluation/src/models/mod.rs +++ b/etherlink/kernel_latest/evm_evaluation/src/models/mod.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023-2024 Functori +// SPDX-FileCopyrightText: 2023-2025 Functori // SPDX-FileCopyrightText: 2021-2023 draganrakita // // SPDX-License-Identifier: MIT @@ -13,6 +13,7 @@ use std::{ collections::{BTreeMap, HashMap}, fmt, }; +use tezos_ethereum::access_list::AccessList; use crate::models::deserializer::*; @@ -173,6 +174,7 @@ pub struct UnitEnv { #[derive(Debug, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransactionParts { + pub access_lists: Option>>, #[serde(deserialize_with = "deserialize_vec_as_vec_bytes")] pub data: Vec, pub gas_limit: Vec, diff --git a/etherlink/kernel_latest/evm_evaluation/src/runner.rs b/etherlink/kernel_latest/evm_evaluation/src/runner.rs index 3f9a37a7b462..91e64e2978ac 100644 --- a/etherlink/kernel_latest/evm_evaluation/src/runner.rs +++ b/etherlink/kernel_latest/evm_evaluation/src/runner.rs @@ -12,6 +12,7 @@ use evm_execution::handler::ExecutionOutcome; use evm_execution::precompiles::{precompile_set, PrecompileBTreeMap}; use evm_execution::{run_transaction, Config, EthereumError}; +use tezos_ethereum::access_list::AccessList; use tezos_ethereum::block::{BlockConstants, BlockFees}; use hex_literal::hex; @@ -197,6 +198,7 @@ fn execute_transaction( env: &mut Env, test: &Test, data: Bytes, + access_list: AccessList, ) -> Result, EthereumError> { let gas_limit = *unit.transaction.gas_limit.get(test.indexes.gas).unwrap(); let gas_limit = u64::try_from(gas_limit).unwrap_or(u64::MAX); @@ -228,10 +230,12 @@ fn execute_transaction( "Executing transaction with:\n\ \t- data: {}\n\ \t- gas: {} gas\n\ - \t- value: {} wei", + \t- value: {} wei\n\ + \t- access list: {:?}", string_of_hexa(&env.tx.data), gas_limit, - env.tx.value + env.tx.value, + access_list ); run_transaction( host, @@ -247,6 +251,7 @@ fn execute_transaction( transaction_value, pay_for_gas, None, + access_list, ) } @@ -373,12 +378,17 @@ pub fn run_test( } } - let data = unit - .transaction - .data - .get(test_execution.indexes.data) - .unwrap() - .clone(); + let data_index = test_execution.indexes.data; + + let data = unit.transaction.data.get(data_index).unwrap().clone(); + + let access_list = match unit.transaction.access_lists { + Some(ref access_list) => match access_list.get(data_index).unwrap() { + Some(access_list) => access_list.to_vec(), + None => vec![], + }, + None => vec![], + }; if data_to_skip(&name, &data, skip_data) { continue; @@ -393,6 +403,7 @@ pub fn run_test( &mut env, test_execution, data, + access_list, ); let labels = LabelIndexes { -- GitLab From 5e61ba690da7aee206872ad010ba2752333c327e Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 17 Apr 2025 11:00:50 +0200 Subject: [PATCH 4/7] EVM/Evaluation: re-enable access lists tests --- etherlink/kernel_latest/evm_evaluation/src/main.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/etherlink/kernel_latest/evm_evaluation/src/main.rs b/etherlink/kernel_latest/evm_evaluation/src/main.rs index 72ecff856c68..ee7cdea6771c 100644 --- a/etherlink/kernel_latest/evm_evaluation/src/main.rs +++ b/etherlink/kernel_latest/evm_evaluation/src/main.rs @@ -396,15 +396,12 @@ pub fn check_skip(test_file_path: &Path) -> bool { | "Create2OnDepth1024.json" | "CallRecursiveBombLog2.json" - // Reason: EIP-2930 (https://eips.ethereum.org/EIPS/eip-2930) concerns optional - // access lists and we don't intend to implement them for now - | "addressOpcodes.json" + // Reason: Coinbase related tests that expects an absurd amount of WEI + // after the execution. + // NB: The expected storage slots for 0x000000000000000000000000000000000000C0DE + // which contains the gas cost with hot/cold access are accurate. | "coinbaseT01.json" | "coinbaseT2.json" - | "manualCreate.json" - | "storageCosts.json" - | "transactionCosts.json" - | "variedContext.json" // Reason: relying on precompile contract kzg_point_evaluation from // EIP-4844 which we don't support. -- GitLab From 3c26e289315c89674f16da931169902124e19e23 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Wed, 14 May 2025 16:03:22 +0200 Subject: [PATCH 5/7] EVM/Tezt: simple EIP-2930 storage access contract --- .../solidity_examples/eip2930_storage_access.sol | 13 +++++++++++++ etherlink/tezt/lib/solidity_contracts.ml | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100644 etherlink/kernel_latest/solidity_examples/eip2930_storage_access.sol diff --git a/etherlink/kernel_latest/solidity_examples/eip2930_storage_access.sol b/etherlink/kernel_latest/solidity_examples/eip2930_storage_access.sol new file mode 100644 index 000000000000..a106577994f6 --- /dev/null +++ b/etherlink/kernel_latest/solidity_examples/eip2930_storage_access.sol @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 Functori +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract StorageAccess { + uint256 public value = 1; + + function setValue(uint256 newValue) public { + value = newValue; + } +} diff --git a/etherlink/tezt/lib/solidity_contracts.ml b/etherlink/tezt/lib/solidity_contracts.ml index 4a0a6ebfea9b..c34cbeaeb570 100644 --- a/etherlink/tezt/lib/solidity_contracts.ml +++ b/etherlink/tezt/lib/solidity_contracts.ml @@ -432,6 +432,12 @@ let incrementor_proxy = ~label:"proxy" ~contract:"Proxy" +let eip2930_storage_access = + compile_contract + ~source:(solidity_contracts_path ^ "/eip2930_storage_access.sol") + ~label:"storageaccess" + ~contract:"StorageAccess" + module Precompile = struct let withdrawal = "0xff00000000000000000000000000000000000001" -- GitLab From f439f990a7e07c1426e86e0a36ac965fea59e336 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Wed, 14 May 2025 16:03:39 +0200 Subject: [PATCH 6/7] EVM/Tezt: minimal E2E test to check EIP-2930's semantic --- etherlink/tezt/tests/evm_sequencer.ml | 89 ++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index d3e5fd426812..bf0d01c7a4bc 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -13474,6 +13474,92 @@ let test_fa_deposit_can_be_claimed = unit +let test_eip2930_storage_access = + register_all + ~kernels:[Latest] + ~tags:["evm"; "eip2930"] + ~title:"Check EIP-2930's semantic correctness" + ~da_fee:Wei.zero + ~time_between_blocks:Nothing + @@ fun {sequencer; evm_version; _} _protocol -> + let whale = Eth_account.bootstrap_accounts.(0) in + let* eip2930_storage_access = + Solidity_contracts.eip2930_storage_access evm_version + in + let* eip2930_contract, _ = + send_transaction_to_sequencer + (Eth_cli.deploy + ~source_private_key:whale.private_key + ~endpoint:(Evm_node.endpoint sequencer) + ~abi:eip2930_storage_access.abi + ~bin:eip2930_storage_access.bin) + sequencer + in + let* gas_price = Rpc.get_gas_price sequencer in + let gas_price = Int32.to_int gas_price in + let base_tx ~nonce ~access_list ~arg = + Cast.craft_tx + ~signature:"setValue(uint256)" + ~source_private_key:whale.private_key + ~chain_id:1337 + ~nonce + ~gas:100_000 + ~gas_price + ~value:Wei.zero + ~access_list + ~address:eip2930_contract + ~arguments:[arg] + ~legacy:false + () + in + let* raw_tx_with_storage_slot_access_list = + base_tx + ~nonce:1 + ~access_list: + [ + ( eip2930_contract, + (* slot 0 = [uint256 public value] in [eip2930_storage_access.sol] *) + [ + "0x0000000000000000000000000000000000000000000000000000000000000000"; + ] ); + ] + ~arg:"42" + in + let* raw_tx_without_storage_slot_access_list = + base_tx ~nonce:2 ~access_list:[(eip2930_contract, [])] ~arg:"43" + in + let*@ tx_with_storage_slot_access_list_hash = + Rpc.send_raw_transaction + ~raw_tx:raw_tx_with_storage_slot_access_list + sequencer + in + let* _ = produce_block sequencer in + let*@ tx_without_storage_slot_access_list_hash = + Rpc.send_raw_transaction + ~raw_tx:raw_tx_without_storage_slot_access_list + sequencer + in + let* _ = produce_block sequencer in + let*@! Transaction.{gasUsed = gas_with_storage_slot_access_list; _} = + Rpc.get_transaction_receipt + ~tx_hash:tx_with_storage_slot_access_list_hash + sequencer + in + let*@! Transaction.{gasUsed = gas_without_storage_slot_access_list; _} = + Rpc.get_transaction_receipt + ~tx_hash:tx_without_storage_slot_access_list_hash + sequencer + in + Check.( + Int64.( + sub gas_without_storage_slot_access_list gas_with_storage_slot_access_list + = of_int 200) + int64) + ~error_msg: + "The gas consumption with the preheated slot should have saved exactly \ + %R but got %L" ; + unit + let protocols = Protocol.all let () = @@ -13659,4 +13745,5 @@ let () = test_tezlink_hash_rpc [Alpha] ; test_tezlink_chain_id [Alpha] ; test_tezlink_bootstrapped [Alpha] ; - test_fa_deposit_can_be_claimed [Alpha] + test_fa_deposit_can_be_claimed [Alpha] ; + test_eip2930_storage_access [Alpha] -- GitLab From c2b386571a2e5b8b48a71021440b39ae72afd6f9 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 17 Apr 2025 16:37:38 +0200 Subject: [PATCH 7/7] Etherlink: add a line in features --- etherlink/CHANGES_KERNEL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index 8d54d0417b01..204e9aae6bb7 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -28,6 +28,8 @@ - [`KT1NnH9DCAoY1pfPNvb9cw9XPKQnHAFYFHXa` for the sequencer governance][sqgov] - Computes the execution gas assuming a minimum base fee per gas instead of current gas price. The paid DA fees remain unchanged. (!17954) +- The EVM now supports optional access lists. + See [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930). (!17766) ### Bug fixes -- GitLab