Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(cheatcodes): Record Account + Storage Access Cheatcode #6125

Closed
refcell opened this issue Oct 26, 2023 · 3 comments
Closed

feat(cheatcodes): Record Account + Storage Access Cheatcode #6125

refcell opened this issue Oct 26, 2023 · 3 comments
Labels
A-cheatcodes Area: cheatcodes T-feature Type: feature

Comments

@refcell
Copy link
Contributor

refcell commented Oct 26, 2023

Component

Forge

Describe the feature you would like

Background Context

During test and script execution, it is currently not possible to get an ordered list of account/storage accesses. For account accesses, cheats such as vm.expectCall requires knowing the exact destination and parameters of calls ahead of time. For storage, vm.record and vm.accesses cheats are not enumerated or ordered. It also requires knowing ahead of time which accounts to check for storage accesses.

Feature Proposal

Introduce a new vm.recordStateDiff() cheatcode that records all account and storage accesses in an ordered, chronological (for scripts, parallelized testing may be more tricky).

struct AccountAccess {
    address account;
    bool isCreate;
    bool initialized;
    uint64 prevNonce;
    uint64 nonce;
    uint256 prevValue;
    uint256 value;
    bytes prevCode;
    bytes code;
    bytes data;
    bool reverted;
    StorageAccess[] storageAccesses;
}

struct StorageAccess {
    address account;
    bytes32 slot;
    bool isWrite;
    bytes32 previousValue;
    bytes32 newValue;
    bool reverted;
}

/// Record all account and storage accesses.
/// CREATE or CALL opcodes produce an AccountAccess.
/// Storage accesses produce a StorageAccess object.
function recordStateDiff() external;

// Returns an ordered array of all account and storage accesses
function getStateDiff() external returns (AccountAccess[] memory);

Implementation

Internally, Forge maintains a Vec<Vec<RecordedAccountAccess>>. The vector is 2-dimensional to allow for grouping account accesses by relative call depth, so they can be updated with the eventual failure status of that callframe before being merged with the previous Vec to preserve absolute ordering.

On each call or create, a single-element Vec<RecordedAccountAccess> is appended to the state with the context of the CALL or CREATE opcode.

On call_end or create_end, the last Vec<RecordedAccountAccess> is pop()'d off and the accesses therein are updated with the failure status of the CREATE or CALL opcode. This Vec<RecordedAccountAccess> is then merged with the previous in the Vec<Vec<...>>, or else re-appended to the empty Vec<Vec<...>>.

Within RecordedAccountAccess, there is a Vec<RecordedStorageAccess> containing a list of storage accesses pertaining to that account. On each SLOAD or SSTORE, a RecordedStorageAccess is appended to the last RecordedAccountAccess.

Reasoning

Ordering is important when manually calculating account or storage-slot "warm-ness," as is the eventual reverted status of a callframe in which an account or storage access happened.

Opcodes have different costs depending on whether or not a storage slot is "warm," "cold," or going from zero-to-non-zero value, or when sending value to an empty/uninitialized account. On an even lower-level, the EVM does not mark "cold" accounts or slots as "warm" if the context from which they were accessed reverts (with the exception of the counterfactual create address).

Thus, context like previousValue, newValue, isCreate, initialized, and reverted are important for understanding and evaluating the (hypothetical) behavior of the EVM.

@refcell refcell added the T-feature Type: feature label Oct 26, 2023
@gakonst gakonst added this to Foundry Oct 26, 2023
@github-project-automation github-project-automation bot moved this to Todo in Foundry Oct 26, 2023
@refcell
Copy link
Contributor Author

refcell commented Oct 26, 2023

This issue replaces the following PRs:

A new pr should introduce this cheatcode following the alloy port.

@mds1
Copy link
Collaborator

mds1 commented Nov 7, 2023

Should the structs also include a forkId or chainId? That way if you record, switch between forks, then stop recording, you know which chain these occurred on. Other recording cheats do not currently have this, but it might be valuable to include anyway

@zerosnacks
Copy link
Member

zerosnacks commented Jul 4, 2024

Resolved by #6310

@jenpaff jenpaff moved this from Todo to Completed in Foundry Sep 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-cheatcodes Area: cheatcodes T-feature Type: feature
Projects
Archived in project
Development

No branches or pull requests

3 participants