diff --git a/op-deployer/pkg/deployer/artifacts/locator.go b/op-deployer/pkg/deployer/artifacts/locator.go index 160e8790420b2..aa44d43644c8d 100644 --- a/op-deployer/pkg/deployer/artifacts/locator.go +++ b/op-deployer/pkg/deployer/artifacts/locator.go @@ -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 diff --git a/op-deployer/pkg/deployer/inspect/semvers.go b/op-deployer/pkg/deployer/inspect/semvers.go index da666096ee183..48e16d21dbc43 100644 --- a/op-deployer/pkg/deployer/inspect/semvers.go +++ b/op-deployer/pkg/deployer/inspect/semvers.go @@ -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 - Name string + 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 diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index bcbc14eb7f194..7f4c0cb06deb6 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -1,12 +1,16 @@ 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) { diff --git a/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160.json.gz b/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160.json.gz new file mode 100644 index 0000000000000..545dc3397bdd3 Binary files /dev/null and b/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160.json.gz differ