Skip to content

Commit 055b0ea

Browse files
feat(script): revert if address(this) used (#10295)
* Update stack.rs * Update lib.rs * Update lib.rs * Update stack.rs * Create ScriptAddressWarn.t.sol * Update mod.rs * Update stack.rs * Update lib.rs * Create script.rs * Fix compilation, cleanup, add new test in script tests * Set and check current address is script address * Update stack.rs * Allow calls to external libraries * changes after review: use sh_err --------- Co-authored-by: grandizzy <[email protected]> Co-authored-by: grandizzy <[email protected]>
1 parent e988893 commit 055b0ea

File tree

7 files changed

+92
-10
lines changed

7 files changed

+92
-10
lines changed

crates/evm/evm/src/executors/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ impl Executor {
257257
self
258258
}
259259

260+
#[inline]
261+
pub fn set_script(&mut self, script_address: Address) {
262+
self.inspector_mut().script(script_address);
263+
}
264+
260265
#[inline]
261266
pub fn set_trace_printer(&mut self, trace_printer: bool) -> &mut Self {
262267
self.inspector_mut().print(trace_printer);

crates/evm/evm/src/inspectors/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ pub use chisel_state::ChiselState;
1313
mod logs;
1414
pub use logs::LogCollector;
1515

16+
mod script;
17+
pub use script::ScriptExecutionInspector;
18+
1619
mod stack;
1720
pub use stack::{InspectorData, InspectorStack, InspectorStackBuilder};
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use alloy_primitives::Address;
2+
use foundry_common::sh_err;
3+
use revm::{
4+
interpreter::{opcode::ADDRESS, InstructionResult, Interpreter},
5+
Database, EvmContext, Inspector,
6+
};
7+
8+
/// An inspector that enforces certain rules during script execution.
9+
///
10+
/// Currently, it only warns if the `ADDRESS` opcode is used within the script's main contract.
11+
#[derive(Clone, Debug, Default)]
12+
pub struct ScriptExecutionInspector {
13+
/// The address of the script contract being executed.
14+
pub script_address: Address,
15+
}
16+
17+
impl<DB: Database> Inspector<DB> for ScriptExecutionInspector {
18+
#[inline]
19+
fn step(&mut self, interpreter: &mut Interpreter, _ecx: &mut EvmContext<DB>) {
20+
// Check if both target and bytecode address are the same as script contract address
21+
// (allow calling external libraries when bytecode address is different).
22+
if interpreter.current_opcode() == ADDRESS &&
23+
interpreter.contract.target_address == self.script_address &&
24+
interpreter.contract.bytecode_address.unwrap_or_default() == self.script_address
25+
{
26+
// Log the reason for revert
27+
let _ = sh_err!(
28+
"Usage of `address(this)` detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon."
29+
);
30+
// Set the instruction result to Revert to stop execution
31+
interpreter.instruction_result = InstructionResult::Revert;
32+
}
33+
// Note: We don't return anything here as step returns void.
34+
// The original check returned InstructionResult::Continue, but that's the default
35+
// behavior.
36+
}
37+
}

crates/evm/evm/src/inspectors/stack.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{
22
Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Fuzzer, LogCollector,
3-
TracingInspector,
3+
ScriptExecutionInspector, TracingInspector,
44
};
55
use alloy_primitives::{map::AddressHashMap, Address, Bytes, Log, TxKind, U256};
66
use foundry_cheatcodes::{CheatcodesExecutor, Wallets};
@@ -199,6 +199,7 @@ impl InspectorStackBuilder {
199199
if let Some(chisel_state) = chisel_state {
200200
stack.set_chisel(chisel_state);
201201
}
202+
202203
stack.collect_coverage(coverage.unwrap_or(false));
203204
stack.collect_logs(logs.unwrap_or(true));
204205
stack.print(print.unwrap_or(false));
@@ -290,6 +291,7 @@ pub struct InspectorStackInner {
290291
pub log_collector: Option<LogCollector>,
291292
pub printer: Option<CustomPrintTracer>,
292293
pub tracer: Option<TracingInspector>,
294+
pub script_execution_inspector: Option<ScriptExecutionInspector>,
293295
pub enable_isolation: bool,
294296
pub odyssey: bool,
295297
pub create2_deployer: Address,
@@ -437,6 +439,13 @@ impl InspectorStack {
437439
}
438440
}
439441

442+
/// Set whether to enable script execution inspector.
443+
#[inline]
444+
pub fn script(&mut self, script_address: Address) {
445+
self.script_execution_inspector.get_or_insert_with(Default::default).script_address =
446+
script_address;
447+
}
448+
440449
/// Collects all the data gathered during inspection into a single struct.
441450
#[inline]
442451
pub fn collect(self) -> InspectorData {
@@ -757,7 +766,13 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> {
757766
ecx: &mut EvmContext<&mut dyn DatabaseExt>,
758767
) {
759768
call_inspectors!(
760-
[&mut self.coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer],
769+
[
770+
&mut self.coverage,
771+
&mut self.tracer,
772+
&mut self.cheatcodes,
773+
&mut self.script_execution_inspector,
774+
&mut self.printer
775+
],
761776
|inspector| inspector.initialize_interp(interpreter, ecx),
762777
);
763778
}
@@ -769,7 +784,8 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> {
769784
&mut self.tracer,
770785
&mut self.coverage,
771786
&mut self.cheatcodes,
772-
&mut self.printer,
787+
&mut self.script_execution_inspector,
788+
&mut self.printer
773789
],
774790
|inspector| inspector.step(interpreter, ecx),
775791
);

crates/forge/tests/cli/script.rs

+25
Original file line numberDiff line numberDiff line change
@@ -2645,3 +2645,28 @@ Script ran successfully.
26452645
26462646
"#]]);
26472647
});
2648+
2649+
// Tests that script reverts if it uses `address(this)`.
2650+
forgetest_init!(should_revert_on_address_opcode, |prj, cmd| {
2651+
prj.add_script(
2652+
"ScriptWithAddress.s.sol",
2653+
r#"
2654+
import {Script, console} from "forge-std/Script.sol";
2655+
2656+
contract ScriptWithAddress is Script {
2657+
function run() public view {
2658+
console.log("script address", address(this));
2659+
}
2660+
}
2661+
"#,
2662+
)
2663+
.unwrap();
2664+
2665+
cmd.arg("script").arg("ScriptWithAddress").assert_failure().stderr_eq(str![[r#"
2666+
...
2667+
Error: Usage of `address(this)` detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon.
2668+
Error: script failed: <empty revert data>
2669+
...
2670+
2671+
"#]]);
2672+
});

crates/script/src/runner.rs

+3
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ impl ScriptRunner {
157157
self.executor.set_nonce(self.evm_opts.sender, prev_sender_nonce)?;
158158
}
159159

160+
// set script address to be used by execution inspector
161+
self.executor.set_script(address);
162+
160163
traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces)));
161164

162165
// Optionally call the `setUp` function

testdata/default/cheats/Broadcast.t.sol

-7
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,6 @@ contract BroadcastTest is DSTest {
112112

113113
vm.stopBroadcast();
114114

115-
require(test.echoSender() == address(this));
116-
117115
vm.broadcast(ACCOUNT_B);
118116
Test tmptest2 = new Test();
119117

@@ -577,11 +575,6 @@ contract ScriptSign is DSTest {
577575
vm.startBroadcast();
578576
(uint8 v, bytes32 r, bytes32 s) = vm.sign(digest);
579577

580-
vm._expectCheatcodeRevert(
581-
bytes(string.concat("signer with address ", vm.toString(address(this)), " is not available"))
582-
);
583-
vm.sign(address(this), digest);
584-
585578
SignatureTester tester = new SignatureTester();
586579
(, address caller,) = vm.readCallers();
587580
assertEq(tester.owner(), caller);

0 commit comments

Comments
 (0)