Commit f94151b6 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

op-deployer: Refactor semver inspector, add L2 genesis test (#12946)

- Refactor the semver inspector so that it can be called from outside the CLI
- Adds a a canonical L2 genesis file for v1.6.0, and a unit test to assert that new chains deployed using v1.6.0 have the right L2 genesis.
parent f6810a40
......@@ -24,6 +24,22 @@ var DefaultL2ContractsLocator = &Locator{
Tag: standard.DefaultL2ContractsTag,
}
func NewLocatorFromTag(tag string) (*Locator, error) {
loc := new(Locator)
if err := loc.UnmarshalText([]byte("tag://" + tag)); err != nil {
return nil, fmt.Errorf("failed to unmarshal tag: %w", err)
}
return loc, nil
}
func MustNewLocatorFromTag(tag string) *Locator {
loc, err := NewLocatorFromTag(tag)
if err != nil {
panic(err)
}
return loc
}
type Locator struct {
URL *url.URL
Tag string
......
......@@ -8,6 +8,10 @@ import (
"regexp"
"time"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"
......@@ -25,8 +29,6 @@ import (
"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 {
......@@ -67,6 +69,60 @@ func L2SemversCLI(cliCtx *cli.Context) error {
}
}()
ps, err := L2Semvers(L2SemversConfig{
Lgr: l,
Artifacts: artifactsFS,
ChainState: chainState,
})
if err != nil {
return fmt.Errorf("failed to get L2 semvers: %w", err)
}
if err := jsonutil.WriteJSON(ps, ioutil.ToStdOutOrFileOrNoop(cliCfg.Outfile, 0o666)); err != nil {
return fmt.Errorf("failed to write rollup config: %w", err)
}
return nil
}
type L2SemversConfig struct {
Lgr log.Logger
Artifacts foundry.StatDirFs
ChainState *state.ChainState
}
type L2PredeploySemvers struct {
L2ToL1MessagePasser string
DeployerWhitelist string
WETH string
L2CrossDomainMessenger string
L2StandardBridge string
SequencerFeeVault string
OptimismMintableERC20Factory string
L1BlockNumber string
GasPriceOracle string
L1Block string
LegacyMessagePasser string
L2ERC721Bridge string
OptimismMintableERC721Factory string
BaseFeeVault string
L1FeeVault string
SchemaRegistry string
EAS string
CrossL2Inbox string
L2toL2CrossDomainMessenger string
SuperchainWETH string
ETHLiquidity string
SuperchainTokenBridge string
OptimismMintableERC20 string
OptimismMintableERC721 string
}
func L2Semvers(cfg L2SemversConfig) (*L2PredeploySemvers, error) {
l := cfg.Lgr
artifactsFS := cfg.Artifacts
chainState := cfg.ChainState
host, err := env.DefaultScriptHost(
broadcaster.NoopBroadcaster(),
l,
......@@ -74,85 +130,89 @@ func L2SemversCLI(cliCtx *cli.Context) error {
artifactsFS,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
return nil, 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
FieldPtr *string
Name string
}
contractsOutput := make(map[string]string)
var ps L2PredeploySemvers
// 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"},
{predeploys.L2ToL1MessagePasserAddr, &ps.L2ToL1MessagePasser, "L2ToL1MessagePasser"},
{predeploys.DeployerWhitelistAddr, &ps.DeployerWhitelist, "DeployerWhitelist"},
{predeploys.WETHAddr, &ps.WETH, "WETH"},
{predeploys.L2CrossDomainMessengerAddr, &ps.L2CrossDomainMessenger, "L2CrossDomainMessenger"},
{predeploys.L2StandardBridgeAddr, &ps.L2StandardBridge, "L2StandardBridge"},
{predeploys.SequencerFeeVaultAddr, &ps.SequencerFeeVault, "SequencerFeeVault"},
{predeploys.OptimismMintableERC20FactoryAddr, &ps.OptimismMintableERC20Factory, "OptimismMintableERC20Factory"},
{predeploys.L1BlockNumberAddr, &ps.L1BlockNumber, "L1BlockNumber"},
{predeploys.GasPriceOracleAddr, &ps.GasPriceOracle, "GasPriceOracle"},
{predeploys.L1BlockAddr, &ps.L1Block, "L1Block"},
{predeploys.LegacyMessagePasserAddr, &ps.LegacyMessagePasser, "LegacyMessagePasser"},
{predeploys.L2ERC721BridgeAddr, &ps.L2ERC721Bridge, "L2ERC721Bridge"},
{predeploys.OptimismMintableERC721FactoryAddr, &ps.OptimismMintableERC721Factory, "OptimismMintableERC721Factory"},
{predeploys.BaseFeeVaultAddr, &ps.BaseFeeVault, "BaseFeeVault"},
{predeploys.L1FeeVaultAddr, &ps.L1FeeVault, "L1FeeVault"},
{predeploys.SchemaRegistryAddr, &ps.SchemaRegistry, "SchemaRegistry"},
{predeploys.EASAddr, &ps.EAS, "EAS"},
}
for _, contract := range contracts {
data, _, err := host.Call(
addr,
contract.Address,
bytes.Clone(versionSelector),
1_000_000_000,
uint256.NewInt(0),
)
semver, err := ReadSemver(host, contract.Address)
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")
return nil, fmt.Errorf("failed to read semver for %s: %w", contract.Name, err)
}
contractsOutput[contract.Name] = string(data[stringStart:stringEnd])
*contract.FieldPtr = semver
}
erc20Semver, err := findSemverBytecode(host, predeploys.OptimismMintableERC20FactoryAddr)
if err == nil {
contractsOutput["OptimismMintableERC20"] = erc20Semver
ps.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
ps.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 &ps, nil
}
var versionSelector = []byte{0x54, 0xfd, 0x4d, 0x50}
func ReadSemver(host *script.Host, addr common.Address) (string, error) {
data, _, err := host.Call(
common.Address{19: 0x01},
addr,
bytes.Clone(versionSelector),
1_000_000_000,
uint256.NewInt(0),
)
if err != nil {
return "", fmt.Errorf("failed to call version on %s: %w", addr, err)
}
return nil
// 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")
}
return string(data[stringStart:stringEnd]), nil
}
const patternLen = 24
......
package integration_test
import (
"bufio"
"bytes"
"compress/gzip"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"log/slog"
"maps"
"math/big"
"os"
"testing"
......@@ -182,13 +186,15 @@ func TestApplyExistingOPCM(t *testing.T) {
l2ChainID := uint256.NewInt(1)
// Hardcode the below tags to ensure the test is validating the correct
// version even if the underlying tag changes
intent, st := newIntent(
t,
l1ChainID,
dk,
l2ChainID,
artifacts.DefaultL1ContractsLocator,
artifacts.DefaultL2ContractsLocator,
artifacts.MustNewLocatorFromTag("op-contracts/v1.6.0"),
artifacts.MustNewLocatorFromTag("op-contracts/v1.7.0-beta.1+l2-contracts"),
)
// Define a new create2 salt to avoid contract address collisions
_, err = rand.Read(st.Create2Salt[:])
......@@ -231,6 +237,130 @@ func TestApplyExistingOPCM(t *testing.T) {
require.Equal(t, tt.expAddr, tt.actAddr)
})
}
artifactsFSL2, cleanupL2, err := artifacts.Download(
ctx,
intent.L2ContractsLocator,
artifacts.LogProgressor(lgr),
)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, cleanupL2())
})
chainState := st.Chains[0]
chainIntent := intent.Chains[0]
semvers, err := inspect.L2Semvers(inspect.L2SemversConfig{
Lgr: lgr,
Artifacts: artifactsFSL2,
ChainState: chainState,
})
require.NoError(t, err)
expectedSemversL2 := &inspect.L2PredeploySemvers{
L2ToL1MessagePasser: "1.1.1-beta.1",
DeployerWhitelist: "1.1.1-beta.1",
WETH: "1.0.0-beta.1",
L2CrossDomainMessenger: "2.1.1-beta.1",
L2StandardBridge: "1.11.1-beta.1",
SequencerFeeVault: "1.5.0-beta.2",
OptimismMintableERC20Factory: "1.10.1-beta.2",
L1BlockNumber: "1.1.1-beta.1",
GasPriceOracle: "1.3.1-beta.1",
L1Block: "1.5.1-beta.1",
LegacyMessagePasser: "1.1.1-beta.1",
L2ERC721Bridge: "1.7.1-beta.2",
OptimismMintableERC721Factory: "1.4.1-beta.1",
BaseFeeVault: "1.5.0-beta.2",
L1FeeVault: "1.5.0-beta.2",
SchemaRegistry: "1.3.1-beta.1",
EAS: "1.4.1-beta.1",
CrossL2Inbox: "",
L2toL2CrossDomainMessenger: "",
SuperchainWETH: "",
ETHLiquidity: "",
SuperchainTokenBridge: "",
OptimismMintableERC20: "1.4.0-beta.1",
OptimismMintableERC721: "1.3.1-beta.1",
}
require.EqualValues(t, expectedSemversL2, semvers)
f, err := os.Open("./testdata/allocs-l2-v160.json.gz")
require.NoError(t, err)
defer f.Close()
gzr, err := gzip.NewReader(f)
require.NoError(t, err)
defer gzr.Close()
dec := json.NewDecoder(bufio.NewReader(gzr))
var expAllocs types.GenesisAlloc
require.NoError(t, dec.Decode(&expAllocs))
type storageCheckerFunc func(addr common.Address, actStorage map[common.Hash]common.Hash)
storageDiff := func(addr common.Address, expStorage, actStorage map[common.Hash]common.Hash) {
require.EqualValues(t, expStorage, actStorage, "storage for %s differs", addr)
}
defaultStorageChecker := func(addr common.Address, actStorage map[common.Hash]common.Hash) {
storageDiff(addr, expAllocs[addr].Storage, actStorage)
}
overrideStorageChecker := func(addr common.Address, actStorage, overrides map[common.Hash]common.Hash) {
expStorage := make(map[common.Hash]common.Hash)
maps.Copy(expStorage, expAllocs[addr].Storage)
maps.Copy(expStorage, overrides)
storageDiff(addr, expStorage, actStorage)
}
storageCheckers := map[common.Address]storageCheckerFunc{
predeploys.L2CrossDomainMessengerAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{31: 0xcf}: common.BytesToHash(chainState.L1CrossDomainMessengerProxyAddress.Bytes()),
})
},
predeploys.L2StandardBridgeAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{31: 0x04}: common.BytesToHash(chainState.L1StandardBridgeProxyAddress.Bytes()),
})
},
predeploys.L2ERC721BridgeAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{31: 0x02}: common.BytesToHash(chainState.L1ERC721BridgeProxyAddress.Bytes()),
})
},
predeploys.ProxyAdminAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{}: common.BytesToHash(intent.Chains[0].Roles.L2ProxyAdminOwner.Bytes()),
})
},
// The ProxyAdmin owner is also set on the ProxyAdmin contract's implementation address, see
// L2Genesis.s.sol line 292.
common.HexToAddress("0xc0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30018"): func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{}: common.BytesToHash(chainIntent.Roles.L2ProxyAdminOwner.Bytes()),
})
},
}
//Use a custom equality function to compare the genesis allocs
//because the reflect-based one is really slow
actAllocs := st.Chains[0].Allocs.Data.Accounts
require.Equal(t, len(expAllocs), len(actAllocs))
for addr, expAcc := range expAllocs {
actAcc, ok := actAllocs[addr]
require.True(t, ok)
require.True(t, expAcc.Balance.Cmp(actAcc.Balance) == 0, "balance for %s differs", addr)
require.Equal(t, expAcc.Nonce, actAcc.Nonce, "nonce for %s differs", addr)
require.Equal(t, hex.EncodeToString(expAllocs[addr].Code), hex.EncodeToString(actAcc.Code), "code for %s differs", addr)
storageChecker, ok := storageCheckers[addr]
if !ok {
storageChecker = defaultStorageChecker
}
storageChecker(addr, actAcc.Storage)
}
}
func TestL2BlockTimeOverride(t *testing.T) {
......
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