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

Add basic forking cheatcodes #259

Closed
wants to merge 2 commits into from
Closed

Add basic forking cheatcodes #259

wants to merge 2 commits into from

Conversation

arcz
Copy link
Collaborator

@arcz arcz commented May 18, 2023

Description

Basic implementation of forking: https://book.getfoundry.sh/forge/fork-testing

Checklist

  • tested locally
  • added automated tests
  • updated the docs
  • updated the changelog

Copy link
Collaborator

@d-xo d-xo left a comment

Choose a reason for hiding this comment

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

CI is only failing because of some unrelated windows build error...

test/contracts/pass/cheatCodesFork.sol Outdated Show resolved Hide resolved
persistentState = 0;
}

function test_ForkedState() external {
Copy link
Collaborator

Choose a reason for hiding this comment

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

according to the foundry docs, the state of msg.sender should also be persisted betweeen forks. Could we also test that behaviour here (I guess by sending some eth to msg.sender and checking that it is persisted between forks?).

-- we don't support this at the moment
pure ()
Nothing ->
pure () -- no-op: fork not found
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we wanna noop or error here? what does foundry do?

(ConcreteStore store, ConcreteStore savedStore) -> do
let
-- the current contract is persited across forks
self = vm.state.contract
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't know exactly how foundry behaves here, but according to the docs only the state of the the test contract itself is persisted between forks, and I think we're going to persist the wrong contract if we call this from a subcontract that's deployed from the top level test contract?

_ ->
-- no-op: current storage and/or saved storage is symbolic
-- we don't support this at the moment
pure ()
Copy link
Collaborator

Choose a reason for hiding this comment

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

What exactly is the difficultty in supporting symbolic storage here? I think if we can't do it easily it's probably better to error out?

@samalws-tob
Copy link
Contributor

@arcz @d-xo I did a bit of work on this PR, result is here. Changes:

  • Brought it up to date to current the main branch
  • Renamed the test function (previously it wasn't actually running the test, and was always passing regardless of what was in the function)
  • Made it error instead of no-op when the fork isn't found in selectFork
  • Made msg.sender get persisted when switching forks
  • Added a test for whether msg.sender.balance is persisted

Right now the test fails, because msg.sender being required to be concrete screws things up. A test that only says { assert(msg.sender.balance == msg.sender.balance); } would also fail at the moment. Any ideas for how to get around this?

@d-xo
Copy link
Collaborator

d-xo commented Oct 26, 2023

Thanks for the work on this pr @samalws-tob, looks super nice.

Right now the test fails, because msg.sender being required to be concrete screws things up. A test that only says { assert(msg.sender.balance == msg.sender.balance); } would also fail at the moment. Any ideas for how to get around this?

I'm not sure I fully understand the question. msg.sender is currently fully abstract, but we do have some restrictions around accessing balances of fully abstract addresses (this is due to potential unsoundness due to aliasing between symbolic addresses), which I guess is the issue that you were running into? The long term fix here would probably be to restructure balances to be sound even if we have aliased symbolic addresses (probably by having one global symbolic mapping storing balances, instead of a concrete haskell mapping as we do at the moment), this is a larger job, and out of scope for this pr. I think in the short term you can maybe rework the tests so that you only look up balances of contracts that have actually been deployed (e.g. would using address(this).balance instead of msg.sender.balance work?).

@samalws-tob
Copy link
Contributor

samalws-tob commented Oct 30, 2023

Using address(this).balance instead of msg.sender.balance wouldn't work, since we want to test that the caller's state is preserved in addition to the test contract's state. One possible workaround solution would be having two contracts, TestContractDeployer and TestContract, where TestContract contains the current test code while TestContractDeployer simply deploys a TestContract and calls its test function; this way, from the point of view of the test code (which lives in TestContract), msg.sender will always be concrete, since it is the address of the TestContractDeployer. Does this seem reasonable? I can make a demo if you want.

(Edit: this ended up working, implemented in #433 )

@arcz arcz closed this Jan 15, 2024
msooseth added a commit that referenced this pull request Feb 9, 2024
Add basic forking cheatcodes (continuation of #259)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants