Skip to content

Commit

Permalink
op-deployer: Add support for inspecting l2 semvers (ethereum-optimism…
Browse files Browse the repository at this point in the history
…#12577)

* op-deployer: Add support for inspecting l2 semvers

* goimports

* add factory targets
  • Loading branch information
mslipper authored and samlaf committed Nov 10, 2024
1 parent 85d0129 commit bbb9e16
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 1 deletion.
5 changes: 5 additions & 0 deletions op-chain-ops/script/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ func (h *Host) HasPrecompileOverride(addr common.Address) bool {
return ok
}

// GetCode returns the code of an account from the state.
func (h *Host) GetCode(addr common.Address) []byte {
return h.state.GetCode(addr)
}

// onEnter is a trace-hook, which we use to apply changes to the state-DB, to simulate isolated broadcast calls,
// for better gas estimation of the exact broadcast call execution.
func (h *Host) onEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
Expand Down
8 changes: 8 additions & 0 deletions op-deployer/pkg/deployer/inspect/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ var Commands = []*cli.Command{
Action: DeployConfigCLI,
Flags: Flags,
},
{
Name: "l2-semvers",
Usage: "outputs the semvers for all L2 chains",
Args: true,
ArgsUsage: "<l2-chain-id>",
Action: L2SemversCLI,
Flags: Flags,
},
}

type cliConfig struct {
Expand Down
198 changes: 198 additions & 0 deletions op-deployer/pkg/deployer/inspect/semvers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package inspect

import (
"bytes"
"context"
"fmt"
"math/big"
"regexp"
"time"

"github.com/ethereum-optimism/optimism/op-chain-ops/script"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
"github.com/urfave/cli/v2"
)

var versionSelector = []byte{0x54, 0xfd, 0x4d, 0x50}

func L2SemversCLI(cliCtx *cli.Context) error {
cliCfg, err := readConfig(cliCtx)
if err != nil {
return err
}

ctx, cancel := context.WithTimeout(cliCtx.Context, time.Minute)
defer cancel()

logCfg := oplog.ReadCLIConfig(cliCtx)
l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg)
oplog.SetGlobalLogHandler(l.Handler())

globalState, err := pipeline.ReadState(cliCfg.Workdir)
if err != nil {
return fmt.Errorf("failed to read intent: %w", err)
}
chainState, err := globalState.Chain(cliCfg.ChainID)
if err != nil {
return fmt.Errorf("failed to find chain state: %w", err)
}

intent := globalState.AppliedIntent
if intent == nil {
return fmt.Errorf("can only run this command following a full apply")
}
if chainState.Allocs == nil {
return fmt.Errorf("chain state does not have allocs")
}

artifactsFS, cleanup, err := pipeline.DownloadArtifacts(ctx, intent.L2ContractsLocator, pipeline.LogProgressor(l))
if err != nil {
return fmt.Errorf("failed to download L2 artifacts: %w", err)
}
defer func() {
if err := cleanup(); err != nil {
l.Warn("failed to clean up L2 artifacts", "err", err)
}
}()

host, err := pipeline.DefaultScriptHost(
broadcaster.NoopBroadcaster(),
l,
common.Address{19: 0x01},
artifactsFS,
0,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}
host.ImportState(chainState.Allocs.Data)

addr := common.Address{19: 0x01}

type contractToCheck struct {
Address common.Address
Name string
}

contractsOutput := make(map[string]string)

// The gov token and the proxy admin do not have semvers.
contracts := []contractToCheck{
{predeploys.L2ToL1MessagePasserAddr, "L2ToL1MessagePasser"},
{predeploys.DeployerWhitelistAddr, "DeployerWhitelist"},
{predeploys.WETHAddr, "WETH"},
{predeploys.L2CrossDomainMessengerAddr, "L2CrossDomainMessenger"},
{predeploys.L2StandardBridgeAddr, "L2StandardBridge"},
{predeploys.SequencerFeeVaultAddr, "SequencerFeeVault"},
{predeploys.OptimismMintableERC20FactoryAddr, "OptimismMintableERC20Factory"},
{predeploys.L1BlockNumberAddr, "L1BlockNumber"},
{predeploys.GasPriceOracleAddr, "GasPriceOracle"},
{predeploys.L1BlockAddr, "L1Block"},
{predeploys.LegacyMessagePasserAddr, "LegacyMessagePasser"},
{predeploys.L2ERC721BridgeAddr, "L2ERC721Bridge"},
{predeploys.OptimismMintableERC721FactoryAddr, "OptimismMintableERC721Factory"},
{predeploys.BaseFeeVaultAddr, "BaseFeeVault"},
{predeploys.L1FeeVaultAddr, "L1FeeVault"},
{predeploys.SchemaRegistryAddr, "SchemaRegistry"},
{predeploys.EASAddr, "EAS"},
{predeploys.WETHAddr, "WETH"},
}
for _, contract := range contracts {
data, _, err := host.Call(
addr,
contract.Address,
bytes.Clone(versionSelector),
1_000_000_000,
uint256.NewInt(0),
)
if err != nil {
return fmt.Errorf("failed to call version on %s: %w", contract.Name, err)
}

// The second 32 bytes contain the length of the string
length := new(big.Int).SetBytes(data[32:64]).Int64()
// Start of the string data (after offset and length)
stringStart := 64
stringEnd := int64(stringStart) + length

// Bounds check
if stringEnd > int64(len(data)) {
return fmt.Errorf("string data out of bounds")
}

contractsOutput[contract.Name] = string(data[stringStart:stringEnd])
}

erc20Semver, err := findSemverBytecode(host, predeploys.OptimismMintableERC20FactoryAddr)
if err == nil {
contractsOutput["OptimismMintableERC20"] = erc20Semver
} else {
l.Warn("failed to find semver for OptimismMintableERC20", "err", err)
}

erc721Semver, err := findSemverBytecode(host, predeploys.OptimismMintableERC721FactoryAddr)
if err == nil {
contractsOutput["OptimismMintableERC721"] = erc721Semver
} else {
l.Warn("failed to find semver for OptimismMintableERC721", "err", err)
}

if err := jsonutil.WriteJSON(contractsOutput, ioutil.ToStdOutOrFileOrNoop(cliCfg.Outfile, 0o666)); err != nil {
return fmt.Errorf("failed to write rollup config: %w", err)
}

return nil
}

