Skip to content

feat(cheatcodes): include slot type and decode values in state diffs #11276

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

Merged
merged 48 commits into from
Aug 19, 2025

Conversation

yash-atreya
Copy link
Member

@yash-atreya yash-atreya commented Aug 12, 2025

Motivation

ref #9504

stacked on #11214

Solution

This features enabled when the compiled artifacts include the storageLayout. This can be achieved by setting the extra_output = ["storageLayout"] in foundry.toml or via the cli flag --extra-output storageLayout

  • Include SlotInfo in the SlotStateDiff
  • Identify 0+n slots of static arrays and include the slot info
  • Decode the storage values

Note: This does not handle types that are non-contiguous in the storage such as dynamic arrays or mappings. This shall be addressed in a followup: #11326 and #11327

Consider the following as an example:

import {Test} from "forge-std/Test.sol";

contract SimpleStorage {
    uint256 public value; // Slot 0
    address public owner; // Slot 1
    uint256[3] public values; // Slots 2, 3, 4

    constructor() {
        owner = msg.sender;
    }

    function setValue(uint256 _value) public {
        value = _value;
    }

    function setOwner(address _owner) public {
        owner = _owner;
    }

    function setValues(uint256 a, uint256 b, uint256 c) public {
        values[0] = a;
        values[1] = b;
        values[2] = c;
    }
}

contract StateDiffStorageLayoutTest is Test {
    SimpleStorage simpleStorage;
    function setUp() public {
        simpleStorage = new SimpleStorage();
    }

    function testStorageLayoutInStateDiff() public {
        vm.startStateDiffRecording();

        simpleStorage.setValue(42); // Modifies slot 0
        simpleStorage.setOwner(address(this)); // Modifies slot 1
        simpleStorage.setValues(100, 200, 300); // Modifies slots 2, 3, 4

        string memory stateDiffStr = vm.getStateDiff();
        string memory stateDiffJson = vm.getStateDiffJson();

        emit log_string("State Diff JSON:");
        emit log_string(stateDiffJson);
        emit log_string("State Diff String:");
        emit log_string(stateDiffStr);
    }
}

The above test will emit the following output:

Plaintext String
0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
contract: src/SimpleStorage.sol:SimpleStorage
- state diff:
@ 0x0000000000000000000000000000000000000000000000000000000000000000 (value, uint256): 0 → 42
@ 0x0000000000000000000000000000000000000000000000000000000000000001 (owner, address): 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 → 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
@ 0x0000000000000000000000000000000000000000000000000000000000000002 (values[0], uint256[3]): 0 → 100
@ 0x0000000000000000000000000000000000000000000000000000000000000003 (values[1], uint256[3]): 0 → 200
@ 0x0000000000000000000000000000000000000000000000000000000000000004 (values[2], uint256[3]): 0 → 300
JSON Output
{
  "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f": {
    "label": null,
    "contract": "src/SimpleStorage.sol:SimpleStorage",
    "balanceDiff": null,
    "stateDiff": {
      "0x0000000000000000000000000000000000000000000000000000000000000000": {
        "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "newValue": "0x000000000000000000000000000000000000000000000000000000000000002a",
        "decoded": {
          "previousValue": "0",
          "newValue": "42"
        },
        "label": "value",
        "type": "uint256",
        "offset": 0,
        "slot": "0"
      },
      "0x0000000000000000000000000000000000000000000000000000000000000001": {
        "previousValue": "0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496",
        "newValue": "0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496",
        "decoded": {
          "previousValue": "0x7fa9385be102ac3eac297483dd6233d62b3e1496",
          "newValue": "0x7fa9385be102ac3eac297483dd6233d62b3e1496"
        },
        "label": "owner",
        "type": "address",
        "offset": 0,
        "slot": "1"
      },
      "0x0000000000000000000000000000000000000000000000000000000000000002": {
        "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "newValue": "0x0000000000000000000000000000000000000000000000000000000000000064",
        "decoded": {
          "previousValue": "0",
          "newValue": "100"
        },
        "label": "values[0]",
        "type": "uint256[3]",
        "offset": 0,
        "slot": "2"
      },
      "0x0000000000000000000000000000000000000000000000000000000000000003": {
        "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "newValue": "0x00000000000000000000000000000000000000000000000000000000000000c8",
        "decoded": {
          "previousValue": "0",
          "newValue": "200"
        },
        "label": "values[1]",
        "type": "uint256[3]",
        "offset": 0,
        "slot": "3"
      },
      "0x0000000000000000000000000000000000000000000000000000000000000004": {
        "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "newValue": "0x000000000000000000000000000000000000000000000000000000000000012c",
        "decoded": {
          "previousValue": "0",
          "newValue": "300"
        },
        "label": "values[2]",
        "type": "uint256[3]",
        "offset": 0,
        "slot": "4"
      }
    }
  }
}

