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

meta(compatibility): handle EVM Semantic differences across chains #748

Open
mds1 opened this issue Feb 16, 2022 · 5 comments
Open

meta(compatibility): handle EVM Semantic differences across chains #748

mds1 opened this issue Feb 16, 2022 · 5 comments
Labels
A-evm Area: EVM C-forge Command: forge T-meta Type: meta

Comments

@mds1
Copy link
Collaborator

mds1 commented Feb 16, 2022

Component

Forge

Describe the feature you would like

As L2s grow in popularity, developers will run into issues due to the fact that not all EVM compatible networks have the same semantics as L1. For example, if you want to develop and test against a forked Arbitrum:

  • On Arbitrum, block.number returns the most recent L1 block. But forge doesn't know this.
  • If I fork Arbitrum mainnet for my tests and rely on a contract with a lastBlockNumber storage variable, that variable will have L1 block numbers
  • If my contracts rely on block delta between the current block and the lastBlockNumber stored, forge's block.number will return the provider's block number, which is about 6M, but the last L1 block is about 14M
  • Therefore by default my tests will fail due to underflow when computing block delta, because currentBlock - lastBlock would evaluate to 6M - 14M
  • One other issue with Arbitrum is that is has different gas accounting than the L1 EVM implementation

In forge, we can use the vm.roll cheatcode to simply move the block number forward or back as required and work around this. And maybe that's ok, but:

  1. It still feels a bit dirty, clunky, and potentially risky to have to hack around differing semantics in this way
  2. I can no longer easily run the same test suite on both arbitrum and mainnet, because I need to execute vm.roll conditionally based on network, and I don't think there's currently an easy way to do that
  3. Other networks might not have such a straightforward workaround
  4. There's still the gas accounting issue

So the scope of this issue is: how should forge handle this? Some possibilities:

  1. Don't handle it: if running tests and the chain ID doesn't correspond to L1 mainnet or testnet, show a warning as the very last thing before tests execute, such as "WARNING: This network may have different EVM semantics than mainnet, and therefore behavior of your contracts and tests may not represent what you'd see in production"
  2. Handle each network's differences: For example, update the VM logic to handle pulling timestamps from a different RPC than the fork RPC. Then if forge detect's the RPC is an Arbitrum network, it can ensure there's also a mainnet RPC also and use that to mirror production behavior.
  3. Push the behavior of 2 into some plugin layer as part of Plugin system #706

The three ideas above a ll have their pros and cons, and there may be other ideas.

Personally I lean away from option 1, and would prefer options 2 or 3. As a developer, those would give me more confidence that my system does behave as intended, whereas with option 1 it's much harder to get that confidence.

Another question is how many networks have differing semantics, and how many differences are there? If it's just Arbitrum's block.number different, option 2 is much more feasible than if there's multiple networks and multiple other differences, that gets much more complex.

Additional context

@mds1 mds1 added the T-feature Type: feature label Feb 16, 2022
@onbjerg onbjerg added T-meta Type: meta C-forge Command: forge and removed T-feature Type: feature labels Feb 16, 2022
@mds1 mds1 mentioned this issue Mar 12, 2022
@onbjerg
Copy link
Member

onbjerg commented Jul 1, 2022

Is this being solved by #1715? cc @mattsse

@onbjerg onbjerg added this to the v1.0.0 milestone Jul 1, 2022
@mattsse
Copy link
Member

mattsse commented Jul 8, 2022

@mds1 with #1715
this would be possible to implement now, but I'm not sure about the ergonomics yet.

how should we handle it when we're forking arbitrum for example?

another forkL2(l1Rpc, l2Rpc, l2Block)(l1Id,l2Id) cheatcode for forking L2s? because we need two endpoints.

then we'd:

  1. setup L2 fork
  2. get l1block
  3. setup L1 fork
  4. pin block.* to L1 env
  5. return fork Ids

@mds1
Copy link
Collaborator Author

mds1 commented Jul 8, 2022

Hmm, something like that would work, however it only solves that one difference and there are many other differences, so it's probably worth thinking of a more generalized approach first before implementing that.

However, both Arbitrum and Optimism have major upgrades coming soon ™️ (Nitro and Bedrock, respectively), which will change some of those semantics. They'll both still be a bit different than L1, but they will get more similar to L1 AFAIK, so some of the above differences go away and some other changes arise. (I only mention those two L2s since they're the most popular, but there are others I'm less familiar with).

Perhaps a good path forward here is to:

  • Enumerate the current differences from L1 semantics for Arbitrum/Optimism
  • Enumerate the future differences from L1 semantics for Arbitrum/Optimism after the upcoming upgrades
  • Get an estimate from their teams about time to release the upgrade (and maybe they can provide us with both of the above also)
  • Use the above to gauge if we should punt until the upgrades, figure out an approach now, etc.

IMO this particular block number difference is easy enough to work around at the moment thanks vm.roll + checking the current chain ID for any conditional test logic, so I don't think we have an urgent need for a forkL2 type of cheatcode. However, @odyslam and @hexonaut do more cross-chain work/testing than I do so I'd love to get their thoughts too.

@onbjerg onbjerg removed this from the v1.0.0 milestone Aug 23, 2022
@Evalir Evalir changed the title meta: how to handle networks with different EVM semantics meta: handle EVM Semantic differences across chains May 24, 2023
@Evalir
Copy link
Member

Evalir commented May 24, 2023

Adding to this issue an issue encountered while fixing an unrelated problem:

@dghelm
Copy link
Contributor

dghelm commented Nov 28, 2023

Do any contributors have a sense of the latest thinking around this? I see revm get mentioned in recent closed issues and in #6222 and some recent refactoring like #6128, and I was wondering if there are less complex options for supporting EVM differences relative to the latest comment over 6 months ago.

For my context: Builders and security researchers on Scroll have run into issues where forge tests don't account for EVM differences on our L2. One example: we revert calls to selfdestruct.

So, we don't need additional precompiles or opcodes, but we do need to revert calls to some of them, and the ability to patch the behavior of others would be very helpful. Even for optimism, who has up-to-date tooling in revm and other libs, is the difficulty for leveraging this in Anvil due to these features needing to be exclusively enabled at compile time?

@zerosnacks zerosnacks added the A-evm Area: EVM label Jun 26, 2024
@zerosnacks zerosnacks changed the title meta: handle EVM Semantic differences across chains meta(compatibility): handle EVM Semantic differences across chains Jun 27, 2024
@zerosnacks zerosnacks modified the milestone: v1.0.0 Jul 26, 2024
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 T-meta Type: meta
Projects
No open projects
Status: Todo
Development

No branches or pull requests

6 participants