Skip to content

Commit e436daa

Browse files
klkvrmattsse
andauthored
feat: support for EIP-7702 in Anvil (#8476)
feat: EIP-7702 support in Anvil Co-authored-by: Matthias Seitz <[email protected]>
1 parent 94b6c6b commit e436daa

File tree

8 files changed

+248
-46
lines changed

8 files changed

+248
-46
lines changed

crates/anvil/core/src/eth/transaction/mod.rs

Lines changed: 138 additions & 45 deletions
Large diffs are not rendered by default.

crates/anvil/src/eth/api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,6 +2708,7 @@ impl EthApi {
27082708
TypedTransaction::EIP2930(_) => self.backend.ensure_eip2930_active(),
27092709
TypedTransaction::EIP1559(_) => self.backend.ensure_eip1559_active(),
27102710
TypedTransaction::EIP4844(_) => self.backend.ensure_eip4844_active(),
2711+
TypedTransaction::EIP7702(_) => self.backend.ensure_eip7702_active(),
27112712
TypedTransaction::Deposit(_) => self.backend.ensure_op_deposits_active(),
27122713
TypedTransaction::Legacy(_) => Ok(()),
27132714
}

crates/anvil/src/eth/backend/executor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ impl ExecutedTransaction {
6666
TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom),
6767
TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom),
6868
TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom),
69+
TypedTransaction::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom),
6970
TypedTransaction::Deposit(tx) => TypedReceipt::Deposit(DepositReceipt {
7071
inner: receipt_with_bloom,
7172
deposit_nonce: Some(tx.nonce),

crates/anvil/src/eth/backend/mem/mod.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,11 @@ impl Backend {
580580
(self.spec_id() as u8) >= (SpecId::CANCUN as u8)
581581
}
582582

583+
/// Returns true for post Prague
584+
pub fn is_eip7702(&self) -> bool {
585+
(self.spec_id() as u8) >= (SpecId::PRAGUE as u8)
586+
}
587+
583588
/// Returns true if op-stack deposits are active
584589
pub fn is_optimism(&self) -> bool {
585590
self.env.read().handler_cfg.is_optimism
@@ -608,6 +613,13 @@ impl Backend {
608613
Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
609614
}
610615

616+
pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> {
617+
if self.is_eip7702() {
618+
return Ok(());
619+
}
620+
Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
621+
}
622+
611623
/// Returns an error if op-stack deposits are not active
612624
pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> {
613625
if self.is_optimism() {
@@ -2135,6 +2147,11 @@ impl Backend {
21352147
.base_fee_per_gas
21362148
.unwrap_or_else(|| self.base_fee())
21372149
.saturating_add(t.tx().tx().max_priority_fee_per_gas),
2150+
TypedTransaction::EIP7702(t) => block
2151+
.header
2152+
.base_fee_per_gas
2153+
.unwrap_or_else(|| self.base_fee())
2154+
.saturating_add(t.tx().max_priority_fee_per_gas),
21382155
TypedTransaction::Deposit(_) => 0_u128,
21392156
};
21402157

@@ -2169,6 +2186,7 @@ impl Backend {
21692186
TypedReceipt::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom),
21702187
TypedReceipt::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom),
21712188
TypedReceipt::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom),
2189+
TypedReceipt::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom),
21722190
TypedReceipt::Deposit(r) => TypedReceipt::Deposit(DepositReceipt {
21732191
inner: receipt_with_bloom,
21742192
deposit_nonce: r.deposit_nonce,
@@ -2482,7 +2500,7 @@ impl TransactionValidator for Backend {
24822500
// Light checks first: see if the blob fee cap is too low.
24832501
if let Some(max_fee_per_blob_gas) = tx.essentials().max_fee_per_blob_gas {
24842502
if let Some(blob_gas_and_price) = &env.block.blob_excess_gas_and_price {
2485-
if max_fee_per_blob_gas.to::<u128>() < blob_gas_and_price.blob_gasprice {
2503+
if max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice {
24862504
warn!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice);
24872505
return Err(InvalidTransactionError::BlobFeeCapTooLow);
24882506
}

crates/anvil/src/eth/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ pub enum BlockchainError {
8585
EIP2930TransactionUnsupportedAtHardfork,
8686
#[error("EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later.")]
8787
EIP4844TransactionUnsupportedAtHardfork,
88+
#[error("EIP-7702 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork prague' or later.")]
89+
EIP7702TransactionUnsupportedAtHardfork,
8890
#[error("op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'.")]
8991
DepositTransactionUnsupported,
9092
#[error("Excess blob gas not set.")]
@@ -418,6 +420,9 @@ impl<T: Serialize> ToRpcResponseResult for Result<T> {
418420
err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => {
419421
RpcError::invalid_params(err.to_string())
420422
}
423+
err @ BlockchainError::EIP7702TransactionUnsupportedAtHardfork => {
424+
RpcError::invalid_params(err.to_string())
425+
}
421426
err @ BlockchainError::DepositTransactionUnsupported => {
422427
RpcError::invalid_params(err.to_string())
423428
}

crates/anvil/src/eth/fees.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ impl FeeHistoryService {
285285
.tx()
286286
.max_priority_fee_per_gas
287287
.min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)),
288+
Some(TypedTransaction::EIP7702(t)) => t
289+
.tx()
290+
.max_priority_fee_per_gas
291+
.min(t.tx().max_fee_per_gas.saturating_sub(base_fee)),
288292
Some(TypedTransaction::Deposit(_)) => 0,
289293
None => 0,
290294
};

crates/anvil/tests/it/eip7702.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use crate::utils::http_provider;
2+
use alloy_consensus::{transaction::TxEip7702, SignableTransaction};
3+
use alloy_eips::eip7702::OptionalNonce;
4+
use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync};
5+
use alloy_primitives::{bytes, TxKind};
6+
use alloy_provider::Provider;
7+
use alloy_rpc_types::{Authorization, TransactionRequest};
8+
use alloy_serde::WithOtherFields;
9+
use alloy_signer::SignerSync;
10+
use anvil::{spawn, Hardfork, NodeConfig};
11+
12+
#[tokio::test(flavor = "multi_thread")]
13+
async fn can_send_eip7702_tx() {
14+
let node_config = NodeConfig::test().with_hardfork(Some(Hardfork::Prague));
15+
let (_api, handle) = spawn(node_config).await;
16+
let provider = http_provider(&handle.http_endpoint());
17+
18+
let wallets = handle.dev_wallets().collect::<Vec<_>>();
19+
20+
// deploy simple contract forwarding calldata to LOG0
21+
// PUSH7(CALLDATASIZE PUSH0 PUSH0 CALLDATACOPY CALLDATASIZE PUSH0 LOG0) PUSH0 MSTORE PUSH1(7)
22+
// PUSH1(25) RETURN
23+
let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3");
24+
25+
let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap();
26+
27+
let from = wallets[0].address();
28+
let tx = TransactionRequest::default()
29+
.with_from(from)
30+
.into_create()
31+
.with_nonce(0)
32+
.with_max_fee_per_gas(eip1559_est.max_fee_per_gas)
33+
.with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas)
34+
.with_input(logger_bytecode);
35+
36+
let receipt = provider
37+
.send_transaction(WithOtherFields::new(tx))
38+
.await
39+
.unwrap()
40+
.get_receipt()
41+
.await
42+
.unwrap();
43+
44+
assert!(receipt.status());
45+
46+
let contract = receipt.contract_address.unwrap();
47+
let authorization = Authorization {
48+
chain_id: 31337,
49+
address: contract,
50+
nonce: OptionalNonce::new(Some(provider.get_transaction_count(from).await.unwrap())),
51+
};
52+
let signature = wallets[0].sign_hash_sync(&authorization.signature_hash()).unwrap();
53+
let authorization = authorization.into_signed(signature);
54+
55+
let log_data = bytes!("11112222");
56+
let mut tx = TxEip7702 {
57+
max_fee_per_gas: eip1559_est.max_fee_per_gas,
58+
max_priority_fee_per_gas: eip1559_est.max_priority_fee_per_gas,
59+
gas_limit: 100000,
60+
chain_id: 31337,
61+
to: TxKind::Call(from),
62+
input: bytes!("11112222"),
63+
authorization_list: vec![authorization],
64+
..Default::default()
65+
};
66+
let signature = wallets[1].sign_transaction_sync(&mut tx).unwrap();
67+
68+
let tx = tx.into_signed(signature);
69+
let mut encoded = Vec::new();
70+
tx.tx().encode_with_signature(tx.signature(), &mut encoded, false);
71+
72+
let receipt =
73+
provider.send_raw_transaction(&encoded).await.unwrap().get_receipt().await.unwrap();
74+
let log = &receipt.inner.inner.logs()[0];
75+
// assert that log was from EOA which signed authorization
76+
assert_eq!(log.address(), from);
77+
assert_eq!(log.topics().len(), 0);
78+
assert_eq!(log.data().data, log_data);
79+
}

crates/anvil/tests/it/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod anvil;
33
mod anvil_api;
44
mod api;
55
mod eip4844;
6+
mod eip7702;
67
mod fork;
78
mod gas;
89
mod genesis;

0 commit comments

Comments
 (0)