Skip to content

Commit 378ded1

Browse files
tushar994DaniPopes
andauthored
feat(forge): add vm.stopRecord (#10370)
* add initial implementation of StopRecord cheatcode * modify command to be stopRecordAndReturnAccesses instead * improve documentation and lint fixes * lint changes in integration tests * implement resetRecord and stopRecord * clippy fix * minor formatting changes * chore: cleanup, rm reset --------- Co-authored-by: DaniPopes <[email protected]>
1 parent a63dbe2 commit 378ded1

File tree

6 files changed

+94
-16
lines changed

6 files changed

+94
-16
lines changed

crates/cheatcodes/assets/cheatcodes.json

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/spec/src/vm.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,10 +399,15 @@ interface Vm {
399399

400400
// -------- Record Storage --------
401401

402-
/// Records all storage reads and writes.
402+
/// Records all storage reads and writes. Use `accesses` to get the recorded data.
403+
/// Subsequent calls to `record` will clear the previous data.
403404
#[cheatcode(group = Evm, safety = Safe)]
404405
function record() external;
405406

407+
/// Stops recording storage reads and writes.
408+
#[cheatcode(group = Evm, safety = Safe)]
409+
function stopRecord() external;
410+
406411
/// Gets all accessed reads and write slot from a `vm.record` session, for a given address.
407412
#[cheatcode(group = Evm, safety = Safe)]
408413
function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots);

crates/cheatcodes/src/evm.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ impl RecordAccess {
5656
self.record_read(target, slot);
5757
self.writes.entry(target).or_default().push(slot);
5858
}
59+
60+
/// Clears the recorded reads and writes.
61+
pub fn clear(&mut self) {
62+
// Also frees memory.
63+
*self = Default::default();
64+
}
5965
}
6066

6167
/// Records the `snapshotGas*` cheatcodes.
@@ -280,24 +286,26 @@ impl Cheatcode for dumpStateCall {
280286
impl Cheatcode for recordCall {
281287
fn apply(&self, state: &mut Cheatcodes) -> Result {
282288
let Self {} = self;
283-
state.accesses = Some(Default::default());
289+
state.recording_accesses = true;
290+
state.accesses.clear();
291+
Ok(Default::default())
292+
}
293+
}
294+
295+
impl Cheatcode for stopRecordCall {
296+
fn apply(&self, state: &mut Cheatcodes) -> Result {
297+
state.recording_accesses = false;
284298
Ok(Default::default())
285299
}
286300
}
287301

288302
impl Cheatcode for accessesCall {
289303
fn apply(&self, state: &mut Cheatcodes) -> Result {
290304
let Self { target } = *self;
291-
let result = state
292-
.accesses
293-
.as_mut()
294-
.map(|accesses| {
295-
(
296-
&accesses.reads.entry(target).or_default()[..],
297-
&accesses.writes.entry(target).or_default()[..],
298-
)
299-
})
300-
.unwrap_or_default();
305+
let result = (
306+
state.accesses.reads.entry(target).or_default().as_slice(),
307+
state.accesses.writes.entry(target).or_default().as_slice(),
308+
);
301309
Ok(result.abi_encode_params())
302310
}
303311
}

crates/cheatcodes/src/inspector.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,10 @@ pub struct Cheatcodes {
420420
pub fork_revert_diagnostic: Option<RevertDiagnostic>,
421421

422422
/// Recorded storage reads and writes
423-
pub accesses: Option<RecordAccess>,
423+
pub accesses: RecordAccess,
424+
425+
/// Whether storage access recording is currently active
426+
pub recording_accesses: bool,
424427

425428
/// Recorded account accesses (calls, creates) organized by relative call depth, where the
426429
/// topmost vector corresponds to accesses at the depth at which account access recording
@@ -538,6 +541,7 @@ impl Cheatcodes {
538541
assume_no_revert: Default::default(),
539542
fork_revert_diagnostic: Default::default(),
540543
accesses: Default::default(),
544+
recording_accesses: Default::default(),
541545
recorded_account_diffs_stack: Default::default(),
542546
recorded_logs: Default::default(),
543547
record_debug_steps_info: Default::default(),
@@ -1332,7 +1336,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes {
13321336
}
13331337

13341338
// `record`: record storage reads and writes.
1335-
if self.accesses.is_some() {
1339+
if self.recording_accesses {
13361340
self.record_accesses(interpreter);
13371341
}
13381342

@@ -1945,7 +1949,7 @@ impl Cheatcodes {
19451949
/// Records storage slots reads and writes.
19461950
#[cold]
19471951
fn record_accesses(&mut self, interpreter: &mut Interpreter) {
1948-
let Some(access) = &mut self.accesses else { return };
1952+
let access = &mut self.accesses;
19491953
match interpreter.current_opcode() {
19501954
op::SLOAD => {
19511955
let key = try_or_return!(interpreter.stack().peek(0));

testdata/cheats/Vm.sol

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

testdata/default/cheats/Record.t.sol

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,44 @@ contract RecordTest is DSTest {
5353
assertEq(innerWrites.length, 1, "number of nested writes is incorrect");
5454
assertEq(innerWrites[0], bytes32(uint256(2)), "key for nested write is incorrect");
5555
}
56+
57+
function testStopRecordAccess() public {
58+
RecordAccess target = new RecordAccess();
59+
60+
// Start recording
61+
vm.record();
62+
NestedRecordAccess inner = target.record();
63+
64+
// Verify Records
65+
(bytes32[] memory reads, bytes32[] memory writes) = vm.accesses(address(target));
66+
67+
assertEq(reads.length, 2, "number of reads is incorrect");
68+
assertEq(reads[0], bytes32(uint256(1)), "key for read 0 is incorrect");
69+
assertEq(reads[1], bytes32(uint256(1)), "key for read 1 is incorrect");
70+
71+
assertEq(writes.length, 1, "number of writes is incorrect");
72+
assertEq(writes[0], bytes32(uint256(1)), "key for write is incorrect");
73+
74+
vm.stopRecord();
75+
inner = target.record();
76+
77+
// Verify that there are no new Records
78+
(reads, writes) = vm.accesses(address(target));
79+
80+
assertEq(reads.length, 2, "number of reads is incorrect");
81+
assertEq(reads[0], bytes32(uint256(1)), "key for read 0 is incorrect");
82+
assertEq(reads[1], bytes32(uint256(1)), "key for read 1 is incorrect");
83+
84+
assertEq(writes.length, 1, "number of writes is incorrect");
85+
assertEq(writes[0], bytes32(uint256(1)), "key for write is incorrect");
86+
87+
vm.record();
88+
vm.stopRecord();
89+
90+
// verify reset all records
91+
(reads, writes) = vm.accesses(address(target));
92+
93+
assertEq(reads.length, 0, "number of reads is incorrect");
94+
assertEq(writes.length, 0, "number of writes is incorrect");
95+
}
5696
}

0 commit comments

Comments
 (0)