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

Allow forking from different RPCs/block numbers when testing #834

Closed
0xTomoyo opened this issue Mar 2, 2022 · 18 comments · Fixed by #1715
Closed

Allow forking from different RPCs/block numbers when testing #834

0xTomoyo opened this issue Mar 2, 2022 · 18 comments · Fixed by #1715
Labels
A-evm Area: EVM C-forge Command: forge Cmd-forge-debug Command: forge run Cmd-forge-test Command: forge test T-feature Type: feature

Comments

@0xTomoyo
Copy link
Contributor

0xTomoyo commented Mar 2, 2022

Component

Forge

Describe the feature you would like

Currently if you want to test against mainnet state, you can pass in an RPC url and block number to fork from. However it isn't possible to run tests on multiple networks or fork from different block numbers.

For example, you might want 1 test to run against the state at block number 10000, and another at block number 20000.

Hardhat has a method called hardhat_reset which allows you to change the RPC url/block number mainnet state is forked from: https://hardhat.org/hardhat-network/guides/mainnet-forking.html#resetting-the-fork

A cheatcode similar to that would be useful. It could look like this:

hevm.reset("https://infura.io/....", 12345);

Additional context

No response

@0xTomoyo 0xTomoyo added the T-feature Type: feature label Mar 2, 2022
@0xTomoyo 0xTomoyo changed the title Allow forking from different RPCs/block numbers in the tests Allow forking from different RPCs/block numbers when testing Mar 2, 2022
@tynes
Copy link
Contributor

tynes commented Mar 2, 2022

This would also allow for testing of apps that run across multiple chains which would be useful

@tynes
Copy link
Contributor

tynes commented Mar 2, 2022

Another thought - perhaps it would be better to have named urls in the config file and then use the name as the first arg to hevm.reset, then you wouldn't need to commit secrets into tests

@gakonst
Copy link
Member

gakonst commented Mar 2, 2022

Another thought - perhaps it would be better to have named urls in the config file and then use the name as the first arg to hevm.reset, then you wouldn't need to commit secrets into tests

Maybe we can add a vm.env(string)(string) cheatcode which loads an environment variable?

Need to think about how to make the cheatcode work. Seems doable. cc @onbjerg, we'd want a way to modify the forked provider's pinned block inside a cheatcode.

@tynes
Copy link
Contributor

tynes commented Mar 2, 2022

vm.env(string)(string) could be generally useful in different ways, could the env vars be automatically injected from .dapprc or something like that at runtime? Also just wondering about the case where the largest possible env var is pulled in, I feel like its an unlikely edge case and should be fine. I also think similar functionality could be achieved with hevm.ffi if you read an env var, use cast to abi encode the data and then pass it to echo -n

@onbjerg onbjerg added this to Foundry Apr 17, 2022
@onbjerg onbjerg moved this to Todo in Foundry Apr 17, 2022
@cantbeevil
Copy link

cantbeevil commented Apr 18, 2022

any status update for this? cc: @gakonst

@onbjerg onbjerg added A-evm Area: EVM Cmd-forge-test Command: forge test C-forge Command: forge Cmd-forge-debug Command: forge run labels Apr 18, 2022
@onbjerg
Copy link
Collaborator

onbjerg commented Apr 18, 2022

I don't think this is being worked on actively - @mattsse might be working on something similar for #1037 that can be ported to Forge as well, but I'm not sure

@mattsse
Copy link
Member

mattsse commented Apr 22, 2022

yes there'll be an option to reset/ and change this on the fly, but since this concerns forge, lets keep this open

@hexonaut
Copy link
Contributor

hexonaut commented May 9, 2022

+1ing this from MakerDAO as we are very focused on cross-chain support for MCD.

@mattsse
Copy link
Member

mattsse commented May 9, 2022

Support for this has landed in Anvil PR, but has not yet been incorporated into testing,

we'd need some cheatcodes first for that @onbjerg ?

@odyslam
Copy link
Contributor

odyslam commented May 9, 2022

  • 1 from Nomad.xyz as well. Being a cross-chain communication protocol, this is going to be a central piece of our testing harness, that I am currently migrating to Foundry.

