diff --git a/.github/workflows/ci_levm.yaml b/.github/workflows/ci_levm.yaml index 5f94cb7da59..dcdb50e79dd 100644 --- a/.github/workflows/ci_levm.yaml +++ b/.github/workflows/ci_levm.yaml @@ -113,6 +113,14 @@ jobs: name: main-ef-test-data path: crates/vm/levm/test_result_main_short.txt + # This is for ensuring test from merge to Prague are passing. + # After adding the Legacy Test suite, not all are passing. + # This is a temporary solution until we fix the legacy tests. + - name: Check EF-TESTS from London to Prague is 100% + run: | + cd crates/vm/levm + awk '/Prague:/, /London:/ {print $1, $3}' test_result_main.txt | sed 's/[()]//g' | grep -v '100.00%' && echo "All percentage are not 100%." && exit 1 || echo "All percentage are 100%." + - name: Check EF-TESTS status is 100% run: | cd crates/vm/levm diff --git a/crates/vm/levm/Makefile b/crates/vm/levm/Makefile index 5a8645220df..ec9e80de611 100644 --- a/crates/vm/levm/Makefile +++ b/crates/vm/levm/Makefile @@ -24,8 +24,8 @@ ETH_TEST_URL := https://github.com/ethereum/tests.git ETH_TEST_TAG := v14.1 COMMIT_LEGACY_TESTS_FOR_TAG := b2e6c9e -STATETEST_VERSION := pectra-devnet-5%40v1.1.0 -STATETEST_NET := pectra-devnet-5 +STATETEST_VERSION := pectra-devnet-6%40v1.0.0 +STATETEST_NET := pectra-devnet-6 STATETEST_ARTIFACT := fixtures_$(STATETEST_NET).tar.gz STATETEST_URL := https://github.com/ethereum/execution-spec-tests/releases/download/$(STATETEST_VERSION)/fixtures_$(STATETEST_NET).tar.gz diff --git a/crates/vm/levm/src/hooks/default_hook.rs b/crates/vm/levm/src/hooks/default_hook.rs index 2669192cce8..ffeb1d491ee 100644 --- a/crates/vm/levm/src/hooks/default_hook.rs +++ b/crates/vm/levm/src/hooks/default_hook.rs @@ -363,14 +363,14 @@ impl Hook for DefaultHook { // 2. Return unused gas + gas refunds to the sender. let max_gas = vm.env.gas_limit; - let consumed_gas = report.gas_used; + let mut consumed_gas = report.gas_used; // [EIP-3529](https://eips.ethereum.org/EIPS/eip-3529) let quotient = if vm.env.config.fork < Fork::London { MAX_REFUND_QUOTIENT_PRE_LONDON } else { MAX_REFUND_QUOTIENT }; - let refunded_gas = report.gas_refunded.min( + let mut refunded_gas = report.gas_refunded.min( consumed_gas .checked_div(quotient) .ok_or(VMError::Internal(InternalError::UndefinedState(-1)))?, @@ -378,6 +378,15 @@ impl Hook for DefaultHook { // "The max refundable proportion of gas was reduced from one half to one fifth by EIP-3529 by Buterin and Swende [2021] in the London release" report.gas_refunded = refunded_gas; + if vm.env.config.fork >= Fork::Prague { + let floor_gas_price = vm.get_floor_gas_price(initial_call_frame)?; + let execution_gas_used = consumed_gas.saturating_sub(refunded_gas); + if floor_gas_price > execution_gas_used { + consumed_gas = floor_gas_price; + refunded_gas = 0; + } + } + let gas_to_return = max_gas .checked_sub(consumed_gas) .and_then(|gas| gas.checked_add(refunded_gas)) diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index 5f296f98ef7..7d1b710c6d7 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -1,10 +1,9 @@ use crate::{ call_frame::CallFrame, - constants::SET_CODE_DELEGATION_BYTES, errors::{InternalError, OpcodeResult, VMError}, gas_cost::{self}, memory::{self, calculate_memory_size}, - utils::{access_account, has_delegation, word_to_address}, + utils::{access_account, word_to_address}, vm::VM, }; use ethrex_common::{types::Fork, U256}; @@ -294,19 +293,14 @@ impl VM { address, ); - // https://eips.ethereum.org/EIPS/eip-7702#delegation-designation - let is_delegation = has_delegation(&account_info)?; - current_call_frame.increase_consumed_gas(gas_cost::extcodesize( address_was_cold, self.env.config.fork, )?)?; - current_call_frame.stack.push(if is_delegation { - SET_CODE_DELEGATION_BYTES[..2].len().into() - } else { - account_info.bytecode.len().into() - })?; + current_call_frame + .stack + .push(account_info.bytecode.len().into())?; Ok(OpcodeResult::Continue { pc_increment: 1 }) } @@ -334,9 +328,6 @@ impl VM { let new_memory_size = calculate_memory_size(dest_offset, size)?; - // https://eips.ethereum.org/EIPS/eip-7702#delegation-designation - let is_delegation = has_delegation(&account_info)?; - current_call_frame.increase_consumed_gas(gas_cost::extcodecopy( size, new_memory_size, @@ -349,11 +340,9 @@ impl VM { return Ok(OpcodeResult::Continue { pc_increment: 1 }); } - let bytecode = if is_delegation { - SET_CODE_DELEGATION_BYTES[..2].into() - } else { - account_info.bytecode - }; + // If the bytecode is a delegation designation, it will copy the marker (0xef0100) || address. + // https://eips.ethereum.org/EIPS/eip-7702#delegation-designation + let bytecode = account_info.bytecode; let mut data = vec![0u8; size]; if offset < bytecode.len().into() { @@ -467,29 +456,20 @@ impl VM { address, ); - // https://eips.ethereum.org/EIPS/eip-7702#delegation-designation - let is_delegation = has_delegation(&account_info)?; - current_call_frame.increase_consumed_gas(gas_cost::extcodehash( address_was_cold, self.env.config.fork, )?)?; - if is_delegation { - let hash = - U256::from_big_endian(keccak(&SET_CODE_DELEGATION_BYTES[..2]).as_fixed_bytes()); - current_call_frame.stack.push(hash)?; - } else { - // An account is considered empty when it has no code and zero nonce and zero balance. [EIP-161] - if account_info.is_empty() { - current_call_frame.stack.push(U256::zero())?; - return Ok(OpcodeResult::Continue { pc_increment: 1 }); - } - - let hash = U256::from_big_endian(keccak(account_info.bytecode).as_fixed_bytes()); - current_call_frame.stack.push(hash)?; + // An account is considered empty when it has no code and zero nonce and zero balance. [EIP-161] + if account_info.is_empty() { + current_call_frame.stack.push(U256::zero())?; + return Ok(OpcodeResult::Continue { pc_increment: 1 }); } + let hash = U256::from_big_endian(keccak(account_info.bytecode).as_fixed_bytes()); + current_call_frame.stack.push(hash)?; + Ok(OpcodeResult::Continue { pc_increment: 1 }) } } diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index d52a0ef60f4..b75b0a08f8a 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -26,7 +26,6 @@ use ethrex_common::{ Address, H256, U256, }; use std::{ - cmp::max, collections::{HashMap, HashSet}, fmt::Debug, sync::Arc, @@ -233,9 +232,15 @@ impl VM { TxKind::Call(address_to) => { default_touched_accounts.insert(address_to); - let bytecode = get_account_no_push_cache(&cache, db.clone(), address_to) - .info - .bytecode; + let mut substate = Substate { + selfdestruct_set: HashSet::new(), + touched_accounts: default_touched_accounts, + touched_storage_slots: default_touched_storage_slots, + created_accounts: HashSet::new(), + }; + + let (_is_delegation, _eip7702_gas_consumed, _code_address, bytecode) = + eip7702_get_code(&mut cache, db.clone(), &mut substate, address_to)?; // CALL tx let initial_call_frame = CallFrame::new( @@ -252,13 +257,6 @@ impl VM { false, ); - let substate = Substate { - selfdestruct_set: HashSet::new(), - touched_accounts: default_touched_accounts, - touched_storage_slots: default_touched_storage_slots, - created_accounts: HashSet::new(), - }; - Ok(Self { call_frames: vec![initial_call_frame], db, @@ -361,42 +359,33 @@ impl VM { matches!(self.tx_kind, TxKind::Create) } - fn gas_used( - &self, - initial_call_frame: &CallFrame, - report: &ExecutionReport, - ) -> Result { - if self.env.config.fork >= Fork::Prague { - // If the transaction is a CREATE transaction, the calldata is emptied and the bytecode is assigned. - let calldata = if self.is_create() { - &initial_call_frame.bytecode - } else { - &initial_call_frame.calldata - }; - - // tokens_in_calldata = nonzero_bytes_in_calldata * 4 + zero_bytes_in_calldata - // tx_calldata = nonzero_bytes_in_calldata * 16 + zero_bytes_in_calldata * 4 - // this is actually tokens_in_calldata * STANDARD_TOKEN_COST - // see it in https://eips.ethereum.org/EIPS/eip-7623 - let tokens_in_calldata: u64 = gas_cost::tx_calldata(calldata, self.env.config.fork) - .map_err(VMError::OutOfGas)? - .checked_div(STANDARD_TOKEN_COST) - .ok_or(VMError::Internal(InternalError::DivisionError))?; - - // floor_gas_price = TX_BASE_COST + TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata - let mut floor_gas_price: u64 = tokens_in_calldata - .checked_mul(TOTAL_COST_FLOOR_PER_TOKEN) - .ok_or(VMError::Internal(InternalError::GasOverflow))?; - - floor_gas_price = floor_gas_price - .checked_add(TX_BASE_COST) - .ok_or(VMError::Internal(InternalError::GasOverflow))?; - - let gas_used = max(floor_gas_price, report.gas_used); - Ok(gas_used) + pub fn get_floor_gas_price(&self, initial_call_frame: &CallFrame) -> Result { + // If the transaction is a CREATE transaction, the calldata is emptied and the bytecode is assigned. + let calldata = if self.is_create() { + &initial_call_frame.bytecode } else { - Ok(report.gas_used) - } + &initial_call_frame.calldata + }; + + // tokens_in_calldata = nonzero_bytes_in_calldata * 4 + zero_bytes_in_calldata + // tx_calldata = nonzero_bytes_in_calldata * 16 + zero_bytes_in_calldata * 4 + // this is actually tokens_in_calldata * STANDARD_TOKEN_COST + // see it in https://eips.ethereum.org/EIPS/eip-7623 + let tokens_in_calldata: u64 = gas_cost::tx_calldata(calldata, self.env.config.fork) + .map_err(VMError::OutOfGas)? + .checked_div(STANDARD_TOKEN_COST) + .ok_or(VMError::Internal(InternalError::DivisionError))?; + + // floor_gas_price = TX_BASE_COST + TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata + let mut floor_gas_price: u64 = tokens_in_calldata + .checked_mul(TOTAL_COST_FLOOR_PER_TOKEN) + .ok_or(VMError::Internal(InternalError::GasOverflow))?; + + floor_gas_price = floor_gas_price + .checked_add(TX_BASE_COST) + .ok_or(VMError::Internal(InternalError::GasOverflow))?; + + Ok(floor_gas_price) } pub fn execute(&mut self) -> Result { @@ -435,8 +424,6 @@ impl VM { let mut report = self.run_execution(&mut initial_call_frame)?; - report.gas_used = self.gas_used(&initial_call_frame, &report)?; - self.finalize_execution(&initial_call_frame, &mut report)?; report.new_state.clone_from(&self.cache);