const patternLen = 24

var semverRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+([\w.+\-]*))\x00`)
var codeAddr = common.HexToAddress("0xc0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30000")

func findSemverBytecode(host *script.Host, proxyAddr common.Address) (string, error) {
var implAddr common.Address
copy(implAddr[:], codeAddr[:])
copy(implAddr[18:], proxyAddr[18:])

bytecode := host.GetCode(implAddr)
if len(bytecode) == 0 {
return "", fmt.Errorf("failed to get bytecode for factory")
}

versionSelectorIndex := bytes.LastIndex(bytecode, versionSelector)
if versionSelectorIndex == -1 {
return "", fmt.Errorf("failed to find semver selector in factory bytecode")
}

for i := versionSelectorIndex; i < len(bytecode); i++ {
if bytecode[i] == 0 {
continue
}

if i+patternLen > len(bytecode) {
break
}

slice := bytecode[i : i+patternLen]
if slice[0] == 0x00 {
continue
}

matches := semverRegexp.FindSubmatch(slice)
if len(matches) == 0 {
continue
}

return string(matches[1]), nil
}

return "", fmt.Errorf("failed to find semver in factory bytecode")
}
2 changes: 1 addition & 1 deletion op-deployer/pkg/deployer/opcm/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func ManagerOwnerAddrFor(chainID uint64) (common.Address, error) {
func StandardArtifactsURLForTag(tag string) (*url.URL, error) {
switch tag {
case "op-contracts/v1.6.0":
return url.Parse(standardArtifactsURL("5ff7fb7f5d3ff30d165fef6cec22c7af8eceb102dd964034762cf988a6678d51"))
return url.Parse(standardArtifactsURL("ee07c78c3d8d4cd8f7a933c050f5afeebaa281b57b226cc6f092b19de2a8d61f"))
case "op-contracts/v1.7.0-beta.1+l2-contracts":
return url.Parse(standardArtifactsURL("b0fb1f6f674519d637cff39a22187a5993d7f81a6d7b7be6507a0b50a5e38597"))
default:
Expand Down
8 changes: 8 additions & 0 deletions op-deployer/pkg/deployer/pipeline/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"strings"
"time"

"github.com/ethereum/go-ethereum/log"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"

"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
Expand All @@ -30,6 +32,12 @@ type CleanupFunc func() error

var noopCleanup = func() error { return nil }

func LogProgressor(lgr log.Logger) DownloadProgressor {
return func(curr, total int64) {
lgr.Info("artifacts download progress", "current", curr, "total", total)
}
}

func DownloadArtifacts(ctx context.Context, loc *opcm.ArtifactsLocator, progress DownloadProgressor) (foundry.StatDirFs, CleanupFunc, error) {
var u *url.URL
var err error
Expand Down

0 comments on commit bbb9e16

Please sign in to comment.