Skip to content

Commit

Permalink
op-chain-ops: create tool to determine which contract versions are de…
Browse files Browse the repository at this point in the history
…ployed for each chain (ethereum-optimism#8859)

* op-chain-ops: create op-version-check

forked from op-upgrade

* op-version-check: drop logic for upgrading contracts

* op-version-check: add logic for outputting contract versions

* op-version-check: improve support for checking multiple chains

* op-version-check: replace legacy upgrade references

* op-chain-ops: create README

* op-version-check: improve README

* op-version-check: drop unnecessary deploy-config flag

* op-version-check: create op-version-check in Makefile

* op-version-check: add usage section to README

* op-chain-ops: add op-version-check to README

* op-version-check: fix path in README

* op-version-check: remove unused function toDeployConfigName

* op-version-check: drop capitalization on error strings

* op-node: capitalize writeJSONFile function

this makes it accessible when op-node/cmd/genesis is imported as a package

* op-chain-ops: use op-node/cmd/genesis for writeJSON

* op-chain-ops: set name op_node_genesis for import

* op-chain-ops: use op-node WriteJSONFile in op-upgrade

* op-chains-ops: move to toSuperchainName to upgrades package

* op-chain-ops: output all contracts for op-version-check

addresses ethereum-optimism#8859 (comment)

* op-chain-ops: add logging if chain ID is skipped by op-version-check

* op-service: create jsonutil/write

* op-chain-ops use jsonutil in op-upgrade, op-version-check

* op-chain-ops: pass RPC URLs as flags in op-version-check

* op-chain-ops: refactor of op-version-check

* op-chain-ops: refactor of op-version-check

* op-chain-ops: add more logging for op-version-check

* op-chain-ops: add more logging for op-version-check
  • Loading branch information
0xfuturistic authored Jan 18, 2024
1 parent 2ce0ccf commit aba06fc
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 29 deletions.
3 changes: 3 additions & 0 deletions op-chain-ops/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
op-version-check:
go build -o ./bin/op-version-check ./cmd/op-version-check/main.go

test:
go test ./...

Expand Down
49 changes: 49 additions & 0 deletions op-chain-ops/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,52 @@
# op-chain-ops

This package contains utilities for working with chain state.

## op-version-check

A CLI tool for determining which contract versions are deployed for
chains in a superchain. It will output a JSON file that contains a
list of each chain's versions. It is assumed that the implementations
that are being checked have already been deployed and their contract
addresses exist inside of the `superchain-registry` repository. It is
also assumed that the semantic version file in the `superchain-registry`
has been updated. The tool will output the semantic versioning to
determine which contract versions are deployed.

### Configuration

#### L1 RPC URL

The L1 RPC URL is used to determine which superchain to target. All
L2s that are not based on top of the L1 chain that corresponds to the
L1 RPC URL are filtered out from being checked. It also is used to
double check that the data in the `superchain-registry` is correct.

#### Chain IDs

A list of L2 chain IDs can be passed that will be used to filter which
L2 chains will have their versions checked. Omitting this argument will
result in all chains in the superchain being considered.

#### Deploy Config

The path to the `deploy-config` directory in the contracts package.
Since multiple L2 networks may be considered in the check, the `deploy-config`
directory must be passed and then the particular deploy config files will
be read out of the directory as needed.

#### Outfile

The file that the versions should be written to. If omitted, the file
will be written to stdout

#### Usage

It can be built and run using the [Makefile](./Makefile) `op-version-check`
target. Run `make op-version-check` to create a binary in [./bin/op-version-check](./bin/op-version-check)
that can be executed, optionally providing the `--l1-rpc-url`, `--chain-ids`,
`--superchain-target`, and `--outfile` flags.

```sh
./bin/op-version-check
```
32 changes: 3 additions & 29 deletions op-chain-ops/cmd/op-upgrade/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"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/optimism/op-service/jsonutil"

"github.com/ethereum-optimism/superchain-registry/superchain"
)
Expand Down Expand Up @@ -79,7 +80,7 @@ func entrypoint(ctx *cli.Context) error {

superchainName := ctx.String("superchain-target")
if superchainName == "" {
superchainName, err = toSuperchainName(l1ChainID.Uint64())
superchainName, err = upgrades.ToSuperchainName(l1ChainID.Uint64())
if err != nil {
return err
}
Expand Down Expand Up @@ -202,7 +203,7 @@ func entrypoint(ctx *cli.Context) error {

// Write the batch to disk or stdout
if outfile := ctx.Path("outfile"); outfile != "" {
if err := writeJSON(outfile, batch); err != nil {
if err := jsonutil.WriteJSON(outfile, batch); err != nil {
return err
}
} else {
Expand Down Expand Up @@ -240,30 +241,3 @@ func toDeployConfigName(cfg *superchain.ChainConfig) (string, error) {
}
return "", fmt.Errorf("unsupported chain name %s", cfg.Name)
}

// toSuperchainName turns a base layer chain id into a superchain
// network name.
func toSuperchainName(chainID uint64) (string, error) {
if chainID == 1 {
return "mainnet", nil
}
if chainID == 5 {
return "goerli", nil
}
if chainID == 11155111 {
return "sepolia", nil
}
return "", fmt.Errorf("unsupported chain ID %d", chainID)
}

func writeJSON(outfile string, input interface{}) error {
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o666)
if err != nil {
return err
}
defer f.Close()

enc := json.NewEncoder(f)
enc.SetIndent("", " ")
return enc.Encode(input)
}
48 changes: 48 additions & 0 deletions op-chain-ops/cmd/op-version-check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# op-version-check

A CLI tool for determining which contract versions are deployed for
chains in a superchain. It will output a JSON file that contains a
list of each chain's versions. It is assumed that the implementations
that are being checked have already been deployed and their contract
addresses exist inside of the `superchain-registry` repository. It is
also assumed that the semantic version file in the `superchain-registry`
has been updated. The tool will output the semantic versioning to
determine which contract versions are deployed.

### Configuration

#### L1 RPC URL

The L1 RPC URL is used to determine which superchain to target. All
L2s that are not based on top of the L1 chain that corresponds to the
L1 RPC URL are filtered out from being checked. It also is used to
double check that the data in the `superchain-registry` is correct.

#### Chain IDs

A list of L2 chain IDs can be passed that will be used to filter which
L2 chains will have their versions checked. Omitting this argument will
result in all chains in the superchain being considered.

#### Deploy Config

The path to the `deploy-config` directory in the contracts package.
Since multiple L2 networks may be considered in the check, the `deploy-config`
directory must be passed and then the particular deploy config files will
be read out of the directory as needed.

#### Outfile

The file that the versions should be written to. If omitted, the file
will be written to stdout

#### Usage

It can be built and run using the [Makefile](../../Makefile) `op-version-check`
target. Run `make op-version-check` to create a binary in [../../bin/op-version-check](../../bin/op-version-check)
that can be executed, optionally providing the `--l1-rpc-url`, `--chain-ids`,
`--superchain-target`, and `--outfile` flags.

```sh
./bin/op-version-check
```
164 changes: 164 additions & 0 deletions op-chain-ops/cmd/op-version-check/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"os"

"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"golang.org/x/exp/maps"

"github.com/ethereum-optimism/optimism/op-chain-ops/upgrades"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/ethereum-optimism/superchain-registry/superchain"
)

type Contract struct {
Version string `yaml:"version"`
Address superchain.Address `yaml:"address"`
}

type ChainVersionCheck struct {
Name string `yaml:"name"`
ChainID uint64 `yaml:"chain_id"`
Contracts map[string]Contract `yaml:"contracts"`
}

func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))

