Commit 3649f327 authored by Mark Tyneway's avatar Mark Tyneway Committed by Andreas Bigger

op-upgrade: introduce

parent c83cd947
......@@ -210,4 +210,5 @@ require (
replace github.com/ethereum/go-ethereum v1.13.1 => github.com/ethereum-optimism/op-geth v1.101202.0-rc.1.0.20230925151842-7dfe72eb2d63
//replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain
//replace github.com/ethereum/go-ethereum v1.13.1 => ../go-ethereum
......@@ -39,5 +39,6 @@
"ProtocolVersions",
"Safe",
"SafeProxyFactory",
"DelayedVetoable"
"DelayedVetoable",
"ISemver"
]
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package bindings
import (
"errors"
"math/big"
"strings"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = errors.New
_ = big.NewInt
_ = strings.NewReader
_ = ethereum.NotFound
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
_ = event.NewSubscription
)
// ISemverMetaData contains all meta data concerning the ISemver contract.
var ISemverMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
}
// ISemverABI is the input ABI used to generate the binding from.
// Deprecated: Use ISemverMetaData.ABI instead.
var ISemverABI = ISemverMetaData.ABI
// ISemver is an auto generated Go binding around an Ethereum contract.
type ISemver struct {
ISemverCaller // Read-only binding to the contract
ISemverTransactor // Write-only binding to the contract
ISemverFilterer // Log filterer for contract events
}
// ISemverCaller is an auto generated read-only Go binding around an Ethereum contract.
type ISemverCaller struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// ISemverTransactor is an auto generated write-only Go binding around an Ethereum contract.
type ISemverTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// ISemverFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type ISemverFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// ISemverSession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options.
type ISemverSession struct {
Contract *ISemver // Generic contract binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// ISemverCallerSession is an auto generated read-only Go binding around an Ethereum contract,
// with pre-set call options.
type ISemverCallerSession struct {
Contract *ISemverCaller // Generic contract caller binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
}
// ISemverTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
// with pre-set transact options.
type ISemverTransactorSession struct {
Contract *ISemverTransactor // Generic contract transactor binding to set the session for
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// ISemverRaw is an auto generated low-level Go binding around an Ethereum contract.
type ISemverRaw struct {
Contract *ISemver // Generic contract binding to access the raw methods on
}
// ISemverCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
type ISemverCallerRaw struct {
Contract *ISemverCaller // Generic read-only contract binding to access the raw methods on
}
// ISemverTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
type ISemverTransactorRaw struct {
Contract *ISemverTransactor // Generic write-only contract binding to access the raw methods on
}
// NewISemver creates a new instance of ISemver, bound to a specific deployed contract.
func NewISemver(address common.Address, backend bind.ContractBackend) (*ISemver, error) {
contract, err := bindISemver(address, backend, backend, backend)
if err != nil {
return nil, err
}
return &ISemver{ISemverCaller: ISemverCaller{contract: contract}, ISemverTransactor: ISemverTransactor{contract: contract}, ISemverFilterer: ISemverFilterer{contract: contract}}, nil
}
// NewISemverCaller creates a new read-only instance of ISemver, bound to a specific deployed contract.
func NewISemverCaller(address common.Address, caller bind.ContractCaller) (*ISemverCaller, error) {
contract, err := bindISemver(address, caller, nil, nil)
if err != nil {
return nil, err
}
return &ISemverCaller{contract: contract}, nil
}
// NewISemverTransactor creates a new write-only instance of ISemver, bound to a specific deployed contract.
func NewISemverTransactor(address common.Address, transactor bind.ContractTransactor) (*ISemverTransactor, error) {
contract, err := bindISemver(address, nil, transactor, nil)
if err != nil {
return nil, err
}
return &ISemverTransactor{contract: contract}, nil
}
// NewISemverFilterer creates a new log filterer instance of ISemver, bound to a specific deployed contract.
func NewISemverFilterer(address common.Address, filterer bind.ContractFilterer) (*ISemverFilterer, error) {
contract, err := bindISemver(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &ISemverFilterer{contract: contract}, nil
}
// bindISemver binds a generic wrapper to an already deployed contract.
func bindISemver(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(ISemverABI))
if err != nil {
return nil, err
}
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_ISemver *ISemverRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _ISemver.Contract.ISemverCaller.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_ISemver *ISemverRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _ISemver.Contract.ISemverTransactor.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_ISemver *ISemverRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _ISemver.Contract.ISemverTransactor.contract.Transact(opts, method, params...)
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_ISemver *ISemverCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _ISemver.Contract.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_ISemver *ISemverTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _ISemver.Contract.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_ISemver *ISemverTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _ISemver.Contract.contract.Transact(opts, method, params...)
}
// Version is a free data retrieval call binding the contract method 0x54fd4d50.
//
// Solidity: function version() view returns(string)
func (_ISemver *ISemverCaller) Version(opts *bind.CallOpts) (string, error) {
var out []interface{}
err := _ISemver.contract.Call(opts, &out, "version")
if err != nil {
return *new(string), err
}
out0 := *abi.ConvertType(out[0], new(string)).(*string)
return out0, err
}
// Version is a free data retrieval call binding the contract method 0x54fd4d50.
//
// Solidity: function version() view returns(string)
func (_ISemver *ISemverSession) Version() (string, error) {
return _ISemver.Contract.Version(&_ISemver.CallOpts)
}
// Version is a free data retrieval call binding the contract method 0x54fd4d50.
//
// Solidity: function version() view returns(string)
func (_ISemver *ISemverCallerSession) Version() (string, error) {
return _ISemver.Contract.Version(&_ISemver.CallOpts)
}
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package bindings
import (
"encoding/json"
"github.com/ethereum-optimism/optimism/op-bindings/solc"
)
const ISemverStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
var ISemverStorageLayout = new(solc.StorageLayout)
var ISemverDeployedBin = "0x"
func init() {
if err := json.Unmarshal([]byte(ISemverStorageLayoutJSON), ISemverStorageLayout); err != nil {
panic(err)
}
layouts["ISemver"] = ISemverStorageLayout
deployedBytecodes["ISemver"] = ISemverDeployedBin
}
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/ethereum/go-ethereum/log"
"github.com/mattn/go-isatty"
"github.com/ethereum-optimism/optimism/op-chain-ops/clients"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/safe"
"github.com/ethereum-optimism/optimism/op-chain-ops/upgrades"
"github.com/ethereum-optimism/superchain-registry/superchain"
"github.com/urfave/cli/v2"
)
func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
app := &cli.App{
Name: "op-upgrade",
Usage: "Build transactions useful for upgrading the Superchain",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "l1-rpc-url",
Value: "http://127.0.0.1:8545",
Usage: "L1 RPC URL",
EnvVars: []string{"L1_RPC_URL"},
},
&cli.StringFlag{
Name: "l2-rpc-url",
Value: "http://127.0.0.1:9545",
Usage: "L2 RPC URL",
EnvVars: []string{"L2_RPC_URL"},
},
&cli.PathFlag{
Name: "deploy-config",
Required: true,
EnvVars: []string{"DEPLOY_CONFIG"},
},
&cli.PathFlag{
Name: "outfile",
Usage: "",
EnvVars: []string{"OUTFILE"},
},
},
Action: entrypoint,
}
if err := app.Run(os.Args); err != nil {
log.Crit("error op-upgrade", "err", err)
}
}
func entrypoint(ctx *cli.Context) error {
config, err := genesis.NewDeployConfig(ctx.Path("deploy-config"))
if err != nil {
return err
}
clients, err := clients.NewClients(ctx)
if err != nil {
return err
}
l1ChainID, err := clients.L1Client.ChainID(ctx.Context)
if err != nil {
return err
}
l2ChainID, err := clients.L2Client.ChainID(ctx.Context)
if err != nil {
return err
}
log.Info("Chain IDs", "l1", l1ChainID, "l2", l2ChainID)
chainConfig, ok := superchain.OPChains[l2ChainID.Uint64()]
if !ok {
return fmt.Errorf("no chain config for chain ID %d", l2ChainID.Uint64())
}
// Tracking the individual addresses can be deprecated once the system is upgraded
// to the new contracts where the system config has a reference to each address.
addresses, ok := superchain.Addresses[l2ChainID.Uint64()]
if !ok {
return fmt.Errorf("no addresses for chain ID %d", l2ChainID.Uint64())
}
versions, err := upgrades.GetContractVersions(ctx.Context, addresses, chainConfig, clients.L1Client)
if err != nil {
return fmt.Errorf("error getting contract versions: %w", err)
}
log.Info(
"Current Versions",
"L1CrossDomainMessenger", versions.L1CrossDomainMessenger,
"L1ERC721Bridge", versions.L1ERC721Bridge,
"L1StandardBridge", versions.L1StandardBridge,
"L2OutputOracle", versions.L2OutputOracle,
"OptimismMintableERC20Factory", versions.OptimismMintableERC20Factory,
"OptimismPortal", versions.OptimismPortal,
"SystemConfig", versions.SystemConfig,
)
implementations, ok := superchain.Implementations[l1ChainID.Uint64()]
if !ok {
return fmt.Errorf("no implementations for chain ID %d", l1ChainID.Uint64())
}
list, err := implementations.Resolve(superchain.SuperchainSemver)
if err != nil {
return err
}
if err := upgrades.CheckL1(ctx.Context, &list, clients.L1Client); err != nil {
return fmt.Errorf("error checking L1: %w", err)
}
batch := safe.Batch{}
if err := upgrades.L1(&batch, list, *addresses, config, chainConfig); err != nil {
return err
}
if outfile := ctx.Path("outfile"); outfile != "" {
if err := writeJSON(outfile, batch); err != nil {
return err
}
} else {
data, err := json.MarshalIndent(batch, "", " ")
if err != nil {
return err
}
fmt.Println(data)
}
return nil
}
func writeJSON(outfile string, input interface{}) error {
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
return enc.Encode(input)
}
......@@ -315,7 +315,7 @@ func (d *DeployConfig) Check() error {
}
// When the initial resource config is made to be configurable by the DeployConfig, ensure
// that this check is updated to use the values from the DeployConfig instead of the defaults.
if uint64(d.L2GenesisBlockGasLimit) < uint64(defaultResourceConfig.MaxResourceLimit+defaultResourceConfig.SystemTxMaxGas) {
if uint64(d.L2GenesisBlockGasLimit) < uint64(DefaultResourceConfig.MaxResourceLimit+DefaultResourceConfig.SystemTxMaxGas) {
return fmt.Errorf("%w: L2 genesis block gas limit is too small", ErrInvalidDeployConfig)
}
if d.L2GenesisBlockBaseFeePerGas == nil {
......
......@@ -21,7 +21,7 @@ var (
uint128Max = new(big.Int)
// The default values for the ResourceConfig, used as part of
// an EIP-1559 curve for deposit gas.
defaultResourceConfig = bindings.ResourceMeteringResourceConfig{
DefaultResourceConfig = bindings.ResourceMeteringResourceConfig{
MaxResourceLimit: 20_000_000,
ElasticityMultiplier: 10,
BaseFeeMaxChangeDenominator: 8,
......@@ -37,7 +37,7 @@ func init() {
panic("bad uint128Max")
}
// Set the maximum base fee on the default config.
defaultResourceConfig.MaximumBaseFee = uint128Max
DefaultResourceConfig.MaximumBaseFee = uint128Max
}
// BuildL1DeveloperGenesis will create a L1 genesis block after creating
......
......@@ -106,7 +106,7 @@ func TestBuildL1DeveloperGenesis(t *testing.T) {
require.NoError(t, err)
cfg, err := sysCfg.ResourceConfig(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, cfg, defaultResourceConfig)
require.Equal(t, cfg, DefaultResourceConfig)
owner, err = sysCfg.Owner(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, owner, config.FinalSystemOwner)
......
package upgrades
import (
"context"
"fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/superchain-registry/superchain"
)
// CheckL1 will check that the versions of the contracts on L1 match the versions
// in the superchain registry.
func CheckL1(ctx context.Context, list *superchain.ImplementationList, backend bind.ContractBackend) error {
if err := CheckVersionedContract(ctx, list.L1CrossDomainMessenger, backend); err != nil {
return fmt.Errorf("L1CrossDomainMessenger: %w", err)
}
if err := CheckVersionedContract(ctx, list.L1ERC721Bridge, backend); err != nil {
return fmt.Errorf("L1ERC721Bridge: %w", err)
}
if err := CheckVersionedContract(ctx, list.L1StandardBridge, backend); err != nil {
return fmt.Errorf("L1StandardBridge: %w", err)
}
if err := CheckVersionedContract(ctx, list.L2OutputOracle, backend); err != nil {
return fmt.Errorf("L2OutputOracle: %w", err)
}
if err := CheckVersionedContract(ctx, list.OptimismMintableERC20Factory, backend); err != nil {
return fmt.Errorf("OptimismMintableERC20Factory: %w", err)
}
if err := CheckVersionedContract(ctx, list.OptimismPortal, backend); err != nil {
return fmt.Errorf("OptimismPortal: %w", err)
}
if err := CheckVersionedContract(ctx, list.SystemConfig, backend); err != nil {
return fmt.Errorf("SystemConfig: %w", err)
}
return nil
}
// CheckVersionedContract will check that the version of the deployed contract matches
// the artifact in the superchain registry.
func CheckVersionedContract(ctx context.Context, contract superchain.VersionedContract, backend bind.ContractBackend) error {
addr := common.HexToAddress(contract.Address.String())
code, err := backend.CodeAt(ctx, addr, nil)
if err != nil {
return err
}
if len(code) == 0 {
return fmt.Errorf("no code at %s", addr)
}
version, err := getVersion(ctx, addr, backend)
if err != nil {
return err
}
if !cmpVersion(version, contract.Version) {
return fmt.Errorf("version mismatch: expected %s, got %s", contract.Version, version)
}
return nil
}
// getContractVersions will fetch the versions of all of the contracts.
func GetContractVersions(ctx context.Context, addresses *superchain.AddressList, chainConfig *superchain.ChainConfig, backend bind.ContractBackend) (superchain.ContractVersions, error) {
var versions superchain.ContractVersions
var err error
versions.L1CrossDomainMessenger, err = getVersion(ctx, common.HexToAddress(addresses.L1CrossDomainMessengerProxy.String()), backend)
if err != nil {
return versions, fmt.Errorf("L1CrossDomainMessenger: %w", err)
}
versions.L1ERC721Bridge, err = getVersion(ctx, common.HexToAddress(addresses.L1ERC721BridgeProxy.String()), backend)
if err != nil {
return versions, fmt.Errorf("L1ERC721Bridge: %w", err)
}
versions.L1StandardBridge, err = getVersion(ctx, common.HexToAddress(addresses.L1StandardBridgeProxy.String()), backend)
if err != nil {
return versions, fmt.Errorf("L1StandardBridge: %w", err)
}
versions.L2OutputOracle, err = getVersion(ctx, common.HexToAddress(addresses.L2OutputOracleProxy.String()), backend)
if err != nil {
return versions, fmt.Errorf("L2OutputOracle: %w", err)
}
versions.OptimismMintableERC20Factory, err = getVersion(ctx, common.HexToAddress(addresses.OptimismMintableERC20FactoryProxy.String()), backend)
if err != nil {
return versions, fmt.Errorf("OptimismMintableERC20Factory: %w", err)
}
versions.OptimismPortal, err = getVersion(ctx, common.HexToAddress(addresses.OptimismPortalProxy.String()), backend)
if err != nil {
return versions, fmt.Errorf("OptimismPortal: %w", err)
}
versions.SystemConfig, err = getVersion(ctx, common.HexToAddress(chainConfig.SystemConfigAddr.String()), backend)
if err != nil {
return versions, fmt.Errorf("SystemConfig: %w", err)
}
return versions, err
}
// getVersion will get the version of a contract at a given address.
func getVersion(ctx context.Context, addr common.Address, backend bind.ContractBackend) (string, error) {
isemver, err := bindings.NewISemver(addr, backend)
if err != nil {
return "", fmt.Errorf("%s: %w", addr, err)
}
version, err := isemver.Version(&bind.CallOpts{
Context: ctx,
})
if err != nil {
return "", fmt.Errorf("%s: %w", addr, err)
}
return version, nil
}
// cmpVersion will compare 2 semver strings, accounting for
// lack of "v" prefix.
func cmpVersion(v1, v2 string) bool {
if !strings.HasPrefix(v1, "v") {
v1 = "v" + v1
}
if !strings.HasPrefix(v2, "v") {
v2 = "v" + v2
}
return v1 == v2
}
This diff is collapsed.
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