Skip to content
43 changes: 42 additions & 1 deletion crates/blockchain/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ use constants::{
};
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
Expand All @@ -70,6 +70,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::utils::keccak;
Expand Down Expand Up @@ -2491,6 +2492,46 @@ 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 (Copilot / @MegaRedHand review).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// sender that hits admission (Copilot / @MegaRedHand review).
// 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 per
// Copilot/@codex review).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// wrongly reject a valid 7702-delegated EOA per
// Copilot/@codex review).
// 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)?;
Expand Down
2 changes: 2 additions & 0 deletions crates/blockchain/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ pub enum MempoolError {
TxMaxInitCodeSizeError,
#[error("Transaction max data size exceeded")]
TxMaxDataSizeError,
#[error("Transaction sender is a contract account (EIP-3607)")]
SenderIsContract,
#[error("Transaction gas limit exceeded")]
TxGasLimitExceededError,
#[error(
Expand Down
58 changes: 58 additions & 0 deletions crates/common/types/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://eips.ethereum.org/EIPS/eip-7702>.
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)
}
Comment thread
MegaRedHand marked this conversation as resolved.

impl RLPEncode for AccountInfo {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
Encoder::new(buf)
Expand Down Expand Up @@ -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));
}
}
9 changes: 4 additions & 5 deletions crates/vm/levm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,10 @@ pub fn word_to_address(word: U256) -> Address {
// ================== EIP-7702 related functions =====================

pub fn code_has_delegation(code: &Bytes) -> Result<bool, VMError> {
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
Expand Down
Loading