From db9edbcc8c3aafa470a5e305e4b224b63a5772cf Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 6 Jan 2025 12:52:43 +0100 Subject: [PATCH 1/2] feat(eip7702): apply latest EIP-7702 changes, backport from v52 --- crates/bytecode/src/eip7702.rs | 6 +- crates/context/interface/src/host.rs | 11 +- crates/context/interface/src/host/dummy.rs | 12 +- .../context/interface/src/journaled_state.rs | 90 +------------- crates/context/src/context.rs | 117 ++++++------------ crates/context/src/journaled_state.rs | 30 +++-- crates/inspector/src/inspector_context.rs | 10 +- crates/interpreter/src/gas/calc.rs | 27 ++-- .../src/instructions/contract/call_helpers.rs | 4 +- crates/interpreter/src/instructions/host.rs | 15 +-- crates/primitives/Cargo.toml | 2 +- 11 files changed, 100 insertions(+), 224 deletions(-) diff --git a/crates/bytecode/src/eip7702.rs b/crates/bytecode/src/eip7702.rs index f7f15248bd..11473d4806 100644 --- a/crates/bytecode/src/eip7702.rs +++ b/crates/bytecode/src/eip7702.rs @@ -1,5 +1,9 @@ use core::fmt; -use primitives::{bytes, Address, Bytes}; +use primitives::{b256, bytes, Address, Bytes, B256}; + +/// Hash of EF01 bytes that is used for EXTCODEHASH when called from legacy bytecode. +pub const EIP7702_MAGIC_HASH: B256 = + b256!("eadcdba66a79ab5dce91622d1d75c8cff5cff0b96944c3bf1072cd08ce018329"); /// EIP-7702 Version Magic in u16 form pub const EIP7702_MAGIC: u16 = 0xEF01; diff --git a/crates/context/interface/src/host.rs b/crates/context/interface/src/host.rs index 22981d8dfc..89dd40eba3 100644 --- a/crates/context/interface/src/host.rs +++ b/crates/context/interface/src/host.rs @@ -3,10 +3,7 @@ mod dummy; pub use crate::journaled_state::StateLoad; pub use dummy::DummyHost; -use crate::{ - journaled_state::{AccountLoad, Eip7702CodeLoad}, - BlockGetter, CfgGetter, TransactionGetter, -}; +use crate::{journaled_state::AccountLoad, BlockGetter, CfgGetter, TransactionGetter}; use auto_impl::auto_impl; use primitives::{Address, Bytes, Log, B256, U256}; @@ -15,7 +12,7 @@ use primitives::{Address, Bytes, Log, B256, U256}; #[auto_impl(&mut, Box)] pub trait Host: TransactionGetter + BlockGetter + CfgGetter { /// Load an account code. - fn load_account_delegated(&mut self, address: Address) -> Option; + fn load_account_delegated(&mut self, address: Address) -> Option>; /// Gets the block hash of the given block `number`. fn block_hash(&mut self, number: u64) -> Option; @@ -24,10 +21,10 @@ pub trait Host: TransactionGetter + BlockGetter + CfgGetter { fn balance(&mut self, address: Address) -> Option>; /// Gets code of `address` and if the account is cold. - fn code(&mut self, address: Address) -> Option>; + fn code(&mut self, address: Address) -> Option>; /// Gets code hash of `address` and if the account is cold. - fn code_hash(&mut self, address: Address) -> Option>; + fn code_hash(&mut self, address: Address) -> Option>; /// Gets storage value of `address` at `index` and if the account is cold. fn sload(&mut self, address: Address, index: U256) -> Option>; diff --git a/crates/context/interface/src/host/dummy.rs b/crates/context/interface/src/host/dummy.rs index 41781d5373..f803023fc1 100644 --- a/crates/context/interface/src/host/dummy.rs +++ b/crates/context/interface/src/host/dummy.rs @@ -3,7 +3,7 @@ use crate::{Block, BlockGetter, Cfg, CfgGetter, Transaction, TransactionGetter}; use primitives::{hash_map::Entry, Address, Bytes, HashMap, Log, B256, KECCAK_EMPTY, U256}; use std::vec::Vec; -use super::{AccountLoad, Eip7702CodeLoad, StateLoad}; +use super::{AccountLoad, StateLoad}; /// A dummy [Host] implementation. #[derive(Clone, Debug, Default)] @@ -74,8 +74,8 @@ impl CfgGetter for DummyHost Host for DummyHost { #[inline] - fn load_account_delegated(&mut self, _address: Address) -> Option { - Some(AccountLoad::default()) + fn load_account_delegated(&mut self, _address: Address) -> Option> { + Some(StateLoad::new(AccountLoad::default(), false)) } #[inline] @@ -89,13 +89,13 @@ impl Host for DummyHost } #[inline] - fn code(&mut self, _address: Address) -> Option> { + fn code(&mut self, _address: Address) -> Option> { Some(Default::default()) } #[inline] - fn code_hash(&mut self, _address: Address) -> Option> { - Some(Eip7702CodeLoad::new_not_delegated(KECCAK_EMPTY, false)) + fn code_hash(&mut self, _address: Address) -> Option> { + Some(StateLoad::new(KECCAK_EMPTY, false)) } #[inline] diff --git a/crates/context/interface/src/journaled_state.rs b/crates/context/interface/src/journaled_state.rs index c4eee4717d..6681a3b6a6 100644 --- a/crates/context/interface/src/journaled_state.rs +++ b/crates/context/interface/src/journaled_state.rs @@ -97,7 +97,7 @@ pub trait Journal { fn load_account_delegated( &mut self, address: Address, - ) -> Result::Error>; + ) -> Result, ::Error>; /// Sets bytecode with hash. Assume that account is warm. fn set_code_with_hash(&mut self, address: Address, code: Bytecode, hash: B256); @@ -200,94 +200,10 @@ impl StateLoad { #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AccountLoad { - /// Is account and delegate code are loaded - pub load: Eip7702CodeLoad<()>, - /// Is account empty, if true account is not created - pub is_empty: bool, -} - -impl Deref for AccountLoad { - type Target = Eip7702CodeLoad<()>; - - fn deref(&self) -> &Self::Target { - &self.load - } -} - -impl DerefMut for AccountLoad { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.load - } -} - -/// EIP-7702 code load result that contains optional delegation is_cold information -/// -/// [`is_delegate_account_cold`][Self::is_delegate_account_cold] will be [`Some`] if account has delegation. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Eip7702CodeLoad { - /// Returned data - pub state_load: StateLoad, /// Does account have delegate code and delegated account is cold loaded pub is_delegate_account_cold: Option, -} - -impl Deref for Eip7702CodeLoad { - type Target = StateLoad; - - fn deref(&self) -> &Self::Target { - &self.state_load - } -} - -impl DerefMut for Eip7702CodeLoad { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.state_load - } -} - -impl Eip7702CodeLoad { - /// Returns a new [`Eip7702CodeLoad`] with the given data and without delegation. - pub fn new_state_load(state_load: StateLoad) -> Self { - Self { - state_load, - is_delegate_account_cold: None, - } - } - - /// Returns a new [`Eip7702CodeLoad`] with the given data and without delegation. - pub fn new_not_delegated(data: T, is_cold: bool) -> Self { - Self { - state_load: StateLoad::new(data, is_cold), - is_delegate_account_cold: None, - } - } - - /// Deconstructs the [`Eip7702CodeLoad`] by extracting data and - /// returning a new [`Eip7702CodeLoad`] with empty data. - pub fn into_components(self) -> (T, Eip7702CodeLoad<()>) { - let is_cold = self.is_cold; - ( - self.state_load.data, - Eip7702CodeLoad { - state_load: StateLoad::new((), is_cold), - is_delegate_account_cold: self.is_delegate_account_cold, - }, - ) - } - - /// Sets the delegation cold load status. - pub fn set_delegate_load(&mut self, is_delegate_account_cold: bool) { - self.is_delegate_account_cold = Some(is_delegate_account_cold); - } - - /// Returns a new [`Eip7702CodeLoad`] with the given data and delegation cold load status. - pub fn new(state_load: StateLoad, is_delegate_account_cold: bool) -> Self { - Self { - state_load, - is_delegate_account_cold: Some(is_delegate_account_cold), - } - } + /// Is account empty, if `true` account is not created + pub is_empty: bool, } /// Helper that extracts database error from [`JournalGetter`]. diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index 91bb13de36..b48969cf6c 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -1,13 +1,14 @@ pub mod performant_access; use crate::{block::BlockEnv, cfg::CfgEnv, journaled_state::JournaledState, tx::TxEnv}; -use bytecode::{Bytecode, EOF_MAGIC_BYTES, EOF_MAGIC_HASH}; +use bytecode::{ + eip7702::{EIP7702_MAGIC_BYTES, EIP7702_MAGIC_HASH}, + EOF_MAGIC_BYTES, EOF_MAGIC_HASH, +}; use context_interface::{ - block::BlockSetter, - journaled_state::{AccountLoad, Eip7702CodeLoad}, - transaction::TransactionSetter, - Block, BlockGetter, Cfg, CfgGetter, DatabaseGetter, ErrorGetter, Journal, JournalGetter, - Transaction, TransactionGetter, + block::BlockSetter, journaled_state::AccountLoad, transaction::TransactionSetter, Block, + BlockGetter, Cfg, CfgGetter, DatabaseGetter, ErrorGetter, Journal, JournalGetter, Transaction, + TransactionGetter, }; use database_interface::{Database, EmptyDB}; use derive_where::derive_where; @@ -84,53 +85,6 @@ where DB: Database, JOURNAL: Journal, { - /// Returns account code bytes and if address is cold loaded. - /// - /// In case of EOF account it will return `EOF_MAGIC` (0xEF00) as code. - /// - // TODO : Move this in Journaled state - #[inline] - pub fn code( - &mut self, - address: Address, - ) -> Result, ::Error> { - let a = self.journaled_state.load_account_code(address)?; - // SAFETY: Safe to unwrap as load_code will insert code if it is empty. - let code = a.info.code.as_ref().unwrap(); - if code.is_eof() { - return Ok(Eip7702CodeLoad::new_not_delegated( - EOF_MAGIC_BYTES.clone(), - a.is_cold, - )); - } - - if let Bytecode::Eip7702(code) = code { - let address = code.address(); - let is_cold = a.is_cold; - - let delegated_account = self.journaled_state.load_account_code(address)?; - - // SAFETY: Safe to unwrap as load_code will insert code if it is empty. - let delegated_code = delegated_account.info.code.as_ref().unwrap(); - - let bytes = if delegated_code.is_eof() { - EOF_MAGIC_BYTES.clone() - } else { - delegated_code.original_bytes() - }; - - return Ok(Eip7702CodeLoad::new( - StateLoad::new(bytes, is_cold), - delegated_account.is_cold, - )); - } - - Ok(Eip7702CodeLoad::new_not_delegated( - code.original_bytes(), - a.is_cold, - )) - } - pub fn with_new_journal>( self, mut journal: OJOURNAL, @@ -324,6 +278,28 @@ where f(&mut self.journaled_state); } + /// Returns account code bytes and if address is cold loaded. + /// + /// In case of EOF account it will return `EOF_MAGIC` (0xEF00) as code. + /// + // TODO : Move this in Journaled state + #[inline] + pub fn code(&mut self, address: Address) -> Result, ::Error> { + let a = self.journaled_state.load_account_code(address)?; + // SAFETY: Safe to unwrap as load_code will insert code if it is empty. + let code = a.info.code.as_ref().unwrap(); + + let code = if code.is_eof() { + EOF_MAGIC_BYTES.clone() + } else if code.is_eip7702() { + EIP7702_MAGIC_BYTES.clone() + } else { + code.original_bytes() + }; + + Ok(StateLoad::new(code, a.is_cold)) + } + /// Gets code hash of address. /// /// In case of EOF account it will return `EOF_MAGIC_HASH` @@ -334,42 +310,23 @@ where pub fn code_hash( &mut self, address: Address, - ) -> Result, ::Error> { + ) -> Result, ::Error> { let acc = self.journaled_state.load_account_code(address)?; if acc.is_empty() { - return Ok(Eip7702CodeLoad::new_not_delegated(B256::ZERO, acc.is_cold)); + return Ok(StateLoad::new(B256::ZERO, acc.is_cold)); } // SAFETY: Safe to unwrap as load_code will insert code if it is empty. let code = acc.info.code.as_ref().unwrap(); - // If bytecode is EIP-7702 then we need to load the delegated account. - if let Bytecode::Eip7702(code) = code { - let address = code.address(); - let is_cold = acc.is_cold; - - let delegated_account = self.journaled_state.load_account_code(address)?; - - let hash = if delegated_account.is_empty() { - B256::ZERO - } else if delegated_account.info.code.as_ref().unwrap().is_eof() { - EOF_MAGIC_HASH - } else { - delegated_account.info.code_hash - }; - - return Ok(Eip7702CodeLoad::new( - StateLoad::new(hash, is_cold), - delegated_account.is_cold, - )); - } - let hash = if code.is_eof() { EOF_MAGIC_HASH + } else if code.is_eip7702() { + EIP7702_MAGIC_HASH } else { acc.info.code_hash }; - Ok(Eip7702CodeLoad::new_not_delegated(hash, acc.is_cold)) + Ok(StateLoad::new(hash, acc.is_cold)) } } @@ -405,7 +362,7 @@ where Some(B256::ZERO) } - fn load_account_delegated(&mut self, address: Address) -> Option { + fn load_account_delegated(&mut self, address: Address) -> Option> { self.journaled_state .load_account_delegated(address) .map_err(|e| self.error = Err(e)) @@ -420,11 +377,11 @@ where .ok() } - fn code(&mut self, address: Address) -> Option> { + fn code(&mut self, address: Address) -> Option> { self.code(address).map_err(|e| self.error = Err(e)).ok() } - fn code_hash(&mut self, address: Address) -> Option> { + fn code_hash(&mut self, address: Address) -> Option> { self.code_hash(address) .map_err(|e| self.error = Err(e)) .ok() diff --git a/crates/context/src/journaled_state.rs b/crates/context/src/journaled_state.rs index 9926f3a098..cbca92fb2f 100644 --- a/crates/context/src/journaled_state.rs +++ b/crates/context/src/journaled_state.rs @@ -1,7 +1,5 @@ use bytecode::Bytecode; -use context_interface::journaled_state::{ - AccountLoad, Eip7702CodeLoad, Journal, JournalCheckpoint, TransferError, -}; +use context_interface::journaled_state::{AccountLoad, Journal, JournalCheckpoint, TransferError}; use database_interface::Database; use interpreter::{SStoreResult, SelfDestructResult, StateLoad}; use primitives::{ @@ -173,7 +171,10 @@ impl Journal for JournaledState { self.load_code(address) } - fn load_account_delegated(&mut self, address: Address) -> Result { + fn load_account_delegated( + &mut self, + address: Address, + ) -> Result, DB::Error> { self.load_account_delegated(address) } @@ -747,22 +748,27 @@ impl JournaledState { } #[inline] - pub fn load_account_delegated(&mut self, address: Address) -> Result { + pub fn load_account_delegated( + &mut self, + address: Address, + ) -> Result, DB::Error> { let spec = self.spec; let account = self.load_code(address)?; let is_empty = account.state_clear_aware_is_empty(spec); - let mut account_load = AccountLoad { - is_empty, - load: Eip7702CodeLoad::new_not_delegated((), account.is_cold), - }; + let mut account_load = StateLoad::new( + AccountLoad { + is_delegate_account_cold: None, + is_empty, + }, + account.is_cold, + ); + // load delegate code if account is EIP-7702 if let Some(Bytecode::Eip7702(code)) = &account.info.code { let address = code.address(); let delegate_account = self.load_account(address)?; - account_load - .load - .set_delegate_load(delegate_account.is_cold); + account_load.data.is_delegate_account_cold = Some(delegate_account.is_cold); } Ok(account_load) diff --git a/crates/inspector/src/inspector_context.rs b/crates/inspector/src/inspector_context.rs index fecbe42d66..2bb39169b5 100644 --- a/crates/inspector/src/inspector_context.rs +++ b/crates/inspector/src/inspector_context.rs @@ -1,8 +1,6 @@ use revm::{ context_interface::{ - block::BlockSetter, - journaled_state::{AccountLoad, Eip7702CodeLoad}, - transaction::TransactionSetter, + block::BlockSetter, journaled_state::AccountLoad, transaction::TransactionSetter, BlockGetter, CfgGetter, DatabaseGetter, ErrorGetter, JournalGetter, PerformantContextAccess, TransactionGetter, }, @@ -56,7 +54,7 @@ where self.inner.block_hash(requested_number) } - fn load_account_delegated(&mut self, address: Address) -> Option { + fn load_account_delegated(&mut self, address: Address) -> Option> { self.inner.load_account_delegated(address) } @@ -64,11 +62,11 @@ where self.inner.balance(address) } - fn code(&mut self, address: Address) -> Option> { + fn code(&mut self, address: Address) -> Option> { self.inner.code(address) } - fn code_hash(&mut self, address: Address) -> Option> { + fn code_hash(&mut self, address: Address) -> Option> { self.inner.code_hash(address) } diff --git a/crates/interpreter/src/gas/calc.rs b/crates/interpreter/src/gas/calc.rs index a65bd32170..d622625e94 100644 --- a/crates/interpreter/src/gas/calc.rs +++ b/crates/interpreter/src/gas/calc.rs @@ -1,6 +1,6 @@ use super::constants::*; use crate::{num_words, tri, SStoreResult, SelfDestructResult, StateLoad}; -use context_interface::journaled_state::{AccountLoad, Eip7702CodeLoad}; +use context_interface::journaled_state::AccountLoad; use handler_interface::InitialAndFloorGas; use primitives::U256; use specification::{eip7702, hardfork::SpecId}; @@ -113,13 +113,9 @@ pub const fn copy_cost_verylow(len: usize) -> Option { /// `EXTCODECOPY` opcode cost calculation. #[inline] -pub const fn extcodecopy_cost( - spec_id: SpecId, - len: usize, - load: Eip7702CodeLoad<()>, -) -> Option { +pub const fn extcodecopy_cost(spec_id: SpecId, len: usize, is_cold: bool) -> Option { let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) { - warm_cold_cost_with_delegation(load) + warm_cold_cost(is_cold) } else if spec_id.is_enabled_in(SpecId::TANGERINE) { 700 } else { @@ -283,10 +279,15 @@ pub const fn selfdestruct_cost(spec_id: SpecId, res: StateLoad u64 { +pub const fn call_cost( + spec_id: SpecId, + transfers_value: bool, + account_load: StateLoad, +) -> u64 { + let is_empty = account_load.data.is_empty; // Account access. let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) { - warm_cold_cost_with_delegation(account_load.load) + warm_cold_cost_with_delegation(account_load) } else if spec_id.is_enabled_in(SpecId::TANGERINE) { // EIP-150: Gas cost changes for IO-heavy operations 700 @@ -300,7 +301,7 @@ pub const fn call_cost(spec_id: SpecId, transfers_value: bool, account_load: Acc } // New account cost - if account_load.is_empty { + if is_empty { // EIP-161: State trie clearing (invariant-preserving alternative) if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) { // Account only if there is value transferred. @@ -329,9 +330,9 @@ pub const fn warm_cold_cost(is_cold: bool) -> u64 { /// /// If delegation is Some, add additional cost for delegation account load. #[inline] -pub const fn warm_cold_cost_with_delegation(load: Eip7702CodeLoad<()>) -> u64 { - let mut gas = warm_cold_cost(load.state_load.is_cold); - if let Some(is_cold) = load.is_delegate_account_cold { +pub const fn warm_cold_cost_with_delegation(load: StateLoad) -> u64 { + let mut gas = warm_cold_cost(load.is_cold); + if let Some(is_cold) = load.data.is_delegate_account_cold { gas += warm_cold_cost(is_cold); } gas diff --git a/crates/interpreter/src/instructions/contract/call_helpers.rs b/crates/interpreter/src/instructions/contract/call_helpers.rs index 760e37d9df..d881d79b2e 100644 --- a/crates/interpreter/src/instructions/contract/call_helpers.rs +++ b/crates/interpreter/src/instructions/contract/call_helpers.rs @@ -3,7 +3,7 @@ use crate::{ interpreter::Interpreter, interpreter_types::{InterpreterTypes, LoopControl, MemoryTrait, RuntimeFlag, StackTrait}, }; -use context_interface::journaled_state::AccountLoad; +use context_interface::{host::StateLoad, journaled_state::AccountLoad}; use core::{cmp::min, ops::Range}; use primitives::{Bytes, U256}; use specification::hardfork::SpecId::*; @@ -47,7 +47,7 @@ pub fn resize_memory( #[inline] pub fn calc_call_gas( interpreter: &mut Interpreter, - account_load: AccountLoad, + account_load: StateLoad, has_transfer: bool, local_gas_limit: u64, ) -> Option { diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index b3b8a69d3e..c3f94a33c6 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -1,6 +1,6 @@ use crate::{ - gas::{self, warm_cold_cost, warm_cold_cost_with_delegation, CALL_STIPEND}, - instructions::utility::IntoAddress, + gas::{self, warm_cold_cost, CALL_STIPEND}, + instructions::utility::{IntoAddress, IntoU256}, interpreter::Interpreter, interpreter_types::{ InputsTrait, InterpreterTypes, LoopControl, MemoryTrait, RuntimeFlag, StackTrait, @@ -68,10 +68,9 @@ pub fn extcodesize( .set_instruction_result(InstructionResult::FatalExternalError); return; }; - let (code, load) = code.into_components(); let spec_id = interpreter.runtime_flag.spec_id(); if spec_id.is_enabled_in(BERLIN) { - gas!(interpreter, warm_cold_cost_with_delegation(load)); + gas!(interpreter, warm_cold_cost(code.is_cold)); } else if spec_id.is_enabled_in(TANGERINE) { gas!(interpreter, 700); } else { @@ -95,16 +94,15 @@ pub fn extcodehash( .set_instruction_result(InstructionResult::FatalExternalError); return; }; - let (code_hash, load) = code_hash.into_components(); let spec_id = interpreter.runtime_flag.spec_id(); if spec_id.is_enabled_in(BERLIN) { - gas!(interpreter, warm_cold_cost_with_delegation(load)) + gas!(interpreter, warm_cold_cost(code_hash.is_cold)); } else if spec_id.is_enabled_in(ISTANBUL) { gas!(interpreter, 700); } else { gas!(interpreter, 400); } - *top = code_hash.into(); + *top = code_hash.into_u256(); } pub fn extcodecopy( @@ -121,10 +119,9 @@ pub fn extcodecopy( }; let len = as_usize_or_fail!(interpreter, len_u256); - let (code, load) = code.into_components(); gas_or_fail!( interpreter, - gas::extcodecopy_cost(interpreter.runtime_flag.spec_id(), len, load) + gas::extcodecopy_cost(interpreter.runtime_flag.spec_id(), len, code.is_cold) ); if len == 0 { return; diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index b20a8f9783..fcf7eb2ea2 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -22,7 +22,7 @@ rust_2018_idioms = "deny" all = "warn" [dependencies] -alloy-primitives = { version = "0.8.5", default-features = false, features = [ +alloy-primitives = { version = "0.8", default-features = false, features = [ "rlp", "map", ] } From 5e90cf078fdb60c3e8203f151313cd1608658caf Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 8 Jan 2025 03:54:37 +0100 Subject: [PATCH 2/2] fix eip7702 tests --- crates/handler/src/pre_execution.rs | 47 ++++++++++++------- crates/precompile/src/secp256k1.rs | 20 ++++---- crates/specification/src/eip7702.rs | 6 +-- .../statetest-types/src/test_authorization.rs | 47 +++++++++++++++++-- 4 files changed, 86 insertions(+), 34 deletions(-) diff --git a/crates/handler/src/pre_execution.rs b/crates/handler/src/pre_execution.rs index a8213028dc..857ddf3dfc 100644 --- a/crates/handler/src/pre_execution.rs +++ b/crates/handler/src/pre_execution.rs @@ -11,7 +11,7 @@ use context_interface::{ PerformantContextAccess, TransactionGetter, }; use handler_interface::PreExecutionHandler; -use primitives::{Address, BLOCKHASH_STORAGE_ADDRESS, U256}; +use primitives::{Address, BLOCKHASH_STORAGE_ADDRESS, KECCAK_EMPTY, U256}; use specification::{eip7702, hardfork::SpecId}; use std::{boxed::Box, vec::Vec}; @@ -145,45 +145,58 @@ pub fn apply_eip7702_auth_list< let mut refunded_accounts = 0; for authorization in authorization_list { - // 1. Recover authority and authorized addresses. - // authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s] - let Some(authority) = authorization.authority else { + // 1. Verify the chain id is either 0 or the chain's current ID. + let auth_chain_id = authorization.chain_id; + if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) { continue; - }; + } - // 2. Verify the chain id is either 0 or the chain's current ID. - if authorization.chain_id.is_zero() && authorization.chain_id != U256::from(chain_id) { + // 2. Verify the `nonce` is less than `2**64 - 1`. + if authorization.nonce == u64::MAX { continue; } - // Warm authority account and check nonce. - // 3. Add authority to accessed_addresses (as defined in EIP-2929.) + // recover authority and authorized addresses. + // 3. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]` + let Some(authority) = authorization.authority else { + continue; + }; + + // warm authority account and check nonce. + // 4. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).) let mut authority_acc = context.journal().load_account_code(authority)?; - // 4. Verify the code of authority is either empty or already delegated. + // 5. Verify the code of `authority` is either empty or already delegated. if let Some(bytecode) = &authority_acc.info.code { - // If it is not empty and it is not eip7702 + // if it is not empty and it is not eip7702 if !bytecode.is_empty() && !bytecode.is_eip7702() { continue; } } - // 5. Verify the nonce of authority is equal to nonce. + // 6. Verify the nonce of `authority` is equal to `nonce`. In case `authority` does not exist in the trie, verify that `nonce` is equal to `0`. if authorization.nonce != authority_acc.info.nonce { continue; } - // 6. Refund the sender PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST gas if authority exists in the trie. + // 7. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if `authority` exists in the trie. if !authority_acc.is_empty() { refunded_accounts += 1; } - // 7. Set the code of authority to be 0xef0100 || address. This is a delegation designation. - let bytecode = Bytecode::new_eip7702(authorization.address); - authority_acc.info.code_hash = bytecode.hash_slow(); + // 8. Set the code of `authority` to be `0xef0100 || address`. This is a delegation designation. + // * As a special case, if `address` is `0x0000000000000000000000000000000000000000` do not write the designation. Clear the accounts code and reset the account's code hash to the empty hash `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`. + let (bytecode, hash) = if authorization.address.is_zero() { + (Bytecode::default(), KECCAK_EMPTY) + } else { + let bytecode = Bytecode::new_eip7702(authorization.address); + let hash = bytecode.hash_slow(); + (bytecode, hash) + }; + authority_acc.info.code_hash = hash; authority_acc.info.code = Some(bytecode); - // 8. Increase the nonce of authority by one. + // 9. Increase the nonce of `authority` by one. authority_acc.info.nonce = authority_acc.info.nonce.saturating_add(1); authority_acc.mark_touch(); } diff --git a/crates/precompile/src/secp256k1.rs b/crates/precompile/src/secp256k1.rs index 6911027cf6..86cfa8f9fe 100644 --- a/crates/precompile/src/secp256k1.rs +++ b/crates/precompile/src/secp256k1.rs @@ -31,16 +31,18 @@ pub fn ec_recover_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let recid = input[63] - 27; let sig = <&B512>::try_from(&input[64..128]).unwrap(); - cfg_if::cfg_if! { - if #[cfg(feature = "secp256k1")] { - let res = bitcoin_secp256k1::ecrecover(sig, recid, msg); - } else if #[cfg(feature = "libsecp256k1")] { - let res = parity_libsecp256k1::ecrecover(sig, recid, msg); - } else { - let res = k256::ecrecover(sig, recid, msg); - } - }; + let res = ecrecover(sig, recid, msg); let out = res.map(|o| o.to_vec().into()).unwrap_or_default(); Ok(PrecompileOutput::new(ECRECOVER_BASE, out)) } + +cfg_if::cfg_if! { + if #[cfg(feature = "secp256k1")] { + pub use bitcoin_secp256k1::ecrecover; + } else if #[cfg(feature = "libsecp256k1")] { + pub use parity_libsecp256k1::ecrecover; + } else { + pub use k256::ecrecover; + } +} diff --git a/crates/specification/src/eip7702.rs b/crates/specification/src/eip7702.rs index a129f76732..248513fb3a 100644 --- a/crates/specification/src/eip7702.rs +++ b/crates/specification/src/eip7702.rs @@ -1,7 +1,7 @@ //! EIP-7702 constants -/// Base cost of updating authorized account -pub const PER_AUTH_BASE_COST: u64 = 2500; +/// Base cost of updating authorized account. +pub const PER_AUTH_BASE_COST: u64 = 12500; -/// Cost of creating authorized account that was previously empty +/// Cost of creating authorized account that was previously empty. pub const PER_EMPTY_ACCOUNT_COST: u64 = 25000; diff --git a/crates/statetest-types/src/test_authorization.rs b/crates/statetest-types/src/test_authorization.rs index 3399969964..327eb9a169 100644 --- a/crates/statetest-types/src/test_authorization.rs +++ b/crates/statetest-types/src/test_authorization.rs @@ -1,16 +1,20 @@ use revm::{ context_interface::transaction::AuthorizationItem, primitives::{Address, U256}, + specification::eip2::SECP256K1N_HALF, }; use serde::{Deserialize, Serialize}; /// Struct for test authorization -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct TestAuthorization { - chain_id: U256, - address: Address, - nonce: U256, + /// The chain ID of the authorization. + pub chain_id: U256, + /// The address of the authorization. + pub address: Address, + /// The nonce for the authorization. + pub nonce: U256, v: U256, r: U256, s: U256, @@ -19,11 +23,44 @@ pub struct TestAuthorization { impl From for AuthorizationItem { fn from(auth: TestAuthorization) -> AuthorizationItem { + let mut signer = auth.signer; + + if auth.s > SECP256K1N_HALF { + signer = None + } + + if auth.v > U256::from(1) { + signer = None + } + ( - auth.signer, + signer, auth.chain_id, auth.nonce.try_into().unwrap_or(u64::MAX), auth.address, ) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn recover_auth() { + // Test named: + // tests/prague/eip7702_set_code_tx/test_gas.py::test_account_warming[fork_Prague-state_test-single_valid_authorization_single_signer-check_delegated_account_first_True] + + let auth = r#"{ + "chainId": "0x00", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "nonce": "0x00", + "v": "0x01", + "r": "0x5a8cac98fd240d8ef83c22db4a061ffa0facb1801245283cc05fc809d8b92837", + "s": "0x1c3162fe11d91bc24d4fa00fb19ca34531e0eacdf8142c804be44058d5b8244f", + "signer": "0x6389e7f33ce3b1e94e4325ef02829cd12297ef71" + }"#; + + let _: TestAuthorization = serde_json::from_str(auth).unwrap(); + } +}