Please drop your comments on the output structure, especially the plaintext string output which looks a bit weird.

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

@yash-atreya yash-atreya changed the base branch from master to yash/state-diff-storage-labels August 12, 2025 07:14
@yash-atreya yash-atreya changed the title Yash/decode label state diffs feat(cheatcodes): include slot type and decode values in state diffs Aug 12, 2025
@yash-atreya yash-atreya added A-cheatcodes Area: cheatcodes C-forge Command: forge T-likely-breaking Type: requires changes that can be breaking labels Aug 12, 2025
@yash-atreya yash-atreya self-assigned this Aug 12, 2025
@yash-atreya yash-atreya requested a review from grandizzy August 19, 2025 07:52
@grandizzy
Copy link
Collaborator

Since, all of this is a breaking change, should we also go ahead and reduce the length of storage slots in the above output i.e only keep the significant bits? So 0x0000000000000000000000000000000000000000000000000000000000000000 becomes 0x00 and 0x0000000000000000000000000000000000000000000000000000000000000001 becomes 0x01?

that makes sense, just to make sure, will it be applied only to formatted output, is this correct?

@yash-atreya
Copy link
Member Author

that makes sense, just to make sure, will it be applied only to formatted output, is this correct?

Yes, it'll only be applied to the formatted output.

@yash-atreya
Copy link
Member Author

that makes sense, just to make sure, will it be applied only to formatted output, is this correct?

Done aa36022, @grandizzy ptal

Copy link
Collaborator

@grandizzy grandizzy left a comment

Choose a reason for hiding this comment

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

lgtm, would be great to have more reviews - we can have more iterations in case a better format suggested. Left one nit re tests, think we could remove extra output for all the tests?

@@ -10,7 +10,7 @@ bytecode_hash = "ipfs"
cache = true
cache_path = "cache"
evm_version = "paris"
extra_output = []
extra_output = ["storageLayout"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

@sakulstra
Copy link
Contributor

sakulstra commented Aug 19, 2025

Since, all of this is a breaking change, should we also go ahead and reduce the length of storage slots in the above output i.e only keep the significant bits? So 0x0000000000000000000000000000000000000000000000000000000000000000 becomes 0x00 and 0x0000000000000000000000000000000000000000000000000000000000000001 becomes 0x01?

No to strong opinion(especially as we're primarily working with the json), but I think i liked it better before.
Feels a bit weird to have different lengths on sequential and non sequential(mappings / arrays) slots

@zerosnacks
Copy link
Member

should we also go ahead and reduce the length of storage slots in the above output i.e only keep the significant bits? So 0x0000000000000000000000000000000000000000000000000000000000000000 becomes 0x00 and 0x0000000000000000000000000000000000000000000000000000000000000001 becomes 0x01?

I would prefer to keep the entire output as in 0x0000000000000000000000000000000000000000000000000000000000000001 as the mental model for the EVM are 256-bit slots. It is also nicer to read as you can scan from the back and don't have different lengths for 0x123 and 0x5678.

@yash-atreya
Copy link
Member Author

yash-atreya commented Aug 19, 2025

I would prefer to keep the entire output as in 0x0000000000000000000000000000000000000000000000000000000000000001 as the mental model for the EVM are 256-bit slots. It is also nicer to read as you can scan from the back and don't have different lengths for 0x123 and 0x5678.

@zerosnacks @grandizzy reverted the change f366667

Copy link
Collaborator

@grandizzy grandizzy left a comment

Choose a reason for hiding this comment

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

👍 needs docs fix

@grandizzy grandizzy self-requested a review August 19, 2025 12:18
Copy link
Member

@zerosnacks zerosnacks left a comment

Choose a reason for hiding this comment

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

lgtm 👍 !

@yash-atreya yash-atreya merged commit a7e5d64 into master Aug 19, 2025
22 checks passed
@yash-atreya yash-atreya deleted the yash/decode-label-state-diffs branch August 19, 2025 13:02
@github-project-automation github-project-automation bot moved this from Ready For Review to Done in Foundry Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-cheatcodes Area: cheatcodes C-forge Command: forge T-likely-breaking Type: requires changes that can be breaking
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

4 participants