app := &cli.App{
Name: "op-version-check",
Usage: "Determine which contract versions are deployed for chains in a superchain",
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "l1-rpc-urls",
Usage: "L1 RPC URLs, the chain ID will be used to determine the superchain",
EnvVars: []string{"L1_RPC_URL"},
},
&cli.StringSliceFlag{
Name: "l2-rpc-urls",
Usage: "L2 RPC URLs, corresponding to chains to check versions for. Corresponds to all chains if empty",
EnvVars: []string{"L1_RPC_URL"},
},
&cli.PathFlag{
Name: "outfile",
Usage: "The file to write the output to. If not specified, output is written to stdout",
EnvVars: []string{"OUTFILE"},
},
},
Action: entrypoint,
}

if err := app.Run(os.Args); err != nil {
log.Crit("error op-version-check", "err", err)
}
}

// entrypoint contains the main logic of the script
func entrypoint(ctx *cli.Context) error {
l1RPCURLs := ctx.StringSlice("l1-rpc-urls")
l2RPCURLs := ctx.StringSlice("l2-rpc-urls")

var l2ChainIDs []uint64

// If no L2 RPC URLs are specified, we check all chains for the L1 RPC URL
if len(l2RPCURLs) == 0 {
l2ChainIDs = maps.Keys(superchain.OPChains)
} else {
for _, l2RPCURL := range l2RPCURLs {
client, err := ethclient.Dial(l2RPCURL)
if err != nil {
return errors.New("cannot create L2 client")
}

l2ChainID, err := client.ChainID(ctx.Context)
if err != nil {
return fmt.Errorf("cannot fetch L2 chain ID: %w", err)
}

l2ChainIDs = append(l2ChainIDs, l2ChainID.Uint64())
}
}

output := []ChainVersionCheck{}

for _, l2ChainID := range l2ChainIDs {
chainConfig := superchain.OPChains[l2ChainID]

if chainConfig.ChainID != l2ChainID {
return fmt.Errorf("mismatched chain IDs: %d != %d", chainConfig.ChainID, l2ChainID)
}

for _, l1RPCURL := range l1RPCURLs {
client, err := ethclient.Dial(l1RPCURL)
if err != nil {
return errors.New("cannot create L1 client")
}

l1ChainID, err := client.ChainID(ctx.Context)
if err != nil {
return fmt.Errorf("cannot fetch L1 chain ID: %w", err)
}

superchainName, err := upgrades.ToSuperchainName(l1ChainID.Uint64())
if err != nil {
return fmt.Errorf("error getting superchain name: %w", err)
}

if superchainName != chainConfig.Superchain {
// L2 corresponds to a different superchain than L1, skip
log.Info("Ignoring L1/L2", "l1-chain-id", l1ChainID, "l2-chain-id", l2ChainID)
continue
}

log.Info(chainConfig.Name, "l1-chain-id", l1ChainID, "l2-chain-id", l2ChainID)

log.Info("Detecting on chain contracts")
// 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]
if !ok {
return fmt.Errorf("no addresses for chain ID %d", l2ChainID)
}
versions, err := upgrades.GetContractVersions(ctx.Context, addresses, chainConfig, client)
if err != nil {
return fmt.Errorf("error getting contract versions: %w", err)
}

contracts := make(map[string]Contract)

contracts["AddressManager"] = Contract{Version: "null", Address: addresses.AddressManager}
contracts["L1CrossDomainMessenger"] = Contract{Version: versions.L1CrossDomainMessenger, Address: addresses.L1CrossDomainMessengerProxy}
contracts["L1ERC721Bridge"] = Contract{Version: versions.L1ERC721Bridge, Address: addresses.L1ERC721BridgeProxy}
contracts["L1StandardBridge"] = Contract{Version: versions.L1ERC721Bridge, Address: addresses.L1StandardBridgeProxy}
contracts["L2OutputOracle"] = Contract{Version: versions.L2OutputOracle, Address: addresses.L2OutputOracleProxy}
contracts["OptimismMintableERC20Factory"] = Contract{Version: versions.OptimismMintableERC20Factory, Address: addresses.OptimismMintableERC20FactoryProxy}
contracts["OptimismPortal"] = Contract{Version: versions.OptimismPortal, Address: addresses.OptimismPortalProxy}
contracts["SystemConfig"] = Contract{Version: versions.SystemConfig, Address: chainConfig.SystemConfigAddr}
contracts["ProxyAdmin"] = Contract{Version: "null", Address: addresses.ProxyAdmin}

output = append(output, ChainVersionCheck{Name: chainConfig.Name, ChainID: l2ChainID, Contracts: contracts})

log.Info("Successfully processed contract versions", "chain", chainConfig.Name, "l1-chain-id", l1ChainID, "l2-chain-id", l2ChainID)
break
}
}
// Write contract versions to disk or stdout
if outfile := ctx.Path("outfile"); outfile != "" {
if err := jsonutil.WriteJSON(outfile, output); err != nil {
return err
}
} else {
data, err := json.MarshalIndent(output, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))
}
return nil
}
14 changes: 14 additions & 0 deletions op-chain-ops/upgrades/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,17 @@ func cmpVersion(v1, v2 string) bool {
}
return v1 == v2
}

// ToSuperchainName turns a base layer chain id into a superchain network name.
func ToSuperchainName(chainID uint64) (string, error) {
if chainID == 1 {
return "mainnet", nil
}
if chainID == 5 {
return "goerli", nil
}
if chainID == 11155111 {
return "sepolia", nil
}
return "", fmt.Errorf("unsupported chain ID %d", chainID)
}
18 changes: 18 additions & 0 deletions op-service/jsonutil/write.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package jsonutil

import (
"encoding/json"
"os"
)

func WriteJSON(outfile string, input interface{}) error {
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o666)
if err != nil {
return err
}
defer f.Close()

enc := json.NewEncoder(f)
enc.SetIndent("", " ")
return enc.Encode(input)
}

0 comments on commit aba06fc

Please sign in to comment.