@onbjerg
Copy link
Collaborator

onbjerg commented May 10, 2022

Support for this has landed in Anvil PR, but has not yet been incorporated into testing,

we'd need some cheatcodes first for that @onbjerg ?

Yes! We'd need two things:

  1. A cheatcode to read config or environment variables so people don't paste RPC URLs directly into tests
  2. A channel between the cheatcodes handler and the backend handler where we can pass messages like connect/disconnect/swap etc.

For 1. we can either add vm.env(string) returns (string) as @gakonst suggested or we can do something like vm.chain(uint256) that then reads the relevant config from foundry.toml and checks that the chain ID matches what we expect.

Also: bonus points if we can make some easy-to-understand or reusable inspector <-> channel abstraction since it is useful for more things. For example, we could use it for a better debugging experience that doesn't crash on big contracts

@odyslam
Copy link
Contributor

odyslam commented May 10, 2022

@onbjerg I like the generality of vm.env.

I sense that this is somewhat relevant to the efforts of @brockelmore to read/write data from files. is there a formula to kill 2 birds with one stone here?

@onbjerg
Copy link
Collaborator

onbjerg commented May 10, 2022

Not really. My only concern about only vm.env for this is that it's easy to set the wrong RPC URL in an environment variable, so to be safe you'd have to check that the chain ID matches what you expect, which some people may want yet another cheatcode for instead of using block.chainid. So, either we use vm.env for this + some helper in Forge Std, or we use vm.chain (or something with a better name) for this feature and add vm.env in a separate task.

@gakonst
Copy link
Member

gakonst commented May 10, 2022

One idea in the middle:

  1. Adding a chainId -> RPC url mapping to Foundry toml
  2. Vm.setFork (h/t @brockelmore) which would reset the url in the backend handler

Main thing to keep in mind imo is how do you maintain the in memory cache as you switch forks.

I'd say vm.env is a little out of scope as a cheatcode , but we want IMO to be able to read env vars into foundry toml so we can have shared prefix urls and only appending the api key cc @mattsse

@mds1
Copy link
Collaborator

mds1 commented May 10, 2022

FWIW this is also relevant to #939. We'll need a block number anyway, so perhaps we can use vm.setFork(string rpcUrl, uint256 blockNumber) to solve both of these

@mattsse
Copy link
Member

mattsse commented May 11, 2022

vm.setFork(string rpcUrl, uint256 blockNumber) would either set or, change the fork for the remainder for the test or until the next vm.setFork is called, right?

the rules regarding what state is kept when switching forks are not clear yet.

Basically a fork DB consists of two halves: read, write:

  • everything fetched from the remote is stored in the read part,
  • All modifications are stored in the write part

when the executor needs a specific storage value then it does this check essentially:

let value = write.get(addr, slot).unwrap_or(read.get(addr, slot))

which reads: "if the value exists in the write halve return this, otherwise return from the read halve and fetch from remote if missing"

so a possible solution would be to switch out the read halve when switching fork

@mds1
Copy link
Collaborator

mds1 commented May 11, 2022

vm.setFork(string rpcUrl, uint256 blockNumber) would either set or, change the fork for the remainder for the test or until the next vm.setFork is called, right?

This sounds right to me, but worth considering fork / startFork / stopFork semantics to be consistent with other cheatcodes + enable switching to no fork if needed.

so a possible solution would be to switch out the read halve when switching fork

Hmm, do we also need to switch out the write half? I'm imagining a case where a contract exists at the same address on two chains, but with different state. If I update that contract's state while forking from chain 1, we should ensure that we don't read the new chain 1 state after switching to chain 2

@mattsse
Copy link
Member

mattsse commented May 11, 2022

so each fork has its own state, which you can load into the current context (setFork)

How would we handle the state of the test contract that we're currently in? clone when changing fork?

Repository owner moved this from Todo to Done in Foundry Jul 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-evm Area: EVM C-forge Command: forge Cmd-forge-debug Command: forge run Cmd-forge-test Command: forge test T-feature Type: feature
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

9 participants