Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cheatcodes): add delegatecall to pranking #8863

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
80 changes: 80 additions & 0 deletions crates/cheatcodes/assets/cheatcodes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions crates/cheatcodes/spec/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,22 @@ interface Vm {
#[cheatcode(group = Evm, safety = Unsafe)]
function startPrank(address msgSender, address txOrigin) external;

/// Sets the *next* delegate call's `msg.sender` to be the input address.
#[cheatcode(group = Evm, safety = Unsafe)]
function prank(address msgSender, bool delegateCall) external;

/// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called.
#[cheatcode(group = Evm, safety = Unsafe)]
function startPrank(address msgSender, bool delegateCall) external;

/// Sets the *next* delegate call's `msg.sender` to be the input address, and the `tx.origin` to be the second input.
#[cheatcode(group = Evm, safety = Unsafe)]
function prank(address msgSender, address txOrigin, bool delegateCall) external;

/// 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.
#[cheatcode(group = Evm, safety = Unsafe)]
function startPrank(address msgSender, address txOrigin, bool delegateCall) external;

/// Resets subsequent calls' `msg.sender` to be `address(this)`.
#[cheatcode(group = Evm, safety = Unsafe)]
function stopPrank() external;
Expand Down
58 changes: 53 additions & 5 deletions crates/cheatcodes/src/evm/prank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct Prank {
pub depth: u64,
/// Whether the prank stops by itself after the next call
pub single_call: bool,
/// Whether the prank should be be applied to delegate call
pub delegate_call: bool,
/// Whether the prank has been used yet (false if unused)
pub used: bool,
}
Expand All @@ -29,8 +31,18 @@ impl Prank {
new_origin: Option<Address>,
depth: u64,
single_call: bool,
delegate_call: bool,
) -> Self {
Self { prank_caller, prank_origin, new_caller, new_origin, depth, single_call, used: false }
Self {
prank_caller,
prank_origin,
new_caller,
new_origin,
depth,
single_call,
delegate_call,
used: false,
}
}

/// Apply the prank by setting `used` to true iff it is false
Expand All @@ -47,28 +59,56 @@ impl Prank {
impl Cheatcode for prank_0Call {
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { msgSender } = self;
prank(ccx, msgSender, None, true)
prank(ccx, msgSender, None, true, false)
}
}

impl Cheatcode for startPrank_0Call {
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { msgSender } = self;
prank(ccx, msgSender, None, false)
prank(ccx, msgSender, None, false, false)
}
}

impl Cheatcode for prank_1Call {
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { msgSender, txOrigin } = self;
prank(ccx, msgSender, Some(txOrigin), true)
prank(ccx, msgSender, Some(txOrigin), true, false)
}
}

impl Cheatcode for startPrank_1Call {
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { msgSender, txOrigin } = self;
prank(ccx, msgSender, Some(txOrigin), false)
prank(ccx, msgSender, Some(txOrigin), false, false)
}
}

impl Cheatcode for prank_2Call {
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { msgSender, delegateCall } = self;
prank(ccx, msgSender, None, true, *delegateCall)
}
}

impl Cheatcode for startPrank_2Call {
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { msgSender, delegateCall } = self;
prank(ccx, msgSender, None, false, *delegateCall)
}
}

impl Cheatcode for prank_3Call {
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { msgSender, txOrigin, delegateCall } = self;
prank(ccx, msgSender, Some(txOrigin), true, *delegateCall)
}
}

impl Cheatcode for startPrank_3Call {
fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { msgSender, txOrigin, delegateCall } = self;
prank(ccx, msgSender, Some(txOrigin), false, *delegateCall)
}
}

Expand All @@ -85,6 +125,7 @@ fn prank<DB: DatabaseExt>(
new_caller: &Address,
new_origin: Option<&Address>,
single_call: bool,
delegate_call: bool,
) -> Result {
let prank = Prank::new(
ccx.caller,
Expand All @@ -93,8 +134,15 @@ fn prank<DB: DatabaseExt>(
new_origin.copied(),
ccx.ecx.journaled_state.depth(),
single_call,
delegate_call,
);

// Ensure that code exists at `msg.sender` if delegate calling.
if delegate_call {
let code = ccx.code(*new_caller)?;
ensure!(!code.is_empty(), "cannot `prank` delegate call from an EOA");
}

if let Some(Prank { used, single_call: current_single_call, .. }) = ccx.state.prank {
ensure!(used, "cannot overwrite a prank until it is applied at least once");
// This case can only fail if the user calls `vm.startPrank` and then `vm.prank` later on.
Expand Down
17 changes: 15 additions & 2 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use itertools::Itertools;
use rand::{rngs::StdRng, Rng, SeedableRng};
use revm::{
interpreter::{
opcode as op, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome,
opcode as op, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome,
EOFCreateInputs, EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterAction,
InterpreterResult,
},
Expand Down Expand Up @@ -833,6 +833,19 @@ impl Cheatcodes {

// Apply our prank
if let Some(prank) = &self.prank {
// Apply delegate call. call.caller will not equal prank.prank_caller
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @zerosnacks, my understanding is a little fuzzy here. It seems in the case of a delegate call that call.caller == prank.prank_caller will not be true. Is it suitable to have this here? Ty.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @DaniPopes would you have more context on this?

if let CallScheme::DelegateCall = call.scheme {
if prank.delegate_call {
call.target_address = prank.new_caller;
call.caller = prank.new_caller;
let acc = ecx.journaled_state.account(prank.new_caller);
call.value = CallValue::Apparent(acc.info.balance);
if let Some(new_origin) = prank.new_origin {
ecx.env.tx.caller = new_origin;
}
}
}

if ecx.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller {
let mut prank_applied = false;

Expand Down Expand Up @@ -946,7 +959,7 @@ impl Cheatcodes {
initialized = false;
old_balance = U256::ZERO;
}
let kind = match call.scheme {
let kind: Vm::AccountAccessKind = match call.scheme {
CallScheme::Call => crate::Vm::AccountAccessKind::Call,
CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode,
CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall,
Expand Down
4 changes: 4 additions & 0 deletions testdata/cheats/Vm.sol

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading