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:
- "packages/contracts-bedrock/tsconfig.tsbuildinfo"
- "packages/contracts-bedrock/tsconfig.build.tsbuildinfo"
- ".devnet/allocs-l1.json"
- ".devnet/allocs-l2.json"
- ".devnet/allocs-l2-delta.json"
- ".devnet/addresses.json"
- ".devnet-fault-proofs/allocs-l1.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/addresses.json"
- ".devnet-plasma/allocs-l2.json"
- ".devnet-plasma/allocs-l2-delta.json"
- "packages/contracts-bedrock/deploy-config/devnetL1.json"
- "packages/contracts-bedrock/deployments/devnetL1"
......@@ -963,6 +969,8 @@ jobs:
name: Load devnet-allocs
command: |
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>>/addresses.json .devnet/addresses.json
cp /tmp/workspace/packages/contracts-bedrock/deploy-config/devnetL1.json packages/contracts-bedrock/deploy-config/devnetL1.json
......@@ -1155,6 +1163,8 @@ jobs:
- persist_to_workspace:
root: .
paths:
- ".devnet/allocs-l2.json"
- ".devnet/allocs-l2-delta.json"
- ".devnet/allocs-l1.json"
- ".devnet/addresses.json"
- "packages/contracts-bedrock/deploy-config/devnetL1.json"
......
......@@ -63,7 +63,7 @@ def main():
devnet_dir = pjoin(monorepo_dir, '.devnet')
contracts_bedrock_dir = pjoin(monorepo_dir, 'packages', 'contracts-bedrock')
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')
ops_bedrock_dir = pjoin(monorepo_dir, 'ops-bedrock')
deploy_config_dir = pjoin(contracts_bedrock_dir, 'deploy-config')
......@@ -77,7 +77,7 @@ def main():
devnet_dir=devnet_dir,
contracts_bedrock_dir=contracts_bedrock_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'),
deploy_config_dir=deploy_config_dir,
devnet_config_path=devnet_config_path,
......@@ -88,7 +88,7 @@ def main():
sdk_dir=sdk_dir,
genesis_l1_path=pjoin(devnet_dir, 'genesis-l1.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'),
sdk_addresses_json_path=pjoin(devnet_dir, 'sdk-addresses.json'),
rollup_config_path=pjoin(devnet_dir, 'rollup.json')
......@@ -102,7 +102,8 @@ def main():
os.makedirs(devnet_dir, exist_ok=True)
if args.allocs:
devnet_l1_genesis(paths)
devnet_l1_allocs(paths)
devnet_l2_allocs(paths)
return
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):
deploy_config['usePlasma'] = True
write_json(paths.devnet_config_path, deploy_config)
def devnet_l1_genesis(paths):
log.info('Generating L1 genesis state')
def devnet_l1_allocs(paths):
log.info('Generating L1 genesis allocs')
init_devnet_l1_deploy_config(paths)
fqn = 'scripts/Deploy.s.sol:Deploy'
......@@ -146,27 +147,52 @@ def devnet_l1_genesis(paths):
'forge', 'script', '--chain-id', '900', fqn, "--sig", "runWithStateDump()", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
], env={}, cwd=paths.contracts_bedrock_dir)
forge_dump = read_json(paths.forge_dump_path)
write_json(paths.allocs_path, { "accounts": forge_dump })
os.remove(paths.forge_dump_path)
forge_dump = read_json(paths.forge_l1_dump_path)
write_json(paths.allocs_l1_path, { "accounts": forge_dump })
os.remove(paths.forge_l1_dump_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
def devnet_deploy(paths):
if os.path.exists(paths.genesis_l1_path):
log.info('L1 genesis already generated.')
else:
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
# file here always. This is because CI will run devnet-allocs
# without DEVNET_FPAC=true which means the allocs will be wrong.
# 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
# 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.
# If someone reads this comment and understands why this is being done, please
# update this comment to explain.
......@@ -174,7 +200,7 @@ def devnet_deploy(paths):
run_command([
'go', 'run', 'cmd/main.go', 'genesis', 'l1',
'--deploy-config', paths.devnet_config_path,
'--l1-allocs', paths.allocs_path,
'--l1-allocs', paths.allocs_l1_path,
'--l1-deployments', paths.addresses_json_path,
'--outfile.l1', paths.genesis_l1_path,
], cwd=paths.op_node_dir)
......@@ -190,10 +216,19 @@ def devnet_deploy(paths):
log.info('L2 genesis and rollup configs already generated.')
else:
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([
'go', 'run', 'cmd/main.go', 'genesis', 'l2',
'--l1-rpc', 'http://localhost:8545',
'--deploy-config', paths.devnet_config_path,
'--l2-allocs', l2_allocs_path,
'--l1-deployments', paths.addresses_json_path,
'--outfile.l2', paths.genesis_l2_path,
'--outfile.rollup', paths.rollup_config_path
......
......@@ -10,9 +10,12 @@ import (
"path/filepath"
"reflect"
"golang.org/x/exp/maps"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
gstate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
......@@ -803,6 +806,7 @@ func (d *ForgeDump) UnmarshalJSON(b []byte) error {
d.Root = dump.Root
d.Accounts = make(map[string]gstate.DumpAccount)
for addr, acc := range dump.Accounts {
acc := acc
d.Accounts[addr.String()] = gstate.DumpAccount{
Balance: acc.Balance,
Nonce: (uint64)(acc.Nonce),
......@@ -817,6 +821,45 @@ func (d *ForgeDump) UnmarshalJSON(b []byte) error {
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
// DeployConfig and a block.
func NewL2ImmutableConfig(config *DeployConfig, block *types.Block) (*immutables.PredeploysImmutableConfig, error) {
......
......@@ -51,7 +51,7 @@ var DevAccounts = []common.Address{
common.HexToAddress("0xcd3B766CCDd6AE721141F452C550Ca635964ce71"),
common.HexToAddress("0xdD2FD4581271e230360230F9337D5c0430Bf44C0"),
common.HexToAddress("0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097"),
common.HexToAddress("0xde3829a23df1479438622a08a116e8eb3f620bb5"),
common.HexToAddress("0xDe3829A23DF1479438622a08a116E8Eb3f620BB5"),
common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
// Test account used by geth tests
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"),
......
package genesis
import (
"encoding/json"
"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/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-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.
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)
if err != nil {
return nil, err
}
db := state.NewMemoryStateDB(genspec)
if config.FundDevAccounts {
log.Info("Funding developer accounts in L2 genesis")
FundDevAccounts(db)
genspec.Alloc = dump.Accounts
// ensure the dev accounts are not funded unintentionally
if hasDevAccounts, err := HasAnyDevAccounts(dump.Accounts); err != nil {
return nil, fmt.Errorf("failed to check dev accounts: %w", err)
} else if hasDevAccounts != config.FundDevAccounts {
return nil, fmt.Errorf("deploy config mismatch with allocs. Deploy config fundDevAccounts: %v, actual allocs: %v", config.FundDevAccounts, hasDevAccounts)
}
SetPrecompileBalances(db)
storage, err := NewL2StorageConfig(config, l1StartBlock)
if err != nil {
return nil, err
// sanity check the permit2 immutable, to verify we using the allocs for the right chain.
chainID := [32]byte(genspec.Alloc[predeploys.Permit2Addr].Code[6945 : 6945+32])
expected := uint256.MustFromBig(genspec.Config.ChainID).Bytes32()
if chainID != expected {
return nil, fmt.Errorf("allocs were generated for chain ID %x, but expected chain %x (%d)", chainID, expected, genspec.Config.ChainID)
}
return genspec, nil
}
immutableConfig, err := NewL2ImmutableConfig(config, l1StartBlock)
if err != nil {
return nil, err
}
var testMnemonic = "test test test test test test test test test test test junk"
// Set up the proxies
err = setProxies(db, predeploys.ProxyAdminAddr, BigL2PredeployNamespace, 2048)
func HasAnyDevAccounts(allocs core.GenesisAlloc) (bool, error) {
wallet, err := hdwallet.NewFromMnemonic(testMnemonic)
if err != nil {
return nil, err
return false, fmt.Errorf("failed to create wallet: %w", err)
}
// Set up the implementations that contain immutables
deployResults, err := immutables.Deploy(immutableConfig)
if err != nil {
return nil, fmt.Errorf("immutables.Deploy failed: %w", err)
account := func(path string) accounts.Account {
return accounts.Account{URL: accounts.URL{Path: path}}
}
for name, predeploy := range predeploys.Predeploys {
if predeploy.Enabled != nil && !predeploy.Enabled(config) {
log.Warn("Skipping disabled predeploy.", "name", name, "address", predeploy.Address)
continue
for i := 0; i < 30; i++ {
key, err := wallet.PrivateKey(account(fmt.Sprintf("m/44'/60'/0'/0/%d", i)))
if err != nil {
return false, err
}
codeAddr := predeploy.Address
switch name {
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)
}
addr := crypto.PubkeyToAddress(key.PublicKey)
if _, ok := allocs[addr]; ok {
return true, nil
}
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 db.Genesis(), nil
return false, nil
}
func PerformUpgradeTxs(db *state.MemoryStateDB) error {
// Only the Ecotone upgrade is performed with upgrade-txs.
if !db.Genesis().Config.IsEcotone(db.Genesis().Timestamp) {
return nil
}
sim := squash.NewSimulator(db)
ecotone, err := derive.EcotoneNetworkUpgradeTransactions()
func LoadForgeAllocs(allocsPath string) (*ForgeAllocs, error) {
path := filepath.Join(allocsPath)
f, err := os.OpenFile(path, os.O_RDONLY, 0644)
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 {
return fmt.Errorf("failed to apply ecotone upgrade txs: %w", err)
defer f.Close()
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
import (
"errors"
"fmt"
"math/big"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
"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
......@@ -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.
// This is an optimization to make calling them cheaper.
func SetPrecompileBalances(db vm.StateDB) {
......@@ -66,29 +31,3 @@ func SetPrecompileBalances(db vm.StateDB) {
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) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
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
......@@ -59,6 +59,10 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
_, _, miner, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log)
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
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
......@@ -101,7 +105,7 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
txn := transactions[i]
receipt, err := ethCl.TransactionReceipt(context.Background(), txn.Hash())
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")
}
......@@ -162,11 +166,14 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
require.NotNil(t, latestBlock.BeaconRoot())
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")
// The first block is an exception in upgrade-networks,
// since the beacon-block root contract isn't there at Ecotone activation,
// and the beacon-block-root insertion is processed at the start of the block before deposit txs.
// 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)")
// Legacy check:
// > The first block is an exception in upgrade-networks,
// > since the beacon-block root contract isn't there at Ecotone activation,
// > and the beacon-block-root insertion is processed at the start of the block before deposit txs.
// > 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
sequencer.ActL2StartBlock(t)
......
package actions
import (
"context"
"crypto/ecdsa"
crand "crypto/rand"
"fmt"
......@@ -501,6 +502,10 @@ func TestSpanBatchLowThroughputChain(gt *testing.T) {
addr := crypto.PubkeyToAddress(privateKey.PublicKey)
require.NoError(t, err)
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)
......
......@@ -44,6 +44,8 @@ var (
// L1Deployments maps contract names to accounts in the L1
// genesis block state.
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 *genesis.DeployConfig
// ExternalL2Shim is the shim to use if external ethereum client testing is
......@@ -57,7 +59,7 @@ var (
)
func init() {
var l1AllocsPath, l1DeploymentsPath, deployConfigPath, externalL2 string
var l1AllocsPath, l2AllocsDir, l1DeploymentsPath, deployConfigPath, externalL2 string
cwd, err := os.Getwd()
if err != nil {
......@@ -69,10 +71,12 @@ func init() {
}
defaultL1AllocsPath := filepath.Join(root, ".devnet", "allocs-l1.json")
defaultL2AllocsDir := filepath.Join(root, ".devnet")
defaultL1DeploymentsPath := filepath.Join(root, ".devnet", "addresses.json")
defaultDeployConfigPath := filepath.Join(root, "packages", "contracts-bedrock", "deploy-config", "devnetL1.json")
flag.StringVar(&l1AllocsPath, "l1-allocs", defaultL1AllocsPath, "")
flag.StringVar(&l2AllocsDir, "l2-allocs-dir", defaultL2AllocsDir, "")
flag.StringVar(&l1DeploymentsPath, "l1-deployments", defaultL1DeploymentsPath, "")
flag.StringVar(&deployConfigPath, "deploy-config", defaultDeployConfigPath, "")
flag.StringVar(&externalL2, "externalL2", "", "Enable tests with external L2")
......@@ -108,6 +112,20 @@ func init() {
if err != nil {
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)
if err != nil {
panic(err)
......@@ -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 {
var err error
ExternalL2Shim, err = filepath.Abs(filepath.Join(externalL2, "shim"))
......
......@@ -122,7 +122,13 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
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")
if alloc.PrefundTestUsers {
for _, addr := range deployParams.Addresses.All() {
......
......@@ -58,7 +58,13 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e
require.Nil(t, err)
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)
l2GenesisBlock := l2Genesis.ToBlock()
......
......@@ -26,7 +26,6 @@ import (
func TestMissingGasLimit(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
......@@ -72,17 +71,19 @@ func TestTxGasSameAsBlockGasLimit(t *testing.T) {
func TestInvalidDepositInFCU(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
// Create a deposit from alice that will always fail (not enough funds)
fromAddr := cfg.Secrets.Addresses().Alice
// Create a deposit from a new account that will always fail (not enough funds)
fromKey, err := crypto.GenerateKey()
require.NoError(t, err)
fromAddr := crypto.PubkeyToAddress(fromKey.PublicKey)
balance, err := opGeth.L2Client.BalanceAt(ctx, fromAddr, nil)
require.Nil(t, err)
t.Logf("alice balance: %d, %s", balance, fromAddr)
require.Equal(t, 0, balance.Cmp(common.Big0))
badDepositTx := types.NewTx(&types.DepositTx{
......@@ -98,7 +99,7 @@ func TestInvalidDepositInFCU(t *testing.T) {
_, err = opGeth.AddL2Block(ctx, badDepositTx)
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)
require.Nil(t, err)
require.Equal(t, 0, balance.Cmp(common.Big0))
......
......@@ -103,7 +103,7 @@ func setupSequencerFailoverTest(t *testing.T) (*System, map[string]*conductor) {
return healthy(t, ctx, c1) &&
healthy(t, ctx, c2) &&
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
require.NoError(t, c1.client.Resume(ctx))
......
......@@ -479,7 +479,14 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
}
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 {
return nil, err
}
......
......@@ -1223,6 +1223,10 @@ func testFees(t *testing.T, cfg SystemConfig) {
l2Verif := sys.Clients["verifier"]
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
sga := &stateGetterAdapter{
......
......@@ -57,6 +57,10 @@ var (
Name: "outfile.l1",
Usage: "Path to L1 genesis output file",
}
l2AllocsFlag = &cli.StringFlag{
Name: "l2-allocs",
Usage: "Path to L2 genesis state dump",
}
l1Flags = []cli.Flag{
deployConfigFlag,
......@@ -69,6 +73,7 @@ var (
l1RPCFlag,
l1StartingBlockFlag,
deployConfigFlag,
l2AllocsFlag,
l1DeploymentsFlag,
outfileL2Flag,
outfileRollupFlag,
......@@ -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 != "" {
client, err := ethclient.Dial(l1RPC)
if err != nil {
......@@ -205,7 +220,7 @@ var Subcommands = cli.Commands{
log.Info("Using L1 Start Block", "number", l1StartBlock.Number(), "hash", l1StartBlock.Hash().Hex())
// Build the L2 genesis block
l2Genesis, err := genesis.BuildL2Genesis(config, l1StartBlock)
l2Genesis, err := genesis.BuildL2Genesis(config, l2Allocs, l1StartBlock)
if err != nil {
return fmt.Errorf("error creating l2 genesis: %w", err)
}
......
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 356411)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2954596)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 549198)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4061174)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 356410)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2954595)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 549197)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4061173)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 450326)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3496075)
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_1() (gas: 69062)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155567)
\ No newline at end of file
......@@ -52,9 +52,10 @@ library Config {
/// @notice Returns the path that the state dump file should be written to or read from
/// 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(
"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 {
return "optimism-goerli";
} else if (chainid == Chains.OPMainnet) {
return "optimism-mainnet";
} else if (chainid == Chains.LocalDevnet || chainid == Chains.GethDevnet) {
} else if (chainid == Chains.LocalDevnet || chainid == Chains.GethDevnet || chainid == Chains.OPLocalDevnet) {
return "devnetL1";
} else if (chainid == Chains.Hardhat) {
return "hardhat";
......
......@@ -262,8 +262,7 @@ contract Deploy is Deployer {
function runWithStateDump() public {
_run();
vm.dumpState(Config.stateDumpPath(name()));
vm.dumpState(Config.stateDumpPath(""));
}
/// @notice Deploy all L1 contracts and write the state diff to a file.
......@@ -273,12 +272,16 @@ contract Deploy is Deployer {
/// @notice Internal function containing the deploy logic.
function _run() internal {
console.log("start of L1 Deploy!");
deploySafe();
console.log("deployed Safe!");
setupSuperchain();
console.log("set up superchain!");
if (cfg.usePlasma()) {
setupOpPlasma();
}
setupOpChain();
console.log("set up op chain!");
}
////////////////////////////////////////////////////////////////
......
......@@ -35,8 +35,10 @@ contract DeployConfig is Script {
address public proxyAdminOwner;
address public baseFeeVaultRecipient;
uint256 public baseFeeVaultMinimumWithdrawalAmount;
uint256 public baseFeeVaultWithdrawalNetwork;
address public l1FeeVaultRecipient;
uint256 public l1FeeVaultMinimumWithdrawalAmount;
uint256 public l1FeeVaultWithdrawalNetwork;
address public sequencerFeeVaultRecipient;
uint256 public sequencerFeeVaultMinimumWithdrawalAmount;
uint256 public sequencerFeeVaultWithdrawalNetwork;
......@@ -44,7 +46,6 @@ contract DeployConfig is Script {
string public governanceTokenSymbol;
address public governanceTokenOwner;
uint256 public l2GenesisBlockGasLimit;
uint256 public l2GenesisBlockBaseFeePerGas;
uint256 public gasPriceOracleOverhead;
uint256 public gasPriceOracleScalar;
bool public enableGovernance;
......@@ -98,12 +99,14 @@ contract DeployConfig is Script {
l2OutputOracleProposer = stdJson.readAddress(_json, "$.l2OutputOracleProposer");
l2OutputOracleChallenger = stdJson.readAddress(_json, "$.l2OutputOracleChallenger");
finalizationPeriodSeconds = stdJson.readUint(_json, "$.finalizationPeriodSeconds");
fundDevAccounts = stdJson.readBool(_json, "$.fundDevAccounts");
fundDevAccounts = _readOr(_json, "$.fundDevAccounts", false);
proxyAdminOwner = stdJson.readAddress(_json, "$.proxyAdminOwner");
baseFeeVaultRecipient = stdJson.readAddress(_json, "$.baseFeeVaultRecipient");
baseFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.baseFeeVaultMinimumWithdrawalAmount");
baseFeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.baseFeeVaultWithdrawalNetwork");
l1FeeVaultRecipient = stdJson.readAddress(_json, "$.l1FeeVaultRecipient");
l1FeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.l1FeeVaultMinimumWithdrawalAmount");
l1FeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.l1FeeVaultWithdrawalNetwork");
sequencerFeeVaultRecipient = stdJson.readAddress(_json, "$.sequencerFeeVaultRecipient");
sequencerFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.sequencerFeeVaultMinimumWithdrawalAmount");
sequencerFeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.sequencerFeeVaultWithdrawalNetwork");
......@@ -111,7 +114,6 @@ contract DeployConfig is Script {
governanceTokenSymbol = stdJson.readString(_json, "$.governanceTokenSymbol");
governanceTokenOwner = stdJson.readAddress(_json, "$.governanceTokenOwner");
l2GenesisBlockGasLimit = stdJson.readUint(_json, "$.l2GenesisBlockGasLimit");
l2GenesisBlockBaseFeePerGas = stdJson.readUint(_json, "$.l2GenesisBlockBaseFeePerGas");
gasPriceOracleOverhead = stdJson.readUint(_json, "$.gasPriceOracleOverhead");
gasPriceOracleScalar = stdJson.readUint(_json, "$.gasPriceOracleScalar");
enableGovernance = stdJson.readBool(_json, "$.enableGovernance");
......@@ -121,10 +123,10 @@ contract DeployConfig is Script {
requiredProtocolVersion = stdJson.readUint(_json, "$.requiredProtocolVersion");
recommendedProtocolVersion = stdJson.readUint(_json, "$.recommendedProtocolVersion");
useFaultProofs = stdJson.readBool(_json, "$.useFaultProofs");
proofMaturityDelaySeconds = stdJson.readUint(_json, "$.proofMaturityDelaySeconds");
disputeGameFinalityDelaySeconds = stdJson.readUint(_json, "$.disputeGameFinalityDelaySeconds");
respectedGameType = stdJson.readUint(_json, "$.respectedGameType");
useFaultProofs = _readOr(_json, "$.useFaultProofs", false);
proofMaturityDelaySeconds = _readOr(_json, "$.proofMaturityDelaySeconds", 0);
disputeGameFinalityDelaySeconds = _readOr(_json, "$.disputeGameFinalityDelaySeconds", 0);
respectedGameType = _readOr(_json, "$.respectedGameType", 0);
faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate");
faultGameMaxDepth = stdJson.readUint(_json, "$.faultGameMaxDepth");
......
......@@ -143,6 +143,14 @@ library ForgeArtifacts {
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.
function getContractFunctionAbis(
string memory path,
......@@ -196,10 +204,7 @@ library ForgeArtifacts {
/// @notice Accepts a filepath and then ensures that the directory
/// exists for the file to live in.
function ensurePath(string memory _path) internal {
(, bytes memory returndata) =
address(vm).call(abi.encodeWithSignature("split(string,string)", _path, string("/")));
string[] memory outputs = abi.decode(returndata, (string[]));
string[] memory outputs = vm.split(_path, "/");
string memory path = "";
for (uint256 i = 0; i < outputs.length - 1; i++) {
path = string.concat(path, outputs[i], "/");
......
......@@ -3,117 +3,149 @@ pragma solidity 0.8.15;
import { Script } from "forge-std/Script.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 { DeployConfig } from "scripts/DeployConfig.s.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { L2StandardBridge } from "src/L2/L2StandardBridge.sol";
import { Preinstalls } from "src/libraries/Preinstalls.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 { FeeVault } from "src/universal/FeeVault.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 { 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";
interface IInitializable {
function initialize(address _addr) external;
}
/// @dev The general flow of adding a predeploy is:
/// 1. _setPredeployProxies uses vm.etch to set the Proxy.sol deployed bytecode for proxy address `0x420...000` to
/// `0x420...000 + PROXY_COUNT - 1`.
/// Additionally, the PROXY_ADMIN_ADDRESS and PROXY_IMPLEMENTATION_ADDRESS storage slots are set for the proxy
/// address.
/// 2. `vm.etch` sets the deployed bytecode for each predeploy at the implementation address (i.e. `0xc0d3`
/// namespace).
/// 3. The `initialize` method is called at the implementation address with zero/dummy vaules if the method exists.
/// 4. The `initialize` method is called at the proxy address with actual vaules if the method exists.
/// 5. A `require` check to verify the expected implementation address is set for the proxy.
/// @notice The following safety invariants are used when setting state:
struct L1Dependencies {
address payable l1CrossDomainMessengerProxy;
address payable l1StandardBridgeProxy;
address payable l1ERC721BridgeProxy;
}
/// @notice Enum representing different ways of outputting genesis allocs.
/// @custom:value DEFAULT_LATEST Represents only latest L2 allocs, written to output path.
/// @custom:value LOCAL_LATEST Represents latest L2 allocs, not output anywhere, but kept in-process.
/// @custom:value LOCAL_DELTA Represents Delta-upgrade L2 allocs, not output anywhere, but kept in-process.
/// @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
/// 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.
/// 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
/// a
/// 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;
contract L2Genesis is Deployer {
uint256 public constant PRECOMPILE_COUNT = 256;
uint80 internal constant DEV_ACCOUNT_FUND_AMT = 10_000 ether;
/// @notice Default Anvil dev accounts. Only funded if `cfg.fundDevAccounts == true`.
address[10] internal devAccounts = [
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266,
0x70997970C51812dc3A010C7d01b50e0d17dc79C8,
0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC,
0x90F79bf6EB2c4f870365E785982E1f101E93b906,
0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65,
0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc,
0x976EA74026E726554dB657fA54763abd0C3a0aa9,
0x14dC79964da2C08b23698B3D3cc7Ca32193d9955,
0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f,
0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
/// Also known as "test test test test test test test test test test test junk" mnemonic accounts,
/// on path "m/44'/60'/0'/0/i" (where i is the account index).
address[30] internal devAccounts = [
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, // 0
0x70997970C51812dc3A010C7d01b50e0d17dc79C8, // 1
0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC, // 2
0x90F79bf6EB2c4f870365E785982E1f101E93b906, // 3
0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65, // 4
0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc, // 5
0x976EA74026E726554dB657fA54763abd0C3a0aa9, // 6
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;
/// @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");
function name() public pure override returns (string memory) {
return "L2Genesis";
}
_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`
/// to generate a L2 genesis alloc.
/// @notice The alloc object is sorted numerically by address.
function run() public {
_dealEthToPrecompiles();
_setPredeployProxies();
_setPredeployImplementations();
function runWithStateDump() public {
runWithOptions(OutputMode.DEFAULT_LATEST, artifactDependencies());
}
// @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()) {
_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
/// not considered empty accounts.
function _dealEthToPrecompiles() internal {
/// @notice Give all of the precompiles 1 wei
function dealEthToPrecompiles() internal {
console.log("Setting precompile 1 wei balances");
for (uint256 i; i < PRECOMPILE_COUNT; i++) {
vm.deal(address(uint160(i)), 1);
}
......@@ -123,66 +155,178 @@ contract L2Genesis is Script, Artifacts {
/// The Proxy bytecode should be set. All proxied predeploys should have
/// the 1967 admin slot set to the ProxyAdmin predeploy. All defined predeploys
/// 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");
uint160 prefix = uint160(0x420) << 148;
console.log(
"Setting proxy deployed bytecode for addresses in range %s through %s",
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));
if (_notProxied(addr)) {
if (Predeploys.notProxied(addr)) {
console.log("Skipping proxy at %s", addr);
continue;
}
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)) {
address implementation = _predeployToCodeNamespace(addr);
if (Predeploys.isSupportedPredeploy(addr)) {
address implementation = Predeploys.predeployToCodeNamespace(addr);
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,
/// sets the deployed bytecode at their expected predeploy address.
function _setPredeployImplementations() internal {
_setLegacyMessagePasser();
_setDeployerWhitelist();
_setWETH9();
_setL2StandardBridge();
_setL2CrossDomainMessenger();
_setSequencerFeeVault();
_setOptimismMintableERC20Factory();
_setL1BlockNumber();
_setGasPriceOracle();
_setGovernanceToken();
_setL1Block();
}
/// @notice This predeploy is following the saftey invariant #1.
function _setLegacyMessagePasser() internal {
_setImplementationCode(Predeploys.LEGACY_MESSAGE_PASSER, "LegacyMessagePasser");
}
/// @notice This predeploy is following the saftey invariant #1.
function _setDeployerWhitelist() internal {
_setImplementationCode(Predeploys.DEPLOYER_WHITELIST, "DeployerWhitelist");
}
/// @notice This predeploy is following the saftey invariant #1.
/// Contract metadata hash appended to deployed bytecode will differ
/// from previous L2 genesis output.
/// This contract is NOT proxied.
/// @dev We're manually setting storage slots because we need to deployment to be at
/// the address `Predeploys.WETH9`, so we can't just deploy a new instance of `WETH9`.
function _setWETH9() internal {
/// LEGACY_ERC20_ETH and L1_MESSAGE_SENDER are deprecated and are not set.
function setPredeployImplementations(L1Dependencies memory _l1Dependencies) internal {
console.log("Setting predeploy implementations, with L1 contract dependencies:");
console.log("- l1CrossDomainMessengerProxy: %s", _l1Dependencies.l1CrossDomainMessengerProxy);
console.log("- l1StandardBridgeProxy: %s", _l1Dependencies.l1StandardBridgeProxy);
console.log("- l1ERC721BridgeProxy: %s", _l1Dependencies.l1ERC721BridgeProxy);
setLegacyMessagePasser(); // 0
// 01: legacy, not used in OP-Stack
setDeployerWhitelist(); // 2
// 3,4,5: legacy, not used in OP-Stack.
setWETH9(); // 6: WETH9 (not behind a proxy)
setL2CrossDomainMessenger(_l1Dependencies.l1CrossDomainMessengerProxy); // 7
// 8,9,A,B,C,D,E: legacy, not used in OP-Stack.
setGasPriceOracle(); // f
setL2StandardBridge(_l1Dependencies.l1StandardBridgeProxy); // 10
setSequencerFeeVault(); // 11
setOptimismMintableERC20Factory(); // 12
setL1BlockNumber(); // 13
setL2ERC721Bridge(_l1Dependencies.l1ERC721BridgeProxy); // 14
setL1Block(); // 15
setL2ToL1MessagePasser(); // 16
setOptimismMintableERC721Factory(); // 17
setProxyAdmin(); // 18
setBaseFeeVault(); // 19
setL1FeeVault(); // 1A
// 1B,1C,1D,1E,1F: not used.
setSchemaRegistry(); // 20
setEAS(); // 21
setGovernanceToken(); // 42: OP (not behind a proxy)
}
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);
vm.etch(Predeploys.WETH9, vm.getDeployedCode("WETH9.sol:WETH9"));
......@@ -209,93 +353,52 @@ contract L2Genesis is Script, Artifacts {
);
}
/// @notice This predeploy is following the saftey invariant #1.
/// We're initializing the implementation with `address(0)` so
/// it's not left uninitialized. After `initialize` is called on the
/// 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 safety invariant #1.
function setL1BlockNumber() public {
_setImplementationCode(Predeploys.L1_BLOCK_NUMBER);
}
/// @notice This predeploy is following the saftey invariant #1.
/// We're initializing the implementation with `address(0)` so
/// it's not left uninitialized. After `initialize` is called on the
/// 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 safety invariant #1.
function setLegacyMessagePasser() public {
_setImplementationCode(Predeploys.LEGACY_MESSAGE_PASSER);
}
/// @notice This predeploy is following the saftey invariant #2,
/// because the constructor args are non-static L1 contract
/// addresses that are being read from the deploy config
/// that are set as immutables.
/// @dev Because the constructor args are stored as immutables,
/// we don't have to worry about setting storage slots.
function _setSequencerFeeVault() internal {
SequencerFeeVault vault = new SequencerFeeVault({
_recipient: cfg.sequencerFeeVaultRecipient(),
_minWithdrawalAmount: cfg.sequencerFeeVaultMinimumWithdrawalAmount(),
_withdrawalNetwork: FeeVault.WithdrawalNetwork(cfg.sequencerFeeVaultWithdrawalNetwork())
/// @notice This predeploy is following the safety invariant #2.
function setBaseFeeVault() public {
BaseFeeVault vault = new BaseFeeVault({
_recipient: cfg.baseFeeVaultRecipient(),
_minWithdrawalAmount: cfg.baseFeeVaultMinimumWithdrawalAmount(),
_withdrawalNetwork: FeeVault.WithdrawalNetwork(cfg.baseFeeVaultWithdrawalNetwork())
});
address impl = _predeployToCodeNamespace(Predeploys.SEQUENCER_FEE_WALLET);
console.log("Setting %s implementation at: %s", "SequencerFeeVault", impl);
address impl = Predeploys.predeployToCodeNamespace(Predeploys.BASE_FEE_VAULT);
console.log("Setting %s implementation at: %s", "BaseFeeVault", impl);
vm.etch(impl, address(vault).code);
/// Reset so its not included state dump
vm.etch(address(vault), "");
vm.resetNonce(address(vault));
_checkSequencerFeeVault(impl);
}
/// @notice This predeploy is following the saftey invariant #1.
/// We're initializing the implementation with `address(0)` so
/// it's not left uninitialized. After `initialize` is called on the
/// proxy to set the storage slot with the expected value.
function _setOptimismMintableERC20Factory() internal {
address impl =
_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 safety invariant #2.
function setL1FeeVault() public {
L1FeeVault vault = new L1FeeVault({
_recipient: cfg.l1FeeVaultRecipient(),
_minWithdrawalAmount: cfg.l1FeeVaultMinimumWithdrawalAmount(),
_withdrawalNetwork: FeeVault.WithdrawalNetwork(cfg.l1FeeVaultWithdrawalNetwork())
});
/// @notice This predeploy is following the saftey invariant #1.
/// This contract has no initializer.
function _setL1BlockNumber() internal {
_setImplementationCode(Predeploys.L1_BLOCK_NUMBER, "L1BlockNumber");
}
address impl = Predeploys.predeployToCodeNamespace(Predeploys.L1_FEE_VAULT);
console.log("Setting %s implementation at: %s", "L1FeeVault", impl);
vm.etch(impl, address(vault).code);
/// @notice This predeploy is following the saftey invariant #1.
/// This contract has no initializer.
function _setGasPriceOracle() internal {
_setImplementationCode(Predeploys.GAS_PRICE_ORACLE, "GasPriceOracle");
/// Reset so its not included state dump
vm.etch(address(vault), "");
vm.resetNonce(address(vault));
}
/// @notice This predeploy is following the saftey invariant #3.
function _setGovernanceToken() internal {
/// @notice This predeploy is following the safety invariant #2.
function setGovernanceToken() public {
if (!cfg.enableGovernance()) {
console.log("Governance not enabled, skipping setting governanace token");
return;
......@@ -318,67 +421,98 @@ contract L2Genesis is Script, Artifacts {
vm.resetNonce(address(token));
}
/// @notice This predeploy is following the saftey invariant #1.
/// This contract has no initializer.
/// @dev Previously the initial L1 attributes was set at genesis, to simplify,
/// they no longer are so the resulting storage slots are no longer set.
function _setL1Block() internal {
_setImplementationCode(Predeploys.L1_BLOCK_ATTRIBUTES, "L1Block");
/// @notice This predeploy is following the safety invariant #1.
function setSchemaRegistry() public {
_setImplementationCode(Predeploys.SCHEMA_REGISTRY);
}
/// @dev Returns true if the address is not proxied.
function _notProxied(address _addr) internal pure returns (bool) {
return _addr == Predeploys.GOVERNANCE_TOKEN || _addr == Predeploys.WETH9;
}
/// @notice This predeploy is following the safety invariant #2,
/// It uses low level create to deploy the contract due to the code
/// 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.
function _isDefinedPredeploy(address _addr) internal pure returns (bool) {
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;
}
console.log("Setting %s implementation at: %s", cname, impl);
vm.etch(impl, eas.code);
/// @dev Function to compute the expected address of the predeploy implementation
/// in the genesis state.
function _predeployToCodeNamespace(address _addr) internal pure returns (address) {
return address(
uint160(uint256(uint160(_addr)) & 0xffff | uint256(uint160(0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000)))
);
/// Reset so its not included state dump
vm.etch(address(eas), "");
vm.resetNonce(address(eas));
}
function _setImplementationCode(address _addr, string memory _name) internal returns (address) {
address impl = _predeployToCodeNamespace(_addr);
console.log("Setting %s implementation at: %s", _name, impl);
vm.etch(impl, vm.getDeployedCode(string.concat(_name, ".sol:", _name)));
/// @notice Sets all the preinstalls.
/// Warning: the creator-accounts of the preinstall contracts have 0 nonce values.
/// When performing a regular user-initiated contract-creation of a preinstall,
/// 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;
}
/// @dev Function to verify the expected implementation address is set for the respective proxy.
function _verifyProxyImplementationAddress(address _proxy, address _impl) internal view {
require(
EIP1967Helper.getImplementation(_proxy) == _impl,
"Expected different address at Proxys PROXY_IMPLEMENTATION_ADDRESS storage slot"
);
/// @notice Sets the bytecode in state
function _setPreinstallCode(address _addr) internal {
string memory cname = Preinstalls.getName(_addr);
console.log("Setting %s preinstall code at: %s", cname, _addr);
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 There isn't a good way to know if the resulting revering is due to abi mismatch
/// or because it's already been initialized
function _verifyCantReinitialize(address _contract, address _arg) internal {
vm.expectRevert("Initializable: contract is already initialized");
IInitializable(_contract).initialize(_arg);
/// @notice Writes the genesis allocs, i.e. the state dump, to disk
function writeGenesisAllocs(string memory _path) public {
/// Reset so its not included state dump
vm.etch(address(cfg), "");
vm.etch(msg.sender, "");
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.
function _sortJsonByKeys(string memory _path) internal {
/// @notice Sorts the allocs by address
function sortJsonByKeys(string memory _path) internal {
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
......@@ -386,44 +520,11 @@ contract L2Genesis is Script, Artifacts {
vm.ffi(commands);
}
function _fundDevAccounts() internal {
/// @notice Funds the default dev accounts with ether
function fundDevAccounts() internal {
for (uint256 i; i < devAccounts.length; i++) {
console.log("Funding dev account %s with %s ETH", devAccounts[i], DEV_ACCOUNT_FUND_AMT / 1e18);
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 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003d"
}
],
"value": 0
......@@ -2014,7 +2014,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003d"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003e"
}
],
"value": 0
......@@ -2475,7 +2475,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003d"
}
],
"value": 0
......@@ -2553,7 +2553,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003d"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003e"
}
],
"value": 0
......@@ -6095,7 +6095,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000834",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000834",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002c"
"slot": "0x000000000000000000000000000000000000000000000000000000000000002d"
}
],
"value": 0
......@@ -6121,7 +6121,7 @@
"newValue": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"previousValue": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002d"
"slot": "0x000000000000000000000000000000000000000000000000000000000000002e"
}
],
"value": 0
......@@ -6147,7 +6147,7 @@
"newValue": "0x00000000000000000000000000000000000000000000000000000000017d7840",
"previousValue": "0x00000000000000000000000000000000000000000000000000000000017d7840",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002a"
"slot": "0x000000000000000000000000000000000000000000000000000000000000002c"
}
],
"value": 0
......@@ -6856,7 +6856,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000834",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000834",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002c"
"slot": "0x000000000000000000000000000000000000000000000000000000000000002d"
}
],
"value": 0
......@@ -6934,7 +6934,7 @@
"newValue": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"previousValue": "0x00000000000000000000000000000000000000000000000000000000000f4240",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002d"
"slot": "0x000000000000000000000000000000000000000000000000000000000000002e"
}
],
"value": 0
......@@ -7090,7 +7090,7 @@
"newValue": "0x00000000000000000000000000000000000000000000000000000000017d7840",
"previousValue": "0x00000000000000000000000000000000000000000000000000000000017d7840",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000002a"
"slot": "0x000000000000000000000000000000000000000000000000000000000000002c"
}
],
"value": 0
......@@ -7246,7 +7246,7 @@
"newValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"previousValue": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reverted": false,
"slot": "0x000000000000000000000000000000000000000000000000000000000000003b"
"slot": "0x000000000000000000000000000000000000000000000000000000000000003c"
}
],
"value": 0
......@@ -2,39 +2,21 @@
pragma solidity ^0.8.0;
/// @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 {
/// @notice Address of the L2ToL1MessagePasser predeploy.
address internal constant L2_TO_L1_MESSAGE_PASSER = 0x4200000000000000000000000000000000000016;
/// @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 Number of predeploy-namespace addresses reserved for protocol usage.
uint256 internal constant PREDEPLOY_COUNT = 2048;
/// @notice Address of the GasPriceOracle predeploy. Includes fee information
/// and helpers for computing the L1 portion of the transaction fee.
address internal constant GAS_PRICE_ORACLE = 0x420000000000000000000000000000000000000F;
/// @custom:legacy
/// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated
/// L2ToL1MessagePasser contract instead.
address internal constant LEGACY_MESSAGE_PASSER = 0x4200000000000000000000000000000000000000;
/// @custom:legacy
/// @notice Address of the L1MessageSender predeploy. Deprecated. Use L2CrossDomainMessenger
/// 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;
/// @custom:legacy
......@@ -44,21 +26,38 @@ library Predeploys {
/// @notice Address of the canonical WETH9 contract.
address internal constant WETH9 = 0x4200000000000000000000000000000000000006;
/// @custom:legacy
/// @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 L2CrossDomainMessenger predeploy.
address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007;
/// @notice Address of the GasPriceOracle predeploy. Includes fee information
/// 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
/// @notice Address of the L1BlockNumber predeploy. Deprecated. Use the L1Block predeploy
/// instead, which exposes more information about the L1 state.
address internal constant L1_BLOCK_NUMBER = 0x4200000000000000000000000000000000000013;
/// @custom:legacy
/// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated
/// L2ToL1MessagePasser contract instead.
address internal constant LEGACY_MESSAGE_PASSER = 0x4200000000000000000000000000000000000000;
/// @notice Address of the L2ERC721Bridge predeploy.
address internal constant L2_ERC721_BRIDGE = 0x4200000000000000000000000000000000000014;
/// @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.
address internal constant PROXY_ADMIN = 0x4200000000000000000000000000000000000018;
......@@ -69,45 +68,75 @@ library Predeploys {
/// @notice Address of the L1FeeVault predeploy.
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.
address internal constant SCHEMA_REGISTRY = 0x4200000000000000000000000000000000000020;
/// @notice Address of the EAS predeploy.
address internal constant EAS = 0x4200000000000000000000000000000000000021;
/// @notice Address of the MultiCall3 predeploy.
address internal constant MultiCall3 = 0xcA11bde05977b3631167028862bE2a173976CA11;
/// @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 GovernanceToken predeploy.
address internal constant GOVERNANCE_TOKEN = 0x4200000000000000000000000000000000000042;
/// @notice Address of the SenderCreator predeploy.
address internal constant SenderCreator = 0x7fc98430eAEdbb6070B35B39D798725049088348;
/// @custom:legacy
/// @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.
address internal constant EntryPoint = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789;
/// @notice Returns the name of the predeploy at the given address.
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;
// Testing utilities
import { CommonTest } from "test/setup/CommonTest.sol";
import { OutputMode } from "scripts/L2Genesis.s.sol";
// Libraries
import { Encoding } from "src/libraries/Encoding.sol";
......@@ -37,7 +38,10 @@ contract GasPriceOracle_Test is CommonTest {
contract GasPriceOracleBedrock_Test is GasPriceOracle_Test {
/// @dev Sets up the test suite.
function setUp() public virtual override {
// The gasPriceOracle tests rely on an L2 genesis that is not past Ecotone.
l2OutputMode = OutputMode.LOCAL_DELTA;
super.setUp();
assertEq(gasPriceOracle.isEcotone(), false);
vm.prank(depositor);
l1Block.setL1BlockValues({
......@@ -109,7 +113,9 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test {
contract GasPriceOracleEcotone_Test is GasPriceOracle_Test {
/// @dev Sets up the test suite.
function setUp() public virtual override {
l2OutputMode = OutputMode.LOCAL_LATEST; // activate ecotone
super.setUp();
assertEq(gasPriceOracle.isEcotone(), true);
bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesEcotone(
baseFeeScalar, blobBaseFeeScalar, sequenceNumber, timestamp, number, baseFee, blobBaseFee, hash, batcherHash
......@@ -119,9 +125,6 @@ contract GasPriceOracleEcotone_Test is GasPriceOracle_Test {
vm.prank(depositor);
(bool success,) = address(l1Block).call(calldataPacked);
require(success, "Function call failed");
vm.prank(depositor);
gasPriceOracle.setEcotone();
}
/// @dev Tests that `setEcotone` is only callable by the depositor.
......
......@@ -28,10 +28,12 @@ contract L2StandardBridge_Test is Bridge_Initializer {
function test_constructor_succeeds() external view {
L2StandardBridge impl =
L2StandardBridge(payable(EIP1967Helper.getImplementation(deploy.mustGetAddress("L2StandardBridge"))));
assertEq(address(impl.MESSENGER()), address(0));
assertEq(address(impl.messenger()), address(0));
assertEq(address(impl.OTHER_BRIDGE()), address(0));
assertEq(address(impl.otherBridge()), address(0));
// The implementation contract is initialized with a 0 L1 bridge address,
// but the L2 cross-domain-messenger is always set to the predeploy address for both proxy and implementation.
assertEq(address(impl.MESSENGER()), Predeploys.L2_CROSS_DOMAIN_MESSENGER, "constructor zero check MESSENGER");
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.
......
// 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";
library EIP1967Helper {
/// @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 =
bytes32 internal constant PROXY_IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/// @notice The storage slot that holds the address of the owner.
/// @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);
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) {
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
pragma solidity 0.8.15;
import { console2 as console } from "forge-std/console2.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { L2CrossDomainMessenger } from "src/L2/L2CrossDomainMessenger.sol";
import { L2StandardBridge } from "src/L2/L2StandardBridge.sol";
import { L2ToL1MessagePasser } from "src/L2/L2ToL1MessagePasser.sol";
......@@ -24,6 +26,7 @@ import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { DeployConfig } from "scripts/DeployConfig.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 { ProtocolVersions } from "src/L1/ProtocolVersions.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
......@@ -51,6 +54,12 @@ contract Setup {
/// mutating any nonces. MUST not have constructor logic.
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;
OptimismPortal2 optimismPortal2;
DisputeGameFactory disputeGameFactory;
......@@ -89,13 +98,22 @@ contract Setup {
/// will also need to include the bytecode for the Deploy contract.
/// This is a hack as we are pushing solidity to the edge.
function setUp() public virtual {
console.log("L1 setup start!");
vm.etch(address(deploy), vm.getDeployedCode("Deploy.s.sol:Deploy"));
vm.allowCheatcodes(address(deploy));
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.
function L1() public {
console.log("Setup: creating L1 deployments");
// Set the deterministic deployer in state to ensure that it is there
vm.etch(
0x4e59b44847b379578588920cA78FbF26c0B4956C,
......@@ -103,6 +121,7 @@ contract Setup {
);
deploy.run();
console.log("Setup: completed L1 deployment, registering addresses now");
optimismPortal = OptimismPortal(deploy.mustGetAddress("OptimismPortalProxy"));
optimismPortal2 = OptimismPortal2(deploy.mustGetAddress("OptimismPortalProxy"));
......@@ -151,61 +170,63 @@ contract Setup {
vm.label(address(dataAvailabilityChallenge), "DataAvailabilityChallengeProxy");
vm.label(deploy.mustGetAddress("DataAvailabilityChallenge"), "DataAvailabilityChallenge");
}
console.log("Setup: registered L1 deployments");
}
/// @dev Sets up the L2 contracts. Depends on `L1()` being called first.
function L2() public {
string memory allocsPath = string.concat(vm.projectRoot(), "/.testdata/genesis.json");
if (vm.isFile(allocsPath) == false) {
string[] memory args = new string[](3);
args[0] = Executables.bash;
args[1] = "-c";
args[2] = string.concat(vm.projectRoot(), "/scripts/generate-l2-genesis.sh");
Vm.FfiResult memory result = vm.tryFfi(args);
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);
console.log("Setup: creating L2 genesis, with output mode %d", uint256(l2OutputMode));
l2Genesis.runWithOptions(
l2OutputMode,
L1Dependencies({
l1CrossDomainMessengerProxy: payable(address(l1CrossDomainMessenger)),
l1StandardBridgeProxy: payable(address(l1StandardBridge)),
l1ERC721BridgeProxy: payable(address(l1ERC721Bridge))
})
);
// Set the governance token's owner to be the final system owner
address finalSystemOwner = deploy.cfg().finalSystemOwner();
vm.prank(governanceToken.owner());
governanceToken.transferOwnership(finalSystemOwner);
vm.label(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY, "OptimismMintableERC20Factory");
vm.label(Predeploys.L2_STANDARD_BRIDGE, "L2StandardBridge");
vm.label(Predeploys.L2_CROSS_DOMAIN_MESSENGER, "L2CrossDomainMessenger");
vm.label(Predeploys.L2_TO_L1_MESSAGE_PASSER, "L2ToL1MessagePasser");
vm.label(Predeploys.SEQUENCER_FEE_WALLET, "SequencerFeeVault");
vm.label(Predeploys.L2_ERC721_BRIDGE, "L2ERC721Bridge");
vm.label(Predeploys.BASE_FEE_VAULT, "BaseFeeVault");
vm.label(Predeploys.L1_FEE_VAULT, "L1FeeVault");
vm.label(Predeploys.L1_BLOCK_ATTRIBUTES, "L1Block");
vm.label(Predeploys.GAS_PRICE_ORACLE, "GasPriceOracle");
vm.label(Predeploys.LEGACY_MESSAGE_PASSER, "LegacyMessagePasser");
vm.label(Predeploys.GOVERNANCE_TOKEN, "GovernanceToken");
vm.label(Predeploys.EAS, "EAS");
vm.label(Predeploys.SCHEMA_REGISTRY, "SchemaRegistry");
// L2 predeploys
labelPredeploy(Predeploys.L2_STANDARD_BRIDGE);
labelPredeploy(Predeploys.L2_CROSS_DOMAIN_MESSENGER);
labelPredeploy(Predeploys.L2_TO_L1_MESSAGE_PASSER);
labelPredeploy(Predeploys.SEQUENCER_FEE_WALLET);
labelPredeploy(Predeploys.L2_ERC721_BRIDGE);
labelPredeploy(Predeploys.BASE_FEE_VAULT);
labelPredeploy(Predeploys.L1_FEE_VAULT);
labelPredeploy(Predeploys.L1_BLOCK_ATTRIBUTES);
labelPredeploy(Predeploys.GAS_PRICE_ORACLE);
labelPredeploy(Predeploys.LEGACY_MESSAGE_PASSER);
labelPredeploy(Predeploys.GOVERNANCE_TOKEN);
labelPredeploy(Predeploys.EAS);
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