Skip to content

Commit 4243e0a

Browse files
authored
fix(anvil): also remove code when impersonating (#2696)
1 parent e2c96c0 commit 4243e0a

File tree

3 files changed

+56
-9
lines changed

3 files changed

+56
-9
lines changed

anvil/src/eth/backend/cheats.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use anvil_core::eth::transaction::TypedTransaction;
44
use ethers::types::{Address, Signature, H256, U256};
5+
use forge::revm::Bytecode;
56
use parking_lot::RwLock;
67
use std::{collections::HashMap, sync::Arc};
78
use tracing::trace;
@@ -32,15 +33,20 @@ impl CheatsManager {
3233
/// This also accepts the actual code hash if the address is a contract to bypass EIP-3607
3334
///
3435
/// Returns `true` if the account is already impersonated
35-
pub fn impersonate(&self, addr: Address, code_hash: Option<H256>) -> bool {
36+
pub fn impersonate(
37+
&self,
38+
addr: Address,
39+
code_hash: Option<H256>,
40+
code: Option<Bytecode>,
41+
) -> bool {
3642
trace!(target: "cheats", "Start impersonating {:?}", addr);
37-
self.state.write().impersonated_account.insert(addr, code_hash).is_some()
43+
self.state.write().impersonated_account.insert(addr, (code_hash, code)).is_some()
3844
}
3945

4046
/// Removes the account that from the impersonated set
41-
pub fn stop_impersonating(&self, addr: &Address) -> Option<H256> {
47+
pub fn stop_impersonating(&self, addr: &Address) -> Option<(Option<H256>, Option<Bytecode>)> {
4248
trace!(target: "cheats", "Stop impersonating {:?}", addr);
43-
self.state.write().impersonated_account.remove(addr).flatten()
49+
self.state.write().impersonated_account.remove(addr)
4450
}
4551

4652
/// Returns true if the `addr` is currently impersonated
@@ -62,7 +68,7 @@ pub struct CheatsState {
6268
/// If the account is a contract it holds the hash of the contracts code that is temporarily
6369
/// set to `KECCAK_EMPTY` to bypass EIP-3607 which rejects transactions from senders with
6470
/// deployed code
65-
pub impersonated_account: HashMap<Address, Option<H256>>,
71+
pub impersonated_account: HashMap<Address, (Option<H256>, Option<Bytecode>)>,
6672
/// The signature used for the `eth_sendUnsignedTransaction` cheat code
6773
pub bypass_signature: Signature,
6874
}

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,22 +203,28 @@ impl Backend {
203203
return true
204204
}
205205
// need to bypass EIP-3607: Reject transactions from senders with deployed code by setting
206-
// the code hash to `KECCAK_EMPTY` temporarily
206+
// the code hash to `KECCAK_EMPTY` temporarily and also remove the code itself and add back
207+
// when we stop impersonating
207208
let mut account = self.db.read().await.basic(addr);
208209
let mut code_hash = None;
210+
let mut code = None;
209211
if account.code_hash != KECCAK_EMPTY {
210212
code_hash = Some(std::mem::replace(&mut account.code_hash, KECCAK_EMPTY));
213+
code = account.code.take();
211214
self.db.write().await.insert_account(addr, account);
212215
}
213-
self.cheats.impersonate(addr, code_hash)
216+
self.cheats.impersonate(addr, code_hash, code)
214217
}
215218

216219
/// Removes the account that from the impersonated set
220+
///
221+
/// If the impersonated `addr` is a contract then we also reset the code here
217222
pub async fn stop_impersonating(&self, addr: Address) {
218-
if let Some(code_hash) = self.cheats.stop_impersonating(&addr) {
223+
if let Some((Some(code_hash), code)) = self.cheats.stop_impersonating(&addr) {
219224
let mut db = self.db.write().await;
220225
let mut account = db.basic(addr);
221226
account.code_hash = code_hash;
227+
account.code = code;
222228
db.insert_account(addr, account)
223229
}
224230
}
@@ -1119,6 +1125,10 @@ impl Backend {
11191125
self.with_database_at(number, |db| {
11201126
trace!(target: "backend", "get code for {:?}", address);
11211127
let account = db.basic(address);
1128+
if account.code_hash == KECCAK_EMPTY {
1129+
// if the code hash is `KECCAK_EMPTY`, we check no further
1130+
return Ok(Default::default())
1131+
}
11221132
let code = if let Some(code) = account.code {
11231133
code
11241134
} else {

anvil/tests/it/anvil_api.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! tests for custom anvil endpoints
2-
use crate::abi::*;
2+
use crate::{abi::*, fork::fork_config};
33
use anvil::{spawn, Hardfork, NodeConfig};
44
use anvil_core::eth::EthRequest;
55
use ethers::{
@@ -117,6 +117,37 @@ async fn can_impersonate_contract() {
117117
assert_eq!("Hello World!", greeting);
118118
}
119119

120+
#[tokio::test(flavor = "multi_thread")]
121+
async fn can_impersonate_gnosis_safe() {
122+
let (api, handle) = spawn(fork_config()).await;
123+
let provider = handle.http_provider();
124+
125+
// <https://help.gnosis-safe.io/en/articles/4971293-i-don-t-remember-my-safe-address-where-can-i-find-it>
126+
let safe: Address = "0xA063Cb7CFd8E57c30c788A0572CBbf2129ae56B6".parse().unwrap();
127+
128+
let code = provider.get_code(safe, None).await.unwrap();
129+
assert!(!code.is_empty());
130+
131+
api.anvil_impersonate_account(safe).await.unwrap();
132+
133+
let code = provider.get_code(safe, None).await.unwrap();
134+
// impersonated contract code is temporarily removed
135+
assert!(code.is_empty());
136+
137+
let balance = U256::from(1e18 as u64);
138+
// fund the impersonated account
139+
api.anvil_set_balance(safe, balance).await.unwrap();
140+
141+
let on_chain_balance = provider.get_balance(safe, None).await.unwrap();
142+
assert_eq!(on_chain_balance, balance);
143+
144+
api.anvil_stop_impersonating_account(safe).await.unwrap();
145+
146+
let code = provider.get_code(safe, None).await.unwrap();
147+
// code is added back after stop impersonating
148+
assert!(!code.is_empty());
149+
}
150+
120151
#[tokio::test(flavor = "multi_thread")]
121152
async fn can_mine_manually() {
122153
let (api, handle) = spawn(NodeConfig::test()).await;

0 commit comments

Comments
 (0)