From 09824ad0cdb4d20e280e1698ca9097b869b2a4da Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 21 Oct 2024 12:49:15 +0400 Subject: [PATCH] fix: script simulation with default sender (#9042) * add test * fix: ensure correct sender nonce when dry-running script in fork * fix test * Fix test --------- Co-authored-by: grandizzy --- crates/cheatcodes/src/inspector.rs | 35 ++++++++-------- crates/forge/tests/cli/script.rs | 64 ++++++++++++++++++++++++++++++ crates/script/src/runner.rs | 15 +++++++ 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 043f8af2fbe4..7d91f5e20311 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -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}, @@ -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 { diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 558580728b00..dda45cf514fd 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -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 +... +"#]]); +}); diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 2b3fc5253b9c..51b1018475dd 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -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 . + 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, @@ -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