Skip to content

Commit 0037ec1

Browse files
mablrshiyasmohdmattsse
authored
feat(anvil): handle signature impersonation for EIP-7702 authorization list (#12553)
* feat(anvil): handle signature impersonation for EIP-7702 authorization list - added an it test with an auth signed w/ fake sig Co-Authored-By: Shiyas Mohammed <[email protected]> * early check --------- Co-authored-by: Shiyas Mohammed <[email protected]> Co-authored-by: Matthias Seitz <[email protected]>
1 parent e8fae1a commit 0037ec1

File tree

2 files changed

+107
-3
lines changed

2 files changed

+107
-3
lines changed

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@ use crate::{
1414
mem::inspector::AnvilInspector,
1515
};
1616
use alloy_consensus::{
17-
Header, Receipt, ReceiptWithBloom, constants::EMPTY_WITHDRAWALS, proofs::calculate_receipt_root,
17+
Header, Receipt, ReceiptWithBloom, constants::EMPTY_WITHDRAWALS,
18+
proofs::calculate_receipt_root, transaction::Either,
19+
};
20+
use alloy_eips::{
21+
eip7685::EMPTY_REQUESTS_HASH,
22+
eip7702::{RecoveredAuthority, RecoveredAuthorization},
23+
eip7840::BlobParams,
1824
};
19-
use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams};
2025
use alloy_evm::{
2126
EthEvm, Evm, FromRecoveredTx,
2227
eth::EthEvmContext,
@@ -276,6 +281,35 @@ impl<DB: Db + ?Sized, V: TransactionValidator> TransactionExecutor<'_, DB, V> {
276281
let mut tx_env: OpTransaction<TxEnv> =
277282
FromRecoveredTx::from_recovered_tx(&tx.transaction.transaction, *tx.sender());
278283

284+
if let TypedTransaction::EIP7702(tx_7702) = &tx.transaction.transaction
285+
&& self.cheats.has_recover_overrides()
286+
{
287+
// Override invalid recovered authorizations with signature overrides from cheat manager
288+
let cheated_auths = tx_7702
289+
.tx()
290+
.authorization_list
291+
.iter()
292+
.zip(tx_env.base.authorization_list)
293+
.map(|(signed_auth, either_auth)| {
294+
either_auth.right_and_then(|recovered_auth| {
295+
if recovered_auth.authority().is_none()
296+
&& let Ok(signature) = signed_auth.signature()
297+
&& let Some(override_addr) =
298+
self.cheats.get_recover_override(&signature.as_bytes().into())
299+
{
300+
Either::Right(RecoveredAuthorization::new_unchecked(
301+
recovered_auth.into_parts().0,
302+
RecoveredAuthority::Valid(override_addr),
303+
))
304+
} else {
305+
Either::Right(recovered_auth)
306+
}
307+
})
308+
})
309+
.collect();
310+
tx_env.base.authorization_list = cheated_auths;
311+
}
312+
279313
if self.networks.is_optimism() {
280314
tx_env.enveloped_tx = Some(alloy_rlp::encode(&tx.transaction.transaction).into());
281315
}

crates/anvil/tests/it/eip7702.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use alloy_primitives::{U256, bytes};
66
use alloy_provider::{PendingTransactionConfig, Provider};
77
use alloy_rpc_types::{Authorization, TransactionRequest};
88
use alloy_serde::WithOtherFields;
9-
use alloy_signer::SignerSync;
9+
use alloy_signer::{Signature, SignerSync};
1010
use anvil::{NodeConfig, spawn};
1111

1212
#[tokio::test(flavor = "multi_thread")]
@@ -153,3 +153,73 @@ async fn can_send_eip7702_request() {
153153
assert_eq!(log.topics().len(), 0);
154154
assert_eq!(log.data().data, log_data);
155155
}
156+
157+
#[tokio::test(flavor = "multi_thread")]
158+
async fn eip7702_authorization_bypass() {
159+
let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()));
160+
let (api, handle) = spawn(node_config).await;
161+
let provider = http_provider(&handle.http_endpoint());
162+
163+
let wallets = handle.dev_wallets().collect::<Vec<_>>();
164+
165+
// deploy simple contract forwarding calldata to LOG0
166+
// PUSH7(CALLDATASIZE PUSH0 PUSH0 CALLDATACOPY CALLDATASIZE PUSH0 LOG0) PUSH0 MSTORE PUSH1(7)
167+
// PUSH1(25) RETURN
168+
let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3");
169+
170+
let eip1559_est = provider.estimate_eip1559_fees().await.unwrap();
171+
172+
let from = wallets[0].address();
173+
let tx = TransactionRequest::default()
174+
.with_from(from)
175+
.into_create()
176+
.with_nonce(0)
177+
.with_max_fee_per_gas(eip1559_est.max_fee_per_gas)
178+
.with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas)
179+
.with_input(logger_bytecode);
180+
181+
let receipt = provider
182+
.send_transaction(WithOtherFields::new(tx))
183+
.await
184+
.unwrap()
185+
.get_receipt()
186+
.await
187+
.unwrap();
188+
189+
assert!(receipt.status());
190+
191+
let contract = receipt.contract_address.unwrap();
192+
let authorization = Authorization {
193+
chain_id: U256::from(31337u64),
194+
address: contract,
195+
nonce: provider.get_transaction_count(from).await.unwrap(),
196+
};
197+
let fake_auth_sig = Signature::new(U256::ZERO, U256::ZERO, true);
198+
api.anvil_impersonate_signature(fake_auth_sig.as_bytes().into(), from).await.unwrap();
199+
let authorization = authorization.into_signed(fake_auth_sig);
200+
201+
let log_data = bytes!("11112222");
202+
let mut tx = TxEip7702 {
203+
max_fee_per_gas: eip1559_est.max_fee_per_gas,
204+
max_priority_fee_per_gas: eip1559_est.max_priority_fee_per_gas,
205+
gas_limit: 100000,
206+
chain_id: 31337,
207+
to: from,
208+
input: bytes!("11112222"),
209+
authorization_list: vec![authorization],
210+
..Default::default()
211+
};
212+
let signature = wallets[1].sign_transaction_sync(&mut tx).unwrap();
213+
214+
let tx = tx.into_signed(signature);
215+
let mut encoded = Vec::new();
216+
tx.eip2718_encode(&mut encoded);
217+
218+
let receipt =
219+
provider.send_raw_transaction(&encoded).await.unwrap().get_receipt().await.unwrap();
220+
let log = &receipt.inner.inner.logs()[0];
221+
// assert that log was from EOA which signed authorization
222+
assert_eq!(log.address(), from);
223+
assert_eq!(log.topics().len(), 0);
224+
assert_eq!(log.data().data, log_data);
225+
}

0 commit comments

Comments
 (0)