diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index 8f3f301d8f..382faeec90 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -54,7 +54,7 @@ use ::tracing::{debug, error, info, instrument, warn}; use constants::{AMSTERDAM_MAX_INITCODE_SIZE, MAX_INITCODE_SIZE, POST_OSAKA_GAS_LIMIT_CAP}; use error::MempoolError; use error::{ChainError, InvalidBlockError}; -use ethrex_common::constants::{EMPTY_TRIE_HASH, MIN_BASE_FEE_PER_BLOB_GAS}; +use ethrex_common::constants::{EMPTY_KECCACK_HASH, EMPTY_TRIE_HASH, MIN_BASE_FEE_PER_BLOB_GAS}; use crossbeam::channel::{self as cb, TryRecvError, select}; // Re-export stateless validation functions for backwards compatibility @@ -67,6 +67,7 @@ use ethrex_common::types::{ AccountInfo, AccountState, AccountUpdate, Block, BlockHash, BlockHeader, BlockNumber, ChainConfig, Code, Receipt, Transaction, WrappedEIP4844Transaction, validate_block_body, }; +use ethrex_common::types::{EIP7702_DELEGATED_CODE_LEN, is_eip7702_delegation}; use ethrex_common::types::{ELASTICITY_MULTIPLIER, P2PTransaction}; use ethrex_common::types::{Fork, MempoolTransaction}; use ethrex_common::types::{MAX_BLOB_TX_SIZE, MAX_TX_SIZE}; @@ -2525,6 +2526,45 @@ impl Blockchain { return Err(MempoolError::NonceTooLow); } + // EIP-3607: reject txs from senders with deployed code, unless + // the code is an EIP-7702 delegation designation (the account is + // still an EOA in spirit, just pointing at delegate code). + // + // Length-based fast path: any code whose length isn't exactly + // `EIP7702_DELEGATED_CODE_LEN` (23) cannot be a delegation, so + // we reject without loading the bytecode. Only when the metadata + // length matches do we fetch + verify the prefix. This avoids + // pulling potentially large contract bytecode on every contract + // sender that hits admission. + if sender_acc_info.code_hash != *EMPTY_KECCACK_HASH { + let metadata_len = self + .storage + .get_code_metadata(sender_acc_info.code_hash)? + .map(|m| m.length); + let is_delegation = if metadata_len == Some(EIP7702_DELEGATED_CODE_LEN as u64) { + // Metadata says the code is delegation-shaped; if the + // bytecode is then missing from the store, the DB is + // inconsistent — surface that as `StoreError` instead of + // silently treating the sender as a contract (which would + // wrongly reject a valid 7702-delegated EOA). + let code = self + .storage + .get_account_code(sender_acc_info.code_hash)? + .ok_or_else(|| { + StoreError::Custom(format!( + "code missing for hash {:?} despite present metadata", + sender_acc_info.code_hash + )) + })?; + is_eip7702_delegation(code.bytecode.as_ref()) + } else { + false + }; + if !is_delegation { + return Err(MempoolError::SenderIsContract); + } + } + let tx_cost = tx .cost_without_base_fee() .ok_or(MempoolError::InvalidTxGasvalues)?; diff --git a/crates/blockchain/error.rs b/crates/blockchain/error.rs index b37336d291..b548a96297 100644 --- a/crates/blockchain/error.rs +++ b/crates/blockchain/error.rs @@ -81,6 +81,10 @@ pub enum MempoolError { BlobsBundleError(#[from] BlobsBundleError), #[error("Transaction max init code size exceeded")] TxMaxInitCodeSizeError, + #[error("Transaction max data size exceeded")] + TxMaxDataSizeError, + #[error("Transaction sender is a contract account (EIP-3607)")] + SenderIsContract, #[error("Transaction encoded size ({actual} bytes) exceeds the {limit}-byte limit")] TxSizeExceeded { actual: usize, limit: usize }, #[error("Transaction gas limit exceeded")] diff --git a/crates/common/types/account.rs b/crates/common/types/account.rs index 589ac4f41c..06f1b444b5 100644 --- a/crates/common/types/account.rs +++ b/crates/common/types/account.rs @@ -191,6 +191,19 @@ pub fn code_hash(code: &Bytes, crypto: &dyn Crypto) -> H256 { H256(crypto.keccak256(code.as_ref())) } +/// EIP-7702 delegation designation: an EOA whose code is `0xef0100 || address`. +/// See . +pub const EIP7702_DELEGATION_PREFIX: [u8; 3] = [0xef, 0x01, 0x00]; +/// Total byte length of an EIP-7702 delegation designation: 3-byte prefix +/// plus the 20-byte target address. +pub const EIP7702_DELEGATED_CODE_LEN: usize = 23; + +/// Returns true iff `code` is a valid EIP-7702 delegation designation +/// (exactly 23 bytes, prefixed with `0xef0100`). +pub fn is_eip7702_delegation(code: &[u8]) -> bool { + code.len() == EIP7702_DELEGATED_CODE_LEN && code.starts_with(&EIP7702_DELEGATION_PREFIX) +} + impl RLPEncode for AccountInfo { fn encode(&self, buf: &mut dyn bytes::BufMut) { Encoder::new(buf) @@ -396,4 +409,49 @@ mod test { .unwrap() ) } + + #[test] + fn test_is_eip7702_delegation_valid() { + // 0xef0100 || 20-byte address + let mut code = Vec::with_capacity(23); + code.extend_from_slice(&EIP7702_DELEGATION_PREFIX); + code.extend_from_slice(&[0x42; 20]); + assert!(is_eip7702_delegation(&code)); + } + + #[test] + fn test_is_eip7702_delegation_rejects_empty() { + assert!(!is_eip7702_delegation(&[])); + } + + #[test] + fn test_is_eip7702_delegation_rejects_short() { + // Prefix only, no address. + assert!(!is_eip7702_delegation(&EIP7702_DELEGATION_PREFIX)); + } + + #[test] + fn test_is_eip7702_delegation_rejects_long() { + // Correct prefix but 24 bytes total. + let mut code = Vec::with_capacity(24); + code.extend_from_slice(&EIP7702_DELEGATION_PREFIX); + code.extend_from_slice(&[0x42; 21]); + assert!(!is_eip7702_delegation(&code)); + } + + #[test] + fn test_is_eip7702_delegation_rejects_wrong_prefix() { + // Right length, wrong magic. + let mut code = Vec::with_capacity(23); + code.extend_from_slice(&[0xef, 0x01, 0x01]); // off by one in the last prefix byte + code.extend_from_slice(&[0x42; 20]); + assert!(!is_eip7702_delegation(&code)); + } + + #[test] + fn test_is_eip7702_delegation_rejects_arbitrary_contract_code() { + // Real contract code starting with anything else. + let code = vec![0x60, 0x60, 0x60, 0x40, 0x52 /* ... */]; + assert!(!is_eip7702_delegation(&code)); + } } diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index 37e79b96a0..11716a3c89 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -179,11 +179,10 @@ pub fn word_to_address(word: U256) -> Address { // ================== EIP-7702 related functions ===================== pub fn code_has_delegation(code: &Bytes) -> Result { - if code.len() == EIP7702_DELEGATED_CODE_LEN { - let first_3_bytes = &code.get(..3).ok_or(InternalError::Slicing)?; - return Ok(*first_3_bytes == SET_CODE_DELEGATION_BYTES); - } - Ok(false) + // Delegate to the canonical predicate in `ethrex-common` so the + // EIP-7702 designation check (`0xef0100 || address`, exactly 23 bytes) + // has a single source of truth. Result kept for caller compatibility. + Ok(ethrex_common::types::is_eip7702_delegation(code.as_ref())) } /// Gets the address inside the bytecode if it has been