Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2146fc5
add vm.prank/startPrank/stopPrank support
alexggh Oct 2, 2025
51804f5
add prank testsuite
alexggh Oct 2, 2025
ad700fb
mocked functions working something
alexggh Oct 3, 2025
08e63fd
add support for mocks
alexggh Oct 7, 2025
aaa472f
add mock
alexggh Oct 8, 2025
f7145b4
remove unneeded changes
alexggh Oct 8, 2025
8608a99
remove unneeded file
alexggh Oct 8, 2025
50e8f43
compile against latest branch
alexggh Oct 20, 2025
0b39a22
rebased on master
alexggh Oct 20, 2025
4158429
Merge remote-tracking branch 'origin/master' into re_merge_prank
alexggh Oct 20, 2025
effbecb
add cheatcode testsuite
alexggh Oct 21, 2025
b005fa0
prank latest
alexggh Oct 23, 2025
80fa8d8
Merge remote-tracking branch 'origin/master' into alexggh/review_pran…
alexggh Oct 27, 2025
e15127f
fixup migration
alexggh Oct 27, 2025
ad1253d
fix build
alexggh Oct 27, 2025
f515d42
add more tests
alexggh Oct 28, 2025
4d1f528
Merge remote-tracking branch 'origin/master' into alexggh/review_pran…
alexggh Oct 28, 2025
3cb2315
fixup tests
alexggh Oct 24, 2025
a629145
switch to master
alexggh Oct 28, 2025
676b2f8
make cargo fmt happy
alexggh Oct 28, 2025
681472c
make clippy happy
alexggh Oct 28, 2025
ed7b936
make forge fmt happy
alexggh Oct 28, 2025
e29737b
fix prank calls
alexggh Oct 29, 2025
9d35e48
Merge remote-tracking branch 'origin/master' into alexggh/review_pran…
alexggh Nov 4, 2025
fedcc8e
Merge remote-tracking branch 'origin/master' into alexggh/review_pran…
alexggh Nov 5, 2025
ee9c3d3
replace with template
alexggh Nov 5, 2025
8d11b21
fixup typo
alexggh Nov 5, 2025
d0a562d
fix extra
alexggh Nov 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/cheatcodes/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use serde::Serialize;

mod fork;
pub(crate) mod mapping;
pub(crate) mod mock;
pub mod mock;
pub(crate) mod prank;

/// Records storage slots reads and writes.
Expand Down
78 changes: 47 additions & 31 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,11 @@ impl Cheatcodes {
self.strategy.runner.revive_remove_duplicate_account_access(self);
}

// Tells whether PVM is enabled or not.
pub fn is_pvm_enabled(&mut self) -> bool {
self.strategy.runner.is_pvm_enabled(self)
}

pub fn call_with_executor(
&mut self,
ecx: Ecx,
Expand Down Expand Up @@ -1022,40 +1027,51 @@ impl Cheatcodes {
}
}

// Handle mocked calls
if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) {
let ctx = MockCallDataContext {
calldata: call.input.bytes(ecx),
value: call.transfer_value(),
};
// Do not handle mocked calls if PVM is enabled and let the revive call handle it.
// There is literally no problem with handling mocked calls with PVM enabled here as well,
// but the downside is that if call a mocked call from the test it will not exercise the
// paths in revive that handle mocked calls and only nested mocks will be handle by the
// revive specific calls.
// This is undesirable because conformity tests could accidentally pass and the revive code
// paths be broken.
if !self.is_pvm_enabled() {
// Handle mocked calls
if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) {
let ctx = MockCallDataContext {
calldata: call.input.bytes(ecx),
value: call.transfer_value(),
};

if let Some(return_data_queue) = match mocks.get_mut(&ctx) {
Some(queue) => Some(queue),
None => mocks
.iter_mut()
.find(|(mock, _)| {
call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..])
&& mock.value.is_none_or(|value| Some(value) == call.transfer_value())
})
.map(|(_, v)| v),
} && let Some(return_data) = if return_data_queue.len() == 1 {
// If the mocked calls stack has a single element in it, don't empty it
return_data_queue.front().map(|x| x.to_owned())
} else {
// Else, we pop the front element
return_data_queue.pop_front()
} {
return Some(CallOutcome {
result: InterpreterResult {
result: return_data.ret_type,
output: return_data.data,
gas,
},
memory_offset: call.return_memory_offset.clone(),
});
if let Some(return_data_queue) = match mocks.get_mut(&ctx) {
Some(queue) => Some(queue),
None => mocks
.iter_mut()
.find(|(mock, _)| {
call.input.bytes(ecx).get(..mock.calldata.len())
== Some(&mock.calldata[..])
&& mock
.value
.is_none_or(|value| Some(value) == call.transfer_value())
})
.map(|(_, v)| v),
} && let Some(return_data) = if return_data_queue.len() == 1 {
// If the mocked calls stack has a single element in it, don't empty it
return_data_queue.front().map(|x| x.to_owned())
} else {
// Else, we pop the front element
return_data_queue.pop_front()
} {
return Some(CallOutcome {
result: InterpreterResult {
result: return_data.ret_type,
output: return_data.data,
gas,
},
memory_offset: call.return_memory_offset.clone(),
});
}
}
}

// Apply our prank
if let Some(prank) = &self.get_prank(curr_depth) {
// Apply delegate call, `call.caller`` will not equal `prank.prank_caller`
Expand Down
1 change: 1 addition & 0 deletions crates/cheatcodes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ mod script;
pub use script::{Broadcast, Wallets, WalletsInner};

mod strategy;
pub use evm::mock::{MockCallDataContext, MockCallReturnData};
pub use strategy::{
CheatcodeInspectorStrategy, CheatcodeInspectorStrategyContext, CheatcodeInspectorStrategyExt,
CheatcodeInspectorStrategyRunner, CheatcodesStrategy, EvmCheatcodeInspectorStrategyRunner,
Expand Down
4 changes: 4 additions & 0 deletions crates/cheatcodes/src/strategy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ impl Clone for CheatcodeInspectorStrategy {

/// Defined in revive-strategy
pub trait CheatcodeInspectorStrategyExt {
fn is_pvm_enabled(&self, _state: &mut crate::Cheatcodes) -> bool {
false
}

fn revive_try_create(
&self,
_state: &mut crate::Cheatcodes,
Expand Down
13 changes: 12 additions & 1 deletion crates/evm/evm/src/inspectors/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -914,8 +914,19 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for InspectorStackRefMut<'_>
);

if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
let is_pvm_enabled = cheatcodes.is_pvm_enabled();
// Handle mocked functions, replace bytecode address with mock if matched.
if let Some(mocks) = cheatcodes.mocked_functions.get(&call.target_address) {

// Do not handle mocked functions if PVM is enabled and let the revive call handle it.
// There is literally no problem with handling mocked functions with PVM enabled here as
// well, but the downside is that if we call a mocked functions from the test it
// will not exercise the paths in revive that handle mocked calls and only
// nested mocks will be handle by the revive specific calls.
// This is undesirable because conformity tests could accidentally pass and the revive
// code paths be broken.
if let Some(mocks) = cheatcodes.mocked_functions.get(&call.target_address)
&& !is_pvm_enabled
{
// Check if any mock function set for call data or if catch-all mock function set
// for selector.
if let Some(target) = mocks.get(&call.input.bytes(ecx)).or_else(|| {
Expand Down
Loading