Skip to content

feat: support for EIP-7702 in Anvil #8476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 138 additions & 45 deletions crates/anvil/core/src/eth/transaction/mod.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2708,6 +2708,7 @@ impl EthApi {
TypedTransaction::EIP2930(_) => self.backend.ensure_eip2930_active(),
TypedTransaction::EIP1559(_) => self.backend.ensure_eip1559_active(),
TypedTransaction::EIP4844(_) => self.backend.ensure_eip4844_active(),
TypedTransaction::EIP7702(_) => self.backend.ensure_eip7702_active(),
TypedTransaction::Deposit(_) => self.backend.ensure_op_deposits_active(),
TypedTransaction::Legacy(_) => Ok(()),
}
Expand Down
1 change: 1 addition & 0 deletions crates/anvil/src/eth/backend/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ impl ExecutedTransaction {
TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom),
TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom),
TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom),
TypedTransaction::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom),
TypedTransaction::Deposit(tx) => TypedReceipt::Deposit(DepositReceipt {
inner: receipt_with_bloom,
deposit_nonce: Some(tx.nonce),
Expand Down
20 changes: 19 additions & 1 deletion crates/anvil/src/eth/backend/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,11 @@ impl Backend {
(self.spec_id() as u8) >= (SpecId::CANCUN as u8)
}

/// Returns true for post Prague
pub fn is_eip7702(&self) -> bool {
(self.spec_id() as u8) >= (SpecId::PRAGUE as u8)
}

/// Returns true if op-stack deposits are active
pub fn is_optimism(&self) -> bool {
self.env.read().handler_cfg.is_optimism
Expand Down Expand Up @@ -608,6 +613,13 @@ impl Backend {
Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
}

pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> {
if self.is_eip7702() {
return Ok(());
}
Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
}

/// Returns an error if op-stack deposits are not active
pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> {
if self.is_optimism() {
Expand Down Expand Up @@ -2135,6 +2147,11 @@ impl Backend {
.base_fee_per_gas
.unwrap_or_else(|| self.base_fee())
.saturating_add(t.tx().tx().max_priority_fee_per_gas),
TypedTransaction::EIP7702(t) => block
.header
.base_fee_per_gas
.unwrap_or_else(|| self.base_fee())
.saturating_add(t.tx().max_priority_fee_per_gas),
TypedTransaction::Deposit(_) => 0_u128,
};

Expand Down Expand Up @@ -2169,6 +2186,7 @@ impl Backend {
TypedReceipt::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom),
TypedReceipt::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom),
TypedReceipt::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom),
TypedReceipt::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom),
TypedReceipt::Deposit(r) => TypedReceipt::Deposit(DepositReceipt {
inner: receipt_with_bloom,
deposit_nonce: r.deposit_nonce,
Expand Down Expand Up @@ -2482,7 +2500,7 @@ impl TransactionValidator for Backend {
// Light checks first: see if the blob fee cap is too low.
if let Some(max_fee_per_blob_gas) = tx.essentials().max_fee_per_blob_gas {
if let Some(blob_gas_and_price) = &env.block.blob_excess_gas_and_price {
if max_fee_per_blob_gas.to::<u128>() < blob_gas_and_price.blob_gasprice {
if max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice {
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);
return Err(InvalidTransactionError::BlobFeeCapTooLow);
}
Expand Down
5 changes: 5 additions & 0 deletions crates/anvil/src/eth/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ pub enum BlockchainError {
EIP2930TransactionUnsupportedAtHardfork,
#[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.")]
EIP4844TransactionUnsupportedAtHardfork,
#[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.")]
EIP7702TransactionUnsupportedAtHardfork,
#[error("op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'.")]
DepositTransactionUnsupported,
#[error("Excess blob gas not set.")]
Expand Down Expand Up @@ -418,6 +420,9 @@ impl<T: Serialize> ToRpcResponseResult for Result<T> {
err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => {
RpcError::invalid_params(err.to_string())
}
err @ BlockchainError::EIP7702TransactionUnsupportedAtHardfork => {
RpcError::invalid_params(err.to_string())
}
err @ BlockchainError::DepositTransactionUnsupported => {
RpcError::invalid_params(err.to_string())
}
Expand Down
4 changes: 4 additions & 0 deletions crates/anvil/src/eth/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ impl FeeHistoryService {
.tx()
.max_priority_fee_per_gas
.min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)),
Some(TypedTransaction::EIP7702(t)) => t
.tx()
.max_priority_fee_per_gas
.min(t.tx().max_fee_per_gas.saturating_sub(base_fee)),
Some(TypedTransaction::Deposit(_)) => 0,
None => 0,
};
Expand Down
79 changes: 79 additions & 0 deletions crates/anvil/tests/it/eip7702.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::utils::http_provider;
use alloy_consensus::{transaction::TxEip7702, SignableTransaction};
use alloy_eips::eip7702::OptionalNonce;
use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync};
use alloy_primitives::{bytes, TxKind};
use alloy_provider::Provider;
use alloy_rpc_types::{Authorization, TransactionRequest};
use alloy_serde::WithOtherFields;
use alloy_signer::SignerSync;
use anvil::{spawn, Hardfork, NodeConfig};

#[tokio::test(flavor = "multi_thread")]
async fn can_send_eip7702_tx() {
let node_config = NodeConfig::test().with_hardfork(Some(Hardfork::Prague));
let (_api, handle) = spawn(node_config).await;
let provider = http_provider(&handle.http_endpoint());

let wallets = handle.dev_wallets().collect::<Vec<_>>();

// deploy simple contract forwarding calldata to LOG0
// PUSH7(CALLDATASIZE PUSH0 PUSH0 CALLDATACOPY CALLDATASIZE PUSH0 LOG0) PUSH0 MSTORE PUSH1(7)
// PUSH1(25) RETURN
let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3");

let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap();

let from = wallets[0].address();
let tx = TransactionRequest::default()
.with_from(from)
.into_create()
.with_nonce(0)
.with_max_fee_per_gas(eip1559_est.max_fee_per_gas)
.with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas)
.with_input(logger_bytecode);

let receipt = provider
.send_transaction(WithOtherFields::new(tx))
.await
.unwrap()
.get_receipt()
.await
.unwrap();

assert!(receipt.status());

let contract = receipt.contract_address.unwrap();
let authorization = Authorization {
chain_id: 31337,
address: contract,
nonce: OptionalNonce::new(Some(provider.get_transaction_count(from).await.unwrap())),
};
let signature = wallets[0].sign_hash_sync(&authorization.signature_hash()).unwrap();
let authorization = authorization.into_signed(signature);

let log_data = bytes!("11112222");
let mut tx = TxEip7702 {
max_fee_per_gas: eip1559_est.max_fee_per_gas,
max_priority_fee_per_gas: eip1559_est.max_priority_fee_per_gas,
gas_limit: 100000,
chain_id: 31337,
to: TxKind::Call(from),
input: bytes!("11112222"),
authorization_list: vec![authorization],
..Default::default()
};
let signature = wallets[1].sign_transaction_sync(&mut tx).unwrap();

let tx = tx.into_signed(signature);
let mut encoded = Vec::new();
tx.tx().encode_with_signature(tx.signature(), &mut encoded, false);

let receipt =
provider.send_raw_transaction(&encoded).await.unwrap().get_receipt().await.unwrap();
let log = &receipt.inner.inner.logs()[0];
// assert that log was from EOA which signed authorization
assert_eq!(log.address(), from);
assert_eq!(log.topics().len(), 0);
assert_eq!(log.data().data, log_data);
}
1 change: 1 addition & 0 deletions crates/anvil/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod anvil;
mod anvil_api;
mod api;
mod eip4844;
mod eip7702;
mod fork;
mod gas;
mod genesis;
Expand Down
Loading