Commit c9567718 authored by protolambda's avatar protolambda Committed by GitHub

L2 genesis - refactor to use solidity script (#10106)

* L2 genesis solidity updates by Wyatt and Mark

Commits:
- Tmp change for Graphite
- Add Missing Predeploys to L2 Genesis Script
- contracts-bedrock: refactor L2 genesis generation
- wip
- temp
- l2 genesis generation wip
- updates
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
Co-authored-by: default avatarWyatt Barnes <me@wyatt.email>

* L2 genesis refactor fixes

Squashed commits:

todo

add ProxyAdmin predeploy, clean up test assertions

order predeploy setters

split Predeploys and Preinstalls

L2 genesis script: bytes32(0) style suggestion

move predeploy utils to predeploy library

preinstalls bytecode

fix typos

permit2 bytecode immutable patching

activate ecotone

cleanup tests, fixes, ecotone

work in progress GenesisL2 addition to Setup()

fixes

devnet allocs

fix go lint

more fixes

fix predeploys proxy impl checking test

fix solady

continue Go integration

l2 genesis Go integration stuff

fix lint

fix go lint

lint fixes

fixes for some PR review comments

Predeploys test clean up

go test superseded by solidity testing

fix lint

cleanup and review fixes

minor fixes

test fixes

op-e2e l2 allocs filepath fix

more devnet test funds

improve logging

fix devnet allocs-l2 path naming and output file moving to .devnet

devnet allocs CI fixes

circle ci workspace allocs-l2 fixes

op-e2e: fix alloc npe

fix

enforce genesis allocs copy

op-e2e fix

go test fixes, 4788 nonce edge case, dev accounts fix, misc fixes

fix test, fix flake

fix tests

Proxy artifact workaround

update gas snapshot

undo workaround, apply config change to fix

undo failed workarounds

* contracts-bedrock: delete dead L2 genesis testing code

* state-diff: update

* ctb: L2 genesis delete dead comment

* contracts-bedrock: small cleanups

* ctb: cleanup L2 genesis comments

---------
Co-authored-by: default avatarWyatt Barnes <me@wyatt.email>
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
parent 53afefdd
...@@ -229,11 +229,17 @@ jobs: ...@@ -229,11 +229,17 @@ jobs:
- "packages/contracts-bedrock/tsconfig.tsbuildinfo" - "packages/contracts-bedrock/tsconfig.tsbuildinfo"
- "packages/contracts-bedrock/tsconfig.build.tsbuildinfo" - "packages/contracts-bedrock/tsconfig.build.tsbuildinfo"
- ".devnet/allocs-l1.json" - ".devnet/allocs-l1.json"
- ".devnet/allocs-l2.json"
- ".devnet/allocs-l2-delta.json"
- ".devnet/addresses.json" - ".devnet/addresses.json"
- ".devnet-fault-proofs/allocs-l1.json" - ".devnet-fault-proofs/allocs-l1.json"
- ".devnet-fault-proofs/addresses.json" - ".devnet-fault-proofs/addresses.json"
- ".devnet-fault-proofs/allocs-l2.json"
- ".devnet-fault-proofs/allocs-l2-delta.json"
- ".devnet-plasma/allocs-l1.json" - ".devnet-plasma/allocs-l1.json"
- ".devnet-plasma/addresses.json" - ".devnet-plasma/addresses.json"
- ".devnet-plasma/allocs-l2.json"
- ".devnet-plasma/allocs-l2-delta.json"
- "packages/contracts-bedrock/deploy-config/devnetL1.json" - "packages/contracts-bedrock/deploy-config/devnetL1.json"
- "packages/contracts-bedrock/deployments/devnetL1" - "packages/contracts-bedrock/deployments/devnetL1"
...@@ -963,6 +969,8 @@ jobs: ...@@ -963,6 +969,8 @@ jobs:
name: Load devnet-allocs name: Load devnet-allocs
command: | command: |
mkdir -p .devnet mkdir -p .devnet
cp /tmp/workspace/.devnet<<parameters.fpac>>/allocs-l2.json .devnet/allocs-l2.json
cp /tmp/workspace/.devnet<<parameters.fpac>>/allocs-l2-delta.json .devnet/allocs-l2-delta.json
cp /tmp/workspace/.devnet<<parameters.fpac>>/allocs-l1.json .devnet/allocs-l1.json cp /tmp/workspace/.devnet<<parameters.fpac>>/allocs-l1.json .devnet/allocs-l1.json
cp /tmp/workspace/.devnet<<parameters.fpac>>/addresses.json .devnet/addresses.json cp /tmp/workspace/.devnet<<parameters.fpac>>/addresses.json .devnet/addresses.json
cp /tmp/workspace/packages/contracts-bedrock/deploy-config/devnetL1.json packages/contracts-bedrock/deploy-config/devnetL1.json cp /tmp/workspace/packages/contracts-bedrock/deploy-config/devnetL1.json packages/contracts-bedrock/deploy-config/devnetL1.json
...@@ -1155,6 +1163,8 @@ jobs: ...@@ -1155,6 +1163,8 @@ jobs:
- persist_to_workspace: - persist_to_workspace:
root: . root: .
paths: paths:
- ".devnet/allocs-l2.json"
- ".devnet/allocs-l2-delta.json"
- ".devnet/allocs-l1.json" - ".devnet/allocs-l1.json"
- ".devnet/addresses.json" - ".devnet/addresses.json"
- "packages/contracts-bedrock/deploy-config/devnetL1.json" - "packages/contracts-bedrock/deploy-config/devnetL1.json"
......
...@@ -63,7 +63,7 @@ def main(): ...@@ -63,7 +63,7 @@ def main():
devnet_dir = pjoin(monorepo_dir, '.devnet') devnet_dir = pjoin(monorepo_dir, '.devnet')
contracts_bedrock_dir = pjoin(monorepo_dir, 'packages', 'contracts-bedrock') contracts_bedrock_dir = pjoin(monorepo_dir, 'packages', 'contracts-bedrock')
deployment_dir = pjoin(contracts_bedrock_dir, 'deployments', 'devnetL1') deployment_dir = pjoin(contracts_bedrock_dir, 'deployments', 'devnetL1')
forge_dump_path = pjoin(contracts_bedrock_dir, 'Deploy-900.json') forge_l1_dump_path = pjoin(contracts_bedrock_dir, 'state-dump-900.json')
op_node_dir = pjoin(args.monorepo_dir, 'op-node') op_node_dir = pjoin(args.monorepo_dir, 'op-node')
ops_bedrock_dir = pjoin(monorepo_dir, 'ops-bedrock') ops_bedrock_dir = pjoin(monorepo_dir, 'ops-bedrock')
deploy_config_dir = pjoin(contracts_bedrock_dir, 'deploy-config') deploy_config_dir = pjoin(contracts_bedrock_dir, 'deploy-config')
...@@ -77,7 +77,7 @@ def main(): ...@@ -77,7 +77,7 @@ def main():
devnet_dir=devnet_dir, devnet_dir=devnet_dir,
contracts_bedrock_dir=contracts_bedrock_dir, contracts_bedrock_dir=contracts_bedrock_dir,
deployment_dir=deployment_dir, deployment_dir=deployment_dir,
forge_dump_path=forge_dump_path, forge_l1_dump_path=forge_l1_dump_path,
l1_deployments_path=pjoin(deployment_dir, '.deploy'), l1_deployments_path=pjoin(deployment_dir, '.deploy'),
deploy_config_dir=deploy_config_dir, deploy_config_dir=deploy_config_dir,
devnet_config_path=devnet_config_path, devnet_config_path=devnet_config_path,
...@@ -88,7 +88,7 @@ def main(): ...@@ -88,7 +88,7 @@ def main():
sdk_dir=sdk_dir, sdk_dir=sdk_dir,
genesis_l1_path=pjoin(devnet_dir, 'genesis-l1.json'), genesis_l1_path=pjoin(devnet_dir, 'genesis-l1.json'),
genesis_l2_path=pjoin(devnet_dir, 'genesis-l2.json'), genesis_l2_path=pjoin(devnet_dir, 'genesis-l2.json'),
allocs_path=pjoin(devnet_dir, 'allocs-l1.json'), allocs_l1_path=pjoin(devnet_dir, 'allocs-l1.json'),
addresses_json_path=pjoin(devnet_dir, 'addresses.json'), addresses_json_path=pjoin(devnet_dir, 'addresses.json'),
sdk_addresses_json_path=pjoin(devnet_dir, 'sdk-addresses.json'), sdk_addresses_json_path=pjoin(devnet_dir, 'sdk-addresses.json'),
rollup_config_path=pjoin(devnet_dir, 'rollup.json') rollup_config_path=pjoin(devnet_dir, 'rollup.json')
...@@ -102,7 +102,8 @@ def main(): ...@@ -102,7 +102,8 @@ def main():
os.makedirs(devnet_dir, exist_ok=True) os.makedirs(devnet_dir, exist_ok=True)
if args.allocs: if args.allocs:
devnet_l1_genesis(paths) devnet_l1_allocs(paths)
devnet_l2_allocs(paths)
return return
git_commit = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True, text=True).stdout.strip() git_commit = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True, text=True).stdout.strip()
...@@ -136,8 +137,8 @@ def init_devnet_l1_deploy_config(paths, update_timestamp=False): ...@@ -136,8 +137,8 @@ def init_devnet_l1_deploy_config(paths, update_timestamp=False):
deploy_config['usePlasma'] = True deploy_config['usePlasma'] = True
write_json(paths.devnet_config_path, deploy_config) write_json(paths.devnet_config_path, deploy_config)
def devnet_l1_genesis(paths): def devnet_l1_allocs(paths):
log.info('Generating L1 genesis state') log.info('Generating L1 genesis allocs')
init_devnet_l1_deploy_config(paths) init_devnet_l1_deploy_config(paths)
fqn = 'scripts/Deploy.s.sol:Deploy' fqn = 'scripts/Deploy.s.sol:Deploy'
...@@ -146,27 +147,52 @@ def devnet_l1_genesis(paths): ...@@ -146,27 +147,52 @@ def devnet_l1_genesis(paths):
'forge', 'script', '--chain-id', '900', fqn, "--sig", "runWithStateDump()", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" 'forge', 'script', '--chain-id', '900', fqn, "--sig", "runWithStateDump()", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
], env={}, cwd=paths.contracts_bedrock_dir) ], env={}, cwd=paths.contracts_bedrock_dir)
forge_dump = read_json(paths.forge_dump_path) forge_dump = read_json(paths.forge_l1_dump_path)
write_json(paths.allocs_path, { "accounts": forge_dump }) write_json(paths.allocs_l1_path, { "accounts": forge_dump })
os.remove(paths.forge_dump_path) os.remove(paths.forge_l1_dump_path)
shutil.copy(paths.l1_deployments_path, paths.addresses_json_path) shutil.copy(paths.l1_deployments_path, paths.addresses_json_path)
def devnet_l2_allocs(paths):
log.info('Generating L2 genesis allocs, with L1 addresses: '+paths.l1_deployments_path)
fqn = 'scripts/L2Genesis.s.sol:L2Genesis'
# Use foundry pre-funded account #1 for the deployer
run_command([
'forge', 'script', '--chain-id', '901', fqn, "--sig", "runWithAllUpgrades()", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
], env={
'CONTRACT_ADDRESSES_PATH': paths.l1_deployments_path,
}, cwd=paths.contracts_bedrock_dir)
# For the previous forks, and the latest fork (default, thus empty prefix),
# move the forge-dumps into place as .devnet allocs.
for suffix in ["-delta", ""]:
input_path = pjoin(paths.contracts_bedrock_dir, f"state-dump-901{suffix}.json")
forge_dump = read_json(input_path)
output_path = pjoin(paths.devnet_dir, f'allocs-l2{suffix}.json')
write_json(output_path, { "accounts": forge_dump })
os.remove(input_path)
log.info("Generated L2 allocs: "+output_path)
# Bring up the devnet where the contracts are deployed to L1 # Bring up the devnet where the contracts are deployed to L1
def devnet_deploy(paths): def devnet_deploy(paths):
if os.path.exists(paths.genesis_l1_path): if os.path.exists(paths.genesis_l1_path):
log.info('L1 genesis already generated.') log.info('L1 genesis already generated.')
else: else:
log.info('Generating L1 genesis.') log.info('Generating L1 genesis.')
if not os.path.exists(paths.allocs_path) or DEVNET_FPAC: if not os.path.exists(paths.allocs_l1_path) or DEVNET_FPAC or DEVNET_PLASMA:
# If this is the FPAC devnet then we need to generate the allocs # If this is the FPAC devnet then we need to generate the allocs
# file here always. This is because CI will run devnet-allocs # file here always. This is because CI will run devnet-allocs
# without DEVNET_FPAC=true which means the allocs will be wrong. # without DEVNET_FPAC=true which means the allocs will be wrong.
# Re-running this step means the allocs will be correct. # Re-running this step means the allocs will be correct.
devnet_l1_genesis(paths) devnet_l1_allocs(paths)
else:
log.info('Re-using existing L1 allocs.')
# It's odd that we want to regenerate the devnetL1.json file with # It's odd that we want to regenerate the devnetL1.json file with
# an updated timestamp different than the one used in the devnet_l1_genesis # an updated timestamp different than the one used in the devnet_l1_allocs
# function. But, without it, CI flakes on this test rather consistently. # function. But, without it, CI flakes on this test rather consistently.
# If someone reads this comment and understands why this is being done, please # If someone reads this comment and understands why this is being done, please
# update this comment to explain. # update this comment to explain.
...@@ -174,7 +200,7 @@ def devnet_deploy(paths): ...@@ -174,7 +200,7 @@ def devnet_deploy(paths):
run_command([ run_command([
'go', 'run', 'cmd/main.go', 'genesis', 'l1', 'go', 'run', 'cmd/main.go', 'genesis', 'l1',
'--deploy-config', paths.devnet_config_path, '--deploy-config', paths.devnet_config_path,
'--l1-allocs', paths.allocs_path, '--l1-allocs', paths.allocs_l1_path,
'--l1-deployments', paths.addresses_json_path, '--l1-deployments', paths.addresses_json_path,
'--outfile.l1', paths.genesis_l1_path, '--outfile.l1', paths.genesis_l1_path,
], cwd=paths.op_node_dir) ], cwd=paths.op_node_dir)
...@@ -190,10 +216,19 @@ def devnet_deploy(paths): ...@@ -190,10 +216,19 @@ def devnet_deploy(paths):
log.info('L2 genesis and rollup configs already generated.') log.info('L2 genesis and rollup configs already generated.')
else: else:
log.info('Generating L2 genesis and rollup configs.') log.info('Generating L2 genesis and rollup configs.')
l2_allocs_path = pjoin(paths.devnet_dir, 'allocs-l2.json')
if os.path.exists(l2_allocs_path) == False or DEVNET_FPAC == True:
# Also regenerate if FPAC.
# The FPAC flag may affect the L1 deployments addresses, which may affect the L2 genesis.
devnet_l2_allocs(paths)
else:
log.info('Re-using existing L2 allocs.')
run_command([ run_command([
'go', 'run', 'cmd/main.go', 'genesis', 'l2', 'go', 'run', 'cmd/main.go', 'genesis', 'l2',
'--l1-rpc', 'http://localhost:8545', '--l1-rpc', 'http://localhost:8545',
'--deploy-config', paths.devnet_config_path, '--deploy-config', paths.devnet_config_path,
'--l2-allocs', l2_allocs_path,
'--l1-deployments', paths.addresses_json_path, '--l1-deployments', paths.addresses_json_path,
'--outfile.l2', paths.genesis_l2_path, '--outfile.l2', paths.genesis_l2_path,
'--outfile.rollup', paths.rollup_config_path '--outfile.rollup', paths.rollup_config_path
......
...@@ -10,9 +10,12 @@ import ( ...@@ -10,9 +10,12 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"golang.org/x/exp/maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
gstate "github.com/ethereum/go-ethereum/core/state" gstate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -803,6 +806,7 @@ func (d *ForgeDump) UnmarshalJSON(b []byte) error { ...@@ -803,6 +806,7 @@ func (d *ForgeDump) UnmarshalJSON(b []byte) error {
d.Root = dump.Root d.Root = dump.Root
d.Accounts = make(map[string]gstate.DumpAccount) d.Accounts = make(map[string]gstate.DumpAccount)
for addr, acc := range dump.Accounts { for addr, acc := range dump.Accounts {
acc := acc
d.Accounts[addr.String()] = gstate.DumpAccount{ d.Accounts[addr.String()] = gstate.DumpAccount{
Balance: acc.Balance, Balance: acc.Balance,
Nonce: (uint64)(acc.Nonce), Nonce: (uint64)(acc.Nonce),
...@@ -817,6 +821,45 @@ func (d *ForgeDump) UnmarshalJSON(b []byte) error { ...@@ -817,6 +821,45 @@ func (d *ForgeDump) UnmarshalJSON(b []byte) error {
return nil return nil
} }
type ForgeAllocs struct {
Accounts core.GenesisAlloc `json:"accounts"`
}
func (d *ForgeAllocs) Copy() *ForgeAllocs {
out := make(core.GenesisAlloc, len(d.Accounts))
maps.Copy(out, d.Accounts)
return &ForgeAllocs{Accounts: out}
}
func (d *ForgeAllocs) UnmarshalJSON(b []byte) error {
// forge, since integrating Alloy, likes to hex-encode everything.
type forgeAllocAccount struct {
Balance hexutil.Big `json:"balance"`
Nonce hexutil.Uint64 `json:"nonce"`
Code hexutil.Bytes `json:"code,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
}
type forgeAllocs struct {
Accounts map[common.Address]forgeAllocAccount `json:"accounts"`
}
var allocs forgeAllocs
if err := json.Unmarshal(b, &allocs); err != nil {
return err
}
d.Accounts = make(core.GenesisAlloc, len(allocs.Accounts))
for addr, acc := range allocs.Accounts {
acc := acc
d.Accounts[addr] = core.GenesisAccount{
Code: acc.Code,
Storage: acc.Storage,
Balance: acc.Balance.ToInt(),
Nonce: (uint64)(acc.Nonce),
PrivateKey: nil,
}
}
return nil
}
// NewL2ImmutableConfig will create an ImmutableConfig given an instance of a // NewL2ImmutableConfig will create an ImmutableConfig given an instance of a
// DeployConfig and a block. // DeployConfig and a block.
func NewL2ImmutableConfig(config *DeployConfig, block *types.Block) (*immutables.PredeploysImmutableConfig, error) { func NewL2ImmutableConfig(config *DeployConfig, block *types.Block) (*immutables.PredeploysImmutableConfig, error) {
......
...@@ -51,7 +51,7 @@ var DevAccounts = []common.Address{ ...@@ -51,7 +51,7 @@ var DevAccounts = []common.Address{
common.HexToAddress("0xcd3B766CCDd6AE721141F452C550Ca635964ce71"), common.HexToAddress("0xcd3B766CCDd6AE721141F452C550Ca635964ce71"),
common.HexToAddress("0xdD2FD4581271e230360230F9337D5c0430Bf44C0"), common.HexToAddress("0xdD2FD4581271e230360230F9337D5c0430Bf44C0"),
common.HexToAddress("0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097"), common.HexToAddress("0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097"),
common.HexToAddress("0xde3829a23df1479438622a08a116e8eb3f620bb5"), common.HexToAddress("0xDe3829A23DF1479438622a08a116E8Eb3f620BB5"),
common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
// Test account used by geth tests // Test account used by geth tests
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"), common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"),
......
package genesis package genesis
import ( import (
"encoding/json"
"fmt" "fmt"
"math/big" "os"
"path/filepath"
"github.com/ethereum/go-ethereum/common" hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/squash"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/eth"
) )
type L2AllocsMode string
const (
L2AllocsDelta L2AllocsMode = "delta"
L2AllocsEcotone L2AllocsMode = "" // the default in solidity scripting / testing
)
type AllocsLoader func(mode L2AllocsMode) *ForgeAllocs
// BuildL2Genesis will build the L2 genesis block. // BuildL2Genesis will build the L2 genesis block.
func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) { func BuildL2Genesis(config *DeployConfig, dump *ForgeAllocs, l1StartBlock *types.Block) (*core.Genesis, error) {
genspec, err := NewL2Genesis(config, l1StartBlock) genspec, err := NewL2Genesis(config, l1StartBlock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
genspec.Alloc = dump.Accounts
db := state.NewMemoryStateDB(genspec) // ensure the dev accounts are not funded unintentionally
if config.FundDevAccounts { if hasDevAccounts, err := HasAnyDevAccounts(dump.Accounts); err != nil {
log.Info("Funding developer accounts in L2 genesis") return nil, fmt.Errorf("failed to check dev accounts: %w", err)
FundDevAccounts(db) } else if hasDevAccounts != config.FundDevAccounts {
return nil, fmt.Errorf("deploy config mismatch with allocs. Deploy config fundDevAccounts: %v, actual allocs: %v", config.FundDevAccounts, hasDevAccounts)
} }
// sanity check the permit2 immutable, to verify we using the allocs for the right chain.
SetPrecompileBalances(db) chainID := [32]byte(genspec.Alloc[predeploys.Permit2Addr].Code[6945 : 6945+32])
expected := uint256.MustFromBig(genspec.Config.ChainID).Bytes32()
storage, err := NewL2StorageConfig(config, l1StartBlock) if chainID != expected {
if err != nil { return nil, fmt.Errorf("allocs were generated for chain ID %x, but expected chain %x (%d)", chainID, expected, genspec.Config.ChainID)
return nil, err
} }
return genspec, nil
}
immutableConfig, err := NewL2ImmutableConfig(config, l1StartBlock) var testMnemonic = "test test test test test test test test test test test junk"
if err != nil {
return nil, err
}
// Set up the proxies func HasAnyDevAccounts(allocs core.GenesisAlloc) (bool, error) {
err = setProxies(db, predeploys.ProxyAdminAddr, BigL2PredeployNamespace, 2048) wallet, err := hdwallet.NewFromMnemonic(testMnemonic)
if err != nil { if err != nil {
return nil, err return false, fmt.Errorf("failed to create wallet: %w", err)
} }
account := func(path string) accounts.Account {
// Set up the implementations that contain immutables return accounts.Account{URL: accounts.URL{Path: path}}
deployResults, err := immutables.Deploy(immutableConfig)
if err != nil {
return nil, fmt.Errorf("immutables.Deploy failed: %w", err)
} }
for name, predeploy := range predeploys.Predeploys { for i := 0; i < 30; i++ {
if predeploy.Enabled != nil && !predeploy.Enabled(config) { key, err := wallet.PrivateKey(account(fmt.Sprintf("m/44'/60'/0'/0/%d", i)))
log.Warn("Skipping disabled predeploy.", "name", name, "address", predeploy.Address) if err != nil {
continue return false, err
} }
addr := crypto.PubkeyToAddress(key.PublicKey)
codeAddr := predeploy.Address if _, ok := allocs[addr]; ok {
switch name { return true, nil
case "Permit2":
deployerAddressBytes, err := bindings.GetDeployerAddress(name)
if err != nil {
return nil, err
}
deployerAddress := common.BytesToAddress(deployerAddressBytes)
predeploys := map[string]*common.Address{
"DeterministicDeploymentProxy": &deployerAddress,
}
backend, err := deployer.NewBackendWithChainIDAndPredeploys(
new(big.Int).SetUint64(config.L2ChainID),
predeploys,
)
if err != nil {
return nil, fmt.Errorf("NewBackendWithChainIDAndPredeploys failed: %w", err)
}
deployedBin, err := deployer.DeployWithDeterministicDeployer(backend, name)
if err != nil {
backend.Close()
return nil, fmt.Errorf("DeployWithDeterministicDeployer failed: %w", err)
}
backend.Close()
deployResults[name] = deployedBin
fallthrough
case "MultiCall3", "Create2Deployer", "Safe_v130",
"SafeL2_v130", "MultiSendCallOnly_v130", "SafeSingletonFactory",
"DeterministicDeploymentProxy", "MultiSend_v130", "SenderCreator", "EntryPoint":
db.CreateAccount(codeAddr)
default:
if !predeploy.ProxyDisabled {
codeAddr, err = AddressToCodeNamespace(predeploy.Address)
if err != nil {
return nil, fmt.Errorf("error converting to code namespace: %w", err)
}
db.CreateAccount(codeAddr)
db.SetState(predeploy.Address, ImplementationSlot, eth.AddressAsLeftPaddedHash(codeAddr))
log.Info("Set proxy", "name", name, "address", predeploy.Address, "implementation", codeAddr)
}
} }
if predeploy.ProxyDisabled && db.Exist(predeploy.Address) {
db.DeleteState(predeploy.Address, AdminSlot)
}
if err := setupPredeploy(db, deployResults, storage, name, predeploy.Address, codeAddr); err != nil {
return nil, fmt.Errorf("setupPredeploy failed: %w", err)
}
code := db.GetCode(codeAddr)
if len(code) == 0 {
return nil, fmt.Errorf("code not set for %s", name)
}
}
if err := PerformUpgradeTxs(db); err != nil {
return nil, fmt.Errorf("failed to perform upgrade txs: %w", err)
} }
return false, nil
return db.Genesis(), nil
} }
func PerformUpgradeTxs(db *state.MemoryStateDB) error { func LoadForgeAllocs(allocsPath string) (*ForgeAllocs, error) {
// Only the Ecotone upgrade is performed with upgrade-txs. path := filepath.Join(allocsPath)
if !db.Genesis().Config.IsEcotone(db.Genesis().Timestamp) { f, err := os.OpenFile(path, os.O_RDONLY, 0644)
return nil
}
sim := squash.NewSimulator(db)
ecotone, err := derive.EcotoneNetworkUpgradeTransactions()
if err != nil { if err != nil {
return fmt.Errorf("failed to build ecotone upgrade txs: %w", err) return nil, fmt.Errorf("failed to open forge allocs %q: %w", path, err)
} }
if err := sim.AddUpgradeTxs(ecotone); err != nil { defer f.Close()
return fmt.Errorf("failed to apply ecotone upgrade txs: %w", err) var out ForgeAllocs
if err := json.NewDecoder(f).Decode(&out); err != nil {
return nil, fmt.Errorf("failed to json-decode forge allocs %q: %w", path, err)
} }
return nil return &out, nil
} }
package genesis_test
import (
"context"
"encoding/json"
"flag"
"math/big"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
)
var writeFile bool
func init() {
flag.BoolVar(&writeFile, "write-file", false, "write the genesis file to disk")
}
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
// Tests the BuildL2MainnetGenesis factory with the provided config.
func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesis {
backend := backends.NewSimulatedBackend( // nolint:staticcheck
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
15000000,
)
block, err := backend.BlockByNumber(context.Background(), common.Big0)
require.NoError(t, err)
gen, err := genesis.BuildL2Genesis(config, block)
require.Nil(t, err)
require.NotNil(t, gen)
proxyBytecode, err := bindings.GetDeployedBytecode("Proxy")
require.NoError(t, err)
// for simulation we need a regular EVM, not with system-deposit information.
chainConfig := params.ChainConfig{
ChainID: big.NewInt(1337),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: false,
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
// Activated proof of stake. We manually build/commit blocks in the simulator anyway,
// and the timestamp verification of PoS is not against the wallclock,
// preventing blocks from getting stuck temporarily in the future-blocks queue, decreasing setup time a lot.
MergeNetsplitBlock: big.NewInt(0),
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
ShanghaiTime: new(uint64),
}
// Apply the genesis to the backend
cfg := ethconfig.Defaults
cfg.Preimages = true
cfg.Genesis = &core.Genesis{
Config: &chainConfig,
Timestamp: 1234567,
Difficulty: big.NewInt(0),
Alloc: gen.Alloc,
GasLimit: 30_000_000,
}
backend = backends.NewSimulatedBackendFromConfig(cfg)
for name, predeploy := range predeploys.Predeploys {
addr := predeploy.Address
if addr == predeploys.L1BlockAddr {
testL1Block(t, backend, config, block)
}
account, ok := gen.Alloc[addr]
require.Equal(t, true, ok, name)
require.Greater(t, len(account.Code), 0)
adminSlot, ok := account.Storage[genesis.AdminSlot]
isProxy := !predeploy.ProxyDisabled ||
(!config.EnableGovernance && addr == predeploys.GovernanceTokenAddr)
if isProxy {
require.Equal(t, true, ok, name)
require.Equal(t, eth.AddressAsLeftPaddedHash(predeploys.ProxyAdminAddr), adminSlot)
require.Equal(t, proxyBytecode, account.Code)
} else {
require.Equal(t, false, ok, name)
require.NotEqual(t, proxyBytecode, account.Code, name)
}
}
// All of the precompile addresses should be funded with a single wei
for i := 0; i < genesis.PrecompileCount; i++ {
addr := common.BytesToAddress([]byte{byte(i)})
require.Equal(t, common.Big1, gen.Alloc[addr].Balance)
}
create2Deployer := gen.Alloc[predeploys.Create2DeployerAddr]
codeHash := crypto.Keccak256Hash(create2Deployer.Code)
require.Equal(t, codeHash, bindings.Create2DeployerCodeHash)
if writeFile {
file, _ := json.MarshalIndent(gen, "", " ")
_ = os.WriteFile("genesis.json", file, 0644)
}
return gen
}
// testL1Block tests that the state is set correctly in the L1Block predeploy
func testL1Block(t *testing.T, caller bind.ContractCaller, config *genesis.DeployConfig, block *types.Block) {
contract, err := bindings.NewL1BlockCaller(predeploys.L1BlockAddr, caller)
require.NoError(t, err)
number, err := contract.Number(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, block.Number().Uint64(), number)
timestamp, err := contract.Timestamp(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, block.Time(), timestamp)
basefee, err := contract.Basefee(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, block.BaseFee(), basefee)
hash, err := contract.Hash(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, block.Hash(), common.Hash(hash))
sequenceNumber, err := contract.SequenceNumber(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, uint64(0), sequenceNumber)
blobBaseFeeScalar, err := contract.BlobBaseFeeScalar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, config.GasPriceOracleBlobBaseFeeScalar, blobBaseFeeScalar)
baseFeeScalar, err := contract.BaseFeeScalar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, config.GasPriceOracleBaseFeeScalar, baseFeeScalar)
batcherHeader, err := contract.BatcherHash(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, eth.AddressAsLeftPaddedHash(config.BatchSenderAddress), common.Hash(batcherHeader))
l1FeeOverhead, err := contract.L1FeeOverhead(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, config.GasPriceOracleOverhead, l1FeeOverhead.Uint64())
l1FeeScalar, err := contract.L1FeeScalar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, config.GasPriceOracleScalar, l1FeeScalar.Uint64())
blobBaseFee, err := contract.BlobBaseFee(&bind.CallOpts{})
require.NoError(t, err)
if excessBlobGas := block.ExcessBlobGas(); excessBlobGas != nil {
require.Equal(t, uint64(0), *excessBlobGas)
}
require.Equal(t, big.NewInt(1), blobBaseFee)
}
func TestBuildL2MainnetGenesis(t *testing.T) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
config.EnableGovernance = true
config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2333, len(gen.Alloc))
}
func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
config.EnableGovernance = false
config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2333, len(gen.Alloc))
}
package genesis package genesis
import ( import (
"errors"
"fmt"
"math/big"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum-optimism/optimism/op-service/eth"
) )
// PrecompileCount represents the number of precompile addresses // PrecompileCount represents the number of precompile addresses
...@@ -32,31 +22,6 @@ func FundDevAccounts(db vm.StateDB) { ...@@ -32,31 +22,6 @@ func FundDevAccounts(db vm.StateDB) {
} }
} }
func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int, count uint64) error {
depBytecode, err := bindings.GetDeployedBytecode("Proxy")
if err != nil {
return err
}
if len(depBytecode) == 0 {
return errors.New("Proxy has empty bytecode")
}
for i := uint64(0); i <= count; i++ {
bigAddr := new(big.Int).Or(namespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
if !db.Exist(addr) {
db.CreateAccount(addr)
}
db.SetCode(addr, depBytecode)
db.SetState(addr, AdminSlot, eth.AddressAsLeftPaddedHash(proxyAdminAddr))
log.Trace("Set proxy", "address", addr, "admin", proxyAdminAddr)
}
return nil
}
// SetPrecompileBalances will set a single wei at each precompile address. // SetPrecompileBalances will set a single wei at each precompile address.
// This is an optimization to make calling them cheaper. // This is an optimization to make calling them cheaper.
func SetPrecompileBalances(db vm.StateDB) { func SetPrecompileBalances(db vm.StateDB) {
...@@ -66,29 +31,3 @@ func SetPrecompileBalances(db vm.StateDB) { ...@@ -66,29 +31,3 @@ func SetPrecompileBalances(db vm.StateDB) {
db.AddBalance(addr, uint256.NewInt(1)) db.AddBalance(addr, uint256.NewInt(1))
} }
} }
func setupPredeploy(db vm.StateDB, deployResults immutables.DeploymentResults, storage state.StorageConfig, name string, proxyAddr common.Address, implAddr common.Address) error {
// Use the generated bytecode when there are immutables
// otherwise use the artifact deployed bytecode
if bytecode, ok := deployResults[name]; ok {
log.Info("Setting deployed bytecode with immutables", "name", name, "address", implAddr)
db.SetCode(implAddr, bytecode)
} else {
depBytecode, err := bindings.GetDeployedBytecode(name)
if err != nil {
return fmt.Errorf("GetDeployedBytecode failed: %w", err)
}
log.Info("Setting deployed bytecode from solc compiler output", "name", name, "address", implAddr)
db.SetCode(implAddr, depBytecode)
}
// Set the storage values
if storageConfig, ok := storage[name]; ok {
log.Info("Setting storage", "name", name, "address", proxyAddr)
if err := state.SetStorage(name, proxyAddr, storageConfig, db); err != nil {
return err
}
}
return nil
}
{
"l1StartingBlockTag": "earliest",
"l1ChainID": 900,
"l2ChainID": 901,
"l2BlockTime": 2,
"maxSequencerDrift": 100,
"sequencerWindowSize": 4,
"channelTimeout": 40,
"p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"batchInboxAddress": "0xff00000000000000000000000000000000000000",
"batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
"l2OutputOracleSubmissionInterval": 20,
"l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleStartingTimestamp": -1,
"l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2OutputOracleChallenger": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l1BlockTime": 15,
"cliqueSignerAddress": "0xca062b0fd91172d89bcd4bb084ac4e21972cc467",
"baseFeeVaultRecipient": "0xBcd4042DE499D14e55001CcbB24a551F3b954096",
"l1FeeVaultRecipient": "0x71bE63f3384f5fb98995898A86B02Fb2426c5788",
"sequencerFeeVaultRecipient": "0x71bE63f3384f5fb98995898A86B02Fb2426c5788",
"baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"baseFeeVaultWithdrawalNetwork": "local",
"l1FeeVaultWithdrawalNetwork": "local",
"sequencerFeeVaultWithdrawalNetwork": "local",
"l1ERC721BridgeProxy": "0xff000000000000000000000000000000000000ff",
"l1StandardBridgeProxy": "0xff000000000000000000000000000000000000fd",
"l1CrossDomainMessengerProxy": "0xff000000000000000000000000000000000000dd",
"deploymentWaitConfirmations": 1,
"fundDevAccounts": true,
"enableGovernance": true,
"governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism",
"governanceTokenOwner": "0x0000000000000000000000000000000000000333",
"l2GenesisRegolithTimeOffset": "0x0",
"l2GenesisCanyonTimeOffset": "0x0"
}
...@@ -43,7 +43,7 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) { ...@@ -43,7 +43,7 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
genesisBlock := hexutil.Uint64(0) genesisBlock := hexutil.Uint64(0)
ecotoneOffset := hexutil.Uint64(2) ecotoneOffset := hexutil.Uint64(4)
dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default
...@@ -59,6 +59,10 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) { ...@@ -59,6 +59,10 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
_, _, miner, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log) _, _, miner, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log)
ethCl := engine.EthClient() ethCl := engine.EthClient()
// build a single block to move away from the genesis with 0-values in L1Block contract
sequencer.ActL2StartBlock(t)
sequencer.ActL2EndBlock(t)
// start op-nodes // start op-nodes
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t)
...@@ -101,7 +105,7 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) { ...@@ -101,7 +105,7 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
txn := transactions[i] txn := transactions[i]
receipt, err := ethCl.TransactionReceipt(context.Background(), txn.Hash()) receipt, err := ethCl.TransactionReceipt(context.Background(), txn.Hash())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status) require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "tx %d must pass", i)
require.NotEmpty(t, txn.Data(), "upgrade tx must provide input data") require.NotEmpty(t, txn.Data(), "upgrade tx must provide input data")
} }
...@@ -162,11 +166,14 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) { ...@@ -162,11 +166,14 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
require.NotNil(t, latestBlock.BeaconRoot()) require.NotNil(t, latestBlock.BeaconRoot())
require.Equal(t, *latestBlock.BeaconRoot(), common.Hash{}, require.Equal(t, *latestBlock.BeaconRoot(), common.Hash{},
"L1 genesis block has zeroed parent-beacon-block-root, since it has no parent block, and that propagates into L2") "L1 genesis block has zeroed parent-beacon-block-root, since it has no parent block, and that propagates into L2")
// The first block is an exception in upgrade-networks, // Legacy check:
// since the beacon-block root contract isn't there at Ecotone activation, // > The first block is an exception in upgrade-networks,
// and the beacon-block-root insertion is processed at the start of the block before deposit txs. // > since the beacon-block root contract isn't there at Ecotone activation,
// If the contract was permissionlessly deployed before, the contract storage will be updated however. // > and the beacon-block-root insertion is processed at the start of the block before deposit txs.
checkBeaconBlockRoot(latestBlock.Time(), common.Hash{}, 0, "ecotone activation block has no data yet (since contract wasn't there)") // > If the contract was permissionlessly deployed before, the contract storage will be updated however.
// > checkBeaconBlockRoot(latestBlock.Time(), common.Hash{}, 0, "ecotone activation block has no data yet (since contract wasn't there)")
// Note: 4788 is now installed as preinstall, and thus always there.
checkBeaconBlockRoot(latestBlock.Time(), common.Hash{}, latestBlock.Time(), "4788 lookup of first cancun block is 0 hash")
// Build empty L2 block, to pass ecotone activation // Build empty L2 block, to pass ecotone activation
sequencer.ActL2StartBlock(t) sequencer.ActL2StartBlock(t)
......
package actions package actions
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
crand "crypto/rand" crand "crypto/rand"
"fmt" "fmt"
...@@ -501,6 +502,10 @@ func TestSpanBatchLowThroughputChain(gt *testing.T) { ...@@ -501,6 +502,10 @@ func TestSpanBatchLowThroughputChain(gt *testing.T) {
addr := crypto.PubkeyToAddress(privateKey.PublicKey) addr := crypto.PubkeyToAddress(privateKey.PublicKey)
require.NoError(t, err) require.NoError(t, err)
addrs[i] = addr addrs[i] = addr
bal, err := cl.BalanceAt(context.Background(), addr, nil)
require.NoError(gt, err)
require.Equal(gt, 1, bal.Cmp(common.Big0), "account %d must have non-zero balance, address: %s, balance: %d", i, addr, bal)
} }
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
......
...@@ -44,6 +44,8 @@ var ( ...@@ -44,6 +44,8 @@ var (
// L1Deployments maps contract names to accounts in the L1 // L1Deployments maps contract names to accounts in the L1
// genesis block state. // genesis block state.
L1Deployments *genesis.L1Deployments L1Deployments *genesis.L1Deployments
// l2Allocs represents the L2 allocs, by hardfork/mode (e.g. delta, ecotone, interop, other)
l2Allocs map[genesis.L2AllocsMode]*genesis.ForgeAllocs
// DeployConfig represents the deploy config used by the system. // DeployConfig represents the deploy config used by the system.
DeployConfig *genesis.DeployConfig DeployConfig *genesis.DeployConfig
// ExternalL2Shim is the shim to use if external ethereum client testing is // ExternalL2Shim is the shim to use if external ethereum client testing is
...@@ -57,7 +59,7 @@ var ( ...@@ -57,7 +59,7 @@ var (
) )
func init() { func init() {
var l1AllocsPath, l1DeploymentsPath, deployConfigPath, externalL2 string var l1AllocsPath, l2AllocsDir, l1DeploymentsPath, deployConfigPath, externalL2 string
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
...@@ -69,10 +71,12 @@ func init() { ...@@ -69,10 +71,12 @@ func init() {
} }
defaultL1AllocsPath := filepath.Join(root, ".devnet", "allocs-l1.json") defaultL1AllocsPath := filepath.Join(root, ".devnet", "allocs-l1.json")
defaultL2AllocsDir := filepath.Join(root, ".devnet")
defaultL1DeploymentsPath := filepath.Join(root, ".devnet", "addresses.json") defaultL1DeploymentsPath := filepath.Join(root, ".devnet", "addresses.json")
defaultDeployConfigPath := filepath.Join(root, "packages", "contracts-bedrock", "deploy-config", "devnetL1.json") defaultDeployConfigPath := filepath.Join(root, "packages", "contracts-bedrock", "deploy-config", "devnetL1.json")
flag.StringVar(&l1AllocsPath, "l1-allocs", defaultL1AllocsPath, "") flag.StringVar(&l1AllocsPath, "l1-allocs", defaultL1AllocsPath, "")
flag.StringVar(&l2AllocsDir, "l2-allocs-dir", defaultL2AllocsDir, "")
flag.StringVar(&l1DeploymentsPath, "l1-deployments", defaultL1DeploymentsPath, "") flag.StringVar(&l1DeploymentsPath, "l1-deployments", defaultL1DeploymentsPath, "")
flag.StringVar(&deployConfigPath, "deploy-config", defaultDeployConfigPath, "") flag.StringVar(&deployConfigPath, "deploy-config", defaultDeployConfigPath, "")
flag.StringVar(&externalL2, "externalL2", "", "Enable tests with external L2") flag.StringVar(&externalL2, "externalL2", "", "Enable tests with external L2")
...@@ -108,6 +112,20 @@ func init() { ...@@ -108,6 +112,20 @@ func init() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
l2Allocs = make(map[genesis.L2AllocsMode]*genesis.ForgeAllocs)
mustL2Allocs := func(mode genesis.L2AllocsMode) {
name := "allocs-l2"
if mode != "" {
name += "-" + string(mode)
}
allocs, err := genesis.LoadForgeAllocs(filepath.Join(l2AllocsDir, name+".json"))
if err != nil {
panic(err)
}
l2Allocs[mode] = allocs
}
mustL2Allocs(genesis.L2AllocsEcotone)
mustL2Allocs(genesis.L2AllocsDelta)
L1Deployments, err = genesis.NewL1Deployments(l1DeploymentsPath) L1Deployments, err = genesis.NewL1Deployments(l1DeploymentsPath)
if err != nil { if err != nil {
panic(err) panic(err)
...@@ -138,6 +156,14 @@ func init() { ...@@ -138,6 +156,14 @@ func init() {
} }
} }
func L2Allocs(mode genesis.L2AllocsMode) *genesis.ForgeAllocs {
allocs, ok := l2Allocs[mode]
if !ok {
panic(fmt.Errorf("unknown L2 allocs mode: %q", mode))
}
return allocs.Copy()
}
func initExternalL2(externalL2 string) error { func initExternalL2(externalL2 string) error {
var err error var err error
ExternalL2Shim, err = filepath.Abs(filepath.Join(externalL2, "shim")) ExternalL2Shim, err = filepath.Abs(filepath.Join(externalL2, "shim"))
......
...@@ -122,7 +122,13 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * ...@@ -122,7 +122,13 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
l1Block := l1Genesis.ToBlock() l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2Genesis(deployConf, l1Block) var allocsMode genesis.L2AllocsMode
allocsMode = genesis.L2AllocsDelta
if ecotoneTime := deployConf.EcotoneTime(l1Block.Time()); ecotoneTime != nil && *ecotoneTime <= 0 {
allocsMode = genesis.L2AllocsEcotone
}
l2Allocs := config.L2Allocs(allocsMode)
l2Genesis, err := genesis.BuildL2Genesis(deployConf, l2Allocs, l1Block)
require.NoError(t, err, "failed to create l2 genesis") require.NoError(t, err, "failed to create l2 genesis")
if alloc.PrefundTestUsers { if alloc.PrefundTestUsers {
for _, addr := range deployParams.Addresses.All() { for _, addr := range deployParams.Addresses.All() {
......
...@@ -58,7 +58,13 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e ...@@ -58,7 +58,13 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e
require.Nil(t, err) require.Nil(t, err)
l1Block := l1Genesis.ToBlock() l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l1Block) var allocsMode genesis.L2AllocsMode
allocsMode = genesis.L2AllocsDelta
if ecotoneTime := cfg.DeployConfig.EcotoneTime(l1Block.Time()); ecotoneTime != nil && *ecotoneTime <= 0 {
allocsMode = genesis.L2AllocsEcotone
}
l2Allocs := config.L2Allocs(allocsMode)
l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l2Allocs, l1Block)
require.Nil(t, err) require.Nil(t, err)
l2GenesisBlock := l2Genesis.ToBlock() l2GenesisBlock := l2Genesis.ToBlock()
......
...@@ -26,7 +26,6 @@ import ( ...@@ -26,7 +26,6 @@ import (
func TestMissingGasLimit(t *testing.T) { func TestMissingGasLimit(t *testing.T) {
InitParallel(t) InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg) opGeth, err := NewOpGeth(t, ctx, &cfg)
...@@ -72,17 +71,19 @@ func TestTxGasSameAsBlockGasLimit(t *testing.T) { ...@@ -72,17 +71,19 @@ func TestTxGasSameAsBlockGasLimit(t *testing.T) {
func TestInvalidDepositInFCU(t *testing.T) { func TestInvalidDepositInFCU(t *testing.T) {
InitParallel(t) InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg) opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err) require.NoError(t, err)
defer opGeth.Close() defer opGeth.Close()
// Create a deposit from alice that will always fail (not enough funds) // Create a deposit from a new account that will always fail (not enough funds)
fromAddr := cfg.Secrets.Addresses().Alice fromKey, err := crypto.GenerateKey()
require.NoError(t, err)
fromAddr := crypto.PubkeyToAddress(fromKey.PublicKey)
balance, err := opGeth.L2Client.BalanceAt(ctx, fromAddr, nil) balance, err := opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.Nil(t, err) require.Nil(t, err)
t.Logf("alice balance: %d, %s", balance, fromAddr)
require.Equal(t, 0, balance.Cmp(common.Big0)) require.Equal(t, 0, balance.Cmp(common.Big0))
badDepositTx := types.NewTx(&types.DepositTx{ badDepositTx := types.NewTx(&types.DepositTx{
...@@ -98,7 +99,7 @@ func TestInvalidDepositInFCU(t *testing.T) { ...@@ -98,7 +99,7 @@ func TestInvalidDepositInFCU(t *testing.T) {
_, err = opGeth.AddL2Block(ctx, badDepositTx) _, err = opGeth.AddL2Block(ctx, badDepositTx)
require.NoError(t, err) require.NoError(t, err)
// Deposit tx was included, but Alice still shouldn't have any ETH // Deposit tx was included, but our account still shouldn't have any ETH
balance, err = opGeth.L2Client.BalanceAt(ctx, fromAddr, nil) balance, err = opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 0, balance.Cmp(common.Big0)) require.Equal(t, 0, balance.Cmp(common.Big0))
......
...@@ -103,7 +103,7 @@ func setupSequencerFailoverTest(t *testing.T) (*System, map[string]*conductor) { ...@@ -103,7 +103,7 @@ func setupSequencerFailoverTest(t *testing.T) (*System, map[string]*conductor) {
return healthy(t, ctx, c1) && return healthy(t, ctx, c1) &&
healthy(t, ctx, c2) && healthy(t, ctx, c2) &&
healthy(t, ctx, c3) healthy(t, ctx, c3)
}, 30*time.Second, 500*time.Millisecond, "Expected sequencers to become healthy") }, 50*time.Second, 500*time.Millisecond, "Expected sequencers to become healthy")
// unpause all conductors // unpause all conductors
require.NoError(t, c1.client.Resume(ctx)) require.NoError(t, c1.client.Resume(ctx))
......
...@@ -479,7 +479,14 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -479,7 +479,14 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
} }
l1Block := l1Genesis.ToBlock() l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l1Block) var allocsMode genesis.L2AllocsMode
allocsMode = genesis.L2AllocsDelta
if ecotoneTime := cfg.DeployConfig.EcotoneTime(l1Block.Time()); ecotoneTime != nil && *ecotoneTime <= 0 {
allocsMode = genesis.L2AllocsEcotone
}
t.Log("Generating L2 genesis", "l2_allocs_mode", string(allocsMode))
l2Allocs := config.L2Allocs(allocsMode)
l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l2Allocs, l1Block)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -1223,6 +1223,10 @@ func testFees(t *testing.T, cfg SystemConfig) { ...@@ -1223,6 +1223,10 @@ func testFees(t *testing.T, cfg SystemConfig) {
l2Verif := sys.Clients["verifier"] l2Verif := sys.Clients["verifier"]
l1 := sys.Clients["l1"] l1 := sys.Clients["l1"]
// Wait for first block after genesis. The genesis block has zero L1Block values and will throw off the GPO checks
_, err = geth.WaitForBlock(big.NewInt(1), l2Verif, time.Minute)
require.NoError(t, err)
config := sys.L2Genesis().Config config := sys.L2Genesis().Config
sga := &stateGetterAdapter{ sga := &stateGetterAdapter{
......
...@@ -57,6 +57,10 @@ var ( ...@@ -57,6 +57,10 @@ var (
Name: "outfile.l1", Name: "outfile.l1",
Usage: "Path to L1 genesis output file", Usage: "Path to L1 genesis output file",
} }
l2AllocsFlag = &cli.StringFlag{
Name: "l2-allocs",
Usage: "Path to L2 genesis state dump",
}
l1Flags = []cli.Flag{ l1Flags = []cli.Flag{
deployConfigFlag, deployConfigFlag,
...@@ -69,6 +73,7 @@ var ( ...@@ -69,6 +73,7 @@ var (
l1RPCFlag, l1RPCFlag,
l1StartingBlockFlag, l1StartingBlockFlag,
deployConfigFlag, deployConfigFlag,
l2AllocsFlag,
l1DeploymentsFlag, l1DeploymentsFlag,
outfileL2Flag, outfileL2Flag,
outfileRollupFlag, outfileRollupFlag,
...@@ -165,6 +170,16 @@ var Subcommands = cli.Commands{ ...@@ -165,6 +170,16 @@ var Subcommands = cli.Commands{
} }
} }
var l2Allocs *genesis.ForgeAllocs
if l2AllocsPath := ctx.String("l2-allocs"); l2AllocsPath != "" {
l2Allocs, err = genesis.LoadForgeAllocs(l2AllocsPath)
if err != nil {
return err
}
} else {
return errors.New("missing l2-allocs")
}
if l1RPC != "" { if l1RPC != "" {
client, err := ethclient.Dial(l1RPC) client, err := ethclient.Dial(l1RPC)
if err != nil { if err != nil {
...@@ -205,7 +220,7 @@ var Subcommands = cli.Commands{ ...@@ -205,7 +220,7 @@ var Subcommands = cli.Commands{
log.Info("Using L1 Start Block", "number", l1StartBlock.Number(), "hash", l1StartBlock.Hash().Hex()) log.Info("Using L1 Start Block", "number", l1StartBlock.Number(), "hash", l1StartBlock.Hash().Hex())
// Build the L2 genesis block // Build the L2 genesis block
l2Genesis, err := genesis.BuildL2Genesis(config, l1StartBlock) l2Genesis, err := genesis.BuildL2Genesis(config, l2Allocs, l1StartBlock)
if err != nil { if err != nil {
return fmt.Errorf("error creating l2 genesis: %w", err) return fmt.Errorf("error creating l2 genesis: %w", err)
} }
......
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 356411) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 356410)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2954596) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2954595)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 549198) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 549197)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4061174) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4061173)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 450326) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 450326)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3496075) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3496075)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 59809) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 59809)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92951) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92950)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68398) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68398)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 69062) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 69062)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155567) GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155567)
\ No newline at end of file
...@@ -52,9 +52,10 @@ library Config { ...@@ -52,9 +52,10 @@ library Config {
/// @notice Returns the path that the state dump file should be written to or read from /// @notice Returns the path that the state dump file should be written to or read from
/// on the local filesystem. /// on the local filesystem.
function stateDumpPath(string memory _name) internal view returns (string memory _env) { function stateDumpPath(string memory _suffix) internal view returns (string memory _env) {
_env = vm.envOr( _env = vm.envOr(
"STATE_DUMP_PATH", string.concat(vm.projectRoot(), "/", _name, "-", vm.toString(block.chainid), ".json") "STATE_DUMP_PATH",
string.concat(vm.projectRoot(), "/state-dump-", vm.toString(block.chainid), _suffix, ".json")
); );
} }
...@@ -94,7 +95,7 @@ library Config { ...@@ -94,7 +95,7 @@ library Config {
return "optimism-goerli"; return "optimism-goerli";
} else if (chainid == Chains.OPMainnet) { } else if (chainid == Chains.OPMainnet) {
return "optimism-mainnet"; return "optimism-mainnet";
} else if (chainid == Chains.LocalDevnet || chainid == Chains.GethDevnet) { } else if (chainid == Chains.LocalDevnet || chainid == Chains.GethDevnet || chainid == Chains.OPLocalDevnet) {
return "devnetL1"; return "devnetL1";
} else if (chainid == Chains.Hardhat) { } else if (chainid == Chains.Hardhat) {
return "hardhat"; return "hardhat";
......
...@@ -262,8 +262,7 @@ contract Deploy is Deployer { ...@@ -262,8 +262,7 @@ contract Deploy is Deployer {
function runWithStateDump() public { function runWithStateDump() public {
_run(); _run();
vm.dumpState(Config.stateDumpPath(""));
vm.dumpState(Config.stateDumpPath(name()));
} }
/// @notice Deploy all L1 contracts and write the state diff to a file. /// @notice Deploy all L1 contracts and write the state diff to a file.
...@@ -273,12 +272,16 @@ contract Deploy is Deployer { ...@@ -273,12 +272,16 @@ contract Deploy is Deployer {
/// @notice Internal function containing the deploy logic. /// @notice Internal function containing the deploy logic.
function _run() internal { function _run() internal {
console.log("start of L1 Deploy!");
deploySafe(); deploySafe();
console.log("deployed Safe!");
setupSuperchain(); setupSuperchain();
console.log("set up superchain!");
if (cfg.usePlasma()) { if (cfg.usePlasma()) {
setupOpPlasma(); setupOpPlasma();
} }
setupOpChain(); setupOpChain();
console.log("set up op chain!");
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
......
...@@ -35,8 +35,10 @@ contract DeployConfig is Script { ...@@ -35,8 +35,10 @@ contract DeployConfig is Script {
address public proxyAdminOwner; address public proxyAdminOwner;
address public baseFeeVaultRecipient; address public baseFeeVaultRecipient;
uint256 public baseFeeVaultMinimumWithdrawalAmount; uint256 public baseFeeVaultMinimumWithdrawalAmount;
uint256 public baseFeeVaultWithdrawalNetwork;
address public l1FeeVaultRecipient; address public l1FeeVaultRecipient;
uint256 public l1FeeVaultMinimumWithdrawalAmount; uint256 public l1FeeVaultMinimumWithdrawalAmount;
uint256 public l1FeeVaultWithdrawalNetwork;
address public sequencerFeeVaultRecipient; address public sequencerFeeVaultRecipient;
uint256 public sequencerFeeVaultMinimumWithdrawalAmount; uint256 public sequencerFeeVaultMinimumWithdrawalAmount;
uint256 public sequencerFeeVaultWithdrawalNetwork; uint256 public sequencerFeeVaultWithdrawalNetwork;
...@@ -44,7 +46,6 @@ contract DeployConfig is Script { ...@@ -44,7 +46,6 @@ contract DeployConfig is Script {
string public governanceTokenSymbol; string public governanceTokenSymbol;
address public governanceTokenOwner; address public governanceTokenOwner;
uint256 public l2GenesisBlockGasLimit; uint256 public l2GenesisBlockGasLimit;
uint256 public l2GenesisBlockBaseFeePerGas;
uint256 public gasPriceOracleOverhead; uint256 public gasPriceOracleOverhead;
uint256 public gasPriceOracleScalar; uint256 public gasPriceOracleScalar;
bool public enableGovernance; bool public enableGovernance;
...@@ -98,12 +99,14 @@ contract DeployConfig is Script { ...@@ -98,12 +99,14 @@ contract DeployConfig is Script {
l2OutputOracleProposer = stdJson.readAddress(_json, "$.l2OutputOracleProposer"); l2OutputOracleProposer = stdJson.readAddress(_json, "$.l2OutputOracleProposer");
l2OutputOracleChallenger = stdJson.readAddress(_json, "$.l2OutputOracleChallenger"); l2OutputOracleChallenger = stdJson.readAddress(_json, "$.l2OutputOracleChallenger");
finalizationPeriodSeconds = stdJson.readUint(_json, "$.finalizationPeriodSeconds"); finalizationPeriodSeconds = stdJson.readUint(_json, "$.finalizationPeriodSeconds");
fundDevAccounts = stdJson.readBool(_json, "$.fundDevAccounts"); fundDevAccounts = _readOr(_json, "$.fundDevAccounts", false);
proxyAdminOwner = stdJson.readAddress(_json, "$.proxyAdminOwner"); proxyAdminOwner = stdJson.readAddress(_json, "$.proxyAdminOwner");
baseFeeVaultRecipient = stdJson.readAddress(_json, "$.baseFeeVaultRecipient"); baseFeeVaultRecipient = stdJson.readAddress(_json, "$.baseFeeVaultRecipient");
baseFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.baseFeeVaultMinimumWithdrawalAmount"); baseFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.baseFeeVaultMinimumWithdrawalAmount");
baseFeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.baseFeeVaultWithdrawalNetwork");
l1FeeVaultRecipient = stdJson.readAddress(_json, "$.l1FeeVaultRecipient"); l1FeeVaultRecipient = stdJson.readAddress(_json, "$.l1FeeVaultRecipient");
l1FeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.l1FeeVaultMinimumWithdrawalAmount"); l1FeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.l1FeeVaultMinimumWithdrawalAmount");
l1FeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.l1FeeVaultWithdrawalNetwork");
sequencerFeeVaultRecipient = stdJson.readAddress(_json, "$.sequencerFeeVaultRecipient"); sequencerFeeVaultRecipient = stdJson.readAddress(_json, "$.sequencerFeeVaultRecipient");
sequencerFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.sequencerFeeVaultMinimumWithdrawalAmount"); sequencerFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.sequencerFeeVaultMinimumWithdrawalAmount");
sequencerFeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.sequencerFeeVaultWithdrawalNetwork"); sequencerFeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.sequencerFeeVaultWithdrawalNetwork");
...@@ -111,7 +114,6 @@ contract DeployConfig is Script { ...@@ -111,7 +114,6 @@ contract DeployConfig is Script {
governanceTokenSymbol = stdJson.readString(_json, "$.governanceTokenSymbol"); governanceTokenSymbol = stdJson.readString(_json, "$.governanceTokenSymbol");
governanceTokenOwner = stdJson.readAddress(_json, "$.governanceTokenOwner"); governanceTokenOwner = stdJson.readAddress(_json, "$.governanceTokenOwner");
l2GenesisBlockGasLimit = stdJson.readUint(_json, "$.l2GenesisBlockGasLimit"); l2GenesisBlockGasLimit = stdJson.readUint(_json, "$.l2GenesisBlockGasLimit");
l2GenesisBlockBaseFeePerGas = stdJson.readUint(_json, "$.l2GenesisBlockBaseFeePerGas");
gasPriceOracleOverhead = stdJson.readUint(_json, "$.gasPriceOracleOverhead"); gasPriceOracleOverhead = stdJson.readUint(_json, "$.gasPriceOracleOverhead");
gasPriceOracleScalar = stdJson.readUint(_json, "$.gasPriceOracleScalar"); gasPriceOracleScalar = stdJson.readUint(_json, "$.gasPriceOracleScalar");
enableGovernance = stdJson.readBool(_json, "$.enableGovernance"); enableGovernance = stdJson.readBool(_json, "$.enableGovernance");
...@@ -121,10 +123,10 @@ contract DeployConfig is Script { ...@@ -121,10 +123,10 @@ contract DeployConfig is Script {
requiredProtocolVersion = stdJson.readUint(_json, "$.requiredProtocolVersion"); requiredProtocolVersion = stdJson.readUint(_json, "$.requiredProtocolVersion");
recommendedProtocolVersion = stdJson.readUint(_json, "$.recommendedProtocolVersion"); recommendedProtocolVersion = stdJson.readUint(_json, "$.recommendedProtocolVersion");
useFaultProofs = stdJson.readBool(_json, "$.useFaultProofs"); useFaultProofs = _readOr(_json, "$.useFaultProofs", false);
proofMaturityDelaySeconds = stdJson.readUint(_json, "$.proofMaturityDelaySeconds"); proofMaturityDelaySeconds = _readOr(_json, "$.proofMaturityDelaySeconds", 0);
disputeGameFinalityDelaySeconds = stdJson.readUint(_json, "$.disputeGameFinalityDelaySeconds"); disputeGameFinalityDelaySeconds = _readOr(_json, "$.disputeGameFinalityDelaySeconds", 0);
respectedGameType = stdJson.readUint(_json, "$.respectedGameType"); respectedGameType = _readOr(_json, "$.respectedGameType", 0);
faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate"); faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate");
faultGameMaxDepth = stdJson.readUint(_json, "$.faultGameMaxDepth"); faultGameMaxDepth = stdJson.readUint(_json, "$.faultGameMaxDepth");
......
...@@ -143,6 +143,14 @@ library ForgeArtifacts { ...@@ -143,6 +143,14 @@ library ForgeArtifacts {
slot_ = abi.decode(rawSlot, (StorageSlot)); slot_ = abi.decode(rawSlot, (StorageSlot));
} }
/// @notice Returns whether or not a contract is initialized.
/// Needs the name to get the storage layout.
function isInitialized(string memory _name, address _address) internal returns (bool initialized_) {
StorageSlot memory slot = ForgeArtifacts.getInitializedSlot(_name);
bytes32 slotVal = vm.load(_address, bytes32(vm.parseUint(slot.slot)));
initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF) != 0;
}
/// @notice Returns the function ABIs of all L1 contracts. /// @notice Returns the function ABIs of all L1 contracts.
function getContractFunctionAbis( function getContractFunctionAbis(
string memory path, string memory path,
...@@ -196,10 +204,7 @@ library ForgeArtifacts { ...@@ -196,10 +204,7 @@ library ForgeArtifacts {
/// @notice Accepts a filepath and then ensures that the directory /// @notice Accepts a filepath and then ensures that the directory
/// exists for the file to live in. /// exists for the file to live in.
function ensurePath(string memory _path) internal { function ensurePath(string memory _path) internal {
(, bytes memory returndata) = string[] memory outputs = vm.split(_path, "/");
address(vm).call(abi.encodeWithSignature("split(string,string)", _path, string("/")));
string[] memory outputs = abi.decode(returndata, (string[]));
string memory path = ""; string memory path = "";
for (uint256 i = 0; i < outputs.length - 1; i++) { for (uint256 i = 0; i < outputs.length - 1; i++) {
path = string.concat(path, outputs[i], "/"); path = string.concat(path, outputs[i], "/");
......
...@@ -3,117 +3,149 @@ pragma solidity 0.8.15; ...@@ -3,117 +3,149 @@ pragma solidity 0.8.15;
import { Script } from "forge-std/Script.sol"; import { Script } from "forge-std/Script.sol";
import { console2 as console } from "forge-std/console2.sol"; import { console2 as console } from "forge-std/console2.sol";
import { Deployer } from "scripts/Deployer.sol";
import { Config } from "scripts/Config.sol";
import { Artifacts } from "scripts/Artifacts.s.sol"; import { Artifacts } from "scripts/Artifacts.s.sol";
import { DeployConfig } from "scripts/DeployConfig.s.sol"; import { DeployConfig } from "scripts/DeployConfig.s.sol";
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { L2StandardBridge } from "src/L2/L2StandardBridge.sol";
import { L2CrossDomainMessenger } from "src/L2/L2CrossDomainMessenger.sol"; import { L2CrossDomainMessenger } from "src/L2/L2CrossDomainMessenger.sol";
import { L1Block } from "src/L2/L1Block.sol";
import { GasPriceOracle } from "src/L2/GasPriceOracle.sol";
import { L2StandardBridge } from "src/L2/L2StandardBridge.sol";
import { L2ERC721Bridge } from "src/L2/L2ERC721Bridge.sol";
import { SequencerFeeVault } from "src/L2/SequencerFeeVault.sol"; import { SequencerFeeVault } from "src/L2/SequencerFeeVault.sol";
import { FeeVault } from "src/universal/FeeVault.sol";
import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol"; import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol";
import { L1Block } from "src/L2/L1Block.sol"; import { OptimismMintableERC721Factory } from "src/universal/OptimismMintableERC721Factory.sol";
import { BaseFeeVault } from "src/L2/BaseFeeVault.sol";
import { L1FeeVault } from "src/L2/L1FeeVault.sol";
import { GovernanceToken } from "src/governance/GovernanceToken.sol"; import { GovernanceToken } from "src/governance/GovernanceToken.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { FeeVault } from "src/universal/FeeVault.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
interface IInitializable { interface IInitializable {
function initialize(address _addr) external; function initialize(address _addr) external;
} }
/// @dev The general flow of adding a predeploy is: struct L1Dependencies {
/// 1. _setPredeployProxies uses vm.etch to set the Proxy.sol deployed bytecode for proxy address `0x420...000` to address payable l1CrossDomainMessengerProxy;
/// `0x420...000 + PROXY_COUNT - 1`. address payable l1StandardBridgeProxy;
/// Additionally, the PROXY_ADMIN_ADDRESS and PROXY_IMPLEMENTATION_ADDRESS storage slots are set for the proxy address payable l1ERC721BridgeProxy;
/// address. }
/// 2. `vm.etch` sets the deployed bytecode for each predeploy at the implementation address (i.e. `0xc0d3`
/// namespace). /// @notice Enum representing different ways of outputting genesis allocs.
/// 3. The `initialize` method is called at the implementation address with zero/dummy vaules if the method exists. /// @custom:value DEFAULT_LATEST Represents only latest L2 allocs, written to output path.
/// 4. The `initialize` method is called at the proxy address with actual vaules if the method exists. /// @custom:value LOCAL_LATEST Represents latest L2 allocs, not output anywhere, but kept in-process.
/// 5. A `require` check to verify the expected implementation address is set for the proxy. /// @custom:value LOCAL_DELTA Represents Delta-upgrade L2 allocs, not output anywhere, but kept in-process.
/// @notice The following safety invariants are used when setting state: /// @custom:value OUTPUT_ALL Represents creation of one L2 allocs file for every upgrade.
enum OutputMode {
DEFAULT_LATEST,
LOCAL_LATEST,
LOCAL_DELTA,
OUTPUT_ALL
}
/// @title L2Genesis
/// @notice Generates the genesis state for the L2 network.
/// The following safety invariants are used when setting state:
/// 1. `vm.getDeployedBytecode` can only be used with `vm.etch` when there are no side /// 1. `vm.getDeployedBytecode` can only be used with `vm.etch` when there are no side
/// effects in the constructor and no immutables in the bytecode. /// effects in the constructor and no immutables in the bytecode.
/// 2. A contract must be deployed using the `new` syntax if there are immutables in the code. /// 2. A contract must be deployed using the `new` syntax if there are immutables in the code.
/// Any other side effects from the init code besides setting the immutables must be cleaned up afterwards. /// Any other side effects from the init code besides setting the immutables must be cleaned up afterwards.
/// 3. A contract is deployed using the `new` syntax, however it's not proxied and is still expected to exist at contract L2Genesis is Deployer {
/// a uint256 public constant PRECOMPILE_COUNT = 256;
/// specific implementation address (i.e. `0xc0d3` namespace). In this case we deploy an instance of the
/// contract
/// using `new` syntax, use `contract.code` to retrieve it's deployed bytecode, `vm.etch` the bytecode at the
/// expected implementation address, and `vm.store` to set any storage slots that are
/// expected to be set after a new deployment. Lastly, we reset the account code and storage slots the contract
/// was initially deployed to so it's not included in the `vm.dumpState`.
contract L2Genesis is Script, Artifacts {
uint256 constant PROXY_COUNT = 2048;
uint256 constant PRECOMPILE_COUNT = 256;
DeployConfig public constant cfg =
DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig"))))));
/// @notice The storage slot that holds the address of a proxy implementation.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)`
bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/// @notice The storage slot that holds the address of the owner.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`
bytes32 internal constant PROXY_ADMIN_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
uint80 internal constant DEV_ACCOUNT_FUND_AMT = 10_000 ether; uint80 internal constant DEV_ACCOUNT_FUND_AMT = 10_000 ether;
/// @notice Default Anvil dev accounts. Only funded if `cfg.fundDevAccounts == true`. /// @notice Default Anvil dev accounts. Only funded if `cfg.fundDevAccounts == true`.
address[10] internal devAccounts = [ /// Also known as "test test test test test test test test test test test junk" mnemonic accounts,
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, /// on path "m/44'/60'/0'/0/i" (where i is the account index).
0x70997970C51812dc3A010C7d01b50e0d17dc79C8, address[30] internal devAccounts = [
0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, // 0
0x90F79bf6EB2c4f870365E785982E1f101E93b906, 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, // 1
0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65, 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC, // 2
0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc, 0x90F79bf6EB2c4f870365E785982E1f101E93b906, // 3
0x976EA74026E726554dB657fA54763abd0C3a0aa9, 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65, // 4
0x14dC79964da2C08b23698B3D3cc7Ca32193d9955, 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc, // 5
0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f, 0x976EA74026E726554dB657fA54763abd0C3a0aa9, // 6
0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955, // 7
0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f, // 8
0xa0Ee7A142d267C1f36714E4a8F75612F20a79720, // 9
0xBcd4042DE499D14e55001CcbB24a551F3b954096, // 10
0x71bE63f3384f5fb98995898A86B02Fb2426c5788, // 11
0xFABB0ac9d68B0B445fB7357272Ff202C5651694a, // 12
0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec, // 13
0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097, // 14
0xcd3B766CCDd6AE721141F452C550Ca635964ce71, // 15
0x2546BcD3c84621e976D8185a91A922aE77ECEc30, // 16
0xbDA5747bFD65F08deb54cb465eB87D40e51B197E, // 17
0xdD2FD4581271e230360230F9337D5c0430Bf44C0, // 18
0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199, // 19
0x09DB0a93B389bEF724429898f539AEB7ac2Dd55f, // 20
0x02484cb50AAC86Eae85610D6f4Bf026f30f6627D, // 21
0x08135Da0A343E492FA2d4282F2AE34c6c5CC1BbE, // 22
0x5E661B79FE2D3F6cE70F5AAC07d8Cd9abb2743F1, // 23
0x61097BA76cD906d2ba4FD106E757f7Eb455fc295, // 24
0xDf37F81dAAD2b0327A0A50003740e1C935C70913, // 25
0x553BC17A05702530097c3677091C5BB47a3a7931, // 26
0x87BdCE72c06C21cd96219BD8521bDF1F42C78b5e, // 27
0x40Fc963A729c542424cD800349a7E4Ecc4896624, // 28
0x9DCCe783B6464611f38631e6C851bf441907c710 // 29
]; ];
string internal outfile; function name() public pure override returns (string memory) {
return "L2Genesis";
/// @dev Reads the deploy config, sets `outfile` which is where the `vm.dumpState` will be saved to, and }
/// loads in the addresses for the L1 contract deployments.
function setUp() public override {
Artifacts.setUp();
string memory path = string.concat(vm.projectRoot(), "/deploy-config/", deploymentContext, ".json");
vm.etch(address(cfg), vm.getDeployedCode("DeployConfig.s.sol:DeployConfig"));
vm.label(address(cfg), "DeployConfig");
vm.allowCheatcodes(address(cfg));
cfg.read(path);
outfile = string.concat(vm.projectRoot(), "/deployments/", deploymentContext, "/genesis-l2.json");
_loadAddresses(string.concat(vm.projectRoot(), "/deployments/", deploymentContext, "/.deploy")); function artifactDependencies() internal view returns (L1Dependencies memory l1Dependencies_) {
console.log("retrieving L1 deployments from artifacts");
return L1Dependencies({
l1CrossDomainMessengerProxy: mustGetAddress("L1CrossDomainMessengerProxy"),
l1StandardBridgeProxy: mustGetAddress("L1StandardBridgeProxy"),
l1ERC721BridgeProxy: mustGetAddress("L1ERC721BridgeProxy")
});
} }
/// @dev Sets the precompiles, proxies, and the implementation accounts to be `vm.dumpState` /// @dev Sets the precompiles, proxies, and the implementation accounts to be `vm.dumpState`
/// to generate a L2 genesis alloc. /// to generate a L2 genesis alloc.
/// @notice The alloc object is sorted numerically by address. /// @notice The alloc object is sorted numerically by address.
function run() public { function runWithStateDump() public {
_dealEthToPrecompiles(); runWithOptions(OutputMode.DEFAULT_LATEST, artifactDependencies());
_setPredeployProxies(); }
_setPredeployImplementations();
// @dev This is used by op-e2e to have a version of the L2 allocs for each upgrade.
function runWithAllUpgrades() public {
runWithOptions(OutputMode.OUTPUT_ALL, artifactDependencies());
}
function runWithOptions(OutputMode _mode, L1Dependencies memory _l1Dependencies) public {
dealEthToPrecompiles();
setPredeployProxies();
setPredeployImplementations(_l1Dependencies);
setPreinstalls();
if (cfg.fundDevAccounts()) { if (cfg.fundDevAccounts()) {
_fundDevAccounts(); fundDevAccounts();
}
// Genesis is "complete" at this point, but some hardfork activation steps remain.
// Depending on the "Output Mode" we perform the activations and output the necessary state dumps.
if (_mode == OutputMode.LOCAL_DELTA) {
return;
}
if (_mode == OutputMode.OUTPUT_ALL) {
writeGenesisAllocs(Config.stateDumpPath("-delta"));
}
activateEcotone();
if (_mode == OutputMode.OUTPUT_ALL || _mode == OutputMode.DEFAULT_LATEST) {
writeGenesisAllocs(Config.stateDumpPath(""));
} }
/// Reset so its not included state dump
vm.etch(address(cfg), "");
vm.dumpState(outfile);
_sortJsonByKeys(outfile);
} }
/// @notice Give all of the precompiles 1 wei so that they are /// @notice Give all of the precompiles 1 wei
/// not considered empty accounts. function dealEthToPrecompiles() internal {
function _dealEthToPrecompiles() internal { console.log("Setting precompile 1 wei balances");
for (uint256 i; i < PRECOMPILE_COUNT; i++) { for (uint256 i; i < PRECOMPILE_COUNT; i++) {
vm.deal(address(uint160(i)), 1); vm.deal(address(uint160(i)), 1);
} }
...@@ -123,66 +155,178 @@ contract L2Genesis is Script, Artifacts { ...@@ -123,66 +155,178 @@ contract L2Genesis is Script, Artifacts {
/// The Proxy bytecode should be set. All proxied predeploys should have /// The Proxy bytecode should be set. All proxied predeploys should have
/// the 1967 admin slot set to the ProxyAdmin predeploy. All defined predeploys /// the 1967 admin slot set to the ProxyAdmin predeploy. All defined predeploys
/// should have their implementations set. /// should have their implementations set.
function _setPredeployProxies() internal { /// Warning: the predeploy accounts have contract code, but 0 nonce value.
function setPredeployProxies() public {
console.log("Setting Predeploy proxies");
bytes memory code = vm.getDeployedCode("Proxy.sol:Proxy"); bytes memory code = vm.getDeployedCode("Proxy.sol:Proxy");
uint160 prefix = uint160(0x420) << 148; uint160 prefix = uint160(0x420) << 148;
console.log( console.log(
"Setting proxy deployed bytecode for addresses in range %s through %s", "Setting proxy deployed bytecode for addresses in range %s through %s",
address(prefix | uint160(0)), address(prefix | uint160(0)),
address(prefix | uint160(PROXY_COUNT - 1)) address(prefix | uint160(Predeploys.PREDEPLOY_COUNT - 1))
); );
for (uint256 i = 0; i < PROXY_COUNT; i++) { for (uint256 i = 0; i < Predeploys.PREDEPLOY_COUNT; i++) {
address addr = address(prefix | uint160(i)); address addr = address(prefix | uint160(i));
if (_notProxied(addr)) { if (Predeploys.notProxied(addr)) {
console.log("Skipping proxy at %s", addr);
continue; continue;
} }
vm.etch(addr, code); vm.etch(addr, code);
vm.store(addr, PROXY_ADMIN_ADDRESS, bytes32(uint256(uint160(Predeploys.PROXY_ADMIN)))); EIP1967Helper.setAdmin(addr, Predeploys.PROXY_ADMIN);
if (_isDefinedPredeploy(addr)) { if (Predeploys.isSupportedPredeploy(addr)) {
address implementation = _predeployToCodeNamespace(addr); address implementation = Predeploys.predeployToCodeNamespace(addr);
console.log("Setting proxy %s implementation: %s", addr, implementation); console.log("Setting proxy %s implementation: %s", addr, implementation);
vm.store(addr, PROXY_IMPLEMENTATION_ADDRESS, bytes32(uint256(uint160(implementation)))); EIP1967Helper.setImplementation(addr, implementation);
} }
} }
} }
/// @notice LEGACY_ERC20_ETH is not being predeployed since it's been deprecated.
/// @dev Sets all the implementations for the predeploy proxies. For contracts without proxies, /// @dev Sets all the implementations for the predeploy proxies. For contracts without proxies,
/// sets the deployed bytecode at their expected predeploy address. /// sets the deployed bytecode at their expected predeploy address.
function _setPredeployImplementations() internal { /// LEGACY_ERC20_ETH and L1_MESSAGE_SENDER are deprecated and are not set.
_setLegacyMessagePasser(); function setPredeployImplementations(L1Dependencies memory _l1Dependencies) internal {
_setDeployerWhitelist(); console.log("Setting predeploy implementations, with L1 contract dependencies:");
_setWETH9(); console.log("- l1CrossDomainMessengerProxy: %s", _l1Dependencies.l1CrossDomainMessengerProxy);
_setL2StandardBridge(); console.log("- l1StandardBridgeProxy: %s", _l1Dependencies.l1StandardBridgeProxy);
_setL2CrossDomainMessenger(); console.log("- l1ERC721BridgeProxy: %s", _l1Dependencies.l1ERC721BridgeProxy);
_setSequencerFeeVault(); setLegacyMessagePasser(); // 0
_setOptimismMintableERC20Factory(); // 01: legacy, not used in OP-Stack
_setL1BlockNumber(); setDeployerWhitelist(); // 2
_setGasPriceOracle(); // 3,4,5: legacy, not used in OP-Stack.
_setGovernanceToken(); setWETH9(); // 6: WETH9 (not behind a proxy)
_setL1Block(); setL2CrossDomainMessenger(_l1Dependencies.l1CrossDomainMessengerProxy); // 7
} // 8,9,A,B,C,D,E: legacy, not used in OP-Stack.
setGasPriceOracle(); // f
/// @notice This predeploy is following the saftey invariant #1. setL2StandardBridge(_l1Dependencies.l1StandardBridgeProxy); // 10
function _setLegacyMessagePasser() internal { setSequencerFeeVault(); // 11
_setImplementationCode(Predeploys.LEGACY_MESSAGE_PASSER, "LegacyMessagePasser"); setOptimismMintableERC20Factory(); // 12
} setL1BlockNumber(); // 13
setL2ERC721Bridge(_l1Dependencies.l1ERC721BridgeProxy); // 14
/// @notice This predeploy is following the saftey invariant #1. setL1Block(); // 15
function _setDeployerWhitelist() internal { setL2ToL1MessagePasser(); // 16
_setImplementationCode(Predeploys.DEPLOYER_WHITELIST, "DeployerWhitelist"); setOptimismMintableERC721Factory(); // 17
} setProxyAdmin(); // 18
setBaseFeeVault(); // 19
/// @notice This predeploy is following the saftey invariant #1. setL1FeeVault(); // 1A
/// Contract metadata hash appended to deployed bytecode will differ // 1B,1C,1D,1E,1F: not used.
/// from previous L2 genesis output. setSchemaRegistry(); // 20
/// This contract is NOT proxied. setEAS(); // 21
/// @dev We're manually setting storage slots because we need to deployment to be at setGovernanceToken(); // 42: OP (not behind a proxy)
/// the address `Predeploys.WETH9`, so we can't just deploy a new instance of `WETH9`. }
function _setWETH9() internal {
function setProxyAdmin() public {
// Note the ProxyAdmin implementation itself is behind a proxy that owns itself.
address impl = _setImplementationCode(Predeploys.PROXY_ADMIN);
bytes32 _ownerSlot = bytes32(0);
// there is no initialize() function, so we just set the storage manually.
vm.store(Predeploys.PROXY_ADMIN, _ownerSlot, bytes32(uint256(uint160(cfg.proxyAdminOwner()))));
// update the proxy to not be uninitialized (although not standard initialize pattern)
vm.store(impl, _ownerSlot, bytes32(uint256(uint160(cfg.proxyAdminOwner()))));
}
function setL2ToL1MessagePasser() public {
_setImplementationCode(Predeploys.L2_TO_L1_MESSAGE_PASSER);
}
/// @notice This predeploy is following the safety invariant #1.
function setL2CrossDomainMessenger(address payable _l1CrossDomainMessengerProxy) public {
address impl = _setImplementationCode(Predeploys.L2_CROSS_DOMAIN_MESSENGER);
L2CrossDomainMessenger(impl).initialize({ _l1CrossDomainMessenger: L1CrossDomainMessenger(address(0)) });
L2CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER).initialize({
_l1CrossDomainMessenger: L1CrossDomainMessenger(_l1CrossDomainMessengerProxy)
});
}
/// @notice This predeploy is following the safety invariant #1.
function setL2StandardBridge(address payable _l1StandardBridgeProxy) public {
address impl = _setImplementationCode(Predeploys.L2_STANDARD_BRIDGE);
L2StandardBridge(payable(impl)).initialize({ _otherBridge: L1StandardBridge(payable(address(0))) });
L2StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)).initialize({
_otherBridge: L1StandardBridge(_l1StandardBridgeProxy)
});
}
/// @notice This predeploy is following the safety invariant #1.
function setL2ERC721Bridge(address payable _l1ERC721BridgeProxy) public {
address impl = _setImplementationCode(Predeploys.L2_ERC721_BRIDGE);
L2ERC721Bridge(impl).initialize({ _l1ERC721Bridge: payable(address(0)) });
L2ERC721Bridge(Predeploys.L2_ERC721_BRIDGE).initialize({ _l1ERC721Bridge: payable(_l1ERC721BridgeProxy) });
}
/// @notice This predeploy is following the safety invariant #2,
function setSequencerFeeVault() public {
SequencerFeeVault vault = new SequencerFeeVault({
_recipient: cfg.sequencerFeeVaultRecipient(),
_minWithdrawalAmount: cfg.sequencerFeeVaultMinimumWithdrawalAmount(),
_withdrawalNetwork: FeeVault.WithdrawalNetwork(cfg.sequencerFeeVaultWithdrawalNetwork())
});
address impl = Predeploys.predeployToCodeNamespace(Predeploys.SEQUENCER_FEE_WALLET);
console.log("Setting %s implementation at: %s", "SequencerFeeVault", impl);
vm.etch(impl, address(vault).code);
/// Reset so its not included state dump
vm.etch(address(vault), "");
vm.resetNonce(address(vault));
}
/// @notice This predeploy is following the safety invariant #1.
function setOptimismMintableERC20Factory() public {
address impl = _setImplementationCode(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY);
OptimismMintableERC20Factory(impl).initialize({ _bridge: address(0) });
OptimismMintableERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).initialize({
_bridge: Predeploys.L2_STANDARD_BRIDGE
});
}
/// @notice This predeploy is following the safety invariant #2,
function setOptimismMintableERC721Factory() public {
OptimismMintableERC721Factory factory =
new OptimismMintableERC721Factory({ _bridge: Predeploys.L2_ERC721_BRIDGE, _remoteChainId: cfg.l1ChainID() });
address impl = Predeploys.predeployToCodeNamespace(Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY);
console.log("Setting %s implementation at: %s", "OptimismMintableERC721Factory", impl);
vm.etch(impl, address(factory).code);
/// Reset so its not included state dump
vm.etch(address(factory), "");
vm.resetNonce(address(factory));
}
/// @notice This predeploy is following the safety invariant #1.
function setL1Block() public {
_setImplementationCode(Predeploys.L1_BLOCK_ATTRIBUTES);
// Note: L1 block attributes are set to 0.
// Before the first user-tx the state is overwritten with actual L1 attributes.
}
/// @notice This predeploy is following the safety invariant #1.
function setGasPriceOracle() public {
_setImplementationCode(Predeploys.GAS_PRICE_ORACLE);
}
/// @notice This predeploy is following the safety invariant #1.
function setDeployerWhitelist() public {
_setImplementationCode(Predeploys.DEPLOYER_WHITELIST);
}
/// @notice This predeploy is following the safety invariant #1.
/// This contract is NOT proxied and the state that is set
/// in the constructor is set manually.
function setWETH9() public {
console.log("Setting %s implementation at: %s", "WETH9", Predeploys.WETH9); console.log("Setting %s implementation at: %s", "WETH9", Predeploys.WETH9);
vm.etch(Predeploys.WETH9, vm.getDeployedCode("WETH9.sol:WETH9")); vm.etch(Predeploys.WETH9, vm.getDeployedCode("WETH9.sol:WETH9"));
...@@ -209,93 +353,52 @@ contract L2Genesis is Script, Artifacts { ...@@ -209,93 +353,52 @@ contract L2Genesis is Script, Artifacts {
); );
} }
/// @notice This predeploy is following the saftey invariant #1. /// @notice This predeploy is following the safety invariant #1.
/// We're initializing the implementation with `address(0)` so function setL1BlockNumber() public {
/// it's not left uninitialized. After `initialize` is called on the _setImplementationCode(Predeploys.L1_BLOCK_NUMBER);
/// proxy to set the storage slot with the expected value.
function _setL2StandardBridge() internal {
address impl = _setImplementationCode(Predeploys.L2_STANDARD_BRIDGE, "L2StandardBridge");
L2StandardBridge(payable(impl)).initialize(L1StandardBridge(payable(address(0))));
L2StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)).initialize(
L1StandardBridge(mustGetAddress("L1StandardBridgeProxy"))
);
_checkL2StandardBridge(impl);
} }
/// @notice This predeploy is following the saftey invariant #1. /// @notice This predeploy is following the safety invariant #1.
/// We're initializing the implementation with `address(0)` so function setLegacyMessagePasser() public {
/// it's not left uninitialized. After `initialize` is called on the _setImplementationCode(Predeploys.LEGACY_MESSAGE_PASSER);
/// proxy to set the storage slot with the expected value.
function _setL2CrossDomainMessenger() internal {
address impl = _setImplementationCode(Predeploys.L2_CROSS_DOMAIN_MESSENGER, "L2CrossDomainMessenger");
L2CrossDomainMessenger(impl).initialize(L1CrossDomainMessenger(address(0)));
L2CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER).initialize(
L1CrossDomainMessenger(mustGetAddress("L1CrossDomainMessengerProxy"))
);
_checkL2CrossDomainMessenger(impl);
} }
/// @notice This predeploy is following the saftey invariant #2, /// @notice This predeploy is following the safety invariant #2.
/// because the constructor args are non-static L1 contract function setBaseFeeVault() public {
/// addresses that are being read from the deploy config BaseFeeVault vault = new BaseFeeVault({
/// that are set as immutables. _recipient: cfg.baseFeeVaultRecipient(),
/// @dev Because the constructor args are stored as immutables, _minWithdrawalAmount: cfg.baseFeeVaultMinimumWithdrawalAmount(),
/// we don't have to worry about setting storage slots. _withdrawalNetwork: FeeVault.WithdrawalNetwork(cfg.baseFeeVaultWithdrawalNetwork())
function _setSequencerFeeVault() internal {
SequencerFeeVault vault = new SequencerFeeVault({
_recipient: cfg.sequencerFeeVaultRecipient(),
_minWithdrawalAmount: cfg.sequencerFeeVaultMinimumWithdrawalAmount(),
_withdrawalNetwork: FeeVault.WithdrawalNetwork(cfg.sequencerFeeVaultWithdrawalNetwork())
}); });
address impl = _predeployToCodeNamespace(Predeploys.SEQUENCER_FEE_WALLET); address impl = Predeploys.predeployToCodeNamespace(Predeploys.BASE_FEE_VAULT);
console.log("Setting %s implementation at: %s", "SequencerFeeVault", impl); console.log("Setting %s implementation at: %s", "BaseFeeVault", impl);
vm.etch(impl, address(vault).code); vm.etch(impl, address(vault).code);
/// Reset so its not included state dump /// Reset so its not included state dump
vm.etch(address(vault), ""); vm.etch(address(vault), "");
vm.resetNonce(address(vault)); vm.resetNonce(address(vault));
_checkSequencerFeeVault(impl);
} }
/// @notice This predeploy is following the saftey invariant #1. /// @notice This predeploy is following the safety invariant #2.
/// We're initializing the implementation with `address(0)` so function setL1FeeVault() public {
/// it's not left uninitialized. After `initialize` is called on the L1FeeVault vault = new L1FeeVault({
/// proxy to set the storage slot with the expected value. _recipient: cfg.l1FeeVaultRecipient(),
function _setOptimismMintableERC20Factory() internal { _minWithdrawalAmount: cfg.l1FeeVaultMinimumWithdrawalAmount(),
address impl = _withdrawalNetwork: FeeVault.WithdrawalNetwork(cfg.l1FeeVaultWithdrawalNetwork())
_setImplementationCode(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY, "OptimismMintableERC20Factory"); });
OptimismMintableERC20Factory(impl).initialize(address(0));
OptimismMintableERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).initialize(
Predeploys.L2_STANDARD_BRIDGE
);
_checkOptimismMintableERC20Factory(impl);
}
/// @notice This predeploy is following the saftey invariant #1. address impl = Predeploys.predeployToCodeNamespace(Predeploys.L1_FEE_VAULT);
/// This contract has no initializer. console.log("Setting %s implementation at: %s", "L1FeeVault", impl);
function _setL1BlockNumber() internal { vm.etch(impl, address(vault).code);
_setImplementationCode(Predeploys.L1_BLOCK_NUMBER, "L1BlockNumber");
}
/// @notice This predeploy is following the saftey invariant #1. /// Reset so its not included state dump
/// This contract has no initializer. vm.etch(address(vault), "");
function _setGasPriceOracle() internal { vm.resetNonce(address(vault));
_setImplementationCode(Predeploys.GAS_PRICE_ORACLE, "GasPriceOracle");
} }
/// @notice This predeploy is following the saftey invariant #3. /// @notice This predeploy is following the safety invariant #2.
function _setGovernanceToken() internal { function setGovernanceToken() public {
if (!cfg.enableGovernance()) { if (!cfg.enableGovernance()) {
console.log("Governance not enabled, skipping setting governanace token"); console.log("Governance not enabled, skipping setting governanace token");
return; return;
...@@ -318,67 +421,98 @@ contract L2Genesis is Script, Artifacts { ...@@ -318,67 +421,98 @@ contract L2Genesis is Script, Artifacts {
vm.resetNonce(address(token)); vm.resetNonce(address(token));
} }
/// @notice This predeploy is following the saftey invariant #1. /// @notice This predeploy is following the safety invariant #1.
/// This contract has no initializer. function setSchemaRegistry() public {
/// @dev Previously the initial L1 attributes was set at genesis, to simplify, _setImplementationCode(Predeploys.SCHEMA_REGISTRY);
/// they no longer are so the resulting storage slots are no longer set.
function _setL1Block() internal {
_setImplementationCode(Predeploys.L1_BLOCK_ATTRIBUTES, "L1Block");
} }
/// @dev Returns true if the address is not proxied. /// @notice This predeploy is following the safety invariant #2,
function _notProxied(address _addr) internal pure returns (bool) { /// It uses low level create to deploy the contract due to the code
return _addr == Predeploys.GOVERNANCE_TOKEN || _addr == Predeploys.WETH9; /// having immutables and being a different compiler version.
} function setEAS() public {
string memory cname = Predeploys.getName(Predeploys.EAS);
address impl = Predeploys.predeployToCodeNamespace(Predeploys.EAS);
bytes memory code = vm.getCode(string.concat(cname, ".sol:", cname));
address eas;
assembly {
eas := create(0, add(code, 0x20), mload(code))
}
/// @dev Returns true if the address is a predeploy. console.log("Setting %s implementation at: %s", cname, impl);
function _isDefinedPredeploy(address _addr) internal pure returns (bool) { vm.etch(impl, eas.code);
return _addr == Predeploys.L2_TO_L1_MESSAGE_PASSER || _addr == Predeploys.L2_CROSS_DOMAIN_MESSENGER
|| _addr == Predeploys.L2_STANDARD_BRIDGE || _addr == Predeploys.L2_ERC721_BRIDGE
|| _addr == Predeploys.SEQUENCER_FEE_WALLET || _addr == Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY
|| _addr == Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == Predeploys.L1_BLOCK_ATTRIBUTES
|| _addr == Predeploys.GAS_PRICE_ORACLE || _addr == Predeploys.DEPLOYER_WHITELIST || _addr == Predeploys.WETH9
|| _addr == Predeploys.L1_BLOCK_NUMBER || _addr == Predeploys.LEGACY_MESSAGE_PASSER
|| _addr == Predeploys.PROXY_ADMIN || _addr == Predeploys.BASE_FEE_VAULT || _addr == Predeploys.L1_FEE_VAULT
|| _addr == Predeploys.GOVERNANCE_TOKEN || _addr == Predeploys.SCHEMA_REGISTRY || _addr == Predeploys.EAS;
}
/// @dev Function to compute the expected address of the predeploy implementation /// Reset so its not included state dump
/// in the genesis state. vm.etch(address(eas), "");
function _predeployToCodeNamespace(address _addr) internal pure returns (address) { vm.resetNonce(address(eas));
return address(
uint160(uint256(uint160(_addr)) & 0xffff | uint256(uint160(0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000)))
);
} }
function _setImplementationCode(address _addr, string memory _name) internal returns (address) { /// @notice Sets all the preinstalls.
address impl = _predeployToCodeNamespace(_addr); /// Warning: the creator-accounts of the preinstall contracts have 0 nonce values.
console.log("Setting %s implementation at: %s", _name, impl); /// When performing a regular user-initiated contract-creation of a preinstall,
vm.etch(impl, vm.getDeployedCode(string.concat(_name, ".sol:", _name))); /// the creation will fail (but nonce will be bumped and not blocked).
/// The preinstalls themselves are all inserted with a nonce of 1, reflecting regular user execution.
function setPreinstalls() internal {
_setPreinstallCode(Preinstalls.MultiCall3);
_setPreinstallCode(Preinstalls.Create2Deployer);
_setPreinstallCode(Preinstalls.Safe_v130);
_setPreinstallCode(Preinstalls.SafeL2_v130);
_setPreinstallCode(Preinstalls.MultiSendCallOnly_v130);
_setPreinstallCode(Preinstalls.SafeSingletonFactory);
_setPreinstallCode(Preinstalls.DeterministicDeploymentProxy);
_setPreinstallCode(Preinstalls.MultiSend_v130);
_setPreinstallCode(Preinstalls.Permit2);
_setPreinstallCode(Preinstalls.SenderCreator);
_setPreinstallCode(Preinstalls.EntryPoint); // ERC 4337
_setPreinstallCode(Preinstalls.BeaconBlockRoots);
// 4788 sender nonce must be incremented, since it's part of later upgrade-transactions.
// For the upgrade-tx to not create a contract that conflicts with an already-existing copy,
// the nonce must be bumped.
vm.setNonce(Preinstalls.BeaconBlockRootsSender, 1);
}
_verifyProxyImplementationAddress(_addr, impl); function activateEcotone() public {
require(Preinstalls.BeaconBlockRoots.code.length > 0, "L2Genesis: must have beacon-block-roots contract");
console.log("Activating ecotone in GasPriceOracle contract");
vm.prank(L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).DEPOSITOR_ACCOUNT());
GasPriceOracle(Predeploys.GAS_PRICE_ORACLE).setEcotone();
}
/// @notice Sets the bytecode in state
function _setImplementationCode(address _addr) internal returns (address) {
string memory cname = Predeploys.getName(_addr);
address impl = Predeploys.predeployToCodeNamespace(_addr);
console.log("Setting %s implementation at: %s", cname, impl);
vm.etch(impl, vm.getDeployedCode(string.concat(cname, ".sol:", cname)));
return impl; return impl;
} }
/// @dev Function to verify the expected implementation address is set for the respective proxy. /// @notice Sets the bytecode in state
function _verifyProxyImplementationAddress(address _proxy, address _impl) internal view { function _setPreinstallCode(address _addr) internal {
require( string memory cname = Preinstalls.getName(_addr);
EIP1967Helper.getImplementation(_proxy) == _impl, console.log("Setting %s preinstall code at: %s", cname, _addr);
"Expected different address at Proxys PROXY_IMPLEMENTATION_ADDRESS storage slot" vm.etch(_addr, Preinstalls.getDeployedCode(_addr, cfg.l2ChainID()));
); // during testing in a shared L1/L2 account namespace some preinstalls may already have been inserted and used.
if (vm.getNonce(_addr) == 0) {
vm.setNonce(_addr, 1);
}
} }
/// @dev Function to verify that a contract was initialized, and can't be reinitialized. /// @notice Writes the genesis allocs, i.e. the state dump, to disk
/// @notice There isn't a good way to know if the resulting revering is due to abi mismatch function writeGenesisAllocs(string memory _path) public {
/// or because it's already been initialized /// Reset so its not included state dump
function _verifyCantReinitialize(address _contract, address _arg) internal { vm.etch(address(cfg), "");
vm.expectRevert("Initializable: contract is already initialized"); vm.etch(msg.sender, "");
IInitializable(_contract).initialize(_arg); vm.resetNonce(msg.sender);
vm.deal(msg.sender, 0);
console.log("Writing state dump to: %s", _path);
vm.dumpState(_path);
sortJsonByKeys(_path);
} }
/// @dev Helper function to sort the genesis alloc numerically by address. /// @notice Sorts the allocs by address
function _sortJsonByKeys(string memory _path) internal { function sortJsonByKeys(string memory _path) internal {
string[] memory commands = new string[](3); string[] memory commands = new string[](3);
commands[0] = "bash"; commands[0] = "bash";
commands[1] = "-c"; commands[1] = "-c";
...@@ -386,44 +520,11 @@ contract L2Genesis is Script, Artifacts { ...@@ -386,44 +520,11 @@ contract L2Genesis is Script, Artifacts {
vm.ffi(commands); vm.ffi(commands);
} }
function _fundDevAccounts() internal { /// @notice Funds the default dev accounts with ether
function fundDevAccounts() internal {
for (uint256 i; i < devAccounts.length; i++) { for (uint256 i; i < devAccounts.length; i++) {
console.log("Funding dev account %s with %s ETH", devAccounts[i], DEV_ACCOUNT_FUND_AMT / 1e18); console.log("Funding dev account %s with %s ETH", devAccounts[i], DEV_ACCOUNT_FUND_AMT / 1e18);
vm.deal(devAccounts[i], DEV_ACCOUNT_FUND_AMT); vm.deal(devAccounts[i], DEV_ACCOUNT_FUND_AMT);
} }
_checkDevAccountsFunded();
}
//////////////////////////////////////////////////////
/// Post Checks
//////////////////////////////////////////////////////
function _checkL2StandardBridge(address _impl) internal {
_verifyCantReinitialize(_impl, address(0));
_verifyCantReinitialize(Predeploys.L2_STANDARD_BRIDGE, mustGetAddress("L1StandardBridgeProxy"));
}
function _checkL2CrossDomainMessenger(address _impl) internal {
_verifyCantReinitialize(_impl, address(0));
_verifyCantReinitialize(Predeploys.L2_CROSS_DOMAIN_MESSENGER, mustGetAddress("L1CrossDomainMessengerProxy"));
}
function _checkSequencerFeeVault(address _impl) internal view {
_verifyProxyImplementationAddress(Predeploys.SEQUENCER_FEE_WALLET, _impl);
}
function _checkOptimismMintableERC20Factory(address _impl) internal {
_verifyCantReinitialize(_impl, address(0));
_verifyCantReinitialize(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY, Predeploys.L2_STANDARD_BRIDGE);
}
function _checkDevAccountsFunded() internal view {
for (uint256 i; i < devAccounts.length; i++) {
if (devAccounts[i].balance != DEV_ACCOUNT_FUND_AMT) {
revert(
string.concat("Dev account not funded with expected amount of ETH: ", vm.toString(devAccounts[i]))
);
}
}
} }
} }
...@@ -1988,7 +1988,7 @@ ...@@ -1988,7 +1988,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c" "slot": "0x000000000000000000000000000000000000000000000000000000000000003d"
} }
], ],
"value": 0 "value": 0
...@@ -2014,7 +2014,7 @@ ...@@ -2014,7 +2014,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003d" "slot": "0x000000000000000000000000000000000000000000000000000000000000003e"
} }
], ],
"value": 0 "value": 0
...@@ -2475,7 +2475,7 @@ ...@@ -2475,7 +2475,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c" "slot": "0x000000000000000000000000000000000000000000000000000000000000003d"
} }
], ],
"value": 0 "value": 0
...@@ -2553,7 +2553,7 @@ ...@@ -2553,7 +2553,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003d" "slot": "0x000000000000000000000000000000000000000000000000000000000000003e"
} }
], ],
"value": 0 "value": 0
...@@ -6095,7 +6095,7 @@ ...@@ -6095,7 +6095,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000834", "newValue": "0x0000000000000000000000000000000000000000000000000000000000000834",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000834", "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000834",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002c" "slot": "0x000000000000000000000000000000000000000000000000000000000000002d"
} }
], ],
"value": 0 "value": 0
...@@ -6121,7 +6121,7 @@ ...@@ -6121,7 +6121,7 @@
"newValue": "0x00000000000000000000000000000000000000000000000000000000000f4240", "newValue": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"previousValue": "0x00000000000000000000000000000000000000000000000000000000000f4240", "previousValue": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002d" "slot": "0x000000000000000000000000000000000000000000000000000000000000002e"
} }
], ],
"value": 0 "value": 0
...@@ -6147,7 +6147,7 @@ ...@@ -6147,7 +6147,7 @@
"newValue": "0x00000000000000000000000000000000000000000000000000000000017d7840", "newValue": "0x00000000000000000000000000000000000000000000000000000000017d7840",
"previousValue": "0x00000000000000000000000000000000000000000000000000000000017d7840", "previousValue": "0x00000000000000000000000000000000000000000000000000000000017d7840",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002a" "slot": "0x000000000000000000000000000000000000000000000000000000000000002c"
} }
], ],
"value": 0 "value": 0
...@@ -6856,7 +6856,7 @@ ...@@ -6856,7 +6856,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000834", "newValue": "0x0000000000000000000000000000000000000000000000000000000000000834",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000834", "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000834",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002c" "slot": "0x000000000000000000000000000000000000000000000000000000000000002d"
} }
], ],
"value": 0 "value": 0
...@@ -6934,7 +6934,7 @@ ...@@ -6934,7 +6934,7 @@
"newValue": "0x00000000000000000000000000000000000000000000000000000000000f4240", "newValue": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"previousValue": "0x00000000000000000000000000000000000000000000000000000000000f4240", "previousValue": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002d" "slot": "0x000000000000000000000000000000000000000000000000000000000000002e"
} }
], ],
"value": 0 "value": 0
...@@ -7090,7 +7090,7 @@ ...@@ -7090,7 +7090,7 @@
"newValue": "0x00000000000000000000000000000000000000000000000000000000017d7840", "newValue": "0x00000000000000000000000000000000000000000000000000000000017d7840",
"previousValue": "0x00000000000000000000000000000000000000000000000000000000017d7840", "previousValue": "0x00000000000000000000000000000000000000000000000000000000017d7840",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002a" "slot": "0x000000000000000000000000000000000000000000000000000000000000002c"
} }
], ],
"value": 0 "value": 0
...@@ -7246,7 +7246,7 @@ ...@@ -7246,7 +7246,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000", "previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false, "reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003b" "slot": "0x000000000000000000000000000000000000000000000000000000000000003c"
} }
], ],
"value": 0 "value": 0
...@@ -2,39 +2,21 @@ ...@@ -2,39 +2,21 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
/// @title Predeploys /// @title Predeploys
/// @notice Contains constant addresses for contracts that are pre-deployed to the L2 system. /// @notice Contains constant addresses for protocol contracts that are pre-deployed to the L2 system.
// This excludes the preinstalls (non-protocol contracts).
library Predeploys { library Predeploys {
/// @notice Address of the L2ToL1MessagePasser predeploy. /// @notice Number of predeploy-namespace addresses reserved for protocol usage.
address internal constant L2_TO_L1_MESSAGE_PASSER = 0x4200000000000000000000000000000000000016; uint256 internal constant PREDEPLOY_COUNT = 2048;
/// @notice Address of the L2CrossDomainMessenger predeploy.
address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007;
/// @notice Address of the L2StandardBridge predeploy.
address internal constant L2_STANDARD_BRIDGE = 0x4200000000000000000000000000000000000010;
/// @notice Address of the L2ERC721Bridge predeploy.
address internal constant L2_ERC721_BRIDGE = 0x4200000000000000000000000000000000000014;
//// @notice Address of the SequencerFeeWallet predeploy.
address internal constant SEQUENCER_FEE_WALLET = 0x4200000000000000000000000000000000000011;
/// @notice Address of the OptimismMintableERC20Factory predeploy.
address internal constant OPTIMISM_MINTABLE_ERC20_FACTORY = 0x4200000000000000000000000000000000000012;
/// @notice Address of the OptimismMintableERC721Factory predeploy.
address internal constant OPTIMISM_MINTABLE_ERC721_FACTORY = 0x4200000000000000000000000000000000000017;
/// @notice Address of the L1Block predeploy.
address internal constant L1_BLOCK_ATTRIBUTES = 0x4200000000000000000000000000000000000015;
/// @notice Address of the GasPriceOracle predeploy. Includes fee information /// @custom:legacy
/// and helpers for computing the L1 portion of the transaction fee. /// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated
address internal constant GAS_PRICE_ORACLE = 0x420000000000000000000000000000000000000F; /// L2ToL1MessagePasser contract instead.
address internal constant LEGACY_MESSAGE_PASSER = 0x4200000000000000000000000000000000000000;
/// @custom:legacy /// @custom:legacy
/// @notice Address of the L1MessageSender predeploy. Deprecated. Use L2CrossDomainMessenger /// @notice Address of the L1MessageSender predeploy. Deprecated. Use L2CrossDomainMessenger
/// or access tx.origin (or msg.sender) in a L1 to L2 transaction instead. /// or access tx.origin (or msg.sender) in a L1 to L2 transaction instead.
/// Not embedded into new OP-Stack chains.
address internal constant L1_MESSAGE_SENDER = 0x4200000000000000000000000000000000000001; address internal constant L1_MESSAGE_SENDER = 0x4200000000000000000000000000000000000001;
/// @custom:legacy /// @custom:legacy
...@@ -44,21 +26,38 @@ library Predeploys { ...@@ -44,21 +26,38 @@ library Predeploys {
/// @notice Address of the canonical WETH9 contract. /// @notice Address of the canonical WETH9 contract.
address internal constant WETH9 = 0x4200000000000000000000000000000000000006; address internal constant WETH9 = 0x4200000000000000000000000000000000000006;
/// @custom:legacy /// @notice Address of the L2CrossDomainMessenger predeploy.
/// @notice Address of the LegacyERC20ETH predeploy. Deprecated. Balances are migrated to the address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007;
/// state trie as of the Bedrock upgrade. Contract has been locked and write functions
/// can no longer be accessed. /// @notice Address of the GasPriceOracle predeploy. Includes fee information
address internal constant LEGACY_ERC20_ETH = 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000; /// and helpers for computing the L1 portion of the transaction fee.
address internal constant GAS_PRICE_ORACLE = 0x420000000000000000000000000000000000000F;
/// @notice Address of the L2StandardBridge predeploy.
address internal constant L2_STANDARD_BRIDGE = 0x4200000000000000000000000000000000000010;
//// @notice Address of the SequencerFeeWallet predeploy.
address internal constant SEQUENCER_FEE_WALLET = 0x4200000000000000000000000000000000000011;
/// @notice Address of the OptimismMintableERC20Factory predeploy.
address internal constant OPTIMISM_MINTABLE_ERC20_FACTORY = 0x4200000000000000000000000000000000000012;
/// @custom:legacy /// @custom:legacy
/// @notice Address of the L1BlockNumber predeploy. Deprecated. Use the L1Block predeploy /// @notice Address of the L1BlockNumber predeploy. Deprecated. Use the L1Block predeploy
/// instead, which exposes more information about the L1 state. /// instead, which exposes more information about the L1 state.
address internal constant L1_BLOCK_NUMBER = 0x4200000000000000000000000000000000000013; address internal constant L1_BLOCK_NUMBER = 0x4200000000000000000000000000000000000013;
/// @custom:legacy /// @notice Address of the L2ERC721Bridge predeploy.
/// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated address internal constant L2_ERC721_BRIDGE = 0x4200000000000000000000000000000000000014;
/// L2ToL1MessagePasser contract instead.
address internal constant LEGACY_MESSAGE_PASSER = 0x4200000000000000000000000000000000000000; /// @notice Address of the L1Block predeploy.
address internal constant L1_BLOCK_ATTRIBUTES = 0x4200000000000000000000000000000000000015;
/// @notice Address of the L2ToL1MessagePasser predeploy.
address internal constant L2_TO_L1_MESSAGE_PASSER = 0x4200000000000000000000000000000000000016;
/// @notice Address of the OptimismMintableERC721Factory predeploy.
address internal constant OPTIMISM_MINTABLE_ERC721_FACTORY = 0x4200000000000000000000000000000000000017;
/// @notice Address of the ProxyAdmin predeploy. /// @notice Address of the ProxyAdmin predeploy.
address internal constant PROXY_ADMIN = 0x4200000000000000000000000000000000000018; address internal constant PROXY_ADMIN = 0x4200000000000000000000000000000000000018;
...@@ -69,45 +68,75 @@ library Predeploys { ...@@ -69,45 +68,75 @@ library Predeploys {
/// @notice Address of the L1FeeVault predeploy. /// @notice Address of the L1FeeVault predeploy.
address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A; address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A;
/// @notice Address of the GovernanceToken predeploy.
address internal constant GOVERNANCE_TOKEN = 0x4200000000000000000000000000000000000042;
/// @notice Address of the SchemaRegistry predeploy. /// @notice Address of the SchemaRegistry predeploy.
address internal constant SCHEMA_REGISTRY = 0x4200000000000000000000000000000000000020; address internal constant SCHEMA_REGISTRY = 0x4200000000000000000000000000000000000020;
/// @notice Address of the EAS predeploy. /// @notice Address of the EAS predeploy.
address internal constant EAS = 0x4200000000000000000000000000000000000021; address internal constant EAS = 0x4200000000000000000000000000000000000021;
/// @notice Address of the MultiCall3 predeploy. /// @notice Address of the GovernanceToken predeploy.
address internal constant MultiCall3 = 0xcA11bde05977b3631167028862bE2a173976CA11; address internal constant GOVERNANCE_TOKEN = 0x4200000000000000000000000000000000000042;
/// @notice Address of the Create2Deployer predeploy.
address internal constant Create2Deployer = 0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2;
/// @notice Address of the Safe_v130 predeploy.
address internal constant Safe_v130 = 0x69f4D1788e39c87893C980c06EdF4b7f686e2938;
/// @notice Address of the SafeL2_v130 predeploy.
address internal constant SafeL2_v130 = 0xfb1bffC9d739B8D520DaF37dF666da4C687191EA;
/// @notice Address of the MultiSendCallOnly_v130 predeploy.
address internal constant MultiSendCallOnly_v130 = 0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B;
/// @notice Address of the SafeSingletonFactory predeploy.
address internal constant SafeSingletonFactory = 0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7;
/// @notice Address of the DeterministicDeploymentProxy predeploy.
address internal constant DeterministicDeploymentProxy = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
/// @notice Address of the MultiSend_v130 predeploy.
address internal constant MultiSend_v130 = 0x998739BFdAAdde7C933B942a68053933098f9EDa;
/// @notice Address of the Permit2 predeploy.
address internal constant Permit2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/// @notice Address of the SenderCreator predeploy. /// @custom:legacy
address internal constant SenderCreator = 0x7fc98430eAEdbb6070B35B39D798725049088348; /// @notice Address of the LegacyERC20ETH predeploy. Deprecated. Balances are migrated to the
/// state trie as of the Bedrock upgrade. Contract has been locked and write functions
/// can no longer be accessed.
address internal constant LEGACY_ERC20_ETH = 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000;
/// @notice Address of the EntryPoint predeploy. /// @notice Returns the name of the predeploy at the given address.
address internal constant EntryPoint = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789; function getName(address _addr) internal pure returns (string memory out_) {
require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy");
if (_addr == LEGACY_MESSAGE_PASSER) return "LegacyMessagePasser";
if (_addr == L1_MESSAGE_SENDER) return "L1MessageSender";
if (_addr == DEPLOYER_WHITELIST) return "DeployerWhitelist";
if (_addr == WETH9) return "WETH9";
if (_addr == L2_CROSS_DOMAIN_MESSENGER) return "L2CrossDomainMessenger";
if (_addr == GAS_PRICE_ORACLE) return "GasPriceOracle";
if (_addr == L2_STANDARD_BRIDGE) return "L2StandardBridge";
if (_addr == SEQUENCER_FEE_WALLET) return "SequencerFeeVault";
if (_addr == OPTIMISM_MINTABLE_ERC20_FACTORY) return "OptimismMintableERC20Factory";
if (_addr == L1_BLOCK_NUMBER) return "L1BlockNumber";
if (_addr == L2_ERC721_BRIDGE) return "L2ERC721Bridge";
if (_addr == L1_BLOCK_ATTRIBUTES) return "L1Block";
if (_addr == L2_TO_L1_MESSAGE_PASSER) return "L2ToL1MessagePasser";
if (_addr == OPTIMISM_MINTABLE_ERC721_FACTORY) return "OptimismMintableERC721Factory";
if (_addr == PROXY_ADMIN) return "ProxyAdmin";
if (_addr == BASE_FEE_VAULT) return "BaseFeeVault";
if (_addr == L1_FEE_VAULT) return "L1FeeVault";
if (_addr == SCHEMA_REGISTRY) return "SchemaRegistry";
if (_addr == EAS) return "EAS";
if (_addr == GOVERNANCE_TOKEN) return "GovernanceToken";
if (_addr == LEGACY_ERC20_ETH) return "LegacyERC20ETH";
revert("Predeploys: unnamed predeploy");
}
/// @notice Returns true if the predeploy is not proxied.
function notProxied(address _addr) internal pure returns (bool) {
return _addr == GOVERNANCE_TOKEN || _addr == WETH9;
}
/// @notice Returns true if the address is a defined predeploy that is embedded into new OP-Stack chains.
function isSupportedPredeploy(address _addr) internal pure returns (bool) {
return _addr == LEGACY_MESSAGE_PASSER || _addr == DEPLOYER_WHITELIST || _addr == WETH9
|| _addr == L2_CROSS_DOMAIN_MESSENGER || _addr == GAS_PRICE_ORACLE || _addr == L2_STANDARD_BRIDGE
|| _addr == SEQUENCER_FEE_WALLET || _addr == OPTIMISM_MINTABLE_ERC20_FACTORY || _addr == L1_BLOCK_NUMBER
|| _addr == L2_ERC721_BRIDGE || _addr == L1_BLOCK_ATTRIBUTES || _addr == L2_TO_L1_MESSAGE_PASSER
|| _addr == OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == PROXY_ADMIN || _addr == BASE_FEE_VAULT
|| _addr == L1_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS || _addr == GOVERNANCE_TOKEN;
}
function isPredeployNamespace(address _addr) internal pure returns (bool) {
return uint160(_addr) >> 11 == uint160(0x4200000000000000000000000000000000000000) >> 11;
}
/// @notice Function to compute the expected address of the predeploy implementation
/// in the genesis state.
function predeployToCodeNamespace(address _addr) internal pure returns (address) {
require(
isPredeployNamespace(_addr), "Predeploys: can only derive code-namespace address for predeploy addresses"
);
return address(
uint160(uint256(uint160(_addr)) & 0xffff | uint256(uint160(0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000)))
);
}
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -3,6 +3,7 @@ pragma solidity 0.8.15; ...@@ -3,6 +3,7 @@ pragma solidity 0.8.15;
// Testing utilities // Testing utilities
import { CommonTest } from "test/setup/CommonTest.sol"; import { CommonTest } from "test/setup/CommonTest.sol";
import { OutputMode } from "scripts/L2Genesis.s.sol";
// Libraries // Libraries
import { Encoding } from "src/libraries/Encoding.sol"; import { Encoding } from "src/libraries/Encoding.sol";
...@@ -37,7 +38,10 @@ contract GasPriceOracle_Test is CommonTest { ...@@ -37,7 +38,10 @@ contract GasPriceOracle_Test is CommonTest {
contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { contract GasPriceOracleBedrock_Test is GasPriceOracle_Test {
/// @dev Sets up the test suite. /// @dev Sets up the test suite.
function setUp() public virtual override { function setUp() public virtual override {
// The gasPriceOracle tests rely on an L2 genesis that is not past Ecotone.
l2OutputMode = OutputMode.LOCAL_DELTA;
super.setUp(); super.setUp();
assertEq(gasPriceOracle.isEcotone(), false);
vm.prank(depositor); vm.prank(depositor);
l1Block.setL1BlockValues({ l1Block.setL1BlockValues({
...@@ -109,7 +113,9 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { ...@@ -109,7 +113,9 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test {
contract GasPriceOracleEcotone_Test is GasPriceOracle_Test { contract GasPriceOracleEcotone_Test is GasPriceOracle_Test {
/// @dev Sets up the test suite. /// @dev Sets up the test suite.
function setUp() public virtual override { function setUp() public virtual override {
l2OutputMode = OutputMode.LOCAL_LATEST; // activate ecotone
super.setUp(); super.setUp();
assertEq(gasPriceOracle.isEcotone(), true);
bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesEcotone( bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesEcotone(
baseFeeScalar, blobBaseFeeScalar, sequenceNumber, timestamp, number, baseFee, blobBaseFee, hash, batcherHash baseFeeScalar, blobBaseFeeScalar, sequenceNumber, timestamp, number, baseFee, blobBaseFee, hash, batcherHash
...@@ -119,9 +125,6 @@ contract GasPriceOracleEcotone_Test is GasPriceOracle_Test { ...@@ -119,9 +125,6 @@ contract GasPriceOracleEcotone_Test is GasPriceOracle_Test {
vm.prank(depositor); vm.prank(depositor);
(bool success,) = address(l1Block).call(calldataPacked); (bool success,) = address(l1Block).call(calldataPacked);
require(success, "Function call failed"); require(success, "Function call failed");
vm.prank(depositor);
gasPriceOracle.setEcotone();
} }
/// @dev Tests that `setEcotone` is only callable by the depositor. /// @dev Tests that `setEcotone` is only callable by the depositor.
......
...@@ -28,10 +28,12 @@ contract L2StandardBridge_Test is Bridge_Initializer { ...@@ -28,10 +28,12 @@ contract L2StandardBridge_Test is Bridge_Initializer {
function test_constructor_succeeds() external view { function test_constructor_succeeds() external view {
L2StandardBridge impl = L2StandardBridge impl =
L2StandardBridge(payable(EIP1967Helper.getImplementation(deploy.mustGetAddress("L2StandardBridge")))); L2StandardBridge(payable(EIP1967Helper.getImplementation(deploy.mustGetAddress("L2StandardBridge"))));
assertEq(address(impl.MESSENGER()), address(0)); // The implementation contract is initialized with a 0 L1 bridge address,
assertEq(address(impl.messenger()), address(0)); // but the L2 cross-domain-messenger is always set to the predeploy address for both proxy and implementation.
assertEq(address(impl.OTHER_BRIDGE()), address(0)); assertEq(address(impl.MESSENGER()), Predeploys.L2_CROSS_DOMAIN_MESSENGER, "constructor zero check MESSENGER");
assertEq(address(impl.otherBridge()), address(0)); assertEq(address(impl.messenger()), Predeploys.L2_CROSS_DOMAIN_MESSENGER, "constructor zero check messenger");
assertEq(address(impl.OTHER_BRIDGE()), address(0), "constructor zero check OTHER_BRIDGE");
assertEq(address(impl.otherBridge()), address(0), "constructor zero check otherBridge");
} }
/// @dev Tests that the bridge is initialized correctly. /// @dev Tests that the bridge is initialized correctly.
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { L2Genesis, OutputMode, L1Dependencies } from "scripts/L2Genesis.s.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Constants } from "src/libraries/Constants.sol";
/// @title L2GenesisTest
/// @notice Test suite for L2Genesis script.
contract L2GenesisTest is Test {
L2Genesis genesis;
function setUp() public {
genesis = new L2Genesis();
// Note: to customize L1 addresses,
// simply pass in the L1 addresses argument for Genesis setup functions that depend on it.
// L1 addresses, or L1 artifacts, are not stored globally.
genesis.setUp();
}
/// @notice Creates a temp file and returns the path to it.
function tmpfile() internal returns (string memory) {
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
commands[2] = "mktemp";
bytes memory result = vm.ffi(commands);
return string(result);
}
/// @notice Deletes a file at a given filesystem path. Does not force delete
/// and does not recursively delete.
function deleteFile(string memory path) internal {
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
commands[2] = string.concat("rm ", path);
vm.ffi(commands);
}
/// @notice Returns the number of top level keys in a JSON object at a given
/// file path.
function getJSONKeyCount(string memory path) internal returns (uint256) {
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
commands[2] = string.concat("jq 'keys | length' < ", path, " | xargs cast abi-encode 'f(uint256)'");
return abi.decode(vm.ffi(commands), (uint256));
}
/// @notice Helper function to run a function with a temporary dump file.
function withTempDump(function (string memory) internal f) internal {
string memory path = tmpfile();
f(path);
deleteFile(path);
}
/// @notice Helper function for reading the number of storage keys for a given account.
function getStorageKeysCount(string memory _path, address _addr) internal returns (uint256) {
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
commands[2] =
string.concat("jq -r '.[\"", vm.toLowercase(vm.toString(_addr)), "\"].storage | length' < ", _path);
return vm.parseUint(string(vm.ffi(commands)));
}
/// @notice Returns the number of accounts that contain particular code at a given path to a genesis file.
function getCodeCount(string memory path, string memory name) internal returns (uint256) {
bytes memory code = vm.getDeployedCode(name);
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
commands[2] = string.concat(
"jq -r 'map_values(select(.code == \"",
vm.toString(code),
"\")) | length' < ",
path,
" | xargs cast abi-encode 'f(uint256)'"
);
return abi.decode(vm.ffi(commands), (uint256));
}
/// @notice Returns the number of accounts that have a particular slot set.
function getPredeployCountWithSlotSet(string memory path, bytes32 slot) internal returns (uint256) {
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
commands[2] = string.concat(
"jq 'map_values(.storage | select(has(\"",
vm.toString(slot),
"\"))) | keys | length' < ",
path,
" | xargs cast abi-encode 'f(uint256)'"
);
return abi.decode(vm.ffi(commands), (uint256));
}
/// @notice Returns the number of accounts that have a particular slot set to a particular value.
function getPredeployCountWithSlotSetToValue(
string memory path,
bytes32 slot,
bytes32 value
)
internal
returns (uint256)
{
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
commands[2] = string.concat(
"jq 'map_values(.storage | select(.\"",
vm.toString(slot),
"\" == \"",
vm.toString(value),
"\")) | length' < ",
path,
" | xargs cast abi-encode 'f(uint256)'"
);
return abi.decode(vm.ffi(commands), (uint256));
}
/// @notice Tests the genesis predeploys setup using a temp file.
function test_genesis_predeploys() external {
withTempDump(_test_genesis_predeploys);
}
/// @notice Tests the genesis predeploys setup.
function _test_genesis_predeploys(string memory _path) internal {
// Set the predeploy proxies into state
genesis.setPredeployProxies();
genesis.writeGenesisAllocs(_path);
// 2 predeploys do not have proxies
assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2);
// 17 proxies have the implementation set
assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), 17);
// All proxies except 2 have the proxy 1967 admin slot set to the proxy admin
assertEq(
getPredeployCountWithSlotSetToValue(
_path, Constants.PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(Predeploys.PROXY_ADMIN)))
),
Predeploys.PREDEPLOY_COUNT - 2
);
// Also see Predeploys.t.test_predeploysSet_succeeds which uses L1Genesis for the CommonTest prestate.
}
/// @notice Tests the number of accounts in the genesis setup
function test_allocs_size() external {
withTempDump(_test_allocs_size);
}
/// @notice Creates mock L1Dependencies for testing purposes.
function _dummyL1Deps() internal pure returns (L1Dependencies memory _deps) {
return L1Dependencies({
l1CrossDomainMessengerProxy: payable(address(0x100000)),
l1StandardBridgeProxy: payable(address(0x100001)),
l1ERC721BridgeProxy: payable(address(0x100002))
});
}
/// @notice Tests the number of accounts in the genesis setup
function _test_allocs_size(string memory _path) internal {
genesis.runWithOptions(OutputMode.LOCAL_LATEST, _dummyL1Deps());
genesis.writeGenesisAllocs(_path);
uint256 expected = 0;
expected += 2048 - 2; // predeploy proxies
expected += 19; // predeploy implementations (excl. legacy erc20-style eth and legacy message sender)
expected += 256; // precompiles
expected += 12; // preinstalls
expected += 1; // 4788 deployer account
// 16 prefunded dev accounts are excluded
assertEq(expected, getJSONKeyCount(_path), "key count check");
// 3 slots: implementation, owner, admin
assertEq(3, getStorageKeysCount(_path, Predeploys.PROXY_ADMIN), "proxy admin storage check");
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { CommonTest } from "test/setup/CommonTest.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { Bytes } from "src/libraries/Bytes.sol";
import { console2 as console } from "forge-std/console2.sol";
interface IEIP712 {
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
/// @title PreinstallsTest
contract PreinstallsTest is CommonTest {
/// @dev The domain separator commits to the chainid of the chain
function test_preinstall_permit2_domain_separator() external view {
bytes32 domainSeparator = IEIP712(Preinstalls.Permit2).DOMAIN_SEPARATOR();
bytes32 typeHash =
keccak256(abi.encodePacked("EIP712Domain(string name,uint256 chainId,address verifyingContract)"));
bytes32 nameHash = keccak256(abi.encodePacked("Permit2"));
uint256 chainId = block.chainid;
bytes memory encoded = abi.encode(typeHash, nameHash, chainId, Preinstalls.Permit2);
bytes32 expectedDomainSeparator = keccak256(encoded);
assertEq(domainSeparator, expectedDomainSeparator, "Domain separator mismatch");
assertEq(chainId, uint256(31337));
assertEq(domainSeparator, bytes32(0x4d553c58ae79a6c4ba64f0e690a5d1cd2deff8c6b91cf38300e0f2b76f9ee346));
// Warning the Permit2 domain separator as cached in the DeployPermit2.sol bytecode is incorrect.
}
function test_permit2_templating() external pure {
bytes memory customCode = Preinstalls.getPermit2Code(1234);
assertNotEq(customCode.length, 0, "must have code");
assertEq(uint256(bytes32(Bytes.slice(customCode, 6945, 32))), uint256(1234), "expecting custom chain ID");
assertEq(
bytes32(Bytes.slice(customCode, 6983, 32)),
bytes32(0x6cda538cafce36292a6ef27740629597f85f6716f5694d26d5c59fc1d07cfd95),
"expecting custom domain separator"
);
bytes memory defaultCode = Preinstalls.getPermit2Code(1);
assertNotEq(defaultCode.length, 0, "must have code");
assertEq(uint256(bytes32(Bytes.slice(defaultCode, 6945, 32))), uint256(1), "expecting default chain ID");
assertEq(
bytes32(Bytes.slice(defaultCode, 6983, 32)),
bytes32(0x866a5aba21966af95d6c7ab78eb2b2fc913915c28be3b9aa07cc04ff903e3f28),
"expecting default domain separator"
);
assertEq(defaultCode, Preinstalls.Permit2TemplateCode, "template is using chain ID 1");
}
function assertPreinstall(address _addr, bytes memory _code) internal view {
assertNotEq(_code.length, 0, "must have code");
assertNotEq(_addr.code.length, 0, "deployed preinstall account must have code");
assertEq(_addr.code, _code, "equal code must be deployed");
assertEq(Preinstalls.getDeployedCode(_addr, block.chainid), _code, "deployed-code getter must match");
assertNotEq(Preinstalls.getName(_addr), "", "must have a name");
if (_addr != Preinstalls.DeterministicDeploymentProxy) {
assertEq(vm.getNonce(_addr), 1, "preinstall account must have 1 nonce");
}
}
function test_preinstall_multicall3_succeeds() external view {
assertPreinstall(Preinstalls.MultiCall3, Preinstalls.MultiCall3Code);
}
function test_preinstall_create2Deployer_succeeds() external view {
assertPreinstall(Preinstalls.Create2Deployer, Preinstalls.Create2DeployerCode);
}
function test_preinstall_safev130_succeeds() external view {
assertPreinstall(Preinstalls.Safe_v130, Preinstalls.Safe_v130Code);
}
function test_preinstall_safeL2v130_succeeds() external view {
assertPreinstall(Preinstalls.SafeL2_v130, Preinstalls.SafeL2_v130Code);
}
function test_preinstall_multisendCallOnlyv130_succeeds() external view {
assertPreinstall(Preinstalls.MultiSendCallOnly_v130, Preinstalls.MultiSendCallOnly_v130Code);
}
function test_preinstall_safeSingletonFactory_succeeds() external view {
assertPreinstall(Preinstalls.SafeSingletonFactory, Preinstalls.SafeSingletonFactoryCode);
}
function test_preinstall_deterministicDeploymentProxy_succeeds() external view {
assertPreinstall(Preinstalls.DeterministicDeploymentProxy, Preinstalls.DeterministicDeploymentProxyCode);
}
function test_preinstall_multisendv130_succeeds() external view {
assertPreinstall(Preinstalls.MultiSend_v130, Preinstalls.MultiSend_v130Code);
}
function test_preinstall_permit2_succeeds() external {
uint256 pre = block.chainid;
vm.chainId(901); // TODO legacy deployment does not use same chainID as tests run with
assertPreinstall(Preinstalls.Permit2, Preinstalls.getPermit2Code(block.chainid));
vm.chainId(pre);
}
function test_preinstall_senderCreator_succeeds() external view {
assertPreinstall(Preinstalls.SenderCreator, Preinstalls.SenderCreatorCode);
}
function test_preinstall_entrypoint_succeeds() external view {
assertPreinstall(Preinstalls.EntryPoint, Preinstalls.EntryPointCode);
}
function test_preinstall_beaconBlockRoots_succeeds() external view {
assertPreinstall(Preinstalls.BeaconBlockRoots, Preinstalls.BeaconBlockRootsCode);
assertEq(vm.getNonce(Preinstalls.BeaconBlockRootsSender), 1, "4788 sender must have nonce=1");
}
}
...@@ -8,20 +8,28 @@ import { Vm } from "forge-std/Vm.sol"; ...@@ -8,20 +8,28 @@ import { Vm } from "forge-std/Vm.sol";
library EIP1967Helper { library EIP1967Helper {
/// @notice The storage slot that holds the address of a proxy implementation. /// @notice The storage slot that holds the address of a proxy implementation.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)` /// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)`
bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS = bytes32 internal constant PROXY_IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/// @notice The storage slot that holds the address of the owner. /// @notice The storage slot that holds the address of the owner.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)` /// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`
bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; bytes32 internal constant PROXY_ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function getAdmin(address _proxy) internal view returns (address) { function getAdmin(address _proxy) internal view returns (address) {
return address(uint160(uint256(vm.load(address(_proxy), PROXY_OWNER_ADDRESS)))); return address(uint160(uint256(vm.load(address(_proxy), PROXY_ADMIN_SLOT))));
}
function setAdmin(address _addr, address _admin) internal {
vm.store(_addr, PROXY_ADMIN_SLOT, bytes32(uint256(uint160(_admin))));
} }
function getImplementation(address _proxy) internal view returns (address) { function getImplementation(address _proxy) internal view returns (address) {
return address(uint160(uint256(vm.load(address(_proxy), PROXY_IMPLEMENTATION_ADDRESS)))); return address(uint160(uint256(vm.load(address(_proxy), PROXY_IMPLEMENTATION_SLOT))));
}
function setImplementation(address _addr, address _impl) internal {
vm.store(_addr, PROXY_IMPLEMENTATION_SLOT, bytes32(uint256(uint160(_impl))));
} }
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { console2 as console } from "forge-std/console2.sol";
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { L2CrossDomainMessenger } from "src/L2/L2CrossDomainMessenger.sol"; import { L2CrossDomainMessenger } from "src/L2/L2CrossDomainMessenger.sol";
import { L2StandardBridge } from "src/L2/L2StandardBridge.sol"; import { L2StandardBridge } from "src/L2/L2StandardBridge.sol";
import { L2ToL1MessagePasser } from "src/L2/L2ToL1MessagePasser.sol"; import { L2ToL1MessagePasser } from "src/L2/L2ToL1MessagePasser.sol";
...@@ -24,6 +26,7 @@ import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; ...@@ -24,6 +26,7 @@ import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { DeployConfig } from "scripts/DeployConfig.s.sol"; import { DeployConfig } from "scripts/DeployConfig.s.sol";
import { Deploy } from "scripts/Deploy.s.sol"; import { Deploy } from "scripts/Deploy.s.sol";
import { L2Genesis, L1Dependencies, OutputMode } from "scripts/L2Genesis.s.sol";
import { L2OutputOracle } from "src/L1/L2OutputOracle.sol"; import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; import { ProtocolVersions } from "src/L1/ProtocolVersions.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol";
...@@ -51,6 +54,12 @@ contract Setup { ...@@ -51,6 +54,12 @@ contract Setup {
/// mutating any nonces. MUST not have constructor logic. /// mutating any nonces. MUST not have constructor logic.
Deploy internal constant deploy = Deploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy")))))); Deploy internal constant deploy = Deploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy"))))));
L2Genesis internal constant l2Genesis =
L2Genesis(address(uint160(uint256(keccak256(abi.encode("optimism.l2genesis"))))));
// @notice Allows users of Setup to override what L2 genesis is being created.
OutputMode l2OutputMode = OutputMode.LOCAL_LATEST;
OptimismPortal optimismPortal; OptimismPortal optimismPortal;
OptimismPortal2 optimismPortal2; OptimismPortal2 optimismPortal2;
DisputeGameFactory disputeGameFactory; DisputeGameFactory disputeGameFactory;
...@@ -89,13 +98,22 @@ contract Setup { ...@@ -89,13 +98,22 @@ contract Setup {
/// will also need to include the bytecode for the Deploy contract. /// will also need to include the bytecode for the Deploy contract.
/// This is a hack as we are pushing solidity to the edge. /// This is a hack as we are pushing solidity to the edge.
function setUp() public virtual { function setUp() public virtual {
console.log("L1 setup start!");
vm.etch(address(deploy), vm.getDeployedCode("Deploy.s.sol:Deploy")); vm.etch(address(deploy), vm.getDeployedCode("Deploy.s.sol:Deploy"));
vm.allowCheatcodes(address(deploy)); vm.allowCheatcodes(address(deploy));
deploy.setUp(); deploy.setUp();
console.log("L1 setup done!");
console.log("L2 setup start!");
vm.etch(address(l2Genesis), vm.getDeployedCode("L2Genesis.s.sol:L2Genesis"));
vm.allowCheatcodes(address(l2Genesis));
l2Genesis.setUp();
console.log("L2 setup done!");
} }
/// @dev Sets up the L1 contracts. /// @dev Sets up the L1 contracts.
function L1() public { function L1() public {
console.log("Setup: creating L1 deployments");
// Set the deterministic deployer in state to ensure that it is there // Set the deterministic deployer in state to ensure that it is there
vm.etch( vm.etch(
0x4e59b44847b379578588920cA78FbF26c0B4956C, 0x4e59b44847b379578588920cA78FbF26c0B4956C,
...@@ -103,6 +121,7 @@ contract Setup { ...@@ -103,6 +121,7 @@ contract Setup {
); );
deploy.run(); deploy.run();
console.log("Setup: completed L1 deployment, registering addresses now");
optimismPortal = OptimismPortal(deploy.mustGetAddress("OptimismPortalProxy")); optimismPortal = OptimismPortal(deploy.mustGetAddress("OptimismPortalProxy"));
optimismPortal2 = OptimismPortal2(deploy.mustGetAddress("OptimismPortalProxy")); optimismPortal2 = OptimismPortal2(deploy.mustGetAddress("OptimismPortalProxy"));
...@@ -151,61 +170,63 @@ contract Setup { ...@@ -151,61 +170,63 @@ contract Setup {
vm.label(address(dataAvailabilityChallenge), "DataAvailabilityChallengeProxy"); vm.label(address(dataAvailabilityChallenge), "DataAvailabilityChallengeProxy");
vm.label(deploy.mustGetAddress("DataAvailabilityChallenge"), "DataAvailabilityChallenge"); vm.label(deploy.mustGetAddress("DataAvailabilityChallenge"), "DataAvailabilityChallenge");
} }
console.log("Setup: registered L1 deployments");
} }
/// @dev Sets up the L2 contracts. Depends on `L1()` being called first. /// @dev Sets up the L2 contracts. Depends on `L1()` being called first.
function L2() public { function L2() public {
string memory allocsPath = string.concat(vm.projectRoot(), "/.testdata/genesis.json"); console.log("Setup: creating L2 genesis, with output mode %d", uint256(l2OutputMode));
if (vm.isFile(allocsPath) == false) { l2Genesis.runWithOptions(
string[] memory args = new string[](3); l2OutputMode,
args[0] = Executables.bash; L1Dependencies({
args[1] = "-c"; l1CrossDomainMessengerProxy: payable(address(l1CrossDomainMessenger)),
args[2] = string.concat(vm.projectRoot(), "/scripts/generate-l2-genesis.sh"); l1StandardBridgeProxy: payable(address(l1StandardBridge)),
Vm.FfiResult memory result = vm.tryFfi(args); l1ERC721BridgeProxy: payable(address(l1ERC721Bridge))
if (result.exitCode != 0) { })
revert FfiFailed( );
string.concat(
"FFI call to generate genesis.json failed with exit code: ",
string(abi.encodePacked(result.exitCode)),
".\nCommand: ",
Executables.bash,
" -c ",
vm.projectRoot(),
"/scripts/generate-l2-genesis.sh",
".\nOutput: ",
string(result.stdout),
"\nError: ",
string(result.stderr)
)
);
}
}
// Prevent race condition where the genesis.json file is not yet created
while (vm.isFile(allocsPath) == false) {
vm.sleep(1);
}
vm.loadAllocs(allocsPath);
// Set the governance token's owner to be the final system owner // Set the governance token's owner to be the final system owner
address finalSystemOwner = deploy.cfg().finalSystemOwner(); address finalSystemOwner = deploy.cfg().finalSystemOwner();
vm.prank(governanceToken.owner()); vm.prank(governanceToken.owner());
governanceToken.transferOwnership(finalSystemOwner); governanceToken.transferOwnership(finalSystemOwner);
vm.label(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY, "OptimismMintableERC20Factory"); // L2 predeploys
vm.label(Predeploys.L2_STANDARD_BRIDGE, "L2StandardBridge"); labelPredeploy(Predeploys.L2_STANDARD_BRIDGE);
vm.label(Predeploys.L2_CROSS_DOMAIN_MESSENGER, "L2CrossDomainMessenger"); labelPredeploy(Predeploys.L2_CROSS_DOMAIN_MESSENGER);
vm.label(Predeploys.L2_TO_L1_MESSAGE_PASSER, "L2ToL1MessagePasser"); labelPredeploy(Predeploys.L2_TO_L1_MESSAGE_PASSER);
vm.label(Predeploys.SEQUENCER_FEE_WALLET, "SequencerFeeVault"); labelPredeploy(Predeploys.SEQUENCER_FEE_WALLET);
vm.label(Predeploys.L2_ERC721_BRIDGE, "L2ERC721Bridge"); labelPredeploy(Predeploys.L2_ERC721_BRIDGE);
vm.label(Predeploys.BASE_FEE_VAULT, "BaseFeeVault"); labelPredeploy(Predeploys.BASE_FEE_VAULT);
vm.label(Predeploys.L1_FEE_VAULT, "L1FeeVault"); labelPredeploy(Predeploys.L1_FEE_VAULT);
vm.label(Predeploys.L1_BLOCK_ATTRIBUTES, "L1Block"); labelPredeploy(Predeploys.L1_BLOCK_ATTRIBUTES);
vm.label(Predeploys.GAS_PRICE_ORACLE, "GasPriceOracle"); labelPredeploy(Predeploys.GAS_PRICE_ORACLE);
vm.label(Predeploys.LEGACY_MESSAGE_PASSER, "LegacyMessagePasser"); labelPredeploy(Predeploys.LEGACY_MESSAGE_PASSER);
vm.label(Predeploys.GOVERNANCE_TOKEN, "GovernanceToken"); labelPredeploy(Predeploys.GOVERNANCE_TOKEN);
vm.label(Predeploys.EAS, "EAS"); labelPredeploy(Predeploys.EAS);
vm.label(Predeploys.SCHEMA_REGISTRY, "SchemaRegistry"); labelPredeploy(Predeploys.SCHEMA_REGISTRY);
// L2 Preinstalls
labelPreinstall(Preinstalls.MultiCall3);
labelPreinstall(Preinstalls.Create2Deployer);
labelPreinstall(Preinstalls.Safe_v130);
labelPreinstall(Preinstalls.SafeL2_v130);
labelPreinstall(Preinstalls.MultiSendCallOnly_v130);
labelPreinstall(Preinstalls.SafeSingletonFactory);
labelPreinstall(Preinstalls.DeterministicDeploymentProxy);
labelPreinstall(Preinstalls.MultiSend_v130);
labelPreinstall(Preinstalls.Permit2);
labelPreinstall(Preinstalls.SenderCreator);
labelPreinstall(Preinstalls.EntryPoint);
labelPreinstall(Preinstalls.BeaconBlockRoots);
console.log("Setup: completed L2 genesis");
}
function labelPredeploy(address _addr) internal {
vm.label(_addr, Predeploys.getName(_addr));
}
function labelPreinstall(address _addr) internal {
vm.label(_addr, Preinstalls.getName(_addr));
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment