Skip to content

Commit 5a4daaf

Browse files
authored
fix: don't reinitialize created accounts (#6534)
1 parent fdad9fb commit 5a4daaf

File tree

3 files changed

+75
-5
lines changed

3 files changed

+75
-5
lines changed

crates/evm/core/src/backend/mod.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,13 @@ impl Backend {
777777
self.inner.precompiles().contains(addr)
778778
}
779779

780+
/// Sets the initial journaled state to use when initializing forks
781+
#[inline]
782+
fn set_init_journaled_state(&mut self, journaled_state: JournaledState) {
783+
trace!("recording fork init journaled_state");
784+
self.fork_init_journaled_state = journaled_state;
785+
}
786+
780787
/// Cleans up already loaded accounts that would be initialized without the correct data from
781788
/// the fork.
782789
///
@@ -800,10 +807,21 @@ impl Backend {
800807
let mut journaled_state = self.fork_init_journaled_state.clone();
801808
for loaded_account in loaded_accounts.iter().copied() {
802809
trace!(?loaded_account, "replacing account on init");
803-
let fork_account = Database::basic(&mut fork.db, loaded_account)?
804-
.ok_or(DatabaseError::MissingAccount(loaded_account))?;
805810
let init_account =
806811
journaled_state.state.get_mut(&loaded_account).expect("exists; qed");
812+
813+
// here's an edge case where we need to check if this account has been created, in
814+
// which case we don't need to replace it with the account from the fork because the
815+
// created account takes precedence: for example contract creation in setups
816+
if init_account.is_created() {
817+
trace!(?loaded_account, "skipping created account");
818+
continue
819+
}
820+
821+
// otherwise we need to replace the account's info with the one from the fork's
822+
// database
823+
let fork_account = Database::basic(&mut fork.db, loaded_account)?
824+
.ok_or(DatabaseError::MissingAccount(loaded_account))?;
807825
init_account.info = fork_account;
808826
}
809827
fork.journaled_state = journaled_state;
@@ -1043,8 +1061,8 @@ impl DatabaseExt for Backend {
10431061
// different forks. Since the `JournaledState` is valid for all forks until the
10441062
// first fork is selected, we need to update it for all forks and use it as init state
10451063
// for all future forks
1046-
trace!("recording fork init journaled_state");
1047-
self.fork_init_journaled_state = active_journaled_state.clone();
1064+
1065+
self.set_init_journaled_state(active_journaled_state.clone());
10481066
self.prepare_init_journal_state()?;
10491067

10501068
// Make sure that the next created fork has a depth of 0.

crates/forge/tests/it/repros.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ async fn repro_config(issue: usize, should_fail: bool, sender: Option<Address>)
4343
let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol"));
4444

4545
let mut config = Config::with_root(PROJECT.root());
46-
config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]);
46+
config.fs_permissions =
47+
FsPermissions::new(vec![PathPermission::read("./fixtures"), PathPermission::read("out")]);
4748
if let Some(sender) = sender {
4849
config.sender = sender;
4950
}
@@ -190,6 +191,9 @@ test_repro!(5948);
190191
// https://github.com/foundry-rs/foundry/issues/6006
191192
test_repro!(6006);
192193

194+
// https://github.com/foundry-rs/foundry/issues/6032
195+
test_repro!(6032);
196+
193197
// https://github.com/foundry-rs/foundry/issues/6070
194198
test_repro!(6070);
195199

testdata/repros/Issue6032.t.sol

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
pragma solidity 0.8.18;
3+
4+
import "ds-test/test.sol";
5+
import "../cheats/Vm.sol";
6+
7+
// https://github.com/foundry-rs/foundry/issues/6032
8+
contract Issue6032Test is DSTest {
9+
Vm constant vm = Vm(HEVM_ADDRESS);
10+
11+
function testEtchFork() public {
12+
// Deploy initial contract
13+
Counter counter = new Counter();
14+
counter.setNumber(42);
15+
16+
address counterAddress = address(counter);
17+
// Enter the fork
18+
vm.createSelectFork("rpcAlias");
19+
assert(counterAddress.code.length > 0);
20+
// `Counter` is not deployed on the fork, which is expected.
21+
22+
// Etch the contract into the fork.
23+
bytes memory code = vm.getDeployedCode("Issue6032.t.sol:CounterEtched");
24+
vm.etch(counterAddress, code);
25+
// `Counter` is now deployed on the fork.
26+
assert(counterAddress.code.length > 0);
27+
28+
// Now we can etch the code, but state will remain.
29+
CounterEtched counterEtched = CounterEtched(counterAddress);
30+
assertEq(counterEtched.numberHalf(), 21);
31+
}
32+
}
33+
34+
contract Counter {
35+
uint256 public number;
36+
37+
function setNumber(uint256 newNumber) public {
38+
number = newNumber;
39+
}
40+
}
41+
42+
contract CounterEtched {
43+
uint256 public number;
44+
45+
function numberHalf() public view returns (uint256) {
46+
return number / 2;
47+
}
48+
}

0 commit comments

Comments
 (0)