Skip to content
17 changes: 16 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 @@ -66,6 +66,7 @@ use ethrex_common::types::EIP4844Transaction;
use ethrex_common::types::block_access_list::BlockAccessList;
use ethrex_common::types::block_execution_witness::ExecutionWitness;
use ethrex_common::types::fee_config::FeeConfig;
use ethrex_common::types::is_eip7702_delegation;
use ethrex_common::types::{
AccountInfo, AccountState, AccountUpdate, Block, BlockHash, BlockHeader, BlockNumber,
ChainConfig, Code, Receipt, Transaction, WrappedEIP4844Transaction, validate_block_body,
Expand Down Expand Up @@ -2491,6 +2492,20 @@ 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).
if sender_acc_info.code_hash != *EMPTY_KECCACK_HASH {
let is_delegation = match self.storage.get_account_code(sender_acc_info.code_hash) {
Comment thread
MegaRedHand marked this conversation as resolved.
Outdated
Ok(Some(code)) => is_eip7702_delegation(code.bytecode.as_ref()),
Ok(None) => false,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Err(e) => return Err(e.into()),
};
if !is_delegation {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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));
}
}
Loading