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): add vm.getStateDiff to get state diffs as string #9435

Merged
merged 15 commits into from
Dec 6, 2024

Conversation

grandizzy
Copy link
Collaborator

@grandizzy grandizzy commented Nov 29, 2024

Motivation

Closes #2846 by adding

    function getStateDiff() external view returns (string memory diff);
    function getStateDiffJson() external view returns (string memory diff);
  • returns diffs recorded by startStateDiffRecording() cheatcode as string (can be printed with console.log or written in file), at the moment of cheatcode call (can be called multiple times between start / stop state diff)
  • diffs contains only storage changes, initial value when start diff called to current (intermediary storage changes are not displayed)

E.g.

    vm.startStateDiffRecording();
    // do work, change state of Counter contract
    console.log(":::: diff 1 ::::");
    console.log(vm.getStateDiff());
    // more work, change state of Counter and AnotherCounter contracts
    console.log(":::: diff 2 ::::");
    console.log(vm.getStateDiff());
    // more work, change state of AnotherCounter contract
    console.log(":::: diff 3 ::::");
    console.log(vm.getStateDiff());

image

Json format vm.getStateDiffJson()

{
   "0x2e234dae75c793f67a35089c9d99245e1c58470b":{
      "label":"NestedStorer",
      "changes":{
         "31391530734884398925509096751136955997235046655136458338700630915422204365175":{
            "previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "86546418208203448386783321347074308435724792809315873744194221534962779865098":{
            "previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "89735575844917174604881245405098157398514761457822262993733937076486162048205":{
            "previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "99655811014363889343382125167956395016210879868288374279890486979400290732814":{
            "previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"
         }
      }
   }
}

Solution

@zerosnacks zerosnacks self-requested a review November 29, 2024 17:39
zerosnacks
zerosnacks previously approved these changes Nov 29, 2024
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

@sakulstra
Copy link
Contributor

I'm honestly a bit conflicted with this api / feature.

With primitive type storage in incremental slots what is proposed here is definitely useful, although I think for us would be more useful in an easier-to-consume & post-process format (e.g. json).

Currently I lack the imagination though to see how i could understand that a change in slot 0x9fe9e472df24c0973d55f45ce8b645fa4fb021df9b3c6db994a661dfb284cb67is a _balances change of some user.
What I originally hoped for when opening #2846 was something closer to evm.storage or tenderly state diff - a state diff including some decoded storage slot description.

@grandizzy
Copy link
Collaborator Author

I'm honestly a bit conflicted with this api / feature.

With primitive type storage in incremental slots what is proposed here is definitely useful, although I think for us would be more useful in an easier-to-consume & post-process format (e.g. json).

can add a flag to cheatcode to return plain text or json formatted

Currently I lack the imagination though to see how i could understand that a change in slot 0x9fe9e472df24c0973d55f45ce8b645fa4fb021df9b3c6db994a661dfb284cb67is a _balances change of some user. What I originally hoped for when opening #2846 was something closer to evm.storage or tenderly state diff - a state diff including some decoded storage slot description.

I think this would imply leveraging of storage layout but probably more complex, will have a look at but most probably feasible to add post v1.

@grandizzy grandizzy changed the title feat(cheatcodes): add vm.getStateDiff() to get state diffs as string feat(cheatcodes): add vm.getStateDiff(isJson) to get state diffs as string Dec 2, 2024
@sakulstra
Copy link
Contributor

sakulstra commented Dec 2, 2024

@grandizzy just had another look at the structure & was wondering it could make sense to change it so sth like:
{<address>: {label: <label>, changes: {<slot>: {from, to}}}} or sth similar.

My thinking is that it's a bit weird that if a label is supplied, the address is lost on the diff.
By providing changes as an object instead of an array, I feel like this might open up the possibility of adding things in the future (like the decoded storage location, or some other meta), without introducing a breaking change on the structure.

@grandizzy
Copy link
Collaborator Author

@grandizzy just had another look at the structure & was wondering it could make sense to change it so sth like: {<address>: {label: <label>, changes: {<slot>: {from, to}}}} or sth similar.

