Skip to content

Commit 5409915

Browse files
authored
add interceptInitcode cheatcode (#10242)
* add interceptInitcode cheatcode * rename intercept_next to intercept_next_create_call * move logic to create_common
1 parent 58cae24 commit 5409915

File tree

6 files changed

+176
-0
lines changed

6 files changed

+176
-0
lines changed

Diff for: crates/cheatcodes/assets/cheatcodes.json

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: crates/cheatcodes/spec/src/vm.rs

+10
Original file line numberDiff line numberDiff line change
@@ -2838,6 +2838,16 @@ interface Vm {
28382838
/// Randomly shuffles an array.
28392839
#[cheatcode(group = Utilities)]
28402840
function shuffle(uint256[] calldata array) external returns (uint256[] memory);
2841+
2842+
/// Causes the next contract creation (via new) to fail and return its initcode in the returndata buffer.
2843+
/// This allows type-safe access to the initcode payload that would be used for contract creation.
2844+
/// Example usage:
2845+
/// vm.interceptInitcode();
2846+
/// bytes memory initcode;
2847+
/// try new MyContract(param1, param2) { assert(false); }
2848+
/// catch (bytes memory interceptedInitcode) { initcode = interceptedInitcode; }
2849+
#[cheatcode(group = Utilities, safety = Unsafe)]
2850+
function interceptInitcode() external;
28412851
}
28422852
}
28432853

Diff for: crates/cheatcodes/src/inspector.rs

