diff --git a/Cargo.lock b/Cargo.lock index 838a95ac93..148e67d1ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc7579e4fb5558af44810f542c90d1145dba8b92c08211c215196160c48d2ea" +checksum = "3f63a6c9eb45684a5468536bc55379a2af0f45ffa5d756e4e4964532737e1836" dependencies = [ "alloy-eips", "alloy-primitives", @@ -70,9 +70,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d6d8118b83b0489cfb7e6435106948add2b35217f4a5004ef895f613f60299" +checksum = "aa4b0fc6a572ef2eebda0a31a5e393d451abda703fec917c75d9615d8c978cf2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06d33b79246313c4103ef9596c721674a926f1ddc8b605aa2bac4d8ba94ee34" +checksum = "d484c2a934d0a4d86f8ad4db8113cb1d607707a6c54f6e78f4f1b4451b47aa70" dependencies = [ "alloy-primitives", "serde", @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef742b478a2db5c27063cde82128dfbecffcd38237d7f682a91d3ecf6aa1836c" +checksum = "7a20eba9bc551037f0626d6d29e191888638d979943fa4e842e9e6fc72bf0565" dependencies = [ "alloy-consensus", "alloy-eips", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5af289798fe8783acd0c5f10644d9d26f54a12bc52a083e4f3b31718e9bf92" +checksum = "ad5d89acb7339fad13bc69e7b925232f242835bfd91c82fcb9326b36481bd0f0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -202,9 +202,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328a6a14aba6152ddf6d01bac5e17a70dbe9d6f343bf402b995c30bac63a1fbf" +checksum = "479ce003e8c74bbbc7d4235131c1d6b7eaf14a533ae850295b90d240340989cb" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -223,9 +223,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bce0676f144be1eae71122d1d417885a3b063add0353b35e46cdf1440d6b33b1" +checksum = "13bd7aa9ff9e67f1ba7ee0dd8cebfc95831d1649b0e4eeefae940dc3681079fa" dependencies = [ "alloy-consensus", "alloy-eips", @@ -241,9 +241,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c224916316519558d8c2b6a60dc7626688c08f1b8951774702562dbcb8666ee" +checksum = "8913f9e825068d77c516188c221c44f78fd814fce8effe550a783295a2757d19" dependencies = [ "alloy-primitives", "arbitrary", @@ -255,9 +255,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227c5fd0ed6e06e1ccc30593f8ff6d9fb907ac5f03a709a6d687f0943494a229" +checksum = "f740e13eb4c6a0e4d0e49738f1e86f31ad2d7ef93be499539f492805000f7237" dependencies = [ "alloy-primitives", "async-trait", @@ -328,9 +328,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245af9541f0a0dbd5258669c80dfe3af118164cacec978a520041fc130550deb" +checksum = "dd9773e4ec6832346171605c776315544bd06e40f803e7b5b7824b325d5442ca" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f35d34e7a51503c9ff267404a5850bd58f991b7ab524b892f364901e3576376" +checksum = "ff8ef947b901c0d4e97370f9fa25844cf8b63b1a58fd4011ee82342dc8a9fc6b" dependencies = [ "alloy-json-rpc", "alloy-transport", diff --git a/Cargo.toml b/Cargo.toml index 426d6e403f..ad5a6ccdc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,8 @@ debug = true [profile.ethtests] inherits = "test" opt-level = 3 + +# [patch.crates-io] +# alloy-eips = { git = "https://github.com/alloy-rs/alloy.git", rev = "41d4c7c" } +# alloy-provider = { git = "https://github.com/alloy-rs/alloy.git", rev = "41d4c7c" } +# alloy-transport = { git = "https://github.com/alloy-rs/alloy.git", rev = "41d4c7c" } \ No newline at end of file diff --git a/crates/interpreter/src/gas/calc.rs b/crates/interpreter/src/gas/calc.rs index 6befc58f06..4d8aba4dd8 100644 --- a/crates/interpreter/src/gas/calc.rs +++ b/crates/interpreter/src/gas/calc.rs @@ -1,9 +1,7 @@ -use revm_primitives::AccessListItem; - use super::constants::*; use crate::{ num_words, - primitives::{SpecId, U256}, + primitives::{AccessListItem, SpecId, U256}, SelfDestructResult, }; @@ -359,6 +357,7 @@ pub fn validate_initial_tx_gas( input: &[u8], is_create: bool, access_list: &[AccessListItem], + authorization_list_num: u64, ) -> u64 { let mut initial_gas = 0; let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64; @@ -399,5 +398,10 @@ pub fn validate_initial_tx_gas( initial_gas += initcode_cost(input.len() as u64) } + // EIP-7702 + if spec_id.is_enabled_in(SpecId::PRAGUE) { + initial_gas += authorization_list_num * PER_CONTRACT_CODE_BASE_COST; + } + initial_gas } diff --git a/crates/interpreter/src/gas/constants.rs b/crates/interpreter/src/gas/constants.rs index 7d7956a4e5..c32abcfae7 100644 --- a/crates/interpreter/src/gas/constants.rs +++ b/crates/interpreter/src/gas/constants.rs @@ -47,6 +47,9 @@ pub const COLD_ACCOUNT_ACCESS_COST: u64 = 2600; pub const WARM_STORAGE_READ_COST: u64 = 100; pub const WARM_SSTORE_RESET: u64 = SSTORE_RESET - COLD_SLOAD_COST; +/// EIP-7702 +pub const PER_CONTRACT_CODE_BASE_COST: u64 = 2400; + /// EIP-3860 : Limit and meter initcode pub const INITCODE_WORD_COST: u64 = 2; diff --git a/crates/interpreter/src/instruction_result.rs b/crates/interpreter/src/instruction_result.rs index 8973908af5..0f12903a09 100644 --- a/crates/interpreter/src/instruction_result.rs +++ b/crates/interpreter/src/instruction_result.rs @@ -61,7 +61,7 @@ pub enum InstructionResult { EOFFunctionStackOverflow, /// Aux data overflow, new aux data is larger tha u16 max size. EofAuxDataOverflow, - /// Aud data is smaller then already present data size. + /// Aux data is smaller then already present data size. EofAuxDataTooSmall, /// EXT*CALL target address needs to be padded with 0s. InvalidEXTCALLTarget, diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index a6a0225ac1..5f5748011c 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -22,7 +22,7 @@ rust_2018_idioms = "deny" all = "warn" [dependencies] -alloy-eips = { version = "0.1", default-features = false } +alloy-eips = { version = "0.1", default-features = false, features = ["k256"]} alloy-primitives = { version = "0.7.2", default-features = false, features = [ "rlp", ] } @@ -104,4 +104,4 @@ optional_beneficiary_reward = [] rand = ["alloy-primitives/rand"] # See comments in `revm-precompile` -c-kzg = ["dep:c-kzg", "dep:once_cell", "dep:derive_more"] +c-kzg = ["dep:c-kzg", "dep:once_cell", "dep:derive_more"] \ No newline at end of file diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 162aa0c130..1ec9c717f8 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -1,6 +1,7 @@ +pub mod eip7702; pub mod handler_cfg; -use alloy_primitives::TxKind; +pub use eip7702::AuthorizationList; pub use handler_cfg::{CfgEnvWithHandlerCfg, EnvWithHandlerCfg, HandlerCfg}; use crate::{ @@ -8,6 +9,7 @@ use crate::{ Spec, SpecId, B256, GAS_PER_BLOB, KECCAK_EMPTY, MAX_BLOB_NUMBER_PER_BLOCK, MAX_INITCODE_SIZE, U256, VERSIONED_HASH_VERSION_KZG, }; +use alloy_primitives::TxKind; use core::cmp::{min, Ordering}; use core::hash::Hash; use std::boxed::Box; @@ -92,6 +94,25 @@ impl Env { /// Return initial spend gas (Gas needed to execute transaction). #[inline] pub fn validate_tx(&self) -> Result<(), InvalidTransaction> { + // Check if the transaction's chain id is correct + if let Some(tx_chain_id) = self.tx.chain_id { + if tx_chain_id != self.cfg.chain_id { + return Err(InvalidTransaction::InvalidChainId); + } + } + + // Check if gas_limit is more than block_gas_limit + if !self.cfg.is_block_gas_limit_disabled() + && U256::from(self.tx.gas_limit) > self.block.gas_limit + { + return Err(InvalidTransaction::CallerGasLimitMoreThanBlock); + } + + // Check that access list is empty for transactions before BERLIN + if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() { + return Err(InvalidTransaction::AccessListNotSupported); + } + // BASEFEE tx check if SPEC::enabled(SpecId::LONDON) { if let Some(priority_fee) = self.tx.gas_priority_fee { @@ -109,13 +130,6 @@ impl Env { } } - // Check if gas_limit is more than block_gas_limit - if !self.cfg.is_block_gas_limit_disabled() - && U256::from(self.tx.gas_limit) > self.block.gas_limit - { - return Err(InvalidTransaction::CallerGasLimitMoreThanBlock); - } - // EIP-3860: Limit and meter initcode if SPEC::enabled(SpecId::SHANGHAI) && self.tx.transact_to.is_create() { let max_initcode_size = self @@ -128,65 +142,66 @@ impl Env { } } - // Check if the transaction's chain id is correct - if let Some(tx_chain_id) = self.tx.chain_id { - if tx_chain_id != self.cfg.chain_id { - return Err(InvalidTransaction::InvalidChainId); - } - } - - // Check that access list is empty for transactions before BERLIN - if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() { - return Err(InvalidTransaction::AccessListNotSupported); + // - For before CANCUN, check that `blob_hashes` and `max_fee_per_blob_gas` are empty / not set + if !SPEC::enabled(SpecId::CANCUN) + && (self.tx.max_fee_per_blob_gas.is_some() || !self.tx.blob_hashes.is_empty()) + { + return Err(InvalidTransaction::BlobVersionedHashesNotSupported); } - // - For CANCUN and later, check that the gas price is not more than the tx max - // - For before CANCUN, check that `blob_hashes` and `max_fee_per_blob_gas` are empty / not set - if SPEC::enabled(SpecId::CANCUN) { - // Presence of max_fee_per_blob_gas means that this is blob transaction. - if let Some(max) = self.tx.max_fee_per_blob_gas { - // ensure that the user was willing to at least pay the current blob gasprice - let price = self.block.get_blob_gasprice().expect("already checked"); - if U256::from(price) > max { - return Err(InvalidTransaction::BlobGasPriceGreaterThanMax); - } + // Presence of max_fee_per_blob_gas means that this is blob transaction. + if let Some(max) = self.tx.max_fee_per_blob_gas { + // ensure that the user was willing to at least pay the current blob gasprice + let price = self.block.get_blob_gasprice().expect("already checked"); + if U256::from(price) > max { + return Err(InvalidTransaction::BlobGasPriceGreaterThanMax); + } - // there must be at least one blob - if self.tx.blob_hashes.is_empty() { - return Err(InvalidTransaction::EmptyBlobs); - } + // there must be at least one blob + if self.tx.blob_hashes.is_empty() { + return Err(InvalidTransaction::EmptyBlobs); + } - // The field `to` deviates slightly from the semantics with the exception - // that it MUST NOT be nil and therefore must always represent - // a 20-byte address. This means that blob transactions cannot - // have the form of a create transaction. - if self.tx.transact_to.is_create() { - return Err(InvalidTransaction::BlobCreateTransaction); - } + // The field `to` deviates slightly from the semantics with the exception + // that it MUST NOT be nil and therefore must always represent + // a 20-byte address. This means that blob transactions cannot + // have the form of a create transaction. + if self.tx.transact_to.is_create() { + return Err(InvalidTransaction::BlobCreateTransaction); + } - // all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG - for blob in self.tx.blob_hashes.iter() { - if blob[0] != VERSIONED_HASH_VERSION_KZG { - return Err(InvalidTransaction::BlobVersionNotSupported); - } + // all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG + for blob in self.tx.blob_hashes.iter() { + if blob[0] != VERSIONED_HASH_VERSION_KZG { + return Err(InvalidTransaction::BlobVersionNotSupported); } + } - // ensure the total blob gas spent is at most equal to the limit - // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK - let num_blobs = self.tx.blob_hashes.len(); - if num_blobs > MAX_BLOB_NUMBER_PER_BLOCK as usize { - return Err(InvalidTransaction::TooManyBlobs { - have: num_blobs, - max: MAX_BLOB_NUMBER_PER_BLOCK as usize, - }); - } + // ensure the total blob gas spent is at most equal to the limit + // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK + let num_blobs = self.tx.blob_hashes.len(); + if num_blobs > MAX_BLOB_NUMBER_PER_BLOCK as usize { + return Err(InvalidTransaction::TooManyBlobs { + have: num_blobs, + max: MAX_BLOB_NUMBER_PER_BLOCK as usize, + }); } } else { + // if max_fee_per_blob_gas is not set, then blob_hashes must be empty if !self.tx.blob_hashes.is_empty() { return Err(InvalidTransaction::BlobVersionedHashesNotSupported); } - if self.tx.max_fee_per_blob_gas.is_some() { - return Err(InvalidTransaction::MaxFeePerBlobGasNotSupported); + } + + // check if EIP-7702 transaction is enabled. + if !SPEC::enabled(SpecId::PRAGUE) && self.tx.authorization_list.is_some() { + return Err(InvalidTransaction::AuthorizationListNotSupported); + } + + if self.tx.authorization_list.is_some() { + // Check if other fields are unset. + if self.tx.max_fee_per_blob_gas.is_some() || !self.tx.blob_hashes.is_empty() { + return Err(InvalidTransaction::AuthorizationListInvalidFields); } } @@ -509,6 +524,7 @@ pub struct TxEnv { pub value: U256, /// The data of the transaction. pub data: Bytes, + /// The nonce of the transaction. /// /// Caution: If set to `None`, then nonce validation against the account's nonce is skipped: [InvalidTransaction::NonceTooHigh] and [InvalidTransaction::NonceTooLow] @@ -550,6 +566,14 @@ pub struct TxEnv { /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 pub max_fee_per_blob_gas: Option, + /// List of authorizations, that contains the signature that authorizes this + /// caller to place the code to signer account. + /// + /// Set EOA account code for one transaction + /// + /// [EIP-Set EOA account code for one transaction](https://eips.ethereum.org/EIPS/eip-7702) + pub authorization_list: Option, + #[cfg_attr(feature = "serde", serde(flatten))] #[cfg(feature = "optimism")] /// Optimism fields. @@ -594,6 +618,7 @@ impl Default for TxEnv { access_list: Vec::new(), blob_hashes: Vec::new(), max_fee_per_blob_gas: None, + authorization_list: None, #[cfg(feature = "optimism")] optimism: OptimismFields::default(), } diff --git a/crates/primitives/src/env/eip7702.rs b/crates/primitives/src/env/eip7702.rs new file mode 100644 index 0000000000..96a685d5be --- /dev/null +++ b/crates/primitives/src/env/eip7702.rs @@ -0,0 +1,36 @@ +use alloy_eips::eip7702::{RecoveredAuthorization, SignedAuthorization}; +use alloy_primitives::Signature; +use std::{boxed::Box, vec::Vec}; + +/// Authorization list for EIP-7702 transaction type. +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum AuthorizationList { + Signed(Vec>), + Recovered(Vec), +} + +impl AuthorizationList { + /// Returns length of the authorization list. + pub fn len(&self) -> usize { + match self { + Self::Signed(signed) => signed.len(), + Self::Recovered(recovered) => recovered.len(), + } + } + + /// Returns true if the authorization list is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns iterator of recovered Authorizations. + pub fn recovered_iter<'a>(&'a self) -> Box + 'a> { + match self { + Self::Signed(signed) => { + Box::new(signed.iter().map(|signed| signed.clone().into_recovered())) + } + Self::Recovered(recovered) => Box::new(recovered.clone().into_iter()), + } + } +} diff --git a/crates/primitives/src/result.rs b/crates/primitives/src/result.rs index c7a4469e71..4d4886d6c1 100644 --- a/crates/primitives/src/result.rs +++ b/crates/primitives/src/result.rs @@ -268,6 +268,10 @@ pub enum InvalidTransaction { BlobVersionNotSupported, /// EOF crate should have `to` address EofCrateShouldHaveToAddress, + /// EIP-7702 is not enabled. + AuthorizationListNotSupported, + /// EIP-7702 transaction has invalid fields set. + AuthorizationListInvalidFields, /// System transactions are not supported post-regolith hardfork. /// /// Before the Regolith hardfork, there was a special field in the `Deposit` transaction @@ -359,6 +363,10 @@ impl fmt::Display for InvalidTransaction { } Self::BlobVersionNotSupported => write!(f, "blob version not supported"), Self::EofCrateShouldHaveToAddress => write!(f, "EOF crate should have `to` address"), + Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"), + Self::AuthorizationListInvalidFields => { + write!(f, "authorization list tx has invalid fields") + } #[cfg(feature = "optimism")] Self::DepositSystemTxPostRegolith => { write!( diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index 0da1234caa..d7b1e556fc 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -292,6 +292,7 @@ pub(crate) mod test_utils { journaled_state: JournaledState::new(SpecId::CANCUN, HashSet::new()), db, error: Ok(()), + valid_authorizations: Vec::new(), #[cfg(feature = "optimism")] l1_block_info: None, }, @@ -307,6 +308,7 @@ pub(crate) mod test_utils { journaled_state: JournaledState::new(SpecId::CANCUN, HashSet::new()), db, error: Ok(()), + valid_authorizations: Default::default(), #[cfg(feature = "optimism")] l1_block_info: None, }, diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index 8475d1dea4..15c59824cb 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -15,7 +15,7 @@ use crate::{ }, FrameOrResult, JournalCheckpoint, CALL_STACK_LIMIT, }; -use std::{boxed::Box, sync::Arc}; +use std::{boxed::Box, sync::Arc, vec::Vec}; /// EVM contexts contains data that EVM needs for execution. #[derive(Debug)] @@ -29,6 +29,8 @@ pub struct InnerEvmContext { pub db: DB, /// Error that happened during execution. pub error: Result<(), EVMError>, + /// EIP-7702 Authorization list of accounts that needs to be cleared. + pub valid_authorizations: Vec
, /// Used as temporary value holder to store L1 block info. #[cfg(feature = "optimism")] pub l1_block_info: Option, @@ -44,6 +46,7 @@ where journaled_state: self.journaled_state.clone(), db: self.db.clone(), error: self.error.clone(), + valid_authorizations: self.valid_authorizations.clone(), #[cfg(feature = "optimism")] l1_block_info: self.l1_block_info.clone(), } @@ -57,6 +60,7 @@ impl InnerEvmContext { journaled_state: JournaledState::new(SpecId::LATEST, HashSet::new()), db, error: Ok(()), + valid_authorizations: Default::default(), #[cfg(feature = "optimism")] l1_block_info: None, } @@ -70,6 +74,7 @@ impl InnerEvmContext { journaled_state: JournaledState::new(SpecId::LATEST, HashSet::new()), db, error: Ok(()), + valid_authorizations: Default::default(), #[cfg(feature = "optimism")] l1_block_info: None, } @@ -85,6 +90,7 @@ impl InnerEvmContext { journaled_state: self.journaled_state, db, error: Ok(()), + valid_authorizations: Default::default(), #[cfg(feature = "optimism")] l1_block_info: self.l1_block_info, } diff --git a/crates/revm/src/handler/mainnet/post_execution.rs b/crates/revm/src/handler/mainnet/post_execution.rs index 0e5cb4e187..9e02234b95 100644 --- a/crates/revm/src/handler/mainnet/post_execution.rs +++ b/crates/revm/src/handler/mainnet/post_execution.rs @@ -1,7 +1,8 @@ use crate::{ interpreter::{Gas, SuccessOrHalt}, primitives::{ - db::Database, EVMError, ExecutionResult, ResultAndState, Spec, SpecId::LONDON, U256, + db::Database, EVMError, ExecutionResult, ResultAndState, Spec, SpecId::LONDON, + KECCAK_EMPTY, U256, }, Context, FrameResult, }; @@ -21,6 +22,9 @@ pub fn clear(context: &mut Context) { // clear error and journaled state. let _ = context.evm.take_error(); context.evm.inner.journaled_state.clear(); + // Clear valid authorizations after each transaction. + // If transaction is valid they are consumed in `output` handler. + context.evm.inner.valid_authorizations.clear(); } /// Reward beneficiary with gas fee. @@ -92,7 +96,17 @@ pub fn output( let instruction_result = result.into_interpreter_result(); // reset journal and return present state. - let (state, logs) = context.evm.journaled_state.finalize(); + let (mut state, logs) = context.evm.journaled_state.finalize(); + + // clear code of authorized accounts. + for authorized in core::mem::take(&mut context.evm.inner.valid_authorizations).into_iter() { + let account = state + .get_mut(&authorized) + .expect("Authorized account must exist"); + account.info.code = None; + account.info.code_hash = KECCAK_EMPTY; + account.storage.clear(); + } let result = match instruction_result.result.into() { SuccessOrHalt::Success(reason) => ExecutionResult::Success { diff --git a/crates/revm/src/handler/mainnet/pre_execution.rs b/crates/revm/src/handler/mainnet/pre_execution.rs index adf0e3bd4f..75bb2d454f 100644 --- a/crates/revm/src/handler/mainnet/pre_execution.rs +++ b/crates/revm/src/handler/mainnet/pre_execution.rs @@ -8,10 +8,11 @@ use crate::{ db::Database, Account, EVMError, Env, Spec, SpecId::{CANCUN, PRAGUE, SHANGHAI}, - TxKind, BLOCKHASH_STORAGE_ADDRESS, U256, + TxKind, BLOCKHASH_STORAGE_ADDRESS, KECCAK_EMPTY, U256, }, Context, ContextPrecompiles, }; +use std::vec::Vec; /// Main precompile load #[inline] @@ -47,6 +48,74 @@ pub fn load_accounts( )?; } + // EIP-7702. Load bytecode to authorized accounts. + if SPEC::enabled(PRAGUE) { + if let Some(authorization_list) = context.evm.inner.env.tx.authorization_list.as_ref() { + let mut valid_auths = Vec::with_capacity(authorization_list.len()); + for authorization in authorization_list.recovered_iter() { + // 1. recover authority and authorized addresses. + let Some(authority) = authorization.authority() else { + continue; + }; + + // 2. Verify the chain id is either 0 or the chain's current ID. + if authorization.chain_id() != 0 + && authorization.chain_id() != context.evm.inner.env.cfg.chain_id + { + continue; + } + + // warm authority account and check nonce. + let (authority_acc, _) = context + .evm + .inner + .journaled_state + .load_account(authority, &mut context.evm.inner.db)?; + + // 3. Verify that the code of authority is empty. + // In case of multiple same authorities this step will skip loading of + // authorized account. + if authority_acc.info.code_hash() != KECCAK_EMPTY { + continue; + } + + // 4. If nonce list item is length one, verify the nonce of authority is equal to nonce. + if let Some(nonce) = authorization.nonce() { + if nonce != authority_acc.info.nonce { + continue; + } + } + + // warm code account and get the code. + // 6. Add the authority account to accessed_addresses + let (account, _) = context + .evm + .inner + .journaled_state + .load_code(authority, &mut context.evm.inner.db)?; + let code = account.info.code.clone(); + let code_hash = account.info.code_hash; + + // If code is empty no need to set code or add it to valid + // authorizations, as it is a noop operation. + if code_hash == KECCAK_EMPTY { + continue; + } + + // 5. Set the code of authority to code associated with address. + context.evm.inner.journaled_state.set_code_with_hash( + authority, + code.unwrap_or_default(), + code_hash, + ); + + valid_auths.push(authority); + } + + context.evm.inner.valid_authorizations = valid_auths; + } + } + context.evm.load_access_list()?; Ok(()) } diff --git a/crates/revm/src/handler/mainnet/validation.rs b/crates/revm/src/handler/mainnet/validation.rs index 176e0e8282..f8840de42c 100644 --- a/crates/revm/src/handler/mainnet/validation.rs +++ b/crates/revm/src/handler/mainnet/validation.rs @@ -42,9 +42,20 @@ pub fn validate_initial_tx_gas( let input = &env.tx.data; let is_create = env.tx.transact_to.is_create(); let access_list = &env.tx.access_list; + let authorization_list_num = env + .tx + .authorization_list + .as_ref() + .map(|l| l.len() as u64) + .unwrap_or_default(); - let initial_gas_spend = - gas::validate_initial_tx_gas(SPEC::SPEC_ID, input, is_create, access_list); + let initial_gas_spend = gas::validate_initial_tx_gas( + SPEC::SPEC_ID, + input, + is_create, + access_list, + authorization_list_num, + ); // Additional check to see if limit is big enough to cover initial gas. if initial_gas_spend > env.tx.gas_limit { diff --git a/crates/revm/src/journaled_state.rs b/crates/revm/src/journaled_state.rs index 3867ef43e8..6ab8c9859c 100644 --- a/crates/revm/src/journaled_state.rs +++ b/crates/revm/src/journaled_state.rs @@ -2,8 +2,8 @@ use crate::{ interpreter::{InstructionResult, LoadAccountResult, SStoreResult, SelfDestructResult}, primitives::{ db::Database, hash_map::Entry, Account, Address, Bytecode, EVMError, EvmState, - EvmStorageSlot, HashMap, HashSet, Log, SpecId, SpecId::*, TransientStorage, KECCAK_EMPTY, - PRECOMPILE3, U256, + EvmStorageSlot, HashMap, HashSet, Log, SpecId, SpecId::*, TransientStorage, B256, + KECCAK_EMPTY, PRECOMPILE3, U256, }, }; use core::mem; @@ -143,10 +143,11 @@ impl JournaledState { self.depth as u64 } - /// use it only if you know that acc is warm - /// Assume account is warm + /// Set code and its hash to the account. + /// + /// Note: Assume account is warm and that hash is calculated from code. #[inline] - pub fn set_code(&mut self, address: Address, code: Bytecode) { + pub fn set_code_with_hash(&mut self, address: Address, code: Bytecode, hash: B256) { let account = self.state.get_mut(&address).unwrap(); Self::touch_account(self.journal.last_mut().unwrap(), &address, account); @@ -155,10 +156,18 @@ impl JournaledState { .unwrap() .push(JournalEntry::CodeChange { address }); - account.info.code_hash = code.hash_slow(); + account.info.code_hash = hash; account.info.code = Some(code); } + /// use it only if you know that acc is warm + /// Assume account is warm + #[inline] + pub fn set_code(&mut self, address: Address, code: Bytecode) { + let hash = code.hash_slow(); + self.set_code_with_hash(address, code, hash) + } + #[inline] pub fn inc_nonce(&mut self, address: Address) -> Option { let account = self.state.get_mut(&address).unwrap();