My thinking is that it's a bit weird that if a label is supplied, the address is lost on the diff. By providing changes as an object instead of an array, I feel like this might open up the possibility of adding things in the future (like the decoded storage location, or some other meta), without introducing a breaking change on the structure.

thank you, makes sense, will structure the output this way

@grandizzy
Copy link
Collaborator Author

grandizzy commented Dec 2, 2024

@grandizzy just had another look at the structure & was wondering it could make sense to change it so sth like: {<address>: {label: <label>, changes: {<slot>: {from, to}}}} or sth similar.
My thinking is that it's a bit weird that if a label is supplied, the address is lost on the diff. By providing changes as an object instead of an array, I feel like this might open up the possibility of adding things in the future (like the decoded storage location, or some other meta), without introducing a breaking change on the structure.

thank you, makes sense, will structure the output this way

@sakulstra I formatted it as below in d46080c

{
   "0x2e234dae75c793f67a35089c9d99245e1c58470b":{
      "label":"NestedStorer",
      "changes":{
         "31391530734884398925509096751136955997235046655136458338700630915422204365175":{
            "original":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "dirty":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "86546418208203448386783321347074308435724792809315873744194221534962779865098":{
            "original":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "dirty":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "89735575844917174604881245405098157398514761457822262993733937076486162048205":{
            "original":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "dirty":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "99655811014363889343382125167956395016210879868288374279890486979400290732814":{
            "original":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "dirty":"0x0000000000000000000000000000000000000000000000000000000000000001"
         }
      }
   }
}

@sakulstra
Copy link
Contributor

sakulstra commented Dec 2, 2024

@grandizzy looks good to me.

I personally find the new naming not so optimal, but i think is just taste.
Checked the naming on other tooling and e.g. tenderly is using original & dirty instead of previousValue and newValue - in the end doesn't matter though.

@grandizzy
Copy link
Collaborator Author

@grandizzy looks good to me.

I personally find the new working not so optimal, but i think is just taste. Checked the naming on other tooling and e.g. tenderly is using original & dirty instead of previousValue and newValue - in the end doesn't matter though.

changed in 1536119 and updated comment above

@grandizzy grandizzy requested a review from zerosnacks December 2, 2024 19:09
@grandizzy grandizzy dismissed zerosnacks’s stale review December 2, 2024 19:10

rerequesting review as there were several changes (json output)

@zerosnacks zerosnacks self-requested a review December 3, 2024 11:25
zerosnacks
zerosnacks previously approved these changes Dec 3, 2024
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.

Nice! Lgtm

@mds1
Copy link
Collaborator

mds1 commented Dec 4, 2024

Agreed with all the feedback from @sakulstra, thanks @grandizzy for implementing that! Some other comments:

  1. Is label the inferred contract name by foundry, or the value returned from vm.getLabel(address)? I think including both by default would be useful, with null being used if vm.getLabel returns nothing
  2. I would also having the slots be hex strings instead of decimal, that is the more common format
  3. This only includes state diffs, but—similar to tenderly—this should also include nonce and balance diffs
  4. +1 to the feedback that having the state diff be decoded would 10x the utility of this feature, but a separate PR for that makes sense to me. To avoid breaking changes, we may want to include all the fields we'd want in this initial PR and default them to null when we don't know the values. For reference, here is an example API response from the Tenderly, check out the balance_diff, nonce_diff, and state_diff fields to see their format. I've found their format has all the data I've needed: https://api.tenderly.co/api/v1/public-contract/1/trace/0x7abecacd8b1a54db8f0835a5c82edfab96ff922a41d2faa914c339e3e9319b43
  5. Related to the above, a strongly typed vm.getStateDiffStruct (or similar name) that returns an array of structs to facilitate introspection directly in solidity would be useful

@grandizzy grandizzy marked this pull request as draft December 4, 2024 09:20
@grandizzy
Copy link
Collaborator Author

grandizzy commented Dec 4, 2024

Agreed with all the feedback from @sakulstra, thanks @grandizzy for implementing that! Some other comments:

Thanks for feedback!

1. Is `label` the inferred contract name by foundry, or the value returned from `vm.getLabel(address)`? I think including both by default would be useful, with `null` being used if `vm.getLabel` returns nothing

This would require matching local artifact / contract by address in cheatcodes, I propose to address in a follow up (post 1.0) PR since it imply some more code changes.

2. I would also having the slots be hex strings instead of decimal, that is the more common format

Yep, changed, see format below

3. This only includes state diffs, but—similar to tenderly—this should also include nonce and balance diffs

I included balance diff as well, rn there's no simple way to include nonce diffs so added only a placeholder -will check it in a (post v1.0) follow up PR

{
   "0x2e234dae75c793f67a35089c9d99245e1c58470b":{
      "label":"NestedStorer",
      "balanceDiff":{
         "previousValue":"0x0",
         "newValue":"0xde0b6b3a7640000"
      },
      "stateDiff":{
         "0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977":{
            "previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a":{
            "previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd":{
            "previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"
         },
         "0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e":{
            "previousValue":"0x0000000000000000000000000000000000000000000000000000000000000000",
            "newValue":"0x0000000000000000000000000000000000000000000000000000000000000001"
         }
      }
   }
}
4. +1 to the feedback that having the state diff be decoded would 10x the utility of this feature, but a separate PR for that makes sense to me. To avoid breaking changes, we may want to include all the fields we'd want in this initial PR and default them to null when we don't know the values. For reference, here is an example API response from the Tenderly, check out the `balance_diff`, `nonce_diff`, and `state_diff` fields to see their format. I've found their format has all the data I've needed: https://api.tenderly.co/api/v1/public-contract/1/trace/0x7abecacd8b1a54db8f0835a5c82edfab96ff922a41d2faa914c339e3e9319b43

5. Related to the above, a strongly typed `vm.getStateDiffStruct` (or similar name) that returns an array of structs to facilitate introspection directly in solidity would be useful

Same, I propose to do add this one in post v1.0 a follow up PR

@grandizzy grandizzy requested a review from zerosnacks December 4, 2024 12:21
@grandizzy grandizzy marked this pull request as ready for review December 4, 2024 12:22
@grandizzy grandizzy dismissed zerosnacks’s stale review December 4, 2024 12:22

please rereview considering changes in #9435 (comment)

@zerosnacks zerosnacks self-requested a review December 4, 2024 13:11
@zerosnacks
Copy link
Member

Overall LGTM, minor note in regards to placeholder nonce_diff

@grandizzy
Copy link
Collaborator Author

@mds1 would #9435 (comment) be OK to give it a go in this stage? thanks!

@mds1
Copy link
Collaborator

mds1 commented Dec 5, 2024

Thanks for the answers @grandizzy, doing the rest in a follow up works for me! Can we just make sure to track all those remaining items somewhere? They would definitely be super helpful so just want to make sure we don't lose them

One thing I am still unsure of is the label field, is that the inferred contract name or the getLabel results?

@grandizzy
Copy link
Collaborator Author

Thanks for the answers @grandizzy, doing the rest in a follow up works for me! Can we just make sure to track all those remaining items somewhere? They would definitely be super helpful so just want to make sure we don't lose them

👍 I am going to update the issue with remaining things to implement.

One thing I am still unsure of is the label field, is that the inferred contract name or the getLabel results?

Yeah, is getLabel result for now.

@mds1
Copy link
Collaborator

mds1 commented Dec 5, 2024

getLabel is great, thank you

@grandizzy
Copy link
Collaborator Author

Thanks for the answers @grandizzy, doing the rest in a follow up works for me! Can we just make sure to track all those remaining items somewhere? They would definitely be super helpful so just want to make sure we don't lose them

Follow up #9504

@grandizzy grandizzy merged commit 00efa0d into foundry-rs:master Dec 6, 2024
22 checks passed
@grandizzy grandizzy added T-feature Type: feature C-forge Command: forge labels Dec 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-forge Command: forge T-feature Type: feature
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

feat(cheatcodes): ability to capture and store state diffs
4 participants