Skip to content

Commit

Permalink
fix: script simulation with default sender (#9042)
Browse files Browse the repository at this point in the history
* add test

* fix: ensure correct sender nonce when dry-running script in fork

* fix test

* Fix test

---------

Co-authored-by: grandizzy <[email protected]>
  • Loading branch information
klkvr and grandizzy authored Oct 21, 2024
1 parent 52b3da2 commit 09824ad
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 19 deletions.
35 changes: 16 additions & 19 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ use alloy_primitives::{
use alloy_rpc_types::request::{TransactionInput, TransactionRequest};
use alloy_sol_types::{SolCall, SolInterface, SolValue};
use foundry_common::{evm::Breakpoints, TransactionMaybeSigned, SELECTOR_LEN};
use foundry_config::Config;
use foundry_evm_core::{
abi::Vm::stopExpectSafeMemoryCall,
backend::{DatabaseError, DatabaseExt, RevertDiagnostic},
Expand Down Expand Up @@ -834,25 +833,23 @@ where {
// broadcasting.
if ecx.journaled_state.depth == 0 {
let sender = ecx.env.tx.caller;
if sender != Config::DEFAULT_SENDER {
let account = match super::evm::journaled_account(ecx, sender) {
Ok(account) => account,
Err(err) => {
return Some(CallOutcome {
result: InterpreterResult {
result: InstructionResult::Revert,
output: err.abi_encode().into(),
gas,
},
memory_offset: call.return_memory_offset.clone(),
})
}
};
let prev = account.info.nonce;
account.info.nonce = prev.saturating_sub(1);
let account = match super::evm::journaled_account(ecx, sender) {
Ok(account) => account,
Err(err) => {
return Some(CallOutcome {
result: InterpreterResult {
result: InstructionResult::Revert,
output: err.abi_encode().into(),
gas,
},
memory_offset: call.return_memory_offset.clone(),
})
}
};
let prev = account.info.nonce;
account.info.nonce = prev.saturating_sub(1);

trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce");
}
trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce");
}

if call.target_address == CHEATCODE_ADDRESS {
Expand Down
64 changes: 64 additions & 0 deletions crates/forge/tests/cli/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2146,3 +2146,67 @@ Script ran successfully.
"#]]);
});

forgetest_async!(can_simulate_with_default_sender, |prj, cmd| {
let (_api, handle) = spawn(NodeConfig::test()).await;

foundry_test_utils::util::initialize(prj.root());
prj.add_script(
"Script.s.sol",
r#"
import "forge-std/Script.sol";
contract A {
function getValue() external pure returns (uint256) {
return 100;
}
}
contract B {
constructor(A a) {
require(a.getValue() == 100);
}
}
contract SimpleScript is Script {
function run() external {
vm.startBroadcast();
A a = new A();
new B(a);
}
}
"#,
)
.unwrap();

cmd.arg("script").args(["SimpleScript", "--fork-url", &handle.http_endpoint(), "-vvvv"]);
cmd.assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
[104553] SimpleScript::run()
├─ [0] VM::startBroadcast()
│ └─ ← [Return]
├─ [23875] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519
│ └─ ← [Return] 119 bytes of code
├─ [13367] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
│ ├─ [146] A::getValue() [staticcall]
│ │ └─ ← [Return] 100
│ └─ ← [Return] 63 bytes of code
└─ ← [Stop]
Script ran successfully.
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
[23875] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519
└─ ← [Return] 119 bytes of code
[15867] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
├─ [146] A::getValue() [staticcall]
│ └─ ← [Return] 100
└─ ← [Return] 63 bytes of code
...
"#]]);
});
15 changes: 15 additions & 0 deletions crates/script/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,17 @@ impl ScriptRunner {
// construction
self.executor.set_balance(address, self.evm_opts.initial_balance)?;

// HACK: if the current sender is the default script sender (which is a default value), we
// set its nonce to a very large value before deploying the script contract. This
// ensures that the nonce increase during this CREATE does not affect deployment
// addresses of contracts that are deployed in the script, Otherwise, we'd have a
// nonce mismatch during script execution and onchain simulation, potentially
// resulting in weird errors like <https://github.com/foundry-rs/foundry/issues/8960>.
let prev_sender_nonce = self.executor.get_nonce(self.evm_opts.sender)?;
if self.evm_opts.sender == CALLER {
self.executor.set_nonce(self.evm_opts.sender, u64::MAX / 2)?;
}

// Deploy an instance of the contract
let DeployResult {
address,
Expand All @@ -142,6 +153,10 @@ impl ScriptRunner {
.deploy(CALLER, code, U256::ZERO, None)
.map_err(|err| eyre::eyre!("Failed to deploy script:\n{}", err))?;

if self.evm_opts.sender == CALLER {
self.executor.set_nonce(self.evm_opts.sender, prev_sender_nonce)?;
}

traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces)));

// Optionally call the `setUp` function
Expand Down

0 comments on commit 09824ad

Please sign in to comment.