Skip to content

Commit e919a63

Browse files
authored
fix: empty setUp with default sender on forge script (foundry-rs#3817)
* clean-up nonce correction * dont correct nonce from default sender * add docs for nonce management on forge script * fix * add test
1 parent 427c1b5 commit e919a63

File tree

8 files changed

+111
-25
lines changed

8 files changed

+111
-25
lines changed

cli/src/cmd/forge/script/runner.rs

+27-18
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,8 @@ impl ScriptRunner {
114114
.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)).into_iter());
115115
logs.extend_from_slice(&setup_logs);
116116

117-
// We call the `setUp()` function with self.sender, and if there haven't been
118-
// any broadcasts, then the EVM cheatcode module hasn't corrected the nonce.
119-
// So we have to
120-
if transactions.is_none() || transactions.as_ref().unwrap().is_empty() {
121-
self.executor.set_nonce(
122-
self.sender,
123-
sender_nonce.as_u64() + libraries.len() as u64,
124-
)?;
125-
}
117+
self.maybe_correct_nonce(sender_nonce, libraries.len())?;
118+
126119
(
127120
!reverted,
128121
gas_used,
@@ -148,15 +141,8 @@ impl ScriptRunner {
148141
.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)).into_iter());
149142
logs.extend_from_slice(&setup_logs);
150143

151-
// We call the `setUp()` function with self.sender, and if there haven't been
152-
// any broadcasts, then the EVM cheatcode module hasn't corrected the nonce.
153-
// So we have to
154-
if transactions.is_none() || transactions.as_ref().unwrap().is_empty() {
155-
self.executor.set_nonce(
156-
self.sender,
157-
sender_nonce.as_u64() + libraries.len() as u64,
158-
)?;
159-
}
144+
self.maybe_correct_nonce(sender_nonce, libraries.len())?;
145+
160146
(
161147
!reverted,
162148
gas_used,
@@ -187,6 +173,29 @@ impl ScriptRunner {
187173
))
188174
}
189175

