Commit 1e475ace authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into cleanup/migration-rehearsal-script

parents 02f74b99 e33d0005
...@@ -74,11 +74,11 @@ nuke: clean devnet-clean ...@@ -74,11 +74,11 @@ nuke: clean devnet-clean
.PHONY: nuke .PHONY: nuke
devnet-up: devnet-up:
@bash ./ops-bedrock/devnet-up.sh PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=.
.PHONY: devnet-up .PHONY: devnet-up
devnet-up-deploy: devnet-up-deploy:
PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=. PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=. --deploy
.PHONY: devnet-up-deploy .PHONY: devnet-up-deploy
devnet-down: devnet-down:
......
...@@ -4,74 +4,142 @@ import os ...@@ -4,74 +4,142 @@ import os
import subprocess import subprocess
import json import json
import socket import socket
import calendar
import datetime
import time import time
import shutil import shutil
import devnet.log_setup import devnet.log_setup
from devnet.genesis import GENESIS_TMPL from devnet.genesis import GENESIS_TMPL
pjoin = os.path.join
parser = argparse.ArgumentParser(description='Bedrock devnet launcher') parser = argparse.ArgumentParser(description='Bedrock devnet launcher')
parser.add_argument('--monorepo-dir', help='Directory of the monorepo', default=os.getcwd()) parser.add_argument('--monorepo-dir', help='Directory of the monorepo', default=os.getcwd())
parser.add_argument('--deploy', help='Whether the contracts should be predeployed or deployed', type=bool, action=argparse.BooleanOptionalAction)
log = logging.getLogger() log = logging.getLogger()
class Bunch:
def __init__(self, **kwds):
self.__dict__.update(kwds)
def main(): def main():
args = parser.parse_args() args = parser.parse_args()
pjoin = os.path.join
monorepo_dir = os.path.abspath(args.monorepo_dir) monorepo_dir = os.path.abspath(args.monorepo_dir)
devnet_dir = pjoin(monorepo_dir, '.devnet') devnet_dir = pjoin(monorepo_dir, '.devnet')
ops_bedrock_dir = pjoin(monorepo_dir, 'ops-bedrock')
contracts_bedrock_dir = pjoin(monorepo_dir, 'packages', 'contracts-bedrock') contracts_bedrock_dir = pjoin(monorepo_dir, 'packages', 'contracts-bedrock')
deployment_dir = pjoin(contracts_bedrock_dir, 'deployments', 'devnetL1') deployment_dir = pjoin(contracts_bedrock_dir, 'deployments', 'devnetL1')
op_node_dir = pjoin(args.monorepo_dir, 'op-node') op_node_dir = pjoin(args.monorepo_dir, 'op-node')
genesis_l1_path = pjoin(devnet_dir, 'genesis-l1.json') ops_bedrock_dir=pjoin(monorepo_dir, 'ops-bedrock')
genesis_l2_path = pjoin(devnet_dir, 'genesis-l2.json')
addresses_json_path = pjoin(devnet_dir, 'addresses.json') paths = Bunch(
sdk_addresses_json_path = pjoin(devnet_dir, 'sdk-addresses.json') mono_repo_dir=monorepo_dir,
rollup_config_path = pjoin(devnet_dir, 'rollup.json') devnet_dir=devnet_dir,
contracts_bedrock_dir=contracts_bedrock_dir,
deployment_dir=deployment_dir,
deploy_config_dir=pjoin(contracts_bedrock_dir, 'deploy-config'),
op_node_dir=op_node_dir,
ops_bedrock_dir=ops_bedrock_dir,
genesis_l1_path=pjoin(devnet_dir, 'genesis-l1.json'),
genesis_l2_path=pjoin(devnet_dir, 'genesis-l2.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')
)
os.makedirs(devnet_dir, exist_ok=True) os.makedirs(devnet_dir, exist_ok=True)
if os.path.exists(genesis_l1_path): if args.deploy:
log.info('L2 genesis already generated.') log.info('Devnet with upcoming smart contract deployments')
devnet_deploy(paths)
else:
log.info('Devnet with smart contracts pre-deployed')
devnet_prestate(paths)
# Bring up the devnet where the L1 contracts are in the genesis state
def devnet_prestate(paths):
date = datetime.datetime.utcnow()
utc_time = hex(calendar.timegm(date.utctimetuple()))
done_file = pjoin(paths.devnet_dir, 'done')
if os.path.exists(done_file):
log.info('Genesis files already exist')
else:
log.info('Creating genesis files')
deploy_config_path = pjoin(paths.deploy_config_dir, 'devnetL1.json')
# read the json file
deploy_config = read_json(deploy_config_path)
deploy_config['l1GenesisBlockTimestamp'] = utc_time
temp_deploy_config = pjoin(paths.devnet_dir, 'deploy-config.json')
write_json(temp_deploy_config, deploy_config)
outfile_l1 = paths.genesis_l1_path
outfile_l2 = paths.genesis_l2_path
outfile_rollup = paths.rollup_config_path
run_command(['go', 'run', 'cmd/main.go', 'genesis', 'devnet', '--deploy-config', temp_deploy_config, '--outfile.l1', outfile_l1, '--outfile.l2', outfile_l2, '--outfile.rollup', outfile_rollup], cwd=paths.op_node_dir)
write_json(done_file, {})
log.info('Bringing up L1.')
run_command(['docker-compose', 'up', '-d', 'l1'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir
})
wait_up(8545)
log.info('Bringing up L2.')
run_command(['docker-compose', 'up', '-d', 'l2'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir
})
wait_up(9545)
log.info('Bringing up the services.')
run_command(['docker-compose', 'up', '-d', 'op-proposer', 'op-batcher'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir,
'L2OO_ADDRESS': '0x6900000000000000000000000000000000000000'
})
# 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: else:
log.info('Generating L1 genesis.') log.info('Generating L1 genesis.')
write_json(genesis_l1_path, GENESIS_TMPL) write_json(paths.genesis_l1_path, GENESIS_TMPL)
log.info('Starting L1.') log.info('Starting L1.')
run_command(['docker-compose', 'up', '-d', 'l1'], cwd=ops_bedrock_dir, env={ run_command(['docker-compose', 'up', '-d', 'l1'], cwd=paths.ops_bedrock_dir, env={
'PWD': ops_bedrock_dir 'PWD': paths.ops_bedrock_dir
}) })
wait_up(8545) wait_up(8545)
log.info('Generating network config.') log.info('Generating network config.')
devnet_cfg_orig = pjoin(contracts_bedrock_dir, 'deploy-config', 'devnetL1.json') devnet_cfg_orig = pjoin(paths.contracts_bedrock_dir, 'deploy-config', 'devnetL1.json')
devnet_cfg_backup = pjoin(devnet_dir, 'devnetL1.json.bak') devnet_cfg_backup = pjoin(paths.devnet_dir, 'devnetL1.json.bak')
shutil.copy(devnet_cfg_orig, devnet_cfg_backup) shutil.copy(devnet_cfg_orig, devnet_cfg_backup)
deploy_config = read_json(devnet_cfg_orig) deploy_config = read_json(devnet_cfg_orig)
deploy_config['l1GenesisBlockTimestamp'] = GENESIS_TMPL['timestamp'] deploy_config['l1GenesisBlockTimestamp'] = GENESIS_TMPL['timestamp']
deploy_config['l1StartingBlockTag'] = 'earliest' deploy_config['l1StartingBlockTag'] = 'earliest'
write_json(devnet_cfg_orig, deploy_config) write_json(devnet_cfg_orig, deploy_config)
if os.path.exists(addresses_json_path): if os.path.exists(paths.addresses_json_path):
log.info('Contracts already deployed.') log.info('Contracts already deployed.')
addresses = read_json(addresses_json_path) addresses = read_json(paths.addresses_json_path)
else: else:
log.info('Deploying contracts.') log.info('Deploying contracts.')
run_command(['yarn', 'hardhat', '--network', 'devnetL1', 'deploy', '--tags', 'l1'], env={ run_command(['yarn', 'hardhat', '--network', 'devnetL1', 'deploy', '--tags', 'l1'], env={
'CHAIN_ID': '900', 'CHAIN_ID': '900',
'L1_RPC': 'http://localhost:8545', 'L1_RPC': 'http://localhost:8545',
'PRIVATE_KEY_DEPLOYER': 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' 'PRIVATE_KEY_DEPLOYER': 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
}, cwd=contracts_bedrock_dir) }, cwd=paths.contracts_bedrock_dir)
contracts = os.listdir(deployment_dir) contracts = os.listdir(paths.deployment_dir)
addresses = {} addresses = {}
for c in contracts: for c in contracts:
if not c.endswith('.json'): if not c.endswith('.json'):
continue continue
data = read_json(pjoin(deployment_dir, c)) data = read_json(pjoin(paths.deployment_dir, c))
addresses[c.replace('.json', '')] = data['address'] addresses[c.replace('.json', '')] = data['address']
sdk_addresses = {} sdk_addresses = {}
sdk_addresses.update({ sdk_addresses.update({
...@@ -84,10 +152,10 @@ def main(): ...@@ -84,10 +152,10 @@ def main():
sdk_addresses['L1StandardBridge'] = addresses['Proxy__OVM_L1StandardBridge'] sdk_addresses['L1StandardBridge'] = addresses['Proxy__OVM_L1StandardBridge']
sdk_addresses['OptimismPortal'] = addresses['OptimismPortalProxy'] sdk_addresses['OptimismPortal'] = addresses['OptimismPortalProxy']
sdk_addresses['L2OutputOracle'] = addresses['L2OutputOracleProxy'] sdk_addresses['L2OutputOracle'] = addresses['L2OutputOracleProxy']
write_json(addresses_json_path, addresses) write_json(paths.addresses_json_path, addresses)
write_json(sdk_addresses_json_path, sdk_addresses) write_json(paths.sdk_addresses_json_path, sdk_addresses)
if os.path.exists(genesis_l2_path): if os.path.exists(paths.genesis_l2_path):
log.info('L2 genesis and rollup configs already generated.') log.info('L2 genesis and rollup configs already generated.')
else: else:
log.info('Generating L2 genesis and rollup configs.') log.info('Generating L2 genesis and rollup configs.')
...@@ -95,25 +163,25 @@ def main(): ...@@ -95,25 +163,25 @@ def main():
'go', 'run', 'cmd/main.go', 'genesis', 'l2', 'go', 'run', 'cmd/main.go', 'genesis', 'l2',
'--l1-rpc', 'http://localhost:8545', '--l1-rpc', 'http://localhost:8545',
'--deploy-config', devnet_cfg_orig, '--deploy-config', devnet_cfg_orig,
'--deployment-dir', deployment_dir, '--deployment-dir', paths.deployment_dir,
'--outfile.l2', pjoin(devnet_dir, 'genesis-l2.json'), '--outfile.l2', pjoin(paths.devnet_dir, 'genesis-l2.json'),
'--outfile.rollup', pjoin(devnet_dir, 'rollup.json') '--outfile.rollup', pjoin(paths.devnet_dir, 'rollup.json')
], cwd=op_node_dir) ], cwd=paths.op_node_dir)
rollup_config = read_json(rollup_config_path) rollup_config = read_json(paths.rollup_config_path)
if os.path.exists(devnet_cfg_backup): if os.path.exists(devnet_cfg_backup):
shutil.move(devnet_cfg_backup, devnet_cfg_orig) shutil.move(devnet_cfg_backup, devnet_cfg_orig)
log.info('Bringing up L2.') log.info('Bringing up L2.')
run_command(['docker-compose', 'up', '-d', 'l2'], cwd=ops_bedrock_dir, env={ run_command(['docker-compose', 'up', '-d', 'l2'], cwd=paths.ops_bedrock_dir, env={
'PWD': ops_bedrock_dir 'PWD': paths.ops_bedrock_dir
}) })
wait_up(9545) wait_up(9545)
log.info('Bringing up everything else.') log.info('Bringing up everything else.')
run_command(['docker-compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher'], cwd=ops_bedrock_dir, env={ run_command(['docker-compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher'], cwd=paths.ops_bedrock_dir, env={
'PWD': ops_bedrock_dir, 'PWD': paths.ops_bedrock_dir,
'L2OO_ADDRESS': addresses['L2OutputOracleProxy'], 'L2OO_ADDRESS': addresses['L2OutputOracleProxy'],
'SEQUENCER_BATCH_INBOX_ADDRESS': rollup_config['batch_inbox_address'] 'SEQUENCER_BATCH_INBOX_ADDRESS': rollup_config['batch_inbox_address']
}) })
......
...@@ -46,6 +46,26 @@ var ( ...@@ -46,6 +46,26 @@ var (
Predeploys = make(map[string]*common.Address) Predeploys = make(map[string]*common.Address)
) )
// IsProxied returns true for predeploys that will sit behind a proxy contract
func IsProxied(predeployAddr common.Address) bool {
switch predeployAddr {
case LegacyERC20ETHAddr:
case WETH9Addr:
case GovernanceTokenAddr:
case ProxyAdminAddr:
default:
return true
}
return false
}
// IsDeprecated returns true for predeploys we should skip in post-bedrock genesis generation
func IsDeprecated(predeployAddr common.Address) bool {
// TODO: confirm if we can safely add the remaining deprecated predeploys here
// (see https://github.com/ethereum-optimism/optimism/blob/develop/specs/predeploys.md#overview)
return predeployAddr == LegacyERC20ETHAddr
}
func init() { func init() {
Predeploys["L2ToL1MessagePasser"] = &L2ToL1MessagePasserAddr Predeploys["L2ToL1MessagePasser"] = &L2ToL1MessagePasserAddr
Predeploys["DeployerWhitelist"] = &DeployerWhitelistAddr Predeploys["DeployerWhitelist"] = &DeployerWhitelistAddr
......
...@@ -113,6 +113,8 @@ type DeployConfig struct { ...@@ -113,6 +113,8 @@ type DeployConfig struct {
GasPriceOracleOverhead uint64 `json:"gasPriceOracleOverhead"` GasPriceOracleOverhead uint64 `json:"gasPriceOracleOverhead"`
// The initial value of the gas scalar // The initial value of the gas scalar
GasPriceOracleScalar uint64 `json:"gasPriceOracleScalar"` GasPriceOracleScalar uint64 `json:"gasPriceOracleScalar"`
// Whether or not include governance token predeploy
EnableGovernance bool `json:"enableGovernance"`
// The ERC20 symbol of the GovernanceToken // The ERC20 symbol of the GovernanceToken
GovernanceTokenSymbol string `json:"governanceTokenSymbol"` GovernanceTokenSymbol string `json:"governanceTokenSymbol"`
// The ERC20 name of the GovernanceToken // The ERC20 name of the GovernanceToken
...@@ -240,14 +242,16 @@ func (d *DeployConfig) Check() error { ...@@ -240,14 +242,16 @@ func (d *DeployConfig) Check() error {
if d.L2GenesisBlockBaseFeePerGas == nil { if d.L2GenesisBlockBaseFeePerGas == nil {
return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig) return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig)
} }
if d.GovernanceTokenName == "" { if d.EnableGovernance {
return fmt.Errorf("%w: GovernanceToken.name cannot be empty", ErrInvalidDeployConfig) if d.GovernanceTokenName == "" {
} return fmt.Errorf("%w: GovernanceToken.name cannot be empty", ErrInvalidDeployConfig)
if d.GovernanceTokenSymbol == "" { }
return fmt.Errorf("%w: GovernanceToken.symbol cannot be empty", ErrInvalidDeployConfig) if d.GovernanceTokenSymbol == "" {
} return fmt.Errorf("%w: GovernanceToken.symbol cannot be empty", ErrInvalidDeployConfig)
if d.GovernanceTokenOwner == (common.Address{}) { }
return fmt.Errorf("%w: GovernanceToken owner cannot be address(0)", ErrInvalidDeployConfig) if d.GovernanceTokenOwner == (common.Address{}) {
return fmt.Errorf("%w: GovernanceToken owner cannot be address(0)", ErrInvalidDeployConfig)
}
} }
return nil return nil
} }
...@@ -492,10 +496,12 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage ...@@ -492,10 +496,12 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage
"symbol": "WETH", "symbol": "WETH",
"decimals": 18, "decimals": 18,
} }
storage["GovernanceToken"] = state.StorageValues{ if config.EnableGovernance {
"_name": config.GovernanceTokenName, storage["GovernanceToken"] = state.StorageValues{
"_symbol": config.GovernanceTokenSymbol, "_name": config.GovernanceTokenName,
"_owner": config.GovernanceTokenOwner, "_symbol": config.GovernanceTokenSymbol,
"_owner": config.GovernanceTokenOwner,
}
} }
storage["ProxyAdmin"] = state.StorageValues{ storage["ProxyAdmin"] = state.StorageValues{
"_owner": config.ProxyAdminOwner, "_owner": config.ProxyAdminOwner,
......
package genesis package genesis
import ( import (
"github.com/ethereum-optimism/optimism/op-chain-ops/state" "errors"
"github.com/ethereum/go-ethereum/core/types" "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
) )
// BuildL2DeveloperGenesis will build the developer Optimism Genesis // BuildL2DeveloperGenesis will build the developer Optimism Genesis
...@@ -46,3 +55,90 @@ func BuildL2DeveloperGenesis(config *DeployConfig, l1StartBlock *types.Block) (* ...@@ -46,3 +55,90 @@ func BuildL2DeveloperGenesis(config *DeployConfig, l1StartBlock *types.Block) (*
return db.Genesis(), nil return db.Genesis(), nil
} }
// BuildL2MainnetGenesis will build an L2 Genesis suitable for a Superchain mainnet that does not
// require a pre-bedrock migration & supports optional governance token predeploy. Details:
//
// - Creates proxies for predeploys in the address space:
// [0x4200000000000000000000000000000000000000, 0x4200000000000000000000000000000000000800)
//
// - All predeploy proxies owned by the ProxyAdmin
//
// - Predeploys as per the spec except for no LegacyERC20ETH predeploy at
// 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000
//
// - optional governance token at 0x4200000000000000000000000000000000000042 if
// config.EnableGovernance is true (& otherwise a no-impl proxy remains at this address)
//
// - no accounts are pre-funded
func BuildL2MainnetGenesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) {
genspec, err := NewL2Genesis(config, l1StartBlock)
if err != nil {
return nil, err
}
db := state.NewMemoryStateDB(genspec)
storage, err := NewL2StorageConfig(config, l1StartBlock)
if err != nil {
return nil, err
}
immutable, err := NewL2ImmutableConfig(config, l1StartBlock)
if err != nil {
return nil, err
}
// Set up the proxies
depBytecode, err := bindings.GetDeployedBytecode("Proxy")
if err != nil {
return nil, err
}
if len(depBytecode) == 0 {
return nil, errors.New("Proxy has empty bytecode")
}
for i := uint64(0); i <= 2048; i++ {
bigAddr := new(big.Int).Or(bigL2PredeployNamespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
db.CreateAccount(addr)
db.SetCode(addr, depBytecode)
db.SetState(addr, AdminSlot, predeploys.ProxyAdminAddr.Hash())
}
// Set up the implementations
deployResults, err := immutables.BuildOptimism(immutable)
if err != nil {
return nil, err
}
for name, predeploy := range predeploys.Predeploys {
addr := *predeploy
if predeploys.IsDeprecated(addr) {
continue
}
if addr == predeploys.GovernanceTokenAddr && !config.EnableGovernance {
// there is no governance token configured, so skip the governance token predeploy
log.Warn("Governance is not enabled, skipping governance token predeploy.")
continue
}
codeAddr := addr
if predeploys.IsProxied(addr) {
codeAddr, err = AddressToCodeNamespace(addr)
if err != nil {
return nil, fmt.Errorf("error converting to code namespace: %w", err)
}
db.CreateAccount(codeAddr)
db.SetState(addr, ImplementationSlot, codeAddr.Hash())
} else {
db.DeleteState(addr, AdminSlot)
}
if err := setupPredeploy(db, deployResults, storage, name, addr, codeAddr); err != nil {
return nil, err
}
code := db.GetCode(codeAddr)
if len(code) == 0 {
return nil, fmt.Errorf("code not set for %s", name)
}
}
return db.Genesis(), nil
}
...@@ -8,17 +8,16 @@ import ( ...@@ -8,17 +8,16 @@ import (
"os" "os"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "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"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/stretchr/testify/require"
) )
var writeFile bool var writeFile bool
...@@ -46,14 +45,14 @@ func TestBuildL2DeveloperGenesis(t *testing.T) { ...@@ -46,14 +45,14 @@ func TestBuildL2DeveloperGenesis(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, gen) require.NotNil(t, gen)
depB, err := bindings.GetDeployedBytecode("Proxy") proxyBytecode, err := bindings.GetDeployedBytecode("Proxy")
require.NoError(t, err) require.NoError(t, err)
for name, address := range predeploys.Predeploys { for name, address := range predeploys.Predeploys {
addr := *address addr := *address
account, ok := gen.Alloc[addr] account, ok := gen.Alloc[addr]
require.Equal(t, ok, true) require.Equal(t, true, ok)
require.Greater(t, len(account.Code), 0) require.Greater(t, len(account.Code), 0)
if name == "GovernanceToken" || name == "LegacyERC20ETH" || name == "ProxyAdmin" || name == "WETH9" { if name == "GovernanceToken" || name == "LegacyERC20ETH" || name == "ProxyAdmin" || name == "WETH9" {
...@@ -61,9 +60,9 @@ func TestBuildL2DeveloperGenesis(t *testing.T) { ...@@ -61,9 +60,9 @@ func TestBuildL2DeveloperGenesis(t *testing.T) {
} }
adminSlot, ok := account.Storage[genesis.AdminSlot] adminSlot, ok := account.Storage[genesis.AdminSlot]
require.Equal(t, ok, true) require.Equal(t, true, ok, name)
require.Equal(t, adminSlot, predeploys.ProxyAdminAddr.Hash()) require.Equal(t, predeploys.ProxyAdminAddr.Hash(), adminSlot)
require.Equal(t, account.Code, depB) require.Equal(t, proxyBytecode, account.Code)
} }
require.Equal(t, 2343, len(gen.Alloc)) require.Equal(t, 2343, len(gen.Alloc))
...@@ -94,3 +93,67 @@ func TestBuildL2DeveloperGenesisDevAccountsFunding(t *testing.T) { ...@@ -94,3 +93,67 @@ func TestBuildL2DeveloperGenesisDevAccountsFunding(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 2321, len(gen.Alloc)) require.Equal(t, 2321, len(gen.Alloc))
} }
// Tests the BuildL2MainnetGenesis factory. enableGovernance is used to override enableGovernance
// config option. When false, the test confirms the governance token predeploy address instead
// holds a proxy contract.
func testBuildL2Genesis(t *testing.T, enableGovernance bool) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
config.EnableGovernance = enableGovernance
backend := backends.NewSimulatedBackend(
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.BuildL2MainnetGenesis(config, block)
require.Nil(t, err)
require.NotNil(t, gen)
proxyBytecode, err := bindings.GetDeployedBytecode("Proxy")
require.NoError(t, err)
for name, predeploy := range predeploys.Predeploys {
addr := *predeploy
account, ok := gen.Alloc[addr]
if predeploys.IsDeprecated(addr) && !predeploys.IsProxied(addr) {
// deprecated, non-proxied predeploys should have no account
require.Equal(t, false, ok, name)
continue
}
require.Equal(t, true, ok, name)
require.Greater(t, len(account.Code), 0)
adminSlot, ok := account.Storage[genesis.AdminSlot]
isProxy := predeploys.IsProxied(addr) ||
(!enableGovernance && addr == predeploys.GovernanceTokenAddr)
if isProxy {
require.Equal(t, true, ok, name)
require.Equal(t, predeploys.ProxyAdminAddr.Hash(), adminSlot)
require.Equal(t, proxyBytecode, account.Code)
} else {
require.Equal(t, false, ok, name)
require.NotEqual(t, proxyBytecode, account.Code, name)
}
}
require.Equal(t, 2063, len(gen.Alloc))
if writeFile {
file, _ := json.MarshalIndent(gen, "", " ")
_ = os.WriteFile("genesis.json", file, 0644)
}
}
func TestBuildL2MainnetGenesis(t *testing.T) {
testBuildL2Genesis(t, true)
}
func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) {
testBuildL2Genesis(t, false)
}
...@@ -35,5 +35,10 @@ ...@@ -35,5 +35,10 @@
"l1CrossDomainMessengerProxy": "0xff000000000000000000000000000000000000dd", "l1CrossDomainMessengerProxy": "0xff000000000000000000000000000000000000dd",
"deploymentWaitConfirmations": 1, "deploymentWaitConfirmations": 1,
"fundDevAccounts": true "fundDevAccounts": true,
"enableGovernance": true,
"governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism",
"governanceTokenOwner": "0x0000000000000000000000000000000000000333"
} }
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
"proxyAdminOwner": "0x0000000000000000000000000000000000000222", "proxyAdminOwner": "0x0000000000000000000000000000000000000222",
"gasPriceOracleOverhead": 2100, "gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000, "gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "0x0000000000000000000000000000000000000333", "governanceTokenOwner": "0x0000000000000000000000000000000000000333",
......
...@@ -226,6 +226,15 @@ func (db *MemoryStateDB) SetState(addr common.Address, key, value common.Hash) { ...@@ -226,6 +226,15 @@ func (db *MemoryStateDB) SetState(addr common.Address, key, value common.Hash) {
db.genesis.Alloc[addr] = account db.genesis.Alloc[addr] = account
} }
func (db *MemoryStateDB) DeleteState(addr common.Address, key common.Hash) {
account, ok := db.genesis.Alloc[addr]
if !ok {
panic(fmt.Sprintf("%s not in state", addr))
}
delete(account.Storage, key)
db.genesis.Alloc[addr] = account
}
func (db *MemoryStateDB) Suicide(common.Address) bool { func (db *MemoryStateDB) Suicide(common.Address) bool {
panic("Suicide unimplemented") panic("Suicide unimplemented")
} }
......
#!/usr/bin/env bash
# This script starts a local devnet using Docker Compose. We have to use
# this more complicated Bash script rather than Compose's native orchestration
# tooling because we need to start each service in a specific order, and specify
# their configuration along the way. The order is:
#
# 1. Start L1.
# 2. Compile contracts.
# 3. Deploy the contracts to L1 if necessary.
# 4. Start L2, inserting the compiled contract artifacts into the genesis.
# 5. Get the genesis hashes and timestamps from L1/L2.
# 6. Generate the rollup driver's config using the genesis hashes and the
# timestamps recovered in step 4 as well as the address of the OptimismPortal
# contract deployed in step 3.
# 7. Start the rollup driver.
# 8. Start the L2 output submitter.
#
# The timestamps are critically important here, since the rollup driver will fill in
# empty blocks if the tip of L1 lags behind the current timestamp. This can lead to
# a perceived infinite loop. To get around this, we set the timestamp to the current
# time in this script.
#
# This script is safe to run multiple times. It stores state in `.devnet`, and
# contracts-bedrock/deployments/devnetL1.
#
# Don't run this script directly. Run it using the makefile, e.g. `make devnet-up`.
# To clean up your devnet, run `make devnet-clean`.
set -eu
L1_URL="http://localhost:8545"
L2_URL="http://localhost:9545"
OP_NODE="$PWD/op-node"
CONTRACTS_BEDROCK="$PWD/packages/contracts-bedrock"
NETWORK=devnetL1
DEVNET="$PWD/.devnet"
# Helper method that waits for a given URL to be up. Can't use
# cURL's built-in retry logic because connection reset errors
# are ignored unless you're using a very recent version of cURL
function wait_up {
echo -n "Waiting for $1 to come up..."
i=0
until curl -s -f -o /dev/null "$1"
do
echo -n .
sleep 0.25
((i=i+1))
if [ "$i" -eq 300 ]; then
echo " Timeout!" >&2
exit 1
fi
done
echo "Done!"
}
mkdir -p ./.devnet
# Regenerate the L1 genesis file if necessary. The existence of the genesis
# file is used to determine if we need to recreate the devnet's state folder.
if [ ! -f "$DEVNET/done" ]; then
echo "Regenerating genesis files"
TIMESTAMP=$(date +%s | xargs printf '0x%x')
cat "$CONTRACTS_BEDROCK/deploy-config/devnetL1.json" | jq -r ".l1GenesisBlockTimestamp = \"$TIMESTAMP\"" > /tmp/bedrock-devnet-deploy-config.json
(
cd "$OP_NODE"
go run cmd/main.go genesis devnet \
--deploy-config /tmp/bedrock-devnet-deploy-config.json \
--outfile.l1 $DEVNET/genesis-l1.json \
--outfile.l2 $DEVNET/genesis-l2.json \
--outfile.rollup $DEVNET/rollup.json
touch "$DEVNET/done"
)
fi
# Bring up L1.
(
cd ops-bedrock
echo "Bringing up L1..."
DOCKER_BUILDKIT=1 docker-compose build --progress plain
docker-compose up -d l1
wait_up $L1_URL
)
# Bring up L2.
(
cd ops-bedrock
echo "Bringing up L2..."
docker-compose up -d l2
wait_up $L2_URL
)
L2OO_ADDRESS="0x6900000000000000000000000000000000000000"
# Bring up everything else.
(
cd ops-bedrock
echo "Bringing up devnet..."
L2OO_ADDRESS="$L2OO_ADDRESS" \
docker-compose up -d op-proposer op-batcher
echo "Bringing up stateviz webserver..."
docker-compose up -d stateviz
)
echo "Devnet ready."
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
"l2GenesisBlockBaseFeePerGas": "0x3B9ACA00", "l2GenesisBlockBaseFeePerGas": "0x3B9ACA00",
"gasPriceOracleOverhead": 2100, "gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000, "gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "0xBcd4042DE499D14e55001CcbB24a551F3b954096", "governanceTokenOwner": "0xBcd4042DE499D14e55001CcbB24a551F3b954096",
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
"gasPriceOracleOverhead": 2100, "gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000, "gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "ADMIN", "governanceTokenOwner": "ADMIN",
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
"proxyAdminOwner": "0x62790eFcB3a5f3A5D398F95B47930A9Addd83807", "proxyAdminOwner": "0x62790eFcB3a5f3A5D398F95B47930A9Addd83807",
"enableGovernance": true,
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76", "governanceTokenOwner": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76",
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
"gasPriceOracleOverhead": 2100, "gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000, "gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76", "governanceTokenOwner": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76",
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
"baseFeeVaultWithdrawalNetwork": 0, "baseFeeVaultWithdrawalNetwork": 0,
"l1FeeVaultWithdrawalNetwork": 0, "l1FeeVaultWithdrawalNetwork": 0,
"sequencerFeeVaultWithdrawalNetwork": 0, "sequencerFeeVaultWithdrawalNetwork": 0,
"enableGovernance": true,
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
"l1FeeVaultWithdrawalNetwork": 0, "l1FeeVaultWithdrawalNetwork": 0,
"sequencerFeeVaultWithdrawalNetwork": 0, "sequencerFeeVaultWithdrawalNetwork": 0,
"enableGovernance": true,
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF", "governanceTokenOwner": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF",
......
...@@ -37,6 +37,7 @@ const config: DeployConfig = { ...@@ -37,6 +37,7 @@ const config: DeployConfig = {
l1FeeVaultWithdrawalNetwork: 0, l1FeeVaultWithdrawalNetwork: 0,
sequencerFeeVaultWithdrawalNetwork: 0, sequencerFeeVaultWithdrawalNetwork: 0,
enableGovernance: true,
governanceTokenName: 'Optimism', governanceTokenName: 'Optimism',
governanceTokenSymbol: 'OP', governanceTokenSymbol: 'OP',
governanceTokenOwner: '0x90F79bf6EB2c4f870365E785982E1f101E93b906', governanceTokenOwner: '0x90F79bf6EB2c4f870365E785982E1f101E93b906',
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"baseFeeVaultWithdrawalNetwork": 0, "baseFeeVaultWithdrawalNetwork": 0,
"l1FeeVaultWithdrawalNetwork": 0, "l1FeeVaultWithdrawalNetwork": 0,
"sequencerFeeVaultWithdrawalNetwork": 0, "sequencerFeeVaultWithdrawalNetwork": 0,
"enableGovernance": true,
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005", "governanceTokenOwner": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005",
......
...@@ -110,6 +110,11 @@ interface RequiredDeployConfig { ...@@ -110,6 +110,11 @@ interface RequiredDeployConfig {
*/ */
l2OutputOracleChallenger: string l2OutputOracleChallenger: string
/**
* Whether to enable governance token predeploy.
*/
enableGovernance: boolean
/** /**
* ERC20 symbol used for the L2 GovernanceToken. * ERC20 symbol used for the L2 GovernanceToken.
*/ */
...@@ -414,13 +419,15 @@ export const deployConfigSpec: { ...@@ -414,13 +419,15 @@ export const deployConfigSpec: {
type: 'number', type: 'number',
default: 1_000_000, default: 1_000_000,
}, },
enableGovernance: {
type: 'boolean',
default: false,
},
governanceTokenSymbol: { governanceTokenSymbol: {
type: 'string', type: 'string',
default: 'OP',
}, },
governanceTokenName: { governanceTokenName: {
type: 'string', type: 'string',
default: 'Optimism',
}, },
governanceTokenOwner: { governanceTokenOwner: {
type: 'string', type: 'string',
......
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