Skip to content

Commit eef4acd

Browse files
authored
feat: add 7702 support to eth-sendtransaction (#10504)
1 parent 12b2ed4 commit eef4acd

File tree

4 files changed

+112
-2
lines changed

4 files changed

+112
-2
lines changed

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ pub fn transaction_request_to_typed(
5454
access_list,
5555
sidecar,
5656
transaction_type,
57-
..
57+
authorization_list,
58+
chain_id: _,
5859
},
5960
other,
6061
} = tx;
@@ -75,6 +76,23 @@ pub fn transaction_request_to_typed(
7576
}));
7677
}
7778

79+
// EIP7702
80+
if transaction_type == Some(4) || authorization_list.is_some() {
81+
return Some(TypedTransactionRequest::EIP7702(TxEip7702 {
82+
nonce: nonce.unwrap_or_default(),
83+
max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
84+
max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
85+
gas_limit: gas.unwrap_or_default(),
86+
value: value.unwrap_or(U256::ZERO),
87+
input: input.into_input().unwrap_or_default(),
88+
// requires to
89+
to: to?.into_to()?,
90+
chain_id: 0,
91+
access_list: access_list.unwrap_or_default(),
92+
authorization_list: authorization_list.unwrap(),
93+
}));
94+
}
95+
7896
match (
7997
transaction_type,
8098
gas_price,
@@ -173,6 +191,7 @@ pub enum TypedTransactionRequest {
173191
Legacy(TxLegacy),
174192
EIP2930(TxEip2930),
175193
EIP1559(TxEip1559),
194+
EIP7702(TxEip7702),
176195
EIP4844(TxEip4844Variant),
177196
Deposit(TxDeposit),
178197
}

crates/anvil/src/eth/api.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,6 +3008,15 @@ impl EthApi {
30083008
}
30093009
TypedTransactionRequest::EIP1559(m)
30103010
}
3011+
Some(TypedTransactionRequest::EIP7702(mut m)) => {
3012+
m.nonce = nonce;
3013+
m.chain_id = chain_id;
3014+
m.gas_limit = gas_limit;
3015+
if max_fee_per_gas.is_none() {
3016+
m.max_fee_per_gas = self.gas_price();
3017+
}
3018+
TypedTransactionRequest::EIP7702(m)
3019+
}
30113020
Some(TypedTransactionRequest::EIP4844(m)) => {
30123021
TypedTransactionRequest::EIP4844(match m {
30133022
// We only accept the TxEip4844 variant which has the sidecar.
@@ -3075,6 +3084,7 @@ impl EthApi {
30753084
),
30763085
TypedTransactionRequest::EIP2930(_) |
30773086
TypedTransactionRequest::EIP1559(_) |
3087+
TypedTransactionRequest::EIP7702(_) |
30783088
TypedTransactionRequest::EIP4844(_) |
30793089
TypedTransactionRequest::Deposit(_) => Signature::from_scalars_and_parity(
30803090
B256::with_last_byte(1),
@@ -3198,6 +3208,7 @@ fn determine_base_gas_by_kind(request: &WithOtherFields<TransactionRequest>) ->
31983208
TxKind::Call(_) => MIN_TRANSACTION_GAS,
31993209
TxKind::Create => MIN_CREATE_GAS,
32003210
},
3211+
TypedTransactionRequest::EIP7702(_) => MIN_TRANSACTION_GAS,
32013212
TypedTransactionRequest::EIP2930(req) => match req.to {
32023213
TxKind::Call(_) => MIN_TRANSACTION_GAS,
32033214
TxKind::Create => MIN_CREATE_GAS,

crates/anvil/src/eth/sign.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ impl Signer for DevSigner {
104104
TypedTransactionRequest::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
105105
TypedTransactionRequest::EIP2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
106106
TypedTransactionRequest::EIP1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
107+
TypedTransactionRequest::EIP7702(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
107108
TypedTransactionRequest::EIP4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
108109
TypedTransactionRequest::Deposit(_) => {
109110
unreachable!("op deposit txs should not be signed")
@@ -129,6 +130,9 @@ pub fn build_typed_transaction(
129130
TypedTransactionRequest::EIP1559(tx) => {
130131
TypedTransaction::EIP1559(tx.into_signed(signature))
131132
}
133+
TypedTransactionRequest::EIP7702(tx) => {
134+
TypedTransaction::EIP7702(tx.into_signed(signature))
135+
}
132136
TypedTransactionRequest::EIP4844(tx) => {
133137
TypedTransaction::EIP4844(tx.into_signed(signature))
134138
}

crates/anvil/tests/it/eip7702.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::utils::http_provider;
22
use alloy_consensus::{transaction::TxEip7702, SignableTransaction};
33
use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync};
44
use alloy_primitives::{bytes, U256};
5-
use alloy_provider::Provider;
5+
use alloy_provider::{PendingTransactionConfig, Provider};
66
use alloy_rpc_types::{Authorization, TransactionRequest};
77
use alloy_serde::WithOtherFields;
88
use alloy_signer::SignerSync;
@@ -76,3 +76,79 @@ async fn can_send_eip7702_tx() {
7676
assert_eq!(log.topics().len(), 0);
7777
assert_eq!(log.data().data, log_data);
7878
}
79+
80+
#[tokio::test(flavor = "multi_thread")]
81+
async fn can_send_eip7702_request() {
82+
let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()));
83+
let (api, handle) = spawn(node_config).await;
84+
let provider = http_provider(&handle.http_endpoint());
85+
86+
let wallets = handle.dev_wallets().collect::<Vec<_>>();
87+
88+
// deploy simple contract forwarding calldata to LOG0
89+
// PUSH7(CALLDATASIZE PUSH0 PUSH0 CALLDATACOPY CALLDATASIZE PUSH0 LOG0) PUSH0 MSTORE PUSH1(7)
90+
// PUSH1(25) RETURN
91+
let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3");
92+
93+
let eip1559_est = provider.estimate_eip1559_fees().await.unwrap();
94+
95+
let from = wallets[0].address();
96+
let tx = TransactionRequest::default()
97+
.with_from(from)
98+
.into_create()
99+
.with_nonce(0)
100+
.with_max_fee_per_gas(eip1559_est.max_fee_per_gas)
101+
.with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas)
102+
.with_input(logger_bytecode);
103+
104+
let receipt = provider
105+
.send_transaction(WithOtherFields::new(tx))
106+
.await
107+
.unwrap()
108+
.get_receipt()
109+
.await
110+
.unwrap();
111+
112+
assert!(receipt.status());
113+
114+
let contract = receipt.contract_address.unwrap();
115+
let authorization = Authorization {
116+
chain_id: U256::from(31337u64),
117+
address: contract,
118+
nonce: provider.get_transaction_count(from).await.unwrap(),
119+
};
120+
let signature = wallets[0].sign_hash_sync(&authorization.signature_hash()).unwrap();
121+
let authorization = authorization.into_signed(signature);
122+
123+
let log_data = bytes!("11112222");
124+
let tx = TxEip7702 {
125+
max_fee_per_gas: eip1559_est.max_fee_per_gas,
126+
max_priority_fee_per_gas: eip1559_est.max_priority_fee_per_gas,
127+
gas_limit: 100000,
128+
chain_id: 31337,
129+
to: from,
130+
input: bytes!("11112222"),
131+
authorization_list: vec![authorization],
132+
..Default::default()
133+
};
134+
135+
let sender = wallets[1].address();
136+
let request = TransactionRequest::from_transaction(tx).with_from(sender);
137+
138+
api.anvil_impersonate_account(sender).await.unwrap();
139+
let txhash = api.send_transaction(WithOtherFields::new(request)).await.unwrap();
140+
141+
let txhash = provider
142+
.watch_pending_transaction(PendingTransactionConfig::new(txhash))
143+
.await
144+
.unwrap()
145+
.await
146+
.unwrap();
147+
148+
let receipt = provider.get_transaction_receipt(txhash).await.unwrap().unwrap();
149+
let log = &receipt.inner.inner.logs()[0];
150+
// assert that log was from EOA which signed authorization
151+
assert_eq!(log.address(), from);
152+
assert_eq!(log.topics().len(), 0);
153+
assert_eq!(log.data().data, log_data);
154+
}

0 commit comments

Comments
 (0)