+23
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ pub struct Cheatcodes {
489489
/// `char -> (address, pc)`
490490
pub breakpoints: Breakpoints,
491491

492+
/// Whether the next contract creation should be intercepted to return its initcode.
493+
pub intercept_next_create_call: bool,
494+
492495
/// Optional cheatcodes `TestRunner`. Used for generating random values from uint and int
493496
/// strategies.
494497
test_runner: Option<TestRunner>,
@@ -549,6 +552,7 @@ impl Cheatcodes {
549552
mapping_slots: Default::default(),
550553
pc: Default::default(),
551554
breakpoints: Default::default(),
555+
intercept_next_create_call: Default::default(),
552556
test_runner: Default::default(),
553557
ignored_traces: Default::default(),
554558
arbitrary_storage: Default::default(),
@@ -656,6 +660,25 @@ impl Cheatcodes {
656660
where
657661
Input: CommonCreateInput,
658662
{
663+
// Check if we should intercept this create
664+
if self.intercept_next_create_call {
665+
// Reset the flag
666+
self.intercept_next_create_call = false;
667+
668+
// Get initcode from the input
669+
let output = input.init_code();
670+
671+
// Return a revert with the initcode as error data
672+
return Some(CreateOutcome {
673+
result: InterpreterResult {
674+
result: InstructionResult::Revert,
675+
output,
676+
gas: Gas::new(input.gas_limit()),
677+
},
678+
address: None,
679+
});
680+
}
681+
659682
let ecx = &mut ecx.inner;
660683
let gas = Gas::new(input.gas_limit());
661684
let curr_depth = ecx.journaled_state.depth();

Diff for: crates/cheatcodes/src/utils.rs

+12
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,18 @@ impl Cheatcode for resumeTracingCall {
193193
}
194194
}
195195

196+
impl Cheatcode for interceptInitcodeCall {
197+
fn apply(&self, state: &mut Cheatcodes) -> Result {
198+
let Self {} = self;
199+
if !state.intercept_next_create_call {
200+
state.intercept_next_create_call = true;
201+
} else {
202+
bail!("vm.interceptInitcode() has already been called")
203+
}
204+
Ok(Default::default())
205+
}
206+
}
207+
196208
impl Cheatcode for setArbitraryStorage_0Call {
197209
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
198210
let Self { target } = self;

Diff for: crates/forge/tests/cli/test_cmd.rs

+110
Original file line numberDiff line numberDiff line change
@@ -3411,3 +3411,113 @@ Selectors successfully uploaded to OpenChain
34113411
34123412
"#]]);
34133413
});
3414+
3415+
// tests `interceptInitcode` function
3416+
forgetest_init!(intercept_initcode, |prj, cmd| {
3417+
prj.wipe_contracts();
3418+
prj.insert_ds_test();
3419+
prj.insert_vm();
3420+
prj.clear();
3421+
3422+
prj.add_source(
3423+
"InterceptInitcode.t.sol",
3424+
r#"
3425+
import {Vm} from "./Vm.sol";
3426+
import {DSTest} from "./test.sol";
3427+
3428+
contract SimpleContract {
3429+
uint256 public value;
3430+
constructor(uint256 _value) {
3431+
value = _value;
3432+
}
3433+
}
3434+
3435+
contract InterceptInitcodeTest is DSTest {
3436+
Vm vm = Vm(HEVM_ADDRESS);
3437+
3438+
function testInterceptRegularCreate() public {
3439+
// Set up interception
3440+
vm.interceptInitcode();
3441+
3442+
// Try to create a contract - this should revert with the initcode
3443+
bytes memory initcode;
3444+
try new SimpleContract(42) {
3445+
assert(false);
3446+
} catch (bytes memory interceptedInitcode) {
3447+
initcode = interceptedInitcode;
3448+
}
3449+
3450+
// Verify the initcode contains the constructor argument
3451+
assertTrue(initcode.length > 0, "initcode should not be empty");
3452+
3453+
// The constructor argument is encoded as a 32-byte value at the end of the initcode
3454+
// We need to convert the last 32 bytes to uint256
3455+
uint256 value;
3456+
assembly {
3457+
value := mload(add(add(initcode, 0x20), sub(mload(initcode), 32)))
3458+
}
3459+
assertEq(value, 42, "initcode should contain constructor arg");
3460+
}
3461+
3462+
function testInterceptCreate2() public {
3463+
// Set up interception
3464+
vm.interceptInitcode();
3465+
3466+
// Try to create a contract with CREATE2 - this should revert with the initcode
3467+
bytes memory initcode;
3468+
try new SimpleContract(1337) {
3469+
assert(false);
3470+
} catch (bytes memory interceptedInitcode) {
3471+
initcode = interceptedInitcode;
3472+
}
3473+
3474+
// Verify the initcode contains the constructor argument
3475+
assertTrue(initcode.length > 0, "initcode should not be empty");
3476+
3477+
// The constructor argument is encoded as a 32-byte value at the end of the initcode
3478+
uint256 value;
3479+
assembly {
3480+
value := mload(add(add(initcode, 0x20), sub(mload(initcode), 32)))
3481+
}
3482+
assertEq(value, 1337, "initcode should contain constructor arg");
3483+
}
3484+
3485+
function testInterceptMultiple() public {
3486+
// First interception
3487+
vm.interceptInitcode();
3488+
bytes memory initcode1;
3489+
try new SimpleContract(1) {
3490+
assert(false);
3491+
} catch (bytes memory interceptedInitcode) {
3492+
initcode1 = interceptedInitcode;
3493+
}
3494+
3495+
// Second interception
3496+
vm.interceptInitcode();
3497+
bytes memory initcode2;
3498+
try new SimpleContract(2) {
3499+
assert(false);
3500+
} catch (bytes memory interceptedInitcode) {
3501+
initcode2 = interceptedInitcode;
3502+
}
3503+
3504+
// Verify different initcodes
3505+
assertTrue(initcode1.length > 0, "first initcode should not be empty");
3506+
assertTrue(initcode2.length > 0, "second initcode should not be empty");
3507+
3508+
// Extract constructor arguments from both initcodes
3509+
uint256 value1;
3510+
uint256 value2;
3511+
assembly {
3512+
value1 := mload(add(add(initcode1, 0x20), sub(mload(initcode1), 32)))
3513+
value2 := mload(add(add(initcode2, 0x20), sub(mload(initcode2), 32)))
3514+
}
3515+
assertEq(value1, 1, "first initcode should contain first arg");
3516+
assertEq(value2, 2, "second initcode should contain second arg");
3517+
}
3518+
}
3519+
"#,
3520+
)
3521+
.unwrap();
3522+
cmd.args(["test", "-vvvvv"]).assert_success();
3523+
});

Diff for: testdata/cheats/Vm.sol

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)