176+
/// We call the `setUp()` function with self.sender, and if there haven't been
177+
/// any broadcasts, then the EVM cheatcode module hasn't corrected the nonce.
178+
/// So we have to.
179+
fn maybe_correct_nonce(
180+
&mut self,
181+
sender_initial_nonce: U256,
182+
libraries_len: usize,
183+
) -> eyre::Result<()> {
184+
if let Some(ref cheatcodes) = self.executor.inspector_config().cheatcodes {
185+
if !cheatcodes.corrected_nonce {
186+
self.executor
187+
.set_nonce(self.sender, sender_initial_nonce.as_u64() + libraries_len as u64)?;
188+
}
189+
self.executor
190+
.inspector_config_mut()
191+
.cheatcodes
192+
.as_mut()
193+
.expect("exists")
194+
.corrected_nonce = false;
195+
}
196+
Ok(())
197+
}
198+
190199
/// Executes the method that will collect all broadcastable transactions.
191200
pub fn script(&mut self, address: Address, calldata: Bytes) -> eyre::Result<ScriptResult> {
192201
self.call(self.sender, address, calldata, U256::zero(), false)

cli/test-utils/src/script.rs

+3
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ impl ScriptTester {
239239
/// Various `forge` script results
240240
#[derive(Debug)]
241241
pub enum ScriptOutcome {
242+
OkNoEndpoint,
242243
OkSimulation,
243244
OkBroadcast,
244245
WarnSpecifyDeployer,
@@ -253,6 +254,7 @@ pub enum ScriptOutcome {
253254
impl ScriptOutcome {
254255
pub fn as_str(&self) -> &'static str {
255256
match self {
257+
ScriptOutcome::OkNoEndpoint => "If you wish to simulate on-chain transactions pass a RPC URL.",
256258
ScriptOutcome::OkSimulation => "SIMULATION COMPLETE. To broadcast these",
257259
ScriptOutcome::OkBroadcast => "ONCHAIN EXECUTION COMPLETE & SUCCESSFUL",
258260
ScriptOutcome::WarnSpecifyDeployer => "You have more than one deployer who could predeploy libraries. Using `--sender` instead.",
@@ -267,6 +269,7 @@ impl ScriptOutcome {
267269

268270
pub fn is_err(&self) -> bool {
269271
match self {
272+
ScriptOutcome::OkNoEndpoint |
270273
ScriptOutcome::OkSimulation |
271274
ScriptOutcome::OkBroadcast |
272275
ScriptOutcome::WarnSpecifyDeployer => false,

cli/tests/it/script.rs

+9
Original file line numberDiff line numberDiff line change
@@ -848,3 +848,12 @@ contract Demo {
848848
.join("tests/fixtures/can_execute_script_and_skip_contracts.stdout"),
849849
);
850850
});
851+
852+
forgetest_async!(
853+
can_run_script_with_empty_setup,
854+
|prj: TestProject, cmd: TestCommand| async move {
855+
let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root());
856+
857+
tester.add_sig("BroadcastEmptySetUp", "run()").simulate(ScriptOutcome::OkNoEndpoint);
858+
}
859+
);

docs/dev/scripting.md

+52
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
1. [High level overview](#high-level-overview)
55
1. [Notes](#notes)
66
2. [Script Execution](#script-execution)
7+
3. [Nonce Management](#nonce-management)
78

89
## High level overview
910

@@ -84,3 +85,54 @@ Executor::call-. BroadcastableTransactions .->ScriptArgs::handle_broadcastable_t
8485
8586
```
8687

88+
89+
## Nonce Management
90+
91+
During the first execution stage on `forge script`, foundry has to adjust the nonce from the sender to make sure the execution and state are as close as possible to its on-chain representation.
92+
93+
Making sure that `msg.sender` is our signer when calling `setUp()` and `run()` and that its nonce is correct (decreased by one on each call) when calling `vm.broadcast` to create a contract.
94+
95+
We skip this, if the user hasn't set a sender and they're using the `Config::DEFAULT_SENDER`.
96+
97+
98+
```mermaid
99+
graph TD
100+
101+
ScriptRunner::setup-->default_foundry_caller-deployScript;
102+
default_foundry_caller-deployScript-->user_sender-deployLibs;
103+
user_sender-deployLibs-->Contract.setUp;
104+
Contract.setUp-->A0{Executor::call};
105+
A0-->vm.broadcast;
106+
A0-->vm.startBroadcast;
107+
A0-->vm.getNonce;
108+
109+
vm.broadcast--> A{cheatcode.corrected_nonce}
110+
vm.startBroadcast-->A
111+
vm.getNonce-->A
112+
113+
A--true-->continue_setUp;
114+
A--false-->B[sender_nonce=-1];
115+
B-->C[cheatcode.corrected_nonce=true];
116+
C-->continue_setUp;
117+
continue_setUp-->end_setUp;
118+
end_setUp-->D{cheatcode.corrected_nonce}
119+
D--true-->E[cheatcode.corrected_nonce=false];
120+
D--false-->F[sender_nonce=initial_nonce+predeployed_libraries_count];
121+
E-->ScriptRunner::script;
122+
F-->ScriptRunner::script;
123+
ScriptRunner::script-->Contract.run;
124+
Contract.run-->G{Executor::call};
125+
G-->H[vm.broadcast];
126+
G-->I[vm.startBroadcast];
127+
G-->J[vm.getNonce];
128+
129+
H--> K{cheatcode.corrected_nonce}
130+
I-->K
131+
J-->K
132+
133+
K--true-->continue_run;
134+
K--false-->L[sender_nonce=-1];
135+
L-->M[cheatcode.corrected_nonce=true];
136+
M-->continue_run;
137+
continue_run-->end_run;
138+
```

evm/src/executor/inspector/cheatcodes/env.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use ethers::{
2020
signers::{LocalWallet, Signer},
2121
types::{Address, U256},
2222
};
23+
use foundry_config::Config;
2324
use revm::{Bytecode, Database, EVMData};
2425
use tracing::trace;
2526

@@ -454,7 +455,7 @@ fn correct_sender_nonce<DB: Database>(
454455
db: &mut DB,
455456
state: &mut Cheatcodes,
456457
) -> Result<(), DB::Error> {
457-
if !state.corrected_nonce {
458+
if !state.corrected_nonce && sender != Config::DEFAULT_SENDER {
458459
with_journaled_account(journaled_state, db, sender, |account| {
459460
account.info.nonce = account.info.nonce.saturating_sub(1);
460461
state.corrected_nonce = true;

evm/src/executor/inspector/cheatcodes/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ pub struct Cheatcodes {
124124
/// Current broadcasting information
125125
pub broadcast: Option<Broadcast>,
126126

127-
/// Used to correct the nonce of --sender after the initiating call
127+
/// Used to correct the nonce of --sender after the initiating call. For more, check
128+
/// `docs/scripting`.
128129
pub corrected_nonce: bool,
129130

130131
/// Scripting based transactions

evm/src/executor/mod.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -373,11 +373,11 @@ impl Executor {
373373
// Persist cheatcode state
374374
let mut cheatcodes = result.cheatcodes.take();
375375
if let Some(cheats) = cheatcodes.as_mut() {
376-
if !cheats.broadcastable_transactions.is_empty() {
377-
// Clear broadcast state from cheatcode state
378-
cheats.broadcastable_transactions.clear();
379-
cheats.corrected_nonce = false;
380-
}
376+
// Clear broadcastable transactions
377+
cheats.broadcastable_transactions.clear();
378+
379+
// corrected_nonce value is needed outside of this context (setUp), so we don't
380+
// reset it.
381381
}
382382
self.inspector_config.cheatcodes = cheatcodes;
383383
}

testdata/cheats/Broadcast.t.sol

+11
Original file line numberDiff line numberDiff line change
@@ -424,3 +424,14 @@ contract MultiChainBroadcastLink is DSTest {
424424
new Test();
425425
}
426426
}
427+
428+
contract BroadcastEmptySetUp is DSTest {
429+
Cheats constant cheats = Cheats(HEVM_ADDRESS);
430+
431+
function setUp() public {}
432+
433+
function run() public {
434+
cheats.broadcast();
435+
new Test();
436+
}
437+
}

0 commit comments

Comments
 (0)