Skip to content

Commit 814a5cf

Browse files
tsnewnamimgiagantegrandizzy
authored andcommitted
feat(cheatcodes): add delegatecall to pranking (foundry-rs#8863)
* begin api and rough comments * impl cheatcode * add check for eoa * fix eoa check on each prank call * add to assets * prank compiling * delegate call working, storage not upating * delegate call working, some tidy up * add prank2 calls * impl remaining tests * formatting * forge fmt * add pranks to cheatcodes.json * run cargo cheats * If verbosity level is 1 or higher, it shows dirty files. * Fix, add EOA prank test * Revert "If verbosity level is 1 or higher, it shows dirty files." This reverts commit d03ac1d. * Fix test * apply on extdelegatecall --------- Co-authored-by: mgiagante <[email protected]> Co-authored-by: grandizzy <[email protected]>
1 parent 5012a4c commit 814a5cf

File tree

6 files changed

+281
-6
lines changed

6 files changed

+281
-6
lines changed

crates/cheatcodes/assets/cheatcodes.json

Lines changed: 80 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/spec/src/vm.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,22 @@ interface Vm {
600600
#[cheatcode(group = Evm, safety = Unsafe)]
601601
function startPrank(address msgSender, address txOrigin) external;
602602

603+
/// Sets the *next* delegate call's `msg.sender` to be the input address.
604+
#[cheatcode(group = Evm, safety = Unsafe)]
605+
function prank(address msgSender, bool delegateCall) external;
606+
607+
/// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called.
608+
#[cheatcode(group = Evm, safety = Unsafe)]
609+
function startPrank(address msgSender, bool delegateCall) external;
610+
611+
/// Sets the *next* delegate call's `msg.sender` to be the input address, and the `tx.origin` to be the second input.
612+
#[cheatcode(group = Evm, safety = Unsafe)]
613+
function prank(address msgSender, address txOrigin, bool delegateCall) external;
614+
615+
/// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input.
616+
#[cheatcode(group = Evm, safety = Unsafe)]
617+
function startPrank(address msgSender, address txOrigin, bool delegateCall) external;
618+
603619
/// Resets subsequent calls' `msg.sender` to be `address(this)`.
604620
#[cheatcode(group = Evm, safety = Unsafe)]
605621
function stopPrank() external;

crates/cheatcodes/src/evm/prank.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub struct Prank {
1616
pub depth: u64,
1717
/// Whether the prank stops by itself after the next call
1818
pub single_call: bool,
19+
/// Whether the prank should be be applied to delegate call
20+
pub delegate_call: bool,
1921
/// Whether the prank has been used yet (false if unused)
2022
pub used: bool,
2123
}
@@ -29,8 +31,18 @@ impl Prank {
2931
new_origin: Option<Address>,
3032
depth: u64,
3133
single_call: bool,
34+
delegate_call: bool,
3235
) -> Self {
33-
Self { prank_caller, prank_origin, new_caller, new_origin, depth, single_call, used: false }
36+
Self {
37+
prank_caller,
38+
prank_origin,
39+
new_caller,
40+
new_origin,
41+
depth,
42+
single_call,
43+
delegate_call,
44+
used: false,
45+
}
3446
}
3547

3648
/// Apply the prank by setting `used` to true iff it is false
@@ -47,28 +59,56 @@ impl Prank {
4759
impl Cheatcode for prank_0Call {
4860
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
4961
let Self { msgSender } = self;
50-
prank(ccx, msgSender, None, true)
62+
prank(ccx, msgSender, None, true, false)
5163
}
5264
}
5365

5466
impl Cheatcode for startPrank_0Call {
5567
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
5668
let Self { msgSender } = self;
57-
prank(ccx, msgSender, None, false)
69+
prank(ccx, msgSender, None, false, false)
5870
}
5971
}
6072

6173
impl Cheatcode for prank_1Call {
6274
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
6375
let Self { msgSender, txOrigin } = self;
64-
prank(ccx, msgSender, Some(txOrigin), true)
76+
prank(ccx, msgSender, Some(txOrigin), true, false)
6577
}
6678
}
6779

6880
impl Cheatcode for startPrank_1Call {
6981
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
7082
let Self { msgSender, txOrigin } = self;
71-
prank(ccx, msgSender, Some(txOrigin), false)
83+
prank(ccx, msgSender, Some(txOrigin), false, false)
84+
}
85+
}
86+
87+
impl Cheatcode for prank_2Call {
88+
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
89+
let Self { msgSender, delegateCall } = self;
90+
prank(ccx, msgSender, None, true, *delegateCall)
91+
}
92+
}
93+
94+
impl Cheatcode for startPrank_2Call {
95+
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
96+
let Self { msgSender, delegateCall } = self;
97+
prank(ccx, msgSender, None, false, *delegateCall)
98+
}
99+
}
100+
101+
impl Cheatcode for prank_3Call {
102+
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
103+
let Self { msgSender, txOrigin, delegateCall } = self;
104+
prank(ccx, msgSender, Some(txOrigin), true, *delegateCall)
105+
}
106+
}
107+
108+
impl Cheatcode for startPrank_3Call {
109+
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
110+
let Self { msgSender, txOrigin, delegateCall } = self;
111+
prank(ccx, msgSender, Some(txOrigin), false, *delegateCall)
72112
}
73113
}
74114

@@ -85,6 +125,7 @@ fn prank(
85125
new_caller: &Address,
86126
new_origin: Option<&Address>,
87127
single_call: bool,
128+
delegate_call: bool,
88129
) -> Result {
89130
let prank = Prank::new(
90131
ccx.caller,
@@ -93,8 +134,15 @@ fn prank(
93134
new_origin.copied(),
94135
ccx.ecx.journaled_state.depth(),
95136
single_call,
137+
delegate_call,
96138
);
97139

140+
// Ensure that code exists at `msg.sender` if delegate calling.
141+
if delegate_call {
142+
let code = ccx.code(*new_caller)?;
143+
ensure!(!code.is_empty(), "cannot `prank` delegate call from an EOA");
144+
}
145+
98146
if let Some(Prank { used, single_call: current_single_call, .. }) = ccx.state.prank {
99147
ensure!(used, "cannot overwrite a prank until it is applied at least once");
100148
// This case can only fail if the user calls `vm.startPrank` and then `vm.prank` later on.

crates/cheatcodes/src/inspector.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner};
4242
use rand::Rng;
4343
use revm::{
4444
interpreter::{
45-
opcode as op, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome,
45+
opcode as op, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome,
4646
EOFCreateInputs, EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterAction,
4747
InterpreterResult,
4848
},
@@ -941,6 +941,19 @@ where {
941941

942942
// Apply our prank
943943
if let Some(prank) = &self.prank {
944+
// Apply delegate call, `call.caller`` will not equal `prank.prank_caller`
945+
if let CallScheme::DelegateCall | CallScheme::ExtDelegateCall = call.scheme {
946+
if prank.delegate_call {
947+
call.target_address = prank.new_caller;
948+
call.caller = prank.new_caller;
949+
let acc = ecx.journaled_state.account(prank.new_caller);
950+
call.value = CallValue::Apparent(acc.info.balance);
951+
if let Some(new_origin) = prank.new_origin {
952+
ecx.env.tx.caller = new_origin;
953+
}
954+
}
955+
}
956+
944957
if ecx.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller {
945958
let mut prank_applied = false;
946959

testdata/cheats/Vm.sol

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)