Skip to content

Commit

Permalink
op-deployer: Support forking live chains (ethereum-optimism#12918)
Browse files Browse the repository at this point in the history
  • Loading branch information
mslipper authored Nov 14, 2024
1 parent ae78b73 commit 50564d4
Show file tree
Hide file tree
Showing 28 changed files with 483 additions and 737 deletions.
10 changes: 1 addition & 9 deletions op-chain-ops/script/cheatcodes_forking.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,7 @@ func (c *CheatCodesPrecompile) CreateSelectFork_84d52b7a(urlOrAlias string, txHa
// createSelectFork implements vm.createSelectFork:
// https://book.getfoundry.sh/cheatcodes/create-select-fork
func (c *CheatCodesPrecompile) createSelectFork(opts ...ForkOption) (*big.Int, error) {
src, err := c.h.onFork(opts...)
if err != nil {
return nil, fmt.Errorf("failed to setup fork source: %w", err)
}
id, err := c.h.state.CreateSelectFork(src)
if err != nil {
return nil, fmt.Errorf("failed to create-select fork: %w", err)
}
return id.U256().ToBig(), nil
return c.h.CreateSelectFork(opts...)
}

// ActiveFork implements vm.activeFork:
Expand Down
4 changes: 3 additions & 1 deletion op-chain-ops/script/forking/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ func NewForkDB(source ForkSource) *ForkDB {
// fakeRoot is just a marker; every account we load into the fork-db has this storage-root.
// When opening a storage-trie, we sanity-check we have this root, or an empty trie.
// And then just return the same global trie view for storage reads/writes.
var fakeRoot = common.Hash{0: 42}
// It needs to be set to EmptyRootHash to avoid contract collision errors when
// deploying contracts, since Geth checks the storage root prior to deployment.
var fakeRoot = types.EmptyRootHash

func (f *ForkDB) OpenTrie(root common.Hash) (state.Trie, error) {
if f.active.stateRoot != root {
Expand Down
18 changes: 16 additions & 2 deletions op-chain-ops/script/forking/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ func NewForkableState(base VMStateDB) *ForkableState {
addresses.DefaultSenderAddr: ForkID{},
addresses.VMAddr: ForkID{},
addresses.ConsoleAddr: ForkID{},
addresses.ScriptDeployer: ForkID{},
addresses.ForgeDeployer: ForkID{},
},
fallback: base,
idCounter: 0,
Expand Down Expand Up @@ -194,11 +192,27 @@ func (fst *ForkableState) MakePersistent(addr common.Address) {
fst.persistent[addr] = fst.activeFork
}

// MakeExcluded excludes an account from forking. This is useful for things like scripts, which
// should always use the fallback state.
func (fst *ForkableState) MakeExcluded(addr common.Address) {
fst.persistent[addr] = ForkID{}
}

// RevokePersistent is like vm.revokePersistent, it undoes a previous vm.makePersistent.
func (fst *ForkableState) RevokePersistent(addr common.Address) {
delete(fst.persistent, addr)
}

// RevokeExcluded undoes MakeExcluded. It will panic if the account was marked as
// persistent in a different fork.
func (fst *ForkableState) RevokeExcluded(addr common.Address) {
forkID, ok := fst.persistent[addr]
if ok && forkID != (ForkID{}) {
panic(fmt.Sprintf("cannot revoke excluded account %s since it was made persistent in fork %q", addr, forkID))
}
delete(fst.persistent, addr)
}

// IsPersistent is like vm.isPersistent, it checks if an account persists across forks.
func (fst *ForkableState) IsPersistent(addr common.Address) bool {
_, ok := fst.persistent[addr]
Expand Down
13 changes: 12 additions & 1 deletion op-chain-ops/script/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"math/big"

"github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses"

"github.com/holiman/uint256"

"github.com/ethereum/go-ethereum/accounts/abi"
Expand Down Expand Up @@ -850,3 +849,15 @@ func (h *Host) RememberOnLabel(label, srcFile, contract string) error {
})
return nil
}

func (h *Host) CreateSelectFork(opts ...ForkOption) (*big.Int, error) {
src, err := h.onFork(opts...)
if err != nil {
return nil, fmt.Errorf("failed to setup fork source: %w", err)
}
id, err := h.state.CreateSelectFork(src)
if err != nil {
return nil, fmt.Errorf("failed to create-select fork: %w", err)
}
return id.U256().ToBig(), nil
}
4 changes: 2 additions & 2 deletions op-chain-ops/script/script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,8 @@ func TestForkingScript(t *testing.T) {
addr, err := h.LoadContract("ScriptExample.s.sol", "ForkTester")
require.NoError(t, err)
h.AllowCheatcodes(addr)
// Make this script persistent so it doesn't call the fork RPC.
h.state.MakePersistent(addr)
// Make this script excluded so it doesn't call the fork RPC.
h.state.MakeExcluded(addr)
t.Logf("allowing %s to access cheatcodes", addr)

input := bytes4("run()")
Expand Down
4 changes: 2 additions & 2 deletions op-chain-ops/script/with.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func WithScript[B any](h *Host, name string, contract string) (b *B, cleanup fun
// compute address of script contract to be deployed
addr := crypto.CreateAddress(deployer, deployNonce)
h.Label(addr, contract)
h.AllowCheatcodes(addr) // before constructor execution, give our script cheatcode access
h.state.MakePersistent(addr) // scripts are persistent across forks
h.AllowCheatcodes(addr) // before constructor execution, give our script cheatcode access
h.state.MakeExcluded(addr) // scripts are persistent across forks

// init bindings (with ABI check)
bindings, err := MakeBindings[B](h.ScriptBackendFn(addr), func(abiDef string) bool {
Expand Down
Loading

0 comments on commit 50564d4

Please sign in to comment.