diff --git a/Cargo.lock b/Cargo.lock index 50177923b8e3b..6ee290ed58383 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14331,6 +14331,7 @@ dependencies = [ "pallet-referenda", "pallet-remark", "pallet-revive", + "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 243cddc261cf9..c5300184ec1fe 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -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. diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 1a9354e382062..3d55d29922e8e 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -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, @@ -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` diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 5fe73d1aaafae..cad78b79a6038 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -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, diff --git a/crates/cheatcodes/src/strategy.rs b/crates/cheatcodes/src/strategy.rs index 2623c3fd04893..e286d66c0fc1b 100644 --- a/crates/cheatcodes/src/strategy.rs +++ b/crates/cheatcodes/src/strategy.rs @@ -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, diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 652d990b3314d..7daed9fbfae3a 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -914,8 +914,19 @@ impl Inspector> 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(|| { diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index aa5a2fecb037b..7f50d54c4e4ee 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -558,7 +558,11 @@ impl<'a> FunctionRunner<'a> { /// test ends, similar to `eth_call`. fn run_unit_test(mut self, func: &Function) -> TestResult { // Prepare unit test execution. + self.executor.strategy.runner.checkpoint(); + if self.prepare_test(func).is_err() { + self.executor.strategy.runner.reload_checkpoint(); + return self.result; } let mut binding = self.executor.clone(); @@ -576,10 +580,14 @@ impl<'a> FunctionRunner<'a> { Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)), Err(EvmError::Skip(reason)) => { self.result.single_skip(reason); + self.executor.strategy.runner.reload_checkpoint(); + return self.result; } Err(err) => { self.result.single_fail(Some(err.to_string())); + self.executor.strategy.runner.reload_checkpoint(); + return self.result; } }; @@ -587,6 +595,8 @@ impl<'a> FunctionRunner<'a> { let success = self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false); self.result.single_result(success, reason, raw_call_result); + self.executor.strategy.runner.reload_checkpoint(); + self.result } diff --git a/crates/forge/tests/cli/revive_vm.rs b/crates/forge/tests/cli/revive_vm.rs index b05744ddb4ac4..60f91b8c8cd87 100644 --- a/crates/forge/tests/cli/revive_vm.rs +++ b/crates/forge/tests/cli/revive_vm.rs @@ -473,52 +473,52 @@ Ran 2 tests for src/CounterTest.t.sol:CounterTest [PASS] test_Increment() ([GAS]) Traces: [..] CounterTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code - ├─ [0] VM::expectEmit() + ├─ [..] VM::expectEmit() │ └─ ← [Return] ├─ emit SetNumber(result: 5) - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::setNumber(5) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::setNumber(5) │ ├─ emit SetNumber(result: 5) │ └─ ← [Stop] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::number() [staticcall] + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::number() [staticcall] │ └─ ← [Return] 5 └─ ← [Stop] [..] CounterTest::test_Increment() - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::number() [staticcall] + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::number() [staticcall] │ └─ ← [Return] 5 - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::setNumber(55) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::setNumber(55) │ ├─ emit SetNumber(result: 55) │ └─ ← [Stop] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::number() [staticcall] + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::number() [staticcall] │ └─ ← [Return] 55 - ├─ [0] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::increment() + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::increment() │ ├─ emit Increment(result: 56) │ └─ ← [Stop] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::number() [staticcall] + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::number() [staticcall] │ └─ ← [Return] 56 └─ ← [Stop] [PASS] test_expectRevert() ([GAS]) Traces: [..] CounterTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code - ├─ [0] VM::expectEmit() + ├─ [..] VM::expectEmit() │ └─ ← [Return] ├─ emit SetNumber(result: 5) - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::setNumber(5) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::setNumber(5) │ ├─ emit SetNumber(result: 5) │ └─ ← [Stop] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::number() [staticcall] + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::number() [staticcall] │ └─ ← [Return] 5 └─ ← [Stop] [..] CounterTest::test_expectRevert() - ├─ [0] VM::expectRevert(custom error 0xf28dceb3: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006456941a80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000076661696c7572650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) + ├─ [..] VM::expectRevert(custom error 0xf28dceb3: 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006456941a80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000076661696c7572650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) │ └─ ← [Return] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::failed_call() [staticcall] + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::failed_call() [staticcall] │ └─ ← [Revert] Revert("failure") └─ ← [Stop] @@ -655,50 +655,50 @@ Ran 2 tests for src/Test.t.sol:RecordTest [PASS] testRecordAccess() ([GAS]) Traces: [..] RecordTest::testRecordAccess() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code - ├─ [..] → new @0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + ├─ [..] → new @0x90193C961A926261B756D1E5bb255e67ff9498A1 │ └─ ← [Return] [..] bytes of code - ├─ [0] VM::record() + ├─ [..] VM::record() │ └─ ← [Return] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::record(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f) - │ ├─ [0] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::record() + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::record(0x90193C961A926261B756D1E5bb255e67ff9498A1) + │ ├─ [..] 0x90193C961A926261B756D1E5bb255e67ff9498A1::record() │ │ └─ ← [Return] │ └─ ← [Stop] - ├─ [0] VM::accesses(0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC) + ├─ [..] VM::accesses(0x34A1D3fff3958843C43aD80F30b94c510645C316) │ └─ ← [Return] [0x0000000000000000000000000000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000000000000000000000000000001], [0x0000000000000000000000000000000000000000000000000000000000000001] - ├─ [0] VM::accesses(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f) + ├─ [..] VM::accesses(0x90193C961A926261B756D1E5bb255e67ff9498A1) │ └─ ← [Return] [0x0000000000000000000000000000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000000000000000000000000000002], [0x0000000000000000000000000000000000000000000000000000000000000002] └─ ← [Stop] [PASS] testStopRecordAccess() ([GAS]) Traces: [..] RecordTest::testStopRecordAccess() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code - ├─ [..] → new @0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + ├─ [..] → new @0x90193C961A926261B756D1E5bb255e67ff9498A1 │ └─ ← [Return] [..] bytes of code - ├─ [0] VM::record() + ├─ [..] VM::record() │ └─ ← [Return] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::record(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f) - │ ├─ [0] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::record() + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::record(0x90193C961A926261B756D1E5bb255e67ff9498A1) + │ ├─ [..] 0x90193C961A926261B756D1E5bb255e67ff9498A1::record() │ │ └─ ← [Return] │ └─ ← [Stop] - ├─ [0] VM::accesses(0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC) + ├─ [..] VM::accesses(0x34A1D3fff3958843C43aD80F30b94c510645C316) │ └─ ← [Return] [0x0000000000000000000000000000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000000000000000000000000000001], [0x0000000000000000000000000000000000000000000000000000000000000001] - ├─ [0] VM::stopRecord() + ├─ [..] VM::stopRecord() │ └─ ← [Return] - ├─ [0] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::record(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f) - │ ├─ [0] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::record() + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::record(0x90193C961A926261B756D1E5bb255e67ff9498A1) + │ ├─ [..] 0x90193C961A926261B756D1E5bb255e67ff9498A1::record() │ │ └─ ← [Return] │ └─ ← [Stop] - ├─ [0] VM::accesses(0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC) + ├─ [..] VM::accesses(0x34A1D3fff3958843C43aD80F30b94c510645C316) │ └─ ← [Return] [0x0000000000000000000000000000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000000000000000000000000000001], [0x0000000000000000000000000000000000000000000000000000000000000001] - ├─ [0] VM::record() + ├─ [..] VM::record() │ └─ ← [Return] - ├─ [0] VM::stopRecord() + ├─ [..] VM::stopRecord() │ └─ ← [Return] - ├─ [0] VM::accesses(0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC) + ├─ [..] VM::accesses(0x34A1D3fff3958843C43aD80F30b94c510645C316) │ └─ ← [Return] [], [] └─ ← [Stop] @@ -972,21 +972,21 @@ Ran 7 tests for src/Test.t.sol:RecordLogsTest [PASS] testEmitRecordEmit() ([GAS]) Traces: [..] RecordLogsTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] RecordLogsTest::testEmitRecordEmit() - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(1, 2, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(1, 2, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350) │ ├─ emit LogTopic12(topic1: 1, topic2: 2, data: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350) │ └─ ← [Stop] - ├─ [0] VM::recordLogs() + ├─ [..] VM::recordLogs() │ └─ ← [Return] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(3, 0x2e38edeff9493e0004540e975027a429) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(3, 0x2e38edeff9493e0004540e975027a429) │ ├─ emit LogTopic1(topic1: 3, data: 0x2e38edeff9493e0004540e975027a429) │ └─ ← [Stop] - ├─ [0] VM::getRecordedLogs() - │ └─ ← [Return] [([0x7c7d81fafce31d4330303f05da0ccb9d970101c475382b40aa072986ee4caaad, 0x0000000000000000000000000000000000000000000000000000000000000003], 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000102e38edeff9493e0004540e975027a42900000000000000000000000000000000, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC)] + ├─ [..] VM::getRecordedLogs() + │ └─ ← [Return] [([0x7c7d81fafce31d4330303f05da0ccb9d970101c475382b40aa072986ee4caaad, 0x0000000000000000000000000000000000000000000000000000000000000003], 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000102e38edeff9493e0004540e975027a42900000000000000000000000000000000, 0x34A1D3fff3958843C43aD80F30b94c510645C316)] ├─ storage changes: │ @ 1: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350 → 0x2e38edeff9493e0004540e975027a429ee666d1289f2c7a4232d03ee63e14e30 └─ ← [Stop] @@ -994,15 +994,15 @@ Traces: [PASS] testRecordOffGetsNothing() ([GAS]) Traces: [..] RecordLogsTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] RecordLogsTest::testRecordOffGetsNothing() - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(1, 2, 3, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c1693502e38edeff9493e0004540e975027a429) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(1, 2, 3, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c1693502e38edeff9493e0004540e975027a429) │ ├─ emit LogTopic123(topic1: 1, topic2: 2, topic3: 3, data: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c1693502e38edeff9493e0004540e975027a429) │ └─ ← [Stop] - ├─ [0] VM::getRecordedLogs() + ├─ [..] VM::getRecordedLogs() │ └─ ← [Return] [] ├─ storage changes: │ @ 1: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350 → 0x2e38edeff9493e0004540e975027a429ee666d1289f2c7a4232d03ee63e14e30 @@ -1011,76 +1011,76 @@ Traces: [PASS] testRecordOnEmitDifferentDepths() ([GAS]) Traces: [..] RecordLogsTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] RecordLogsTest::testRecordOnEmitDifferentDepths() - ├─ [0] VM::recordLogs() + ├─ [..] VM::recordLogs() │ └─ ← [Return] ├─ emit LogTopic(topic1: 1, data: 0x43a26051362b8040b289abe93334a5e3) - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(2, 3, 0x43a26051362b8040b289abe93334a5e3662751aa) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(2, 3, 0x43a26051362b8040b289abe93334a5e3662751aa) │ ├─ emit LogTopic12(topic1: 2, topic2: 3, data: 0x43a26051362b8040b289abe93334a5e3662751aa) │ └─ ← [Stop] - ├─ [..] → new @0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + ├─ [..] → new @0x90193C961A926261B756D1E5bb255e67ff9498A1 │ └─ ← [Return] [..] bytes of code - ├─ [0] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::emitEvent(4, 5, 6, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae) - │ ├─ [0] 0x104fBc016F4bb334D775a19E8A6510109AC63E00::emitEvent(4, 5, 6, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae) + ├─ [..] 0x90193C961A926261B756D1E5bb255e67ff9498A1::emitEvent(4, 5, 6, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae) + │ ├─ [..] 0xd04404bcf6d969FC0Ec22021b4736510CAcec492::emitEvent(4, 5, 6, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae) │ │ ├─ emit LogTopic123(topic1: 4, topic2: 5, topic3: 6, data: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae) │ │ └─ ← [Return] │ └─ ← [Stop] - ├─ [0] VM::getRecordedLogs() - │ └─ ← [Return] [([0x61fb7db3625c10432927a76bb32400c33a94e9bb6374137c4cd59f6e465bfdcb, 0x0000000000000000000000000000000000000000000000000000000000000001], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001043a26051362b8040b289abe93334a5e300000000000000000000000000000000, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496), ([0x7af92d5e3102a27d908bb1859fdef71b723f3c438e5d84f3af49dab68e18dc6d, 0x0000000000000000000000000000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000000000000000000000000000003], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001443a26051362b8040b289abe93334a5e3662751aa000000000000000000000000, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC), ([0xb6d650e5d0bbc0e92ff784e346ada394e49aa2d74a5cee8b099fa1a469bdc452, 0x0000000000000000000000000000000000000000000000000000000000000004, 0x0000000000000000000000000000000000000000000000000000000000000005, 0x0000000000000000000000000000000000000000000000000000000000000006], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001843a26051362b8040b289abe93334a5e3662751aa691185ae0000000000000000, 0x104fBc016F4bb334D775a19E8A6510109AC63E00)] - ├─ [..] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::getEmitterAddr() [staticcall] - │ └─ ← [Return] 0x104fBc016F4bb334D775a19E8A6510109AC63E00 + ├─ [..] VM::getRecordedLogs() + │ └─ ← [Return] [([0x61fb7db3625c10432927a76bb32400c33a94e9bb6374137c4cd59f6e465bfdcb, 0x0000000000000000000000000000000000000000000000000000000000000001], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001043a26051362b8040b289abe93334a5e300000000000000000000000000000000, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496), ([0x7af92d5e3102a27d908bb1859fdef71b723f3c438e5d84f3af49dab68e18dc6d, 0x0000000000000000000000000000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000000000000000000000000000003], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001443a26051362b8040b289abe93334a5e3662751aa000000000000000000000000, 0x34A1D3fff3958843C43aD80F30b94c510645C316), ([0xb6d650e5d0bbc0e92ff784e346ada394e49aa2d74a5cee8b099fa1a469bdc452, 0x0000000000000000000000000000000000000000000000000000000000000004, 0x0000000000000000000000000000000000000000000000000000000000000005, 0x0000000000000000000000000000000000000000000000000000000000000006], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001843a26051362b8040b289abe93334a5e3662751aa691185ae0000000000000000, 0xd04404bcf6d969FC0Ec22021b4736510CAcec492)] + ├─ [..] 0x90193C961A926261B756D1E5bb255e67ff9498A1::getEmitterAddr() [staticcall] + │ └─ ← [Return] 0xd04404bcf6d969FC0Ec22021b4736510CAcec492 └─ ← [Stop] [PASS] testRecordOnNoLogs() ([GAS]) Traces: [..] RecordLogsTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] - [4118] RecordLogsTest::testRecordOnNoLogs() - ├─ [0] VM::recordLogs() + [..] RecordLogsTest::testRecordOnNoLogs() + ├─ [..] VM::recordLogs() │ └─ ← [Return] - ├─ [0] VM::getRecordedLogs() + ├─ [..] VM::getRecordedLogs() │ └─ ← [Return] [] └─ ← [Stop] [PASS] testRecordOnSingleLog() ([GAS]) Traces: [..] RecordLogsTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] RecordLogsTest::testRecordOnSingleLog() - ├─ [0] VM::recordLogs() + ├─ [..] VM::recordLogs() │ └─ ← [Return] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(1, 2, 3, 0x4576656e74204461746120696e20537472696e67) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(1, 2, 3, 0x4576656e74204461746120696e20537472696e67) │ ├─ emit LogTopic123(topic1: 1, topic2: 2, topic3: 3, data: 0x4576656e74204461746120696e20537472696e67) │ └─ ← [Stop] - ├─ [0] VM::getRecordedLogs() - │ └─ ← [Return] [([0xb6d650e5d0bbc0e92ff784e346ada394e49aa2d74a5cee8b099fa1a469bdc452, 0x0000000000000000000000000000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000000000000000000000000000003], 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000144576656e74204461746120696e20537472696e67000000000000000000000000, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC)] + ├─ [..] VM::getRecordedLogs() + │ └─ ← [Return] [([0xb6d650e5d0bbc0e92ff784e346ada394e49aa2d74a5cee8b099fa1a469bdc452, 0x0000000000000000000000000000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000000000000000000000000000003], 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000144576656e74204461746120696e20537472696e67000000000000000000000000, 0x34A1D3fff3958843C43aD80F30b94c510645C316)] └─ ← [Stop] [PASS] testRecordOnSingleLogTopic0() ([GAS]) Traces: [..] RecordLogsTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] RecordLogsTest::testRecordOnSingleLogTopic0() - ├─ [0] VM::recordLogs() + ├─ [..] VM::recordLogs() │ └─ ← [Return] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c1693502e38edeff9493e0004540e975027a429) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c1693502e38edeff9493e0004540e975027a429) │ ├─ emit LogTopic0(data: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c1693502e38edeff9493e0004540e975027a429) │ └─ ← [Stop] - ├─ [0] VM::getRecordedLogs() - │ └─ ← [Return] [([0x0a28c6fad56bcbad1788721e440963b3b762934a3134924733eaf8622cb44279], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003043a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c1693502e38edeff9493e0004540e975027a42900000000000000000000000000000000, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC)] + ├─ [..] VM::getRecordedLogs() + │ └─ ← [Return] [([0x0a28c6fad56bcbad1788721e440963b3b762934a3134924733eaf8622cb44279], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003043a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c1693502e38edeff9493e0004540e975027a42900000000000000000000000000000000, 0x34A1D3fff3958843C43aD80F30b94c510645C316)] ├─ storage changes: │ @ 1: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350 → 0x2e38edeff9493e0004540e975027a429ee666d1289f2c7a4232d03ee63e14e30 └─ ← [Stop] @@ -1088,36 +1088,36 @@ Traces: [PASS] testRecordsConsumednAsRead() ([GAS]) Traces: [..] RecordLogsTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] RecordLogsTest::testRecordsConsumednAsRead() - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(1, 0x43a26051362b8040b289abe93334a5e3) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(1, 0x43a26051362b8040b289abe93334a5e3) │ ├─ emit LogTopic1(topic1: 1, data: 0x43a26051362b8040b289abe93334a5e3) │ └─ ← [Stop] - ├─ [0] VM::recordLogs() + ├─ [..] VM::recordLogs() │ └─ ← [Return] - ├─ [0] VM::getRecordedLogs() + ├─ [..] VM::getRecordedLogs() │ └─ ← [Return] [] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(2, 3, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(2, 3, 0x43a26051362b8040b289abe93334a5e3662751aa691185ae) │ ├─ emit LogTopic12(topic1: 2, topic2: 3, data: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae) │ └─ ← [Stop] - ├─ [0] VM::getRecordedLogs() - │ └─ ← [Return] [([0x7af92d5e3102a27d908bb1859fdef71b723f3c438e5d84f3af49dab68e18dc6d, 0x0000000000000000000000000000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000000000000000000000000000003], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001843a26051362b8040b289abe93334a5e3662751aa691185ae0000000000000000, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC)] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(4, 5, 6, 0x43a26051362b8040b289abe93334a5e3662751aa) + ├─ [..] VM::getRecordedLogs() + │ └─ ← [Return] [([0x7af92d5e3102a27d908bb1859fdef71b723f3c438e5d84f3af49dab68e18dc6d, 0x0000000000000000000000000000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000000000000000000000000000003], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001843a26051362b8040b289abe93334a5e3662751aa691185ae0000000000000000, 0x34A1D3fff3958843C43aD80F30b94c510645C316)] + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(4, 5, 6, 0x43a26051362b8040b289abe93334a5e3662751aa) │ ├─ emit LogTopic123(topic1: 4, topic2: 5, topic3: 6, data: 0x43a26051362b8040b289abe93334a5e3662751aa) │ └─ ← [Stop] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350) │ ├─ emit LogTopic0(data: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350) │ └─ ← [Stop] - ├─ [0] VM::getRecordedLogs() - │ └─ ← [Return] [([0xb6d650e5d0bbc0e92ff784e346ada394e49aa2d74a5cee8b099fa1a469bdc452, 0x0000000000000000000000000000000000000000000000000000000000000004, 0x0000000000000000000000000000000000000000000000000000000000000005, 0x0000000000000000000000000000000000000000000000000000000000000006], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001443a26051362b8040b289abe93334a5e3662751aa000000000000000000000000, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC), ([0x0a28c6fad56bcbad1788721e440963b3b762934a3134924733eaf8622cb44279], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC)] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::emitEvent(7, 8, 9, 0x2e38edeff9493e0004540e975027a429ee666d1289f2c7a4) + ├─ [..] VM::getRecordedLogs() + │ └─ ← [Return] [([0xb6d650e5d0bbc0e92ff784e346ada394e49aa2d74a5cee8b099fa1a469bdc452, 0x0000000000000000000000000000000000000000000000000000000000000004, 0x0000000000000000000000000000000000000000000000000000000000000005, 0x0000000000000000000000000000000000000000000000000000000000000006], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001443a26051362b8040b289abe93334a5e3662751aa000000000000000000000000, 0x34A1D3fff3958843C43aD80F30b94c510645C316), ([0x0a28c6fad56bcbad1788721e440963b3b762934a3134924733eaf8622cb44279], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350, 0x34A1D3fff3958843C43aD80F30b94c510645C316)] + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::emitEvent(7, 8, 9, 0x2e38edeff9493e0004540e975027a429ee666d1289f2c7a4) │ ├─ emit LogTopic123(topic1: 7, topic2: 8, topic3: 9, data: 0x2e38edeff9493e0004540e975027a429ee666d1289f2c7a4) │ └─ ← [Stop] - ├─ [0] VM::getRecordedLogs() - │ └─ ← [Return] [([0xb6d650e5d0bbc0e92ff784e346ada394e49aa2d74a5cee8b099fa1a469bdc452, 0x0000000000000000000000000000000000000000000000000000000000000007, 0x0000000000000000000000000000000000000000000000000000000000000008, 0x0000000000000000000000000000000000000000000000000000000000000009], 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000182e38edeff9493e0004540e975027a429ee666d1289f2c7a40000000000000000, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC)] + ├─ [..] VM::getRecordedLogs() + │ └─ ← [Return] [([0xb6d650e5d0bbc0e92ff784e346ada394e49aa2d74a5cee8b099fa1a469bdc452, 0x0000000000000000000000000000000000000000000000000000000000000007, 0x0000000000000000000000000000000000000000000000000000000000000008, 0x0000000000000000000000000000000000000000000000000000000000000009], 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000182e38edeff9493e0004540e975027a429ee666d1289f2c7a40000000000000000, 0x34A1D3fff3958843C43aD80F30b94c510645C316)] ├─ storage changes: │ @ 1: 0x43a26051362b8040b289abe93334a5e3662751aa691185ae9e9a2e1e0c169350 → 0x2e38edeff9493e0004540e975027a429ee666d1289f2c7a4232d03ee63e14e30 └─ ← [Stop] @@ -1268,57 +1268,57 @@ Ran 3 tests for src/Test.t.sol:StateDiffTest [PASS] testCallProxyaccesses() ([GAS]) Traces: [..] StateDiffTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code - ├─ [..] → new @0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + ├─ [..] → new @0x90193C961A926261B756D1E5bb255e67ff9498A1 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] StateDiffTest::testCallProxyaccesses() - ├─ [0] VM::startStateDiffRecording() + ├─ [..] VM::startStateDiffRecording() │ └─ ← [Return] - ├─ [..] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::proxyCall(55) - │ ├─ [0] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::setter(55) + ├─ [..] 0x90193C961A926261B756D1E5bb255e67ff9498A1::proxyCall(55) + │ ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::setter(55) │ │ └─ ← [Return] │ └─ ← [Stop] - ├─ [0] VM::stopAndReturnStateDiff() - │ └─ ← [Return] [((0, 31337 [3.133e4]), 0, 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, true, 0, 1000000000000000000 [1e18], 0x, 0, 0xac1b14ff0000000000000000000000000000000000000000000000000000000000000037, false, [(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, 0x0000000000000000000000000000000000000000000000000000000000000000, false, 0x0000000000000000000000007d8cb8f412b3ee9ac79558791333f41d2b1ccdac, 0x0000000000000000000000007d8cb8f412b3ee9ac79558791333f41d2b1ccdac, false)], 1), ((0, 31337 [3.133e4]), 0, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC, 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, true, 1000000000000000000 [1e18], 1000000000000000000 [1e18], 0x, 0, 0xd423740b0000000000000000000000000000000000000000000000000000000000000037, false, [(0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC, 0x0000000000000000000000000000000000000000000000000000000000000001, false, 0x0000000000000000000000000000000000000000000000000000000000000064, 0x0000000000000000000000000000000000000000000000000000000000000064, false), (0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC, 0x0000000000000000000000000000000000000000000000000000000000000001, true, 0x0000000000000000000000000000000000000000000000000000000000000064, 0x0000000000000000000000000000000000000000000000000000000000000037, false)], 2)] + ├─ [..] VM::stopAndReturnStateDiff() + │ └─ ← [Return] [((0, 31337 [3.133e4]), 0, 0x90193C961A926261B756D1E5bb255e67ff9498A1, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, true, 0, 1000000000000000000 [1e18], 0x, 0, 0xac1b14ff0000000000000000000000000000000000000000000000000000000000000037, false, [(0x90193C961A926261B756D1E5bb255e67ff9498A1, 0x0000000000000000000000000000000000000000000000000000000000000000, false, 0x00000000000000000000000034a1d3fff3958843c43ad80f30b94c510645c316, 0x00000000000000000000000034a1d3fff3958843c43ad80f30b94c510645c316, false)], 1), ((0, 31337 [3.133e4]), 0, 0x34A1D3fff3958843C43aD80F30b94c510645C316, 0x90193C961A926261B756D1E5bb255e67ff9498A1, true, 1000000000000000000 [1e18], 1000000000000000000 [1e18], 0x, 0, 0xd423740b0000000000000000000000000000000000000000000000000000000000000037, false, [(0x34A1D3fff3958843C43aD80F30b94c510645C316, 0x0000000000000000000000000000000000000000000000000000000000000001, false, 0x0000000000000000000000000000000000000000000000000000000000000064, 0x0000000000000000000000000000000000000000000000000000000000000064, false), (0x34A1D3fff3958843C43aD80F30b94c510645C316, 0x0000000000000000000000000000000000000000000000000000000000000001, true, 0x0000000000000000000000000000000000000000000000000000000000000064, 0x0000000000000000000000000000000000000000000000000000000000000037, false)], 2)] └─ ← [Stop] [PASS] testCallaccesses() ([GAS]) Traces: [..] StateDiffTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code - ├─ [..] → new @0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + ├─ [..] → new @0x90193C961A926261B756D1E5bb255e67ff9498A1 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] StateDiffTest::testCallaccesses() - ├─ [0] VM::startStateDiffRecording() + ├─ [..] VM::startStateDiffRecording() │ └─ ← [Return] - ├─ [..] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::setter(55) + ├─ [..] 0x34A1D3fff3958843C43aD80F30b94c510645C316::setter(55) │ └─ ← [Stop] - ├─ [0] VM::stopAndReturnStateDiff() - │ └─ ← [Return] [((0, 31337 [3.133e4]), 0, 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, true, 1000000000000000000 [1e18], 1000000000000000000 [1e18], 0x, 0, 0xd423740b0000000000000000000000000000000000000000000000000000000000000037, false, [(0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC, 0x0000000000000000000000000000000000000000000000000000000000000001, false, 0x0000000000000000000000000000000000000000000000000000000000000064, 0x0000000000000000000000000000000000000000000000000000000000000064, false), (0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC, 0x0000000000000000000000000000000000000000000000000000000000000001, true, 0x0000000000000000000000000000000000000000000000000000000000000064, 0x0000000000000000000000000000000000000000000000000000000000000037, false)], 1)] + ├─ [..] VM::stopAndReturnStateDiff() + │ └─ ← [Return] [((0, 31337 [3.133e4]), 0, 0x34A1D3fff3958843C43aD80F30b94c510645C316, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, true, 1000000000000000000 [1e18], 1000000000000000000 [1e18], 0x, 0, 0xd423740b0000000000000000000000000000000000000000000000000000000000000037, false, [(0x34A1D3fff3958843C43aD80F30b94c510645C316, 0x0000000000000000000000000000000000000000000000000000000000000001, false, 0x0000000000000000000000000000000000000000000000000000000000000064, 0x0000000000000000000000000000000000000000000000000000000000000064, false), (0x34A1D3fff3958843C43aD80F30b94c510645C316, 0x0000000000000000000000000000000000000000000000000000000000000001, true, 0x0000000000000000000000000000000000000000000000000000000000000064, 0x0000000000000000000000000000000000000000000000000000000000000037, false)], 1)] └─ ← [Stop] [PASS] testCreateaccesses() ([GAS]) Traces: [..] StateDiffTest::setUp() - ├─ [..] → new @0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC + ├─ [..] → new @0x34A1D3fff3958843C43aD80F30b94c510645C316 │ └─ ← [Return] [..] bytes of code - ├─ [..] → new @0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + ├─ [..] → new @0x90193C961A926261B756D1E5bb255e67ff9498A1 │ └─ ← [Return] [..] bytes of code └─ ← [Stop] [..] StateDiffTest::testCreateaccesses() - ├─ [0] VM::startStateDiffRecording() + ├─ [..] VM::startStateDiffRecording() │ └─ ← [Return] - ├─ [..] → new @0x2e234DAe75C793f67A35089C9d99245E1C58470b + ├─ [..] → new @0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496 │ └─ ← [Return] [..] bytes of code - ├─ [0] VM::stopAndReturnStateDiff() - │ └─ ← [Return] [((0, 31337 [3.133e4]), 4, 0x2e234DAe75C793f67A35089C9d99245E1C58470b, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, true, 0, 1000000000000000000 [1e18], 0x, 1000000000000000000 [1e18], 0x0000000000000000000000000000000000000000000000000000000000000064, false, [(0x2e234DAe75C793f67A35089C9d99245E1C58470b, 0x0000000000000000000000000000000000000000000000000000000000000001, false, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, false), (0x2e234DAe75C793f67A35089C9d99245E1C58470b, 0x0000000000000000000000000000000000000000000000000000000000000001, true, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000064, false)], 1)] + ├─ [..] VM::stopAndReturnStateDiff() + │ └─ ← [Return] [((0, 31337 [3.133e4]), 4, 0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496, 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, true, 0, 1000000000000000000 [1e18], 0x, 1000000000000000000 [1e18], 0x0000000000000000000000000000000000000000000000000000000000000064, false, [(0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496, 0x0000000000000000000000000000000000000000000000000000000000000001, false, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, false), (0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496, 0x0000000000000000000000000000000000000000000000000000000000000001, true, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000064, false)], 1)] └─ ← [Stop] Suite result: ok. 3 passed; 0 failed; 0 skipped; [ELAPSED] diff --git a/crates/forge/tests/it/revive/cheat_mock_call.rs b/crates/forge/tests/it/revive/cheat_mock_call.rs new file mode 100644 index 0000000000000..d291db31cc65a --- /dev/null +++ b/crates/forge/tests/it/revive/cheat_mock_call.rs @@ -0,0 +1,16 @@ +use crate::{config::*, test_helpers::TEST_DATA_REVIVE}; +use foundry_test_utils::Filter; +use revive_strategy::ReviveRuntimeMode; +use revm::primitives::hardfork::SpecId; +use rstest::rstest; + +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] +#[tokio::test(flavor = "multi_thread")] +async fn test_mock_call(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); + let filter = Filter::new(".*", "MockCall", ".*/revive/MockCall.t.sol"); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; +} diff --git a/crates/forge/tests/it/revive/cheat_mock_calls.rs b/crates/forge/tests/it/revive/cheat_mock_calls.rs new file mode 100644 index 0000000000000..a4fa94f31ce41 --- /dev/null +++ b/crates/forge/tests/it/revive/cheat_mock_calls.rs @@ -0,0 +1,16 @@ +use crate::{config::*, test_helpers::TEST_DATA_REVIVE}; +use foundry_test_utils::Filter; +use revive_strategy::ReviveRuntimeMode; +use revm::primitives::hardfork::SpecId; +use rstest::rstest; + +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] +#[tokio::test(flavor = "multi_thread")] +async fn test_mock_calls(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); + let filter = Filter::new(".*", ".*", ".*/revive/MockCalls.t.sol"); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; +} diff --git a/crates/forge/tests/it/revive/cheat_mock_functions.rs b/crates/forge/tests/it/revive/cheat_mock_functions.rs new file mode 100644 index 0000000000000..f31e5ed17e91c --- /dev/null +++ b/crates/forge/tests/it/revive/cheat_mock_functions.rs @@ -0,0 +1,43 @@ +use crate::{config::*, test_helpers::TEST_DATA_REVIVE}; +use foundry_test_utils::Filter; +use revive_strategy::ReviveRuntimeMode; +use revm::primitives::hardfork::SpecId; +use rstest::rstest; + +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] +#[tokio::test(flavor = "multi_thread")] +async fn test_mockx_function(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); + let filter = Filter::new("test_mockx_function", "MockFunction", ".*/revive/MockFunction.t.sol"); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; +} + +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] +#[tokio::test(flavor = "multi_thread")] +async fn test_mock_function_concrete_args(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); + let filter = Filter::new( + "test_mock_function_concrete_args", + "MockFunction", + ".*/revive/MockFunction.t.sol", + ); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; +} + +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] +#[tokio::test(flavor = "multi_thread")] +async fn test_mock_function_all_args(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); + let filter = + Filter::new("test_mock_function_all_args", "MockFunction", ".*/revive/MockFunction.t.sol"); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; +} diff --git a/crates/forge/tests/it/revive/cheat_prank.rs b/crates/forge/tests/it/revive/cheat_prank.rs new file mode 100644 index 0000000000000..31c850d07b0ef --- /dev/null +++ b/crates/forge/tests/it/revive/cheat_prank.rs @@ -0,0 +1,16 @@ +use crate::{config::*, test_helpers::TEST_DATA_REVIVE}; +use foundry_test_utils::Filter; +use revive_strategy::ReviveRuntimeMode; +use revm::primitives::hardfork::SpecId; +use rstest::rstest; + +#[rstest] +#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] +#[tokio::test(flavor = "multi_thread")] +async fn test_revive_prank(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); + let filter = Filter::new(".*", ".*", ".*/revive/Prank.t.sol.*"); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; +} diff --git a/crates/forge/tests/it/revive/mod.rs b/crates/forge/tests/it/revive/mod.rs index dd8e7248c9892..f2adea64037fc 100644 --- a/crates/forge/tests/it/revive/mod.rs +++ b/crates/forge/tests/it/revive/mod.rs @@ -1,4 +1,8 @@ //! Revive strategy tests +pub mod cheat_mock_call; +pub mod cheat_mock_calls; +pub mod cheat_mock_functions; +pub mod cheat_prank; pub mod cheat_store; pub mod migration; diff --git a/crates/revive-strategy/Cargo.toml b/crates/revive-strategy/Cargo.toml index a6141c95fe9e3..93c181e6df8cf 100644 --- a/crates/revive-strategy/Cargo.toml +++ b/crates/revive-strategy/Cargo.toml @@ -41,6 +41,7 @@ polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch "runtime", "polkadot-runtime-common", "pallet-revive", + "pallet-revive-uapi", "pallet-balances", "pallet-timestamp" ]} diff --git a/crates/revive-strategy/src/cheatcodes/mock_handler.rs b/crates/revive-strategy/src/cheatcodes/mock_handler.rs new file mode 100644 index 0000000000000..a81a8d0cf2d06 --- /dev/null +++ b/crates/revive-strategy/src/cheatcodes/mock_handler.rs @@ -0,0 +1,211 @@ +use std::{ + cell::RefCell, + collections::{BTreeMap, VecDeque}, + rc::Rc, +}; + +use alloy_primitives::{Address, Bytes, map::foldhash::HashMap, ruint::aliases::U256}; +use foundry_cheatcodes::{Ecx, MockCallDataContext, MockCallReturnData}; +use polkadot_sdk::{ + frame_system, + pallet_revive::{ + self, AddressMapper, DelegateInfo, ExecOrigin, ExecReturnValue, Pallet, mock::MockHandler, + }, + pallet_revive_uapi::ReturnFlags, + polkadot_sdk_frame::prelude::OriginFor, + sp_core::H160, +}; +use revive_env::{AccountId, Runtime}; + +use revm::{context::JournalTr, interpreter::InstructionResult}; + +// Implementation object that holds the mock state and implements the MockHandler trait for Revive. +// It is only purpose is to make transferring the mock state into the Revive EVM easier and then +// synchronize whatever mocks got consumed back into the Cheatcodes state after the call. +#[derive(Clone)] +pub(crate) struct MockHandlerImpl { + inner: Rc>>, + pub _prank_enabled: bool, +} + +impl MockHandlerImpl { + /// Creates a new MockHandlerImpl from the given Ecx and Cheatcodes state. + pub(crate) fn new( + ecx: &Ecx<'_, '_, '_>, + caller: &Address, + target_address: Option<&Address>, + callee: Option<&Address>, + state: &mut foundry_cheatcodes::Cheatcodes, + ) -> Self { + let (inject_env, prank_enabled) = + MockHandlerInner::new(ecx, caller, target_address, callee, state); + Self { inner: Rc::new(RefCell::new(inject_env)), _prank_enabled: prank_enabled } + } + + /// Updates the given Cheatcodes state with the current mock state. + /// This is used to synchronize the mock state after a call has been executed in Revive + pub(crate) fn update_state_mocks(&self, state: &mut foundry_cheatcodes::Cheatcodes) { + let mock_inner = self.inner.borrow(); + state.mocked_calls = mock_inner.mocked_calls.clone(); + state.mocked_functions = mock_inner.mocked_functions.clone(); + } + + pub(crate) fn fund_pranked_accounts(&self, account: Address) { + // Fuzzed prank addresses have no balance, so they won't exist in revive, and + // calls will fail, this is not a problem when running in REVM. + // TODO: Figure it out why this is still needed. + let balance = Pallet::::evm_balance(&H160::from_slice(account.as_slice())); + if balance == 0.into() { + Pallet::::set_evm_balance( + &H160::from_slice(account.as_slice()), + u128::MAX.into(), + ) + .expect("Could not fund pranked account"); + } + } +} + +impl MockHandler for MockHandlerImpl { + fn mock_call( + &self, + callee: H160, + call_data: &[u8], + value_transferred: polkadot_sdk::pallet_revive::U256, + ) -> Option { + let mut mock_inner = self.inner.borrow_mut(); + let ctx = MockCallDataContext { + calldata: call_data.to_vec().into(), + value: Some(U256::from_limbs(value_transferred.0)), + }; + + // Use the same logic as in inspect.rs to find the correct mocked call and consume some of + // them. https://github.com/paritytech/foundry-polkadot/blob/26eda0de53ac03f7ac9b6a6023d8243101cffaf1/crates/cheatcodes/src/inspector.rs#L1013 + if let Some(mock_data) = + mock_inner.mocked_calls.get_mut(&Address::from_slice(callee.as_bytes())) + { + if let Some(return_data_queue) = match mock_data.get_mut(&ctx) { + Some(found) => Some(found), + None => mock_data + .iter_mut() + .find(|(key, _)| { + ctx.calldata.starts_with(&key.calldata) + && (key.value.is_none() + || ctx.value == key.value + || (ctx.value == Some(U256::ZERO) && key.value.is_none())) + }) + .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(ExecReturnValue { + flags: if matches!(return_data.ret_type, InstructionResult::Revert) { + ReturnFlags::REVERT + } else { + ReturnFlags::default() + }, + data: return_data.data.0.to_vec(), + }); + } + }; + None + } + + fn mock_caller(&self, frames_len: usize) -> Option> { + let mock_inner = self.inner.borrow(); + if frames_len == 0 && mock_inner.delegated_caller.is_none() { + return Some(mock_inner.caller.clone()); + } + None + } + + fn mock_delegated_caller( + &self, + dest: H160, + input_data: &[u8], + ) -> Option> { + let mock_inner = self.inner.borrow(); + + // Mocked functions are implemented by making use of the hooks for delegated calls. + if let Some(mocked_function) = + mock_inner.mocked_functions.get(&Address::from_slice(dest.as_bytes())) + { + let input_data = Bytes::from(input_data.to_vec()); + if let Some(target) = mocked_function + .get(&input_data) + .or_else(|| input_data.get(..4).and_then(|selector| mocked_function.get(selector))) + { + return Some(DelegateInfo { + caller: + ExecOrigin::::from_runtime_origin(OriginFor::::signed( + ::AddressMapper::to_account_id(&dest), + )).ok()?, + callee: H160::from_slice(target.as_slice()) + } + ); + } + } + + mock_inner.delegated_caller.as_ref().and_then(|delegate_caller| { + Some(DelegateInfo { + caller: ExecOrigin::::from_runtime_origin(delegate_caller.clone()).ok()?, + callee: mock_inner.callee, + }) + }) + } +} + +// Internal struct that holds the mock state. It is wrapped in an Arc> in MockHandlerImpl +// to make it easier to transfer the state into Revive and back and be able to mutate it from the +// MockHandler trait methods. +#[derive(Clone)] +struct MockHandlerInner { + pub caller: OriginFor, + pub delegated_caller: Option>, + pub callee: H160, + + pub mocked_calls: HashMap>>, + pub mocked_functions: HashMap>, +} + +impl MockHandlerInner { + /// Creates a new MockHandlerInner from the given Ecx and Cheatcodes state. + /// Also returns whether a prank is currently enabled. + fn new( + ecx: &Ecx<'_, '_, '_>, + caller: &Address, + target_address: Option<&Address>, + callee: Option<&Address>, + state: &mut foundry_cheatcodes::Cheatcodes, + ) -> (Self, bool) { + let curr_depth = ecx.journaled_state.depth(); + let mut prank_enabled = false; + let pranked_caller = OriginFor::::signed(AccountId::to_fallback_account_id( + &H160::from_slice(caller.as_slice()), + )); + + let delegated_caller = target_address.map(|addr| { + OriginFor::::signed(AccountId::to_fallback_account_id(&H160::from_slice( + addr.as_slice(), + ))) + }); + + let state_inject = Self { + caller: pranked_caller, + delegated_caller, + mocked_calls: state.mocked_calls.clone(), + callee: callee.map(|addr| H160::from_slice(addr.as_slice())).unwrap_or_default(), + mocked_functions: state.mocked_functions.clone(), + }; + if let Some(prank) = &state.get_prank(curr_depth) { + if curr_depth >= prank.depth { + prank_enabled = true; + } + } + (state_inject, prank_enabled) + } +} diff --git a/crates/revive-strategy/src/cheatcodes/mod.rs b/crates/revive-strategy/src/cheatcodes/mod.rs index 5fe5c4d81af6e..3706fe5e50559 100644 --- a/crates/revive-strategy/src/cheatcodes/mod.rs +++ b/crates/revive-strategy/src/cheatcodes/mod.rs @@ -1,3 +1,5 @@ +mod mock_handler; + use alloy_primitives::{Address, B256, Bytes, Log, hex, ruint::aliases::U256}; use alloy_rpc_types::BlobTransactionSidecar; use alloy_sol_types::SolValue; @@ -11,6 +13,7 @@ use foundry_cheatcodes::{ }, journaled_account, precompile_error, }; + use foundry_compilers::resolc::dual_compiled_contracts::DualCompiledContracts; use revive_env::{AccountId, Runtime, System, Timestamp}; use std::{ @@ -31,6 +34,7 @@ use polkadot_sdk::{ }; use crate::{ + cheatcodes::mock_handler::MockHandlerImpl, execute_with_externalities, tracing::{Tracer, storage_tracer::AccountAccess}, }; @@ -41,7 +45,7 @@ use revm::{ bytecode::opcode as op, context::{CreateScheme, JournalTr}, interpreter::{ - CallInputs, CallOutcome, CreateOutcome, Gas, InstructionResult, Interpreter, + CallInputs, CallOutcome, CallScheme, CreateOutcome, Gas, InstructionResult, Interpreter, InterpreterResult, interpreter_types::Jumps, }, state::Bytecode, @@ -709,6 +713,12 @@ fn select_evm(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, } impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspectorStrategyRunner { + fn is_pvm_enabled(&self, state: &mut foundry_cheatcodes::Cheatcodes) -> bool { + let ctx = get_context_ref_mut(state.strategy.context.as_mut()); + + ctx.using_pvm + } + /// Try handling the `CREATE` within PVM. /// /// If `Some` is returned then the result must be returned immediately, else the call must be @@ -720,7 +730,10 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector input: &dyn CommonCreateInput, executor: &mut dyn foundry_cheatcodes::CheatcodesExecutor, ) -> Option { - let ctx = get_context_ref_mut(state.strategy.context.as_mut()); + let mock_handler = MockHandlerImpl::new(&ecx, &input.caller(), None, None, state); + + let ctx: &mut PvmCheatcodeInspectorStrategyContext = + get_context_ref_mut(state.strategy.context.as_mut()); if !ctx.using_pvm { return None; @@ -777,10 +790,25 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector externalities.execute_with(|| { tracer.trace(|| { let origin = OriginFor::::signed(AccountId::to_fallback_account_id( - &H160::from_slice(input.caller().as_slice()), + &H160::from_slice(ecx.tx.caller.as_slice()), )); let evm_value = sp_core::U256::from_little_endian(&input.value().as_le_bytes()); + mock_handler.fund_pranked_accounts(ecx.tx.caller); + + // Pre-Dispatch Increments the nonce of the origin, so let's make sure we do + // that here too to replicate the same address generation. + System::inc_account_nonce(AccountId::to_fallback_account_id( + &H160::from_slice(ecx.tx.caller.as_slice()), + )); + + let exec_config = ExecConfig { + bump_nonce: true, + collect_deposit_from_hold: None, + effective_gas_price: Some(>::evm_base_fee()), + mock_handler: Some(Box::new(mock_handler.clone())), + is_dry_run: false, + }; let code = Code::Upload(code_bytes.clone()); let data = constructor_args; let salt = match input.scheme() { @@ -804,17 +832,18 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector code, data, salt, - ExecConfig::new_substrate_tx(), + exec_config, ) }) }) }); - let mut gas = Gas::new(input.gas_limit()); if res.result.as_ref().is_ok_and(|r| !r.result.did_revert()) { self.append_recorded_accesses(state, ecx, tracer.get_recorded_accesses()); } post_exec(state, ecx, executor, &mut tracer, false); + mock_handler.update_state_mocks(state); + match &res.result { Ok(result) => { let _ = gas.record_cost(res.gas_required.ref_time()); @@ -869,6 +898,10 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector executor: &mut dyn foundry_cheatcodes::CheatcodesExecutor, ) -> Option { let ctx = get_context_ref_mut(state.strategy.context.as_mut()); + let target_address = match call.scheme { + CallScheme::DelegateCall => Some(call.target_address), + _ => None, + }; if !ctx.using_pvm { return None; @@ -889,19 +922,35 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector } tracing::info!("running call on pallet-revive with {} {:#?}", ctx.runtime_mode, call); + + let mock_handler = MockHandlerImpl::new( + &ecx, + &call.caller, + target_address.as_ref(), + Some(&call.bytecode_address), + state, + ); + let mut tracer = Tracer::new(true); let res = execute_with_externalities(|externalities| { externalities.execute_with(|| { tracer.trace(|| { let origin = OriginFor::::signed(AccountId::to_fallback_account_id( - &H160::from_slice(call.caller.as_slice()), + &H160::from_slice(ecx.tx.caller.as_slice()), )); + mock_handler.fund_pranked_accounts(ecx.tx.caller); + let evm_value = sp_core::U256::from_little_endian(&call.call_value().as_le_bytes()); - let target = H160::from_slice(call.target_address.as_slice()); - + let exec_config = ExecConfig { + bump_nonce: true, + collect_deposit_from_hold: None, + effective_gas_price: Some(>::evm_base_fee()), + mock_handler: Some(Box::new(mock_handler.clone())), + is_dry_run: false, + }; Pallet::::bare_call( origin, target, @@ -910,12 +959,12 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector // TODO: fixing. BalanceOf::::MAX, call.input.bytes(ecx).to_vec(), - ExecConfig::new_substrate_tx(), + exec_config, ) }) }) }); - + mock_handler.update_state_mocks(state); let mut gas = Gas::new(call.gas_limit); if res.result.as_ref().is_ok_and(|r| !r.did_revert()) { self.append_recorded_accesses(state, ecx, tracer.get_recorded_accesses()); diff --git a/testdata/default/revive/MockCall.t.sol b/testdata/default/revive/MockCall.t.sol new file mode 100644 index 0000000000000..43b04d5d5c29b --- /dev/null +++ b/testdata/default/revive/MockCall.t.sol @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../../default/logs/console.sol"; + +contract Mock { + uint256 state = 0; + + function numberA() public pure returns (uint256) { + return 1; + } + + function numberB() public pure returns (uint256) { + return 2; + } + + function numberBPayable() public payable returns (uint256) { + return 2; + } + + function add(uint256 a, uint256 b) public pure returns (uint256) { + return a + b; + } + + function pay(uint256 a) public payable returns (uint256) { + return a; + } + + function noReturnValue() public { + // Does nothing of value, but also ensures that Solidity will 100% + // generate an `extcodesize` check. + state += 1; + } +} + +contract NestedMock { + Mock private inner; + + constructor(Mock _inner) { + inner = _inner; + } + + function sum() public view returns (uint256) { + return inner.numberA() + inner.numberB(); + } + + function sumPay() public returns (uint256) { + return inner.numberA() + inner.numberBPayable{value: 10}(); + } +} + +contract NestedMockDelegateCall { + Mock private inner; + + constructor(Mock _inner) { + inner = _inner; + } + + function sum() public returns (uint256) { + (, bytes memory dataA) = address(inner).delegatecall(abi.encodeWithSelector(Mock.numberA.selector)); + (, bytes memory dataB) = address(inner).delegatecall(abi.encodeWithSelector(Mock.numberB.selector)); + return abi.decode(dataA, (uint256)) + abi.decode(dataB, (uint256)); + } +} + +contract MockCallTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testMockGetters() public { + vm.pvm(true); + Mock target = new Mock(); + + // pre-mock + assertEq(target.numberA(), 1); + assertEq(target.numberB(), 2); + + vm.mockCall(address(target), abi.encodeWithSelector(target.numberB.selector), abi.encode(10)); + + // post-mock + assertEq(target.numberA(), 1); + assertEq(target.numberB(), 10); + } + + function testMockNestedSimple() public { + vm.pvm(true); + + Mock inner = new Mock(); + NestedMock target = new NestedMock(inner); + + // pre-mock + assertEq(target.sum(), 3); + console.log("SUM BEFORE MOCK", address(inner)); + vm.mockCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), abi.encode(9)); + + // post-mock + assertEq(target.sum(), 10); + } + + function testMockNestedEmptyAccount() public { + vm.pvm(true); + + Mock inner = Mock(address(100)); + NestedMock target = new NestedMock(inner); + + vm.mockCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), abi.encode(9)); + vm.mockCall(address(inner), abi.encodeWithSelector(inner.numberA.selector), abi.encode(1)); + + // post-mock + assertEq(target.sum(), 10); + } + + function testMockNestedPayDoesntTransfer() public { + vm.pvm(true); + + Mock inner = new Mock(); + NestedMock target = new NestedMock(inner); + + vm.mockCall(address(inner), abi.encodeWithSelector(inner.numberBPayable.selector), abi.encode(9)); + // Check balance of inner before and after call to ensure no ETH was transferred + uint256 balance_before = address(inner).balance; + assertEq(target.sumPay(), 10); + uint256 balance_after = address(inner).balance; + assertEq(balance_before, balance_after); + } + + // Ref: https://github.com/foundry-rs/foundry/issues/8066 + function testMockNestedDelegate() public { + vm.pvm(true); + + Mock inner = new Mock(); + NestedMockDelegateCall target = new NestedMockDelegateCall(inner); + + assertEq(target.sum(), 3); + + vm.mockCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), abi.encode(9)); + + assertEq(target.sum(), 10); + } + + function testMockSelector() public { + vm.pvm(true); + + Mock target = new Mock(); + assertEq(target.add(5, 5), 10); + + vm.mockCall(address(target), abi.encodeWithSelector(target.add.selector), abi.encode(11)); + + assertEq(target.add(5, 5), 11); + } + + function testMockCalldata() public { + vm.pvm(true); + + Mock target = new Mock(); + assertEq(target.add(5, 5), 10); + assertEq(target.add(6, 4), 10); + + vm.mockCall(address(target), abi.encodeWithSelector(target.add.selector, 5, 5), abi.encode(11)); + + assertEq(target.add(5, 5), 11); + assertEq(target.add(6, 4), 10); + } + + function testClearMockedCalls() public { + vm.pvm(true); + + Mock target = new Mock(); + + vm.mockCall(address(target), abi.encodeWithSelector(target.numberB.selector), abi.encode(10)); + + assertEq(target.numberA(), 1); + assertEq(target.numberB(), 10); + + vm.clearMockedCalls(); + + assertEq(target.numberA(), 1); + assertEq(target.numberB(), 2); + } + + function testMockCallMultiplePartialMatch() public { + vm.pvm(true); + + Mock mock = new Mock(); + + vm.mockCall(address(mock), abi.encodeWithSelector(mock.add.selector), abi.encode(10)); + vm.mockCall(address(mock), abi.encodeWithSelector(mock.add.selector, 2), abi.encode(20)); + vm.mockCall(address(mock), abi.encodeWithSelector(mock.add.selector, 2, 3), abi.encode(30)); + + assertEq(mock.add(1, 2), 10); + assertEq(mock.add(2, 2), 20); + assertEq(mock.add(2, 3), 30); + } + + function testMockCallWithValue() public { + vm.pvm(true); + Mock mock = new Mock(); + + vm.mockCall(address(mock), 10, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + + assertEq(mock.pay{value: 10}(1), 10); + assertEq(mock.pay(1), 1); + + for (uint256 i = 0; i < 100; i++) { + vm.mockCall(address(mock), i, abi.encodeWithSelector(mock.pay.selector), abi.encode(i * 2)); + } + + assertEq(mock.pay(1), 0); + assertEq(mock.pay{value: 10}(1), 20); + assertEq(mock.pay{value: 50}(1), 100); + } + + function testMockCallWithValueCalldataPrecedence() public { + vm.pvm(true); + + Mock mock = new Mock(); + + vm.mockCall(address(mock), 10, abi.encodeWithSelector(mock.pay.selector), abi.encode(10)); + vm.mockCall(address(mock), abi.encodeWithSelector(mock.pay.selector, 2), abi.encode(2)); + + assertEq(mock.pay{value: 10}(1), 10); + assertEq(mock.pay{value: 10}(2), 2); + assertEq(mock.pay(2), 2); + } + + function testMockCallEmptyAccount() public { + vm.pvm(true); + + Mock mock = Mock(address(100)); + + vm.mockCall(address(mock), abi.encodeWithSelector(mock.add.selector), abi.encode(10)); + vm.mockCall(address(mock), mock.noReturnValue.selector, abi.encode()); + + assertEq(mock.add(1, 2), 10); + mock.noReturnValue(); + } +} + +contract MockCallRevertTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + error TestError(bytes msg); + + bytes constant ERROR_MESSAGE = "ERROR_MESSAGE"; + + function testMockGettersRevert() public { + vm.pvm(true); + + Mock target = new Mock(); + + // pre-mock + assertEq(target.numberA(), 1); + assertEq(target.numberB(), 2); + + vm.mockCallRevert(address(target), target.numberB.selector, ERROR_MESSAGE); + + // post-mock + assertEq(target.numberA(), 1); + try target.numberB() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } + + function testMockRevertWithCustomError() public { + vm.pvm(true); + + Mock target = new Mock(); + + assertEq(target.numberA(), 1); + assertEq(target.numberB(), 2); + + bytes memory customError = abi.encodeWithSelector(TestError.selector, ERROR_MESSAGE); + + vm.mockCallRevert(address(target), abi.encodeWithSelector(target.numberB.selector), customError); + + assertEq(target.numberA(), 1); + try target.numberB() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(customError)); + } + } + + function testMockNestedRevert() public { + vm.pvm(true); + Mock inner = new Mock(); + NestedMock target = new NestedMock(inner); + + assertEq(target.sum(), 3); + + vm.mockCallRevert(address(inner), abi.encodeWithSelector(inner.numberB.selector), ERROR_MESSAGE); + + try target.sum() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } + + function testMockCalldataRevert() public { + vm.pvm(true); + + Mock target = new Mock(); + assertEq(target.add(5, 5), 10); + assertEq(target.add(6, 4), 10); + + vm.mockCallRevert(address(target), abi.encodeWithSelector(target.add.selector, 5, 5), ERROR_MESSAGE); + + assertEq(target.add(6, 4), 10); + + try target.add(5, 5) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } + + function testClearMockRevertedCalls() public { + vm.pvm(true); + + Mock target = new Mock(); + + vm.mockCallRevert(address(target), abi.encodeWithSelector(target.numberB.selector), ERROR_MESSAGE); + + vm.clearMockedCalls(); + + assertEq(target.numberA(), 1); + assertEq(target.numberB(), 2); + } + + function testMockCallRevertPartialMatch() public { + vm.pvm(true); + + Mock mock = new Mock(); + + vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector, 2), ERROR_MESSAGE); + + assertEq(mock.add(1, 2), 3); + + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } + + function testMockCallRevertWithValue() public { + vm.pvm(true); + + Mock mock = new Mock(); + + vm.mockCallRevert(address(mock), 10, abi.encodeWithSelector(mock.pay.selector), ERROR_MESSAGE); + + assertEq(mock.pay(1), 1); + assertEq(mock.pay(2), 2); + + try mock.pay{value: 10}(1) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } + + function testMockCallResetsMockCallRevert() public { + vm.pvm(true); + + Mock mock = new Mock(); + + vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector), ERROR_MESSAGE); + + vm.mockCall(address(mock), abi.encodeWithSelector(mock.add.selector), abi.encode(5)); + assertEq(mock.add(2, 3), 5); + } + + function testMockCallRevertResetsMockCall() public { + vm.pvm(true); + + Mock mock = new Mock(); + + vm.mockCall(address(mock), abi.encodeWithSelector(mock.add.selector), abi.encode(5)); + assertEq(mock.add(2, 3), 5); + + vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector), ERROR_MESSAGE); + + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } + + function testMockCallRevertWithCall() public { + vm.pvm(true); + + Mock mock = new Mock(); + + bytes memory customError = abi.encodeWithSelector(TestError.selector, ERROR_MESSAGE); + + vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector), customError); + + (bool success, bytes memory data) = address(mock).call(abi.encodeWithSelector(Mock.add.selector, 2, 3)); + assertEq(success, false); + assertEq(data, customError); + } + + function testMockCallEmptyAccountRevert() public { + vm.pvm(true); + + Mock mock = Mock(address(100)); + + vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector), ERROR_MESSAGE); + + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } + } +} diff --git a/testdata/default/revive/MockCalls.t.sol b/testdata/default/revive/MockCalls.t.sol new file mode 100644 index 0000000000000..445279db832e5 --- /dev/null +++ b/testdata/default/revive/MockCalls.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../../default/logs/console.sol"; + +contract MockCallsTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testMockCallsLastShouldPersist() public { + vm.pvm(true); + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](2); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(7.219 ether); + vm.mockCalls(mockErc20, data, mocks); + (, bytes memory ret1) = mockErc20.call(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call(data); + assertEq(abi.decode(ret2, (uint256)), 7.219 ether); + (, bytes memory ret3) = mockErc20.call(data); + assertEq(abi.decode(ret3, (uint256)), 7.219 ether); + } + + function testMockCallsWithValue() public { + vm.pvm(true); + + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](3); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(1 ether); + mocks[2] = abi.encode(6.423 ether); + vm.mockCalls(mockErc20, 1 ether, data, mocks); + (, bytes memory ret1) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret2, (uint256)), 1 ether); + (, bytes memory ret3) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + } + + function testMockCalls() public { + vm.pvm(true); + + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](3); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(1 ether); + mocks[2] = abi.encode(6.423 ether); + vm.mockCalls(mockErc20, data, mocks); + (, bytes memory ret1) = mockErc20.call(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call(data); + assertEq(abi.decode(ret2, (uint256)), 1 ether); + (, bytes memory ret3) = mockErc20.call(data); + assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + } +} diff --git a/testdata/default/revive/MockFunction.t.sol b/testdata/default/revive/MockFunction.t.sol new file mode 100644 index 0000000000000..c3b99ad812215 --- /dev/null +++ b/testdata/default/revive/MockFunction.t.sol @@ -0,0 +1,75 @@ +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../../default/logs/console.sol"; + +contract MockFunctionContract { + uint256 public a; + + function mocked_function() public { + a = 321; + } + + function mocked_args_function(uint256 x) public { + a = 321 + x; + } +} + +contract ModelMockFunctionContract { + uint256 public a; + + function mocked_function() public { + a = 123; + } + + function mocked_args_function(uint256 x) public { + a = 123 + x; + } +} + +contract MockFunctionTest is DSTest { + MockFunctionContract my_contract; + ModelMockFunctionContract model_contract; + Vm vm = Vm(HEVM_ADDRESS); + + function setUp() public { + vm.pvm(true); + my_contract = new MockFunctionContract(); + model_contract = new ModelMockFunctionContract(); + } + + function test_mockx_function() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_contract.mocked_function(); + assertEq(my_contract.a(), 123); + } + + function test_mock_function_concrete_args() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 456) + ); + my_contract.mocked_args_function(456); + assertEq(my_contract.a(), 123 + 456); + my_contract.mocked_args_function(567); + assertEq(my_contract.a(), 321 + 567); + } + + function test_mock_function_all_args() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_contract.mocked_args_function(678); + assertEq(my_contract.a(), 123 + 678); + my_contract.mocked_args_function(789); + assertEq(my_contract.a(), 123 + 789); + } +} diff --git a/testdata/default/revive/Prank.t.sol b/testdata/default/revive/Prank.t.sol new file mode 100644 index 0000000000000..45383c1b62824 --- /dev/null +++ b/testdata/default/revive/Prank.t.sol @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../../default/logs/console.sol"; + +contract Victim { + function assertCallerAndOrigin( + address expectedSender, + string memory senderMessage, + address expectedOrigin, + string memory originMessage + ) public view { + require(msg.sender == expectedSender, senderMessage); + require(tx.origin == expectedOrigin, originMessage); + } +} + +contract ConstructorVictim is Victim { + constructor( + address expectedSender, + string memory senderMessage, + address expectedOrigin, + string memory originMessage + ) { + require(msg.sender == expectedSender, senderMessage); + require(tx.origin == expectedOrigin, originMessage); + } +} + +contract NestedVictim { + Victim innerVictim; + + constructor(Victim victim) { + innerVictim = victim; + } + + function assertCallerAndOrigin( + address expectedSender, + string memory senderMessage, + address expectedOrigin, + string memory originMessage + ) public view { + require(msg.sender == expectedSender, senderMessage); + require(tx.origin == expectedOrigin, originMessage); + innerVictim.assertCallerAndOrigin( + address(this), + "msg.sender was incorrectly set for nested victim", + expectedOrigin, + "tx.origin was incorrectly set for nested victim" + ); + } +} + +contract NestedPranker { + Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + + address newSender; + address newOrigin; + address oldOrigin; + + constructor(address _newSender, address _newOrigin) { + newSender = _newSender; + newOrigin = _newOrigin; + oldOrigin = tx.origin; + } + + function incompletePrank() public { + vm.startPrank(newSender, newOrigin); + } + + function completePrank(NestedVictim victim) public { + vm.pvm(true); + + victim.assertCallerAndOrigin( + newSender, "msg.sender was not set in nested prank", newOrigin, "tx.origin was not set in nested prank" + ); + + vm.pvm(false); + + vm.stopPrank(); + + vm.pvm(true); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), + "msg.sender was not cleaned up in nested prank", + oldOrigin, + "tx.origin was not cleaned up in nested prank" + ); + } +} + +contract ImplementationTest { + uint256 public num; + address public sender; + + function assertCorrectCaller(address expectedSender) public { + require(msg.sender == expectedSender); + } + + function assertCorrectOrigin(address expectedOrigin) public { + require(tx.origin == expectedOrigin); + } + + function setNum(uint256 _num) public { + num = _num; + } +} + +contract ProxyTest { + uint256 public num; + address public sender; +} + +contract PrankTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testPrankDelegateCallPrank2() public { + vm.pvm(true); + ProxyTest proxy = new ProxyTest(); + ImplementationTest impl = new ImplementationTest(); + vm.prank(address(proxy), true); + // console.log("Proxy address:", address(proxy)); + // console.log("Impl address:", address(impl)); + // console.log("THIS address:", address(this)); + // Assert correct `msg.sender` + (bool success,) = + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", address(proxy))); + + require(success, "prank2: delegate call failed assertCorrectCaller"); + + // Assert storage updates + uint256 num = 42; + vm.prank(address(proxy), true); + (bool successTwo,) = address(impl).delegatecall(abi.encodeWithSignature("setNum(uint256)", num)); + require(successTwo, "prank2: delegate call failed setNum"); + require(proxy.num() == num, "prank2: proxy's storage was not set correctly"); + vm.stopPrank(); + } + + function testPrankDelegateCallStartPrank2() public { + vm.pvm(true); + ProxyTest proxy = new ProxyTest(); + ImplementationTest impl = new ImplementationTest(); + vm.startPrank(address(proxy), true); + + // Assert correct `msg.sender` + (bool success,) = + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", address(proxy))); + require(success, "startPrank2: delegate call failed assertCorrectCaller"); + + // Assert storage updates + uint256 num = 42; + (bool successTwo,) = address(impl).delegatecall(abi.encodeWithSignature("setNum(uint256)", num)); + require(successTwo, "startPrank2: delegate call failed setNum"); + require(proxy.num() == num, "startPrank2: proxy's storage was not set correctly"); + vm.stopPrank(); + } + + function testPrankDelegateCallPrank3() public { + address origin = address(999); + vm.assume(isNotReserved(origin)); + vm.pvm(true); + ProxyTest proxy = new ProxyTest(); + ImplementationTest impl = new ImplementationTest(); + vm.prank(address(proxy), origin, true); + + // Assert correct `msg.sender` + (bool success,) = + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", address(proxy))); + require(success, "prank3: delegate call failed assertCorrectCaller"); + + // Assert correct `tx.origin` + vm.prank(address(proxy), origin, true); + (bool successTwo,) = address(impl).delegatecall(abi.encodeWithSignature("assertCorrectOrigin(address)", origin)); + require(successTwo, "prank3: delegate call failed assertCorrectOrigin"); + + // Assert storage updates + uint256 num = 42; + vm.prank(address(proxy), address(origin), true); + (bool successThree,) = address(impl).delegatecall(abi.encodeWithSignature("setNum(uint256)", num)); + require(successThree, "prank3: delegate call failed setNum"); + require(proxy.num() == num, "prank3: proxy's storage was not set correctly"); + vm.stopPrank(); + } + + function testPrankDelegateCallStartPrank3(address origin) public { + vm.assume(isNotReserved(origin)); + vm.pvm(true); + + ProxyTest proxy = new ProxyTest(); + ImplementationTest impl = new ImplementationTest(); + vm.startPrank(address(proxy), origin, true); + + // Assert correct `msg.sender` + (bool success,) = + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", address(proxy))); + require(success, "startPrank3: delegate call failed assertCorrectCaller"); + + // Assert correct `tx.origin` + (bool successTwo,) = address(impl).delegatecall(abi.encodeWithSignature("assertCorrectOrigin(address)", origin)); + require(successTwo, "startPrank3: delegate call failed assertCorrectOrigin"); + + // Assert storage updates + uint256 num = 42; + (bool successThree,) = address(impl).delegatecall(abi.encodeWithSignature("setNum(uint256)", num)); + require(successThree, "startPrank3: delegate call failed setNum"); + require(proxy.num() == num, "startPrank3: proxy's storage was not set correctly"); + vm.stopPrank(); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfPrankDelegateCalltoEOA() public { + uint256 privateKey = uint256(keccak256(abi.encodePacked("alice"))); + address alice = vm.addr(privateKey); + ImplementationTest impl = new ImplementationTest(); + vm.expectRevert("vm.prank: cannot `prank` delegate call from an EOA"); + vm.prank(alice, true); + // Should fail when EOA pranked with delegatecall. + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", alice)); + } + + function testPrankSender(address sender) public { + vm.assume(isNotReserved(sender)); + // Perform the prank + vm.pvm(true); + + Victim victim = new Victim(); + vm.prank(sender); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", tx.origin, "tx.origin invariant failed" + ); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", tx.origin, "tx.origin invariant failed" + ); + } + + function testPrankOrigin(address sender, address origin) public { + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + address oldOrigin = tx.origin; + vm.pvm(true); + + // Perform the prank + Victim victim = new Victim(); + vm.prank(sender, origin); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" + ); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin was not cleaned up" + ); + } + + function testPrank1AfterPrank0(address sender, address origin) public { + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + address oldOrigin = tx.origin; + vm.pvm(true); + + Victim victim = new Victim(); + vm.prank(sender); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", oldOrigin, "tx.origin was not set during prank" + ); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin invariant failed" + ); + + // Overwrite the prank + vm.prank(sender, origin); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", origin, "tx.origin invariant failed" + ); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin invariant failed" + ); + } + + function isNotReserved(address addr) internal returns (bool) { + // Check for zero address and common precompiles (addresses 1-9) + if ( + addr == address(0) || addr == address(1) || addr == address(2) || addr == address(3) || addr == address(4) + || addr == address(5) || addr == address(6) || addr == address(7) || addr == address(8) + || addr == address(9) || addr == address(10) || addr == address(11) || addr == address(12) + || addr == address(13) || addr == address(14) || addr == address(15) || addr == address(this) + ) { + return false; + } + return true; + } + + function testPrank0AfterPrank1(address sender, address origin) public { + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + address oldOrigin = tx.origin; + vm.pvm(true); + Victim victim = new Victim(); + console.log("Balance of sender before prank:", sender.balance); + console.log("Balance of origin before prank:", origin.balance); + vm.prank(sender, origin); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" + ); + + console.log("After first prank - msg.sender:", address(this)); + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin invariant failed" + ); + + // Overwrite the prank + vm.prank(sender); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", oldOrigin, "tx.origin invariant failed" + ); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin invariant failed" + ); + } + + function testStartPrank0AfterPrank1(address sender, address origin) public { + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + + // Perform the prank + vm.pvm(true); + address oldOrigin = tx.origin; + Victim victim = new Victim(); + vm.startPrank(sender, origin); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" + ); + + // Overwrite the prank + vm.startPrank(sender); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", oldOrigin, "tx.origin invariant failed" + ); + + vm.stopPrank(); + // Ensure we cleaned up correctly after stopping the prank + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin invariant failed" + ); + } + + function testStartPrank1AfterStartPrank0(address sender, address origin) public { + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + vm.pvm(true); + // Perform the prank + address oldOrigin = tx.origin; + Victim victim = new Victim(); + vm.startPrank(sender); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", oldOrigin, "tx.origin was set during prank incorrectly" + ); + + // Ensure prank is still up as startPrank covers multiple calls + victim.assertCallerAndOrigin( + sender, "msg.sender was cleaned up incorrectly", oldOrigin, "tx.origin invariant failed" + ); + + // Overwrite the prank + vm.startPrank(sender, origin); + victim.assertCallerAndOrigin(sender, "msg.sender was not set during prank", origin, "tx.origin was not set"); + + // Ensure prank is still up as startPrank covers multiple calls + victim.assertCallerAndOrigin( + sender, "msg.sender was cleaned up incorrectly", origin, "tx.origin invariant failed" + ); + + vm.stopPrank(); + // Ensure everything is back to normal after stopPrank + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin invariant failed" + ); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfOverwriteUnusedPrank(address sender, address origin) public { + // Set the prank, but not use it + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + vm.pvm(true); + address oldOrigin = tx.origin; + Victim victim = new Victim(); + vm.startPrank(sender, origin); + // try to overwrite the prank. This should fail. + vm.expectRevert("vm.startPrank: cannot overwrite a prank until it is applied at least once"); + vm.startPrank(address(this), origin); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfOverwriteUnusedPrankAfterSuccessfulPrank(address sender, address origin) public { + // Set the prank, but not use it + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Set the prank, but not use it + address oldOrigin = tx.origin; + vm.pvm(true); + Victim victim = new Victim(); + vm.startPrank(sender, origin); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", origin, "tx.origin was set during prank incorrectly" + ); + vm.startPrank(address(this), origin); + // try to overwrite the prank. This should fail. + vm.expectRevert("vm.startPrank: cannot overwrite a prank until it is applied at least once"); + vm.startPrank(sender, origin); + } + + function testStartPrank0AfterStartPrank1(address sender, address origin) public { + // Perform the prank + // Set the prank, but not use it + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + vm.pvm(true); + address oldOrigin = tx.origin; + Victim victim = new Victim(); + vm.startPrank(sender, origin); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" + ); + + // Ensure prank is still ongoing as we haven't called stopPrank + victim.assertCallerAndOrigin( + sender, "msg.sender was cleaned up incorrectly", origin, "tx.origin was cleaned up incorrectly" + ); + + // Overwrite the prank + vm.startPrank(sender); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", oldOrigin, "tx.origin was not reset correctly" + ); + + vm.stopPrank(); + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin invariant failed" + ); + } + + function testPrankConstructorSender(address sender) public { + // Set the prank, but not use it + vm.assume(isNotReserved(sender)); + // Perform the prank + vm.pvm(true); + vm.prank(sender); + ConstructorVictim victim = new ConstructorVictim( + sender, "msg.sender was not set during prank", tx.origin, "tx.origin invariant failed" + ); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", tx.origin, "tx.origin invariant failed" + ); + } + + function testPrankConstructorOrigin(address sender, address origin) public { + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + vm.pvm(true); + // Perform the prank + vm.prank(sender, origin); + ConstructorVictim victim = new ConstructorVictim( + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" + ); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", tx.origin, "tx.origin was not cleaned up" + ); + } + + function testPrankStartStop(address sender, address origin) public { + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + vm.pvm(true); + address oldOrigin = tx.origin; + + // Perform the prank + Victim victim = new Victim(); + vm.startPrank(sender, origin); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" + ); + victim.assertCallerAndOrigin( + sender, + "msg.sender was not set during prank (call 2)", + origin, + "tx.origin was not set during prank (call 2)" + ); + vm.stopPrank(); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", oldOrigin, "tx.origin was not cleaned up" + ); + } + + function testPrankStartStopConstructor(address sender, address origin) public { + // Perform the prank + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + vm.pvm(true); + vm.startPrank(sender, origin); + ConstructorVictim victim = new ConstructorVictim( + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" + ); + new ConstructorVictim( + sender, + "msg.sender was not set during prank (call 2)", + origin, + "tx.origin was not set during prank (call 2)" + ); + vm.stopPrank(); + + // Ensure we cleaned up correctly + victim.assertCallerAndOrigin( + address(this), "msg.sender was not cleaned up", tx.origin, "tx.origin was not cleaned up" + ); + } + + /// This test checks that depth is working correctly with respect + /// to the `startPrank` and `stopPrank` cheatcodes. + /// + /// The nested pranker calls `startPrank` but does not call + /// `stopPrank` at first. + /// + /// Then, we call our victim from the main test: this call + /// should NOT have altered `msg.sender` or `tx.origin`. + /// + /// Then, the nested pranker will complete their prank: this call + /// SHOULD have altered `msg.sender` and `tx.origin`. + /// + /// Each call to the victim calls yet another victim. The expected + /// behavior for this call is that `tx.origin` is altered when + /// the nested pranker calls, otherwise not. In both cases, + /// `msg.sender` should be the address of the first victim. + /// + /// Success case: + /// + /// ┌────┐ ┌───────┐ ┌──────┐ ┌──────┐ ┌────────────┐ + /// │Test│ │Pranker│ │Vm│ │Victim│ │Inner Victim│ + /// └─┬──┘ └───┬───┘ └──┬───┘ └──┬───┘ └─────┬──────┘ + /// │ │ │ │ │ + /// │incompletePrank()│ │ │ │ + /// │────────────────>│ │ │ │ + /// │ │ │ │ │ + /// │ │startPrank()│ │ │ + /// │ │───────────>│ │ │ + /// │ │ │ │ │ + /// │ should not be pranked│ │ │ + /// │──────────────────────────────────────>│ │ + /// │ │ │ │ │ + /// │ │ │ │ should not be pranked │ + /// │ │ │ │────────────────────────>│ + /// │ │ │ │ │ + /// │ completePrank() │ │ │ │ + /// │────────────────>│ │ │ │ + /// │ │ │ │ │ + /// │ │ should be pranked │ │ + /// │ │────────────────────>│ │ + /// │ │ │ │ │ + /// │ │ │ │only tx.origin is pranked│ + /// │ │ │ │────────────────────────>│ + /// │ │ │ │ │ + /// │ │stopPrank() │ │ │ + /// │ │───────────>│ │ │ + /// │ │ │ │ │ + /// │ │should not be pranked│ │ + /// │ │────────────────────>│ │ + /// │ │ │ │ │ + /// │ │ │ │ should not be pranked │ + /// │ │ │ │────────────────────────>│ + /// ┌─┴──┐ ┌───┴───┐ ┌──┴───┐ ┌──┴───┐ ┌─────┴──────┐ + /// │Test│ │Pranker│ │Vm│ │Victim│ │Inner Victim│ + /// └────┘ └───────┘ └──────┘ └──────┘ └────────────┘ + /// If this behavior is incorrectly implemented then the victim + /// will be pranked the first time it is called. + /// + /// !!!!! Currently failing until switch back to evm is added !!!! + // function testPrankComplex(address sender, address origin) public { + // vm.assume(isNotReserved(sender)); + // vm.assume(isNotReserved(origin)); + // // Perform the prank + // address oldOrigin = tx.origin; + + // NestedPranker pranker = new NestedPranker(sender, origin); + + // vm.pvm(true); + // Victim innerVictim = new Victim(); + // NestedVictim victim = new NestedVictim(innerVictim); + + // vm.pvm(false); + // pranker.incompletePrank(); + // vm.pvm(true); + + // victim.assertCallerAndOrigin( + // address(this), + // "msg.sender was altered at an incorrect depth", + // oldOrigin, + // "tx.origin was altered at an incorrect depth" + // ); + + // pranker.completePrank(victim); + // } + + /// Checks that `tx.origin` is set for all subcalls of a `prank`. + /// + /// Ref: issue #1210 + function testTxOriginInNestedPrank(address sender, address origin) public { + vm.assume(isNotReserved(sender)); + vm.assume(isNotReserved(origin)); + // Perform the prank + vm.pvm(true); + address oldSender = msg.sender; + address oldOrigin = tx.origin; + + Victim innerVictim = new Victim(); + NestedVictim victim = new NestedVictim(innerVictim); + + vm.prank(sender, origin); + victim.assertCallerAndOrigin( + sender, "msg.sender was not set correctly", origin, "tx.origin was not set correctly" + ); + } +} + +contract Issue9990 is DSTest { + Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + + // TODO: Enable when Etch support is merged. + // function testDelegatePrank() external { + // A a = new A(); + // vm.etch(address(0x11111), hex"11"); + // vm.startPrank(address(0x11111), true); + // (bool success,) = address(a).delegatecall(abi.encodeWithSelector(A.foo.selector)); + // require(success, "MyTest: error calling foo on A"); + // vm.stopPrank(); + // } +} + +// Contracts for DELEGATECALL test case: testDelegatePrank +contract A { + function foo() external { + require(address(0x11111) == msg.sender, "wrong msg.sender in A"); + require(address(0x11111) == address(this), "wrong address(this) in A"); + B b = new B(); + (bool success,) = address(b).call(abi.encodeWithSelector(B.bar.selector)); + require(success, "A: error calling B.bar"); + } +} + +contract B { + function bar() external { + require(address(0x11111) == msg.sender, "wrong msg.sender in B"); + require(0x769A6A5f81bD725e4302751162A7cb30482A222d == address(this), "wrong address(this) in B"); + C c = new C(); + (bool success,) = address(c).delegatecall(abi.encodeWithSelector(C.bar.selector)); + require(success, "B: error calling C.bar"); + } +} + +contract C { + function bar() external view { + require(address(0x11111) == msg.sender, "wrong msg.sender in C"); + require(0x769A6A5f81bD725e4302751162A7cb30482A222d == address(this), "wrong address(this) in C"); + } +} + +contract Counter { + uint256 number; + + function increment() external { + number++; + } +}