From 09dc9d5ef3b7b7b92cd20c95c8efebf2c73be319 Mon Sep 17 00:00:00 2001 From: serpixel <5087962+serpixel@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:30:15 +0100 Subject: [PATCH 1/6] feat(op-validator): add v5.0.0 contracts support --- op-deployer/pkg/deployer/opcm/contract.go | 4 + op-deployer/pkg/deployer/standard/standard.go | 3 +- op-validator/cmd/main.go | 2 + op-validator/pkg/service/flags.go | 26 +++ op-validator/pkg/service/validate.go | 44 +++- op-validator/pkg/validations/addresses.go | 8 + .../pkg/validations/addresses_test.go | 195 +++++++++++------- op-validator/pkg/validations/codes.go | 37 ++++ op-validator/pkg/validations/validations.go | 83 +++++++- .../pkg/validations/validations_test.go | 12 ++ 10 files changed, 330 insertions(+), 84 deletions(-) diff --git a/op-deployer/pkg/deployer/opcm/contract.go b/op-deployer/pkg/deployer/opcm/contract.go index 8d02f77e8e0..b928c49e132 100644 --- a/op-deployer/pkg/deployer/opcm/contract.go +++ b/op-deployer/pkg/deployer/opcm/contract.go @@ -28,6 +28,10 @@ func (c *Contract) ProtocolVersions(ctx context.Context) (common.Address, error) return c.getAddress(ctx, "protocolVersions") } +func (c *Contract) OPCMStandardValidator(ctx context.Context) (common.Address, error) { + return c.getAddress(ctx, "opcmStandardValidator") +} + func (c *Contract) getAddress(ctx context.Context, name string) (common.Address, error) { return c.callContractMethod(ctx, name, abi.Arguments{}) } diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go index 5ec49224b86..e36060c63da 100644 --- a/op-deployer/pkg/deployer/standard/standard.go +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -42,6 +42,7 @@ const ( ContractsV300Tag = "op-contracts/v3.0.0" ContractsV400Tag = "op-contracts/v4.0.0-rc.7" ContractsV410Tag = "op-contracts/v4.1.0" + ContractsV500Tag = "op-contracts/v5.0.0-rc.2" CurrentTag = ContractsV410Tag ) @@ -184,7 +185,7 @@ func DefaultHardforkScheduleForTag(tag string) *genesis.UpgradeScheduleDeployCon return sched case ContractsV180Tag, ContractsV200Tag, ContractsV300Tag: sched.ActivateForkAtGenesis(forks.Holocene) - case ContractsV400Tag, ContractsV410Tag: + case ContractsV400Tag, ContractsV410Tag, ContractsV500Tag: sched.ActivateForkAtGenesis(forks.Holocene) sched.ActivateForkAtGenesis(forks.Isthmus) default: diff --git a/op-validator/cmd/main.go b/op-validator/cmd/main.go index 30ab65b9490..10767dc3cdc 100644 --- a/op-validator/cmd/main.go +++ b/op-validator/cmd/main.go @@ -36,6 +36,8 @@ func main() { versionCmd(standard.ContractsV200Tag), versionCmd(standard.ContractsV300Tag), versionCmd(standard.ContractsV400Tag), + versionCmd(standard.ContractsV410Tag), + versionCmd(standard.ContractsV500Tag), }, }, } diff --git a/op-validator/pkg/service/flags.go b/op-validator/pkg/service/flags.go index cba8fbd1764..642e22827ec 100644 --- a/op-validator/pkg/service/flags.go +++ b/op-validator/pkg/service/flags.go @@ -34,6 +34,16 @@ var ( Usage: "L2 chain ID", Required: true, } + ProposerFlag = &cli.StringFlag{ + Name: "proposer", + Usage: "Proposer address as hex string (required for OPCMStandardValidator)", + Required: false, + } + ValidatorAddressFlag = &cli.StringFlag{ + Name: "validator-address", + Usage: "OPCMStandardValidator contract address (required for custom deployments)", + Required: false, + } FailOnErrorFlag = &cli.BoolFlag{ Name: "fail", Usage: "Exit with non-zero code if validation errors are found", @@ -48,6 +58,8 @@ var ValidateFlags = []cli.Flag{ ProxyAdminFlag, SystemConfigFlag, L2ChainIDFlag, + ProposerFlag, + ValidatorAddressFlag, FailOnErrorFlag, } @@ -58,6 +70,8 @@ type Config struct { ProxyAdmin common.Address SystemConfig common.Address L2ChainID *big.Int + Proposer common.Address + ValidatorAddress common.Address } // NewConfig creates a new Config from CLI context @@ -70,11 +84,23 @@ func NewConfig(ctx *cli.Context) (*Config, error) { return nil, fmt.Errorf("invalid L2 chain ID: %s", ctx.String(L2ChainIDFlag.Name)) } + var proposer common.Address + if proposerStr := ctx.String(ProposerFlag.Name); proposerStr != "" { + proposer = common.HexToAddress(proposerStr) + } + + var validatorAddr common.Address + if validatorAddrStr := ctx.String(ValidatorAddressFlag.Name); validatorAddrStr != "" { + validatorAddr = common.HexToAddress(validatorAddrStr) + } + return &Config{ L1RPCURL: ctx.String(L1RPCURLFlag.Name), AbsolutePrestate: absolutePrestate, ProxyAdmin: proxyAdmin, SystemConfig: systemConfig, L2ChainID: l2ChainID, + Proposer: proposer, + ValidatorAddress: validatorAddr, }, nil } diff --git a/op-validator/pkg/service/validate.go b/op-validator/pkg/service/validate.go index 846ee2743ff..50627220501 100644 --- a/op-validator/pkg/service/validate.go +++ b/op-validator/pkg/service/validate.go @@ -8,6 +8,7 @@ import ( oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/optimism/op-validator/pkg/validations" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/urfave/cli/v2" @@ -46,17 +47,37 @@ func Validate(ctx context.Context, lgr log.Logger, release string, cfg *Config) } var validator validations.Validator - switch release { - case standard.ContractsV180Tag: - validator = validations.NewV180Validator(l1Client) - case standard.ContractsV200Tag: - validator = validations.NewV200Validator(l1Client) - case standard.ContractsV300Tag: - validator = validations.NewV300Validator(l1Client) - case standard.ContractsV400Tag: - validator = validations.NewV400Validator(l1Client) - default: - return nil, fmt.Errorf("invalid release: %s", release) + + // Use new validator if: + // 1. Version is v5.0.0+, OR + // 2. Validator address is provided (for custom deployments with OPCMStandardValidator) + useNewValidator := release == standard.ContractsV500Tag || cfg.ValidatorAddress != (common.Address{}) + + if useNewValidator { + if cfg.ValidatorAddress == (common.Address{}) { + return nil, fmt.Errorf("validator-address is required for OPCMStandardValidator (v5.0.0+ or custom deployments)") + } + if cfg.Proposer == (common.Address{}) { + return nil, fmt.Errorf("proposer address is required for OPCMStandardValidator") + } + validator = validations.NewOPCMStandardValidator(l1Client, cfg.ValidatorAddress) + lgr.Info("Using OPCMStandardValidator", "validator", cfg.ValidatorAddress.Hex(), "proposer", cfg.Proposer.Hex()) + } else { + switch release { + case standard.ContractsV180Tag: + validator = validations.NewV180Validator(l1Client) + case standard.ContractsV200Tag: + validator = validations.NewV200Validator(l1Client) + case standard.ContractsV300Tag: + validator = validations.NewV300Validator(l1Client) + case standard.ContractsV400Tag: + validator = validations.NewV400Validator(l1Client) + case standard.ContractsV410Tag: + validator = validations.NewV410Validator(l1Client) + default: + return nil, fmt.Errorf("invalid release: %s", release) + } + lgr.Info("Using BaseValidator", "version", release) } return validator.Validate(ctx, validations.BaseValidatorInput{ @@ -64,5 +85,6 @@ func Validate(ctx context.Context, lgr log.Logger, release string, cfg *Config) SystemConfigAddress: cfg.SystemConfig, AbsolutePrestate: cfg.AbsolutePrestate, L2ChainID: cfg.L2ChainID, + Proposer: cfg.Proposer, }) } diff --git a/op-validator/pkg/validations/addresses.go b/op-validator/pkg/validations/addresses.go index 92891f60f6e..96c0fe41490 100644 --- a/op-validator/pkg/validations/addresses.go +++ b/op-validator/pkg/validations/addresses.go @@ -18,6 +18,10 @@ var addresses = map[uint64]map[string]common.Address{ standard.ContractsV300Tag: common.HexToAddress("0xf989Df70FB46c581ba6157Ab335c0833bA60e1f0"), // Bootstrapped on 06/03/2025 using OP Deployer. standard.ContractsV400Tag: common.HexToAddress("0x3dfc5e44043DC5998928E0b8280136b7352d3F70"), + // Bootstrapped on 10/02/2025 using OP Deployer. + standard.ContractsV410Tag: common.HexToAddress("0x845FEF377Fa9C678B3eBe33B024678538f1215dD"), + // Bootstrapped on 10/27/2025 using OP Deployer (v5.0.0-rc.2). + standard.ContractsV500Tag: common.HexToAddress("0xDCE1A51A25dD5BF02ccB4264D039EDdF11A95b43"), }, 11155111: { // Bootstrapped on 03/02/2025 using OP Deployer. @@ -28,6 +32,10 @@ var addresses = map[uint64]map[string]common.Address{ standard.ContractsV300Tag: common.HexToAddress("0x2d56022cb84ce6b961c3b4288ca36386bcd9024c"), // Bootstrapped on 06/03/2025 using OP Deployer. standard.ContractsV400Tag: common.HexToAddress("0xA8a1529547306FEC7A32a001705160f2110451aE"), + // Bootstrapped on 10/02/2025 using OP Deployer. + standard.ContractsV410Tag: common.HexToAddress("0x7B4d2a02d5fa6C7C98D835d819956EBB876Ff439"), + // Bootstrapped on 10/27/2025 using OP Deployer (v5.0.0-rc.2). + standard.ContractsV500Tag: common.HexToAddress("0x757bFA3AAABcE60112Cee3239DCD05b5F6EFaE3A"), }, } diff --git a/op-validator/pkg/validations/addresses_test.go b/op-validator/pkg/validations/addresses_test.go index 8cd670f7342..79a2be48086 100644 --- a/op-validator/pkg/validations/addresses_test.go +++ b/op-validator/pkg/validations/addresses_test.go @@ -9,8 +9,6 @@ import ( "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" - "github.com/ethereum-optimism/superchain-registry/validation" "github.com/ethereum/go-ethereum/rpc" "github.com/lmittmann/w3" @@ -74,11 +72,11 @@ func TestValidatorAddress(t *testing.T) { } func TestAddressValidDeployment(t *testing.T) { - op_e2e.InitParallel(t) + t.Parallel() for _, network := range []string{"mainnet", "sepolia"} { t.Run(network, func(t *testing.T) { - op_e2e.InitParallel(t) + t.Parallel() testStandardVersionNetwork(t, network) }) } @@ -90,114 +88,169 @@ func testStandardVersionNetwork(t *testing.T, network string) { var chainID uint64 if network == "mainnet" { rpcURL = os.Getenv("MAINNET_RPC_URL") + if rpcURL == "" { + rpcURL = "https://ethereum.publicnode.com" + } stdVersDefs = validation.StandardVersionsMainnet chainID = 1 } else if network == "sepolia" { rpcURL = os.Getenv("SEPOLIA_RPC_URL") + if rpcURL == "" { + rpcURL = "https://ethereum-sepolia-rpc.publicnode.com" + } stdVersDefs = validation.StandardVersionsSepolia chainID = 11155111 } else { t.Fatalf("Invalid network: %s", network) } - require.NotEmpty(t, rpcURL, "RPC URL is empty") - contractVersions := []string{ standard.ContractsV180Tag, standard.ContractsV200Tag, standard.ContractsV300Tag, standard.ContractsV400Tag, + standard.ContractsV410Tag, + standard.ContractsV500Tag, } for _, semver := range contractVersions { - version := stdVersDefs[validation.Semver(semver)] + version, ok := stdVersDefs[validation.Semver(semver)] + require.True(t, ok, "version %s not found in registry", semver) address, err := ValidatorAddress(chainID, semver) - require.NoError(t, err) + require.NoError(t, err, "failed to get validator address for %s", semver) + require.NotEqual(t, common.Address{}, address, "validator address is zero for %s", semver) rpcClient, err := rpc.Dial(rpcURL) require.NoError(t, err) t.Run(semver, func(t *testing.T) { - testStandardVersion(t, address, rpcClient, version) + testStandardVersion(t, address, rpcClient, version, semver) }) } } -func testStandardVersion(t *testing.T, address common.Address, rpcClient *rpc.Client, version validation.VersionConfig) { - type fieldDef struct { - getter string - semver string - } - fields := []fieldDef{ - { - "systemConfigVersion", - version.SystemConfig.Version, - }, - { - "mipsVersion", - version.Mips.Version, - }, - { - "optimismPortalVersion", - version.OptimismPortal.Version, - }, - { - "anchorStateRegistryVersion", - version.AnchorStateRegistry.Version, - }, - { - "delayedWETHVersion", - version.DelayedWeth.Version, - }, - { - "disputeGameFactoryVersion", - version.DisputeGameFactory.Version, - }, - { - "preimageOracleVersion", - version.PreimageOracle.Version, - }, - { - "l1CrossDomainMessengerVersion", - version.L1CrossDomainMessenger.Version, - }, - { - "l1ERC721BridgeVersion", - version.L1ERC721Bridge.Version, - }, - { - "l1StandardBridgeVersion", - version.L1StandardBridge.Version, - }, - { - "optimismMintableERC20FactoryVersion", - version.OptimismMintableERC20Factory.Version, - }, - } - +func testStandardVersion(t *testing.T, address common.Address, rpcClient *rpc.Client, version validation.VersionConfig, semverTag string) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() w3c := w3.NewClient(rpcClient) - for _, field := range fields { - fn := w3.MustNewFunc(fmt.Sprintf("%s()", field.getter), "string") - var outBytes []byte + + if semverTag == standard.ContractsV500Tag { + // For v5.0.0+ + type implFieldDef struct { + implGetter string + semver string + } + implFields := []implFieldDef{ + {"systemConfigImpl", version.SystemConfig.Version}, + {"mipsImpl", version.Mips.Version}, + {"optimismPortalImpl", version.OptimismPortal.Version}, + {"anchorStateRegistryImpl", version.AnchorStateRegistry.Version}, + {"delayedWETHImpl", version.DelayedWeth.Version}, + {"disputeGameFactoryImpl", version.DisputeGameFactory.Version}, + {"l1CrossDomainMessengerImpl", version.L1CrossDomainMessenger.Version}, + {"l1ERC721BridgeImpl", version.L1ERC721Bridge.Version}, + {"l1StandardBridgeImpl", version.L1StandardBridge.Version}, + {"optimismMintableERC20FactoryImpl", version.OptimismMintableERC20Factory.Version}, + } + + versionFn := w3.MustNewFunc("version()", "string") + for _, field := range implFields { + implGetterFn := w3.MustNewFunc(fmt.Sprintf("%s()", field.implGetter), "address") + var implAddrBytes []byte + require.NoError( + t, + w3c.CallCtx( + ctx, + eth.Call(&w3types.Message{ + To: &address, + Func: implGetterFn, + }, nil, nil).Returns(&implAddrBytes), + ), + "failed to call %s", + field.implGetter, + ) + + var implAddr common.Address + require.NoError(t, implGetterFn.DecodeReturns(implAddrBytes, &implAddr), "failed to decode %s", field.implGetter) + require.NotEqual(t, common.Address{}, implAddr, "implementation address is zero for %s", field.implGetter) + + var versionBytes []byte + require.NoError( + t, + w3c.CallCtx( + ctx, + eth.Call(&w3types.Message{ + To: &implAddr, + Func: versionFn, + }, nil, nil).Returns(&versionBytes), + ), + "failed to call version() on %s implementation", + field.implGetter, + ) + + var outVersion string + require.NoError(t, versionFn.DecodeReturns(versionBytes, &outVersion), "failed to decode version for %s", field.implGetter) + require.Equal(t, field.semver, outVersion, "version mismatch for %s", field.implGetter) + } + + preimageOracleVersionFn := w3.MustNewFunc("preimageOracleVersion()", "string") + var preimageOracleVersionBytes []byte require.NoError( t, w3c.CallCtx( ctx, eth.Call(&w3types.Message{ To: &address, - Func: fn, - }, nil, nil).Returns(&outBytes), + Func: preimageOracleVersionFn, + }, nil, nil).Returns(&preimageOracleVersionBytes), ), - "failed to call %s", - field.getter, + "failed to call preimageOracleVersion", ) - var outVersion string - require.NoError(t, fn.DecodeReturns(outBytes, &outVersion)) - require.Equal(t, field.semver, outVersion) + var preimageOracleVersion string + require.NoError(t, preimageOracleVersionFn.DecodeReturns(preimageOracleVersionBytes, &preimageOracleVersion), "failed to decode preimageOracleVersion") + require.Equal(t, version.PreimageOracle.Version, preimageOracleVersion, "version mismatch for preimageOracleVersion") + } else { + // Older versions < v5.0.0 + type fieldDef struct { + getter string + semver string + } + fields := []fieldDef{ + {"systemConfigVersion", version.SystemConfig.Version}, + {"mipsVersion", version.Mips.Version}, + {"optimismPortalVersion", version.OptimismPortal.Version}, + {"anchorStateRegistryVersion", version.AnchorStateRegistry.Version}, + {"delayedWETHVersion", version.DelayedWeth.Version}, + {"disputeGameFactoryVersion", version.DisputeGameFactory.Version}, + {"preimageOracleVersion", version.PreimageOracle.Version}, + {"l1CrossDomainMessengerVersion", version.L1CrossDomainMessenger.Version}, + {"l1ERC721BridgeVersion", version.L1ERC721Bridge.Version}, + {"l1StandardBridgeVersion", version.L1StandardBridge.Version}, + {"optimismMintableERC20FactoryVersion", version.OptimismMintableERC20Factory.Version}, + } + + for _, field := range fields { + fn := w3.MustNewFunc(fmt.Sprintf("%s()", field.getter), "string") + var outBytes []byte + require.NoError( + t, + w3c.CallCtx( + ctx, + eth.Call(&w3types.Message{ + To: &address, + Func: fn, + }, nil, nil).Returns(&outBytes), + ), + "failed to call %s", + field.getter, + ) + + var outVersion string + require.NoError(t, fn.DecodeReturns(outBytes, &outVersion), "failed to decode response for %s", field.getter) + require.Equal(t, field.semver, outVersion, "version mismatch for %s", field.getter) + } } } diff --git a/op-validator/pkg/validations/codes.go b/op-validator/pkg/validations/codes.go index 731366ee5f7..bdd4e42df89 100644 --- a/op-validator/pkg/validations/codes.go +++ b/op-validator/pkg/validations/codes.go @@ -93,6 +93,8 @@ var descriptions = map[string]string{ "PDDG-100": "Permissioned dispute game max game depth not set to 73", "PDDG-110": "Permissioned dispute game max clock duration not set to 302400", "PDDG-120": "Permissioned dispute game challenger address mismatch", + "PDDG-130": "Permissioned dispute game challenger address mismatch (from game implementation)", + "PDDG-140": "Permissioned dispute game proposer address mismatch", // Permissionless Dispute Game validations "PLDG-10": "Permissionless dispute game implementation not found", @@ -142,6 +144,41 @@ var descriptions = map[string]string{ "PLDG-PIMGO-10": "Permissionless dispute game preimage oracle version mismatch", "PLDG-PIMGO-20": "Permissionless dispute game preimage oracle challenge period not set to 86400", "PLDG-PIMGO-30": "Permissionless dispute game preimage oracle min proposal size not set to 126000", + + // Custom/Override validations + "OVERRIDES-L1PAOMULTISIG": "L1 Proxy Admin Owner multisig override detected (non-standard deployment)", + "OVERRIDES-CHALLENGER": "Challenger address override detected (non-standard deployment)", + + // Custom Dispute Game validations (CKDG) + "CKDG-10": "Custom dispute game implementation not found", + "CKDG-20": "Custom dispute game version mismatch", + "CKDG-40": "Custom dispute game absolute prestate mismatch", + "CKDG-60": "Custom dispute game L2 chain ID mismatch", + "CKDG-70": "Custom dispute game L2 block number not set to 0", + "CKDG-80": "Custom dispute game clock extension not set to 10800", + "CKDG-90": "Custom dispute game split depth not set to 30", + "CKDG-100": "Custom dispute game max game depth not set to 73", + "CKDG-110": "Custom dispute game max clock duration not set to 302400", + "CKDG-120": "Custom dispute game challenger address mismatch", + "CKDG-VM-10": "Custom dispute game VM version mismatch", + "CKDG-VM-20": "Custom dispute game VM implementation address mismatch", + "CKDG-VM-30": "Custom dispute game VM address mismatch", + "CKDG-ANCHORP-10": "Custom dispute game anchor state registry version mismatch", + "CKDG-ANCHORP-20": "Custom dispute game anchor state registry implementation address mismatch", + "CKDG-ANCHORP-30": "Custom dispute game anchor state registry dispute game factory address mismatch", + "CKDG-ANCHORP-40": "Custom dispute game anchor state registry root hash mismatch", + "CKDG-ANCHORP-50": "Custom dispute game anchor state registry superchain config address mismatch", + "CKDG-ANCHORP-60": "Custom dispute game anchor state registry retirement timestamp is not set", + "CKDG-DWETH-10": "Custom dispute game delayed WETH version mismatch", + "CKDG-DWETH-20": "Custom dispute game delayed WETH implementation address mismatch", + "CKDG-DWETH-30": "Custom dispute game delayed WETH owner mismatch", + "CKDG-DWETH-40": "Custom dispute game delayed WETH delay not set to 1 week", + "CKDG-DWETH-50": "Custom dispute game delayed WETH system config address mismatch", + "CKDG-DWETH-60": "Custom dispute game delayed WETH proxy admin mismatch", + "CKDG-PIMGO-10": "Custom dispute game preimage oracle version mismatch", + "CKDG-PIMGO-20": "Custom dispute game preimage oracle challenge period not set to 86400", + "CKDG-PIMGO-30": "Custom dispute game preimage oracle min proposal size not set to 126000", + "CKDG-GARGS-10": "Custom dispute game game args mismatch", } func ErrorDescription(code string) string { diff --git a/op-validator/pkg/validations/validations.go b/op-validator/pkg/validations/validations.go index 0d670015d56..c82d918228a 100644 --- a/op-validator/pkg/validations/validations.go +++ b/op-validator/pkg/validations/validations.go @@ -18,6 +18,8 @@ import ( var validateFunc = w3.MustNewFunc("validate((address proxyAdminAddress,address systemConfigAddress,bytes32 absolutePrestate,uint256 chainID) input,bool allowFailure)", "string") +var validateFuncOpcmValidator = w3.MustNewFunc("validate((address sysCfg,bytes32 absolutePrestate,uint256 l2ChainID,address proposer) input,bool allowFailure)", "string") + type validateFuncArgs struct { ProxyAdminAddress common.Address SystemConfigAddress common.Address @@ -25,6 +27,13 @@ type validateFuncArgs struct { ChainID *big.Int } +type validateFuncArgsOpcmValidator struct { + SysCfg common.Address `w3:"sysCfg"` + AbsolutePrestate common.Hash `w3:"absolutePrestate"` + L2ChainID *big.Int `w3:"l2ChainID"` + Proposer common.Address `w3:"proposer"` +} + type Validator interface { Validate(ctx context.Context, input BaseValidatorInput) ([]string, error) } @@ -34,11 +43,17 @@ type BaseValidator struct { release string } +type OPCMStandardValidator struct { + client *rpc.Client + validatorAddr common.Address +} + type BaseValidatorInput struct { ProxyAdminAddress common.Address SystemConfigAddress common.Address AbsolutePrestate common.Hash L2ChainID *big.Int + Proposer common.Address } func newBaseValidator(client *rpc.Client, release string) *BaseValidator { @@ -80,7 +95,50 @@ func (v *BaseValidator) Validate(ctx context.Context, input BaseValidatorInput) if err := validateFunc.DecodeReturns(rawOutput, &output); err != nil { return nil, fmt.Errorf("failed to unmarshal output: %w", err) } - return strings.Split(output, ","), nil + return parseErrors(output), nil +} + +func (v *OPCMStandardValidator) Validate(ctx context.Context, input BaseValidatorInput) ([]string, error) { + if input.Proposer == (common.Address{}) { + return nil, fmt.Errorf("proposer address is required for OPCMStandardValidator") + } + + if v.validatorAddr == (common.Address{}) { + return nil, fmt.Errorf("validator address is required for OPCMStandardValidator") + } + + var rawOutput []byte + if err := w3.NewClient(v.client).CallCtx( + ctx, + eth.Call(&w3types.Message{ + To: &v.validatorAddr, + Func: validateFuncOpcmValidator, + Args: []any{ + validateFuncArgsOpcmValidator{ + SysCfg: input.SystemConfigAddress, + AbsolutePrestate: input.AbsolutePrestate, + L2ChainID: input.L2ChainID, + Proposer: input.Proposer, + }, + true, + }, + }, nil, nil).Returns(&rawOutput), + ); err != nil { + return nil, fmt.Errorf("failed to call validate: %w", err) + } + + var output string + if err := validateFuncOpcmValidator.DecodeReturns(rawOutput, &output); err != nil { + return nil, fmt.Errorf("failed to unmarshal output: %w", err) + } + return parseErrors(output), nil +} + +func NewOPCMStandardValidator(client *rpc.Client, validatorAddr common.Address) *OPCMStandardValidator { + return &OPCMStandardValidator{ + client: client, + validatorAddr: validatorAddr, + } } func NewV180Validator(client *rpc.Client) *BaseValidator { @@ -98,3 +156,26 @@ func NewV300Validator(client *rpc.Client) *BaseValidator { func NewV400Validator(client *rpc.Client) *BaseValidator { return newBaseValidator(client, standard.ContractsV400Tag) } + +func NewV410Validator(client *rpc.Client) *BaseValidator { + return newBaseValidator(client, standard.ContractsV410Tag) +} + +func NewV500Validator(client *rpc.Client) *BaseValidator { + return newBaseValidator(client, standard.ContractsV500Tag) +} + +func parseErrors(output string) []string { + if idx := strings.Index(output, ":"); idx != -1 && strings.HasPrefix(output, "Chain") { + output = output[idx+1:] + } + parts := strings.Split(output, ",") + var errors []string + for _, part := range parts { + part = strings.TrimSpace(part) + if part != "" { + errors = append(errors, part) + } + } + return errors +} diff --git a/op-validator/pkg/validations/validations_test.go b/op-validator/pkg/validations/validations_test.go index cd9e462fa64..9ec05f14e92 100644 --- a/op-validator/pkg/validations/validations_test.go +++ b/op-validator/pkg/validations/validations_test.go @@ -60,6 +60,18 @@ func TestValidate_Mocked(t *testing.T) { return NewV400Validator(rpcClient) }, }, + { + version: standard.ContractsV410Tag, + validator: func(rpcClient *rpc.Client) Validator { + return NewV410Validator(rpcClient) + }, + }, + { + version: standard.ContractsV500Tag, + validator: func(rpcClient *rpc.Client) Validator { + return NewV500Validator(rpcClient) + }, + }, } for _, tt := range tests { t.Run(string(tt.version), func(t *testing.T) { From d0c0043b9dba7981952fd88d3e30e62c30835d87 Mon Sep 17 00:00:00 2001 From: serpixel <5087962+serpixel@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:34:28 +0100 Subject: [PATCH 2/6] fix(op-validator): lint --- op-validator/pkg/service/validate.go | 3 -- op-validator/pkg/validations/codes.go | 48 +++++++++++++-------------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/op-validator/pkg/service/validate.go b/op-validator/pkg/service/validate.go index 50627220501..9d993eab151 100644 --- a/op-validator/pkg/service/validate.go +++ b/op-validator/pkg/service/validate.go @@ -48,9 +48,6 @@ func Validate(ctx context.Context, lgr log.Logger, release string, cfg *Config) var validator validations.Validator - // Use new validator if: - // 1. Version is v5.0.0+, OR - // 2. Validator address is provided (for custom deployments with OPCMStandardValidator) useNewValidator := release == standard.ContractsV500Tag || cfg.ValidatorAddress != (common.Address{}) if useNewValidator { diff --git a/op-validator/pkg/validations/codes.go b/op-validator/pkg/validations/codes.go index bdd4e42df89..3f9c4a3deb1 100644 --- a/op-validator/pkg/validations/codes.go +++ b/op-validator/pkg/validations/codes.go @@ -147,38 +147,38 @@ var descriptions = map[string]string{ // Custom/Override validations "OVERRIDES-L1PAOMULTISIG": "L1 Proxy Admin Owner multisig override detected (non-standard deployment)", - "OVERRIDES-CHALLENGER": "Challenger address override detected (non-standard deployment)", + "OVERRIDES-CHALLENGER": "Challenger address override detected (non-standard deployment)", // Custom Dispute Game validations (CKDG) - "CKDG-10": "Custom dispute game implementation not found", - "CKDG-20": "Custom dispute game version mismatch", - "CKDG-40": "Custom dispute game absolute prestate mismatch", - "CKDG-60": "Custom dispute game L2 chain ID mismatch", - "CKDG-70": "Custom dispute game L2 block number not set to 0", - "CKDG-80": "Custom dispute game clock extension not set to 10800", - "CKDG-90": "Custom dispute game split depth not set to 30", - "CKDG-100": "Custom dispute game max game depth not set to 73", - "CKDG-110": "Custom dispute game max clock duration not set to 302400", - "CKDG-120": "Custom dispute game challenger address mismatch", - "CKDG-VM-10": "Custom dispute game VM version mismatch", - "CKDG-VM-20": "Custom dispute game VM implementation address mismatch", - "CKDG-VM-30": "Custom dispute game VM address mismatch", + "CKDG-10": "Custom dispute game implementation not found", + "CKDG-20": "Custom dispute game version mismatch", + "CKDG-40": "Custom dispute game absolute prestate mismatch", + "CKDG-60": "Custom dispute game L2 chain ID mismatch", + "CKDG-70": "Custom dispute game L2 block number not set to 0", + "CKDG-80": "Custom dispute game clock extension not set to 10800", + "CKDG-90": "Custom dispute game split depth not set to 30", + "CKDG-100": "Custom dispute game max game depth not set to 73", + "CKDG-110": "Custom dispute game max clock duration not set to 302400", + "CKDG-120": "Custom dispute game challenger address mismatch", + "CKDG-VM-10": "Custom dispute game VM version mismatch", + "CKDG-VM-20": "Custom dispute game VM implementation address mismatch", + "CKDG-VM-30": "Custom dispute game VM address mismatch", "CKDG-ANCHORP-10": "Custom dispute game anchor state registry version mismatch", "CKDG-ANCHORP-20": "Custom dispute game anchor state registry implementation address mismatch", "CKDG-ANCHORP-30": "Custom dispute game anchor state registry dispute game factory address mismatch", "CKDG-ANCHORP-40": "Custom dispute game anchor state registry root hash mismatch", "CKDG-ANCHORP-50": "Custom dispute game anchor state registry superchain config address mismatch", "CKDG-ANCHORP-60": "Custom dispute game anchor state registry retirement timestamp is not set", - "CKDG-DWETH-10": "Custom dispute game delayed WETH version mismatch", - "CKDG-DWETH-20": "Custom dispute game delayed WETH implementation address mismatch", - "CKDG-DWETH-30": "Custom dispute game delayed WETH owner mismatch", - "CKDG-DWETH-40": "Custom dispute game delayed WETH delay not set to 1 week", - "CKDG-DWETH-50": "Custom dispute game delayed WETH system config address mismatch", - "CKDG-DWETH-60": "Custom dispute game delayed WETH proxy admin mismatch", - "CKDG-PIMGO-10": "Custom dispute game preimage oracle version mismatch", - "CKDG-PIMGO-20": "Custom dispute game preimage oracle challenge period not set to 86400", - "CKDG-PIMGO-30": "Custom dispute game preimage oracle min proposal size not set to 126000", - "CKDG-GARGS-10": "Custom dispute game game args mismatch", + "CKDG-DWETH-10": "Custom dispute game delayed WETH version mismatch", + "CKDG-DWETH-20": "Custom dispute game delayed WETH implementation address mismatch", + "CKDG-DWETH-30": "Custom dispute game delayed WETH owner mismatch", + "CKDG-DWETH-40": "Custom dispute game delayed WETH delay not set to 1 week", + "CKDG-DWETH-50": "Custom dispute game delayed WETH system config address mismatch", + "CKDG-DWETH-60": "Custom dispute game delayed WETH proxy admin mismatch", + "CKDG-PIMGO-10": "Custom dispute game preimage oracle version mismatch", + "CKDG-PIMGO-20": "Custom dispute game preimage oracle challenge period not set to 86400", + "CKDG-PIMGO-30": "Custom dispute game preimage oracle min proposal size not set to 126000", + "CKDG-GARGS-10": "Custom dispute game game args mismatch", } func ErrorDescription(code string) string { From 3de61c8812572e13c41e1c64793032a09af7d611 Mon Sep 17 00:00:00 2001 From: serpixel <5087962+serpixel@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:50:00 +0100 Subject: [PATCH 3/6] fix(op-validator): disable 5.0.0 in tests --- op-validator/pkg/validations/addresses_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/op-validator/pkg/validations/addresses_test.go b/op-validator/pkg/validations/addresses_test.go index 79a2be48086..b4728a28bc6 100644 --- a/op-validator/pkg/validations/addresses_test.go +++ b/op-validator/pkg/validations/addresses_test.go @@ -110,7 +110,8 @@ func testStandardVersionNetwork(t *testing.T, network string) { standard.ContractsV300Tag, standard.ContractsV400Tag, standard.ContractsV410Tag, - standard.ContractsV500Tag, + // Enable whenever we upgrade the superchain registry + //standard.ContractsV500Tag, } for _, semver := range contractVersions { From fb245da07b807b936b76b586ca3ce94f88485d34 Mon Sep 17 00:00:00 2001 From: serpixel <5087962+serpixel@users.noreply.github.com> Date: Wed, 19 Nov 2025 23:39:58 +0100 Subject: [PATCH 4/6] fix(op-validator): detect validatoraddress --- op-validator/pkg/service/flags.go | 13 ---- op-validator/pkg/service/validate.go | 44 ++++------- op-validator/pkg/validations/validations.go | 36 +++++---- .../pkg/validations/validations_test.go | 74 +++++++++++++++++-- 4 files changed, 105 insertions(+), 62 deletions(-) diff --git a/op-validator/pkg/service/flags.go b/op-validator/pkg/service/flags.go index 642e22827ec..02d6f46fb49 100644 --- a/op-validator/pkg/service/flags.go +++ b/op-validator/pkg/service/flags.go @@ -39,11 +39,6 @@ var ( Usage: "Proposer address as hex string (required for OPCMStandardValidator)", Required: false, } - ValidatorAddressFlag = &cli.StringFlag{ - Name: "validator-address", - Usage: "OPCMStandardValidator contract address (required for custom deployments)", - Required: false, - } FailOnErrorFlag = &cli.BoolFlag{ Name: "fail", Usage: "Exit with non-zero code if validation errors are found", @@ -59,7 +54,6 @@ var ValidateFlags = []cli.Flag{ SystemConfigFlag, L2ChainIDFlag, ProposerFlag, - ValidatorAddressFlag, FailOnErrorFlag, } @@ -71,7 +65,6 @@ type Config struct { SystemConfig common.Address L2ChainID *big.Int Proposer common.Address - ValidatorAddress common.Address } // NewConfig creates a new Config from CLI context @@ -89,11 +82,6 @@ func NewConfig(ctx *cli.Context) (*Config, error) { proposer = common.HexToAddress(proposerStr) } - var validatorAddr common.Address - if validatorAddrStr := ctx.String(ValidatorAddressFlag.Name); validatorAddrStr != "" { - validatorAddr = common.HexToAddress(validatorAddrStr) - } - return &Config{ L1RPCURL: ctx.String(L1RPCURLFlag.Name), AbsolutePrestate: absolutePrestate, @@ -101,6 +89,5 @@ func NewConfig(ctx *cli.Context) (*Config, error) { SystemConfig: systemConfig, L2ChainID: l2ChainID, Proposer: proposer, - ValidatorAddress: validatorAddr, }, nil } diff --git a/op-validator/pkg/service/validate.go b/op-validator/pkg/service/validate.go index 9d993eab151..c257dea3028 100644 --- a/op-validator/pkg/service/validate.go +++ b/op-validator/pkg/service/validate.go @@ -8,7 +8,6 @@ import ( oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/optimism/op-validator/pkg/validations" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/urfave/cli/v2" @@ -48,34 +47,23 @@ func Validate(ctx context.Context, lgr log.Logger, release string, cfg *Config) var validator validations.Validator - useNewValidator := release == standard.ContractsV500Tag || cfg.ValidatorAddress != (common.Address{}) - - if useNewValidator { - if cfg.ValidatorAddress == (common.Address{}) { - return nil, fmt.Errorf("validator-address is required for OPCMStandardValidator (v5.0.0+ or custom deployments)") - } - if cfg.Proposer == (common.Address{}) { - return nil, fmt.Errorf("proposer address is required for OPCMStandardValidator") - } - validator = validations.NewOPCMStandardValidator(l1Client, cfg.ValidatorAddress) - lgr.Info("Using OPCMStandardValidator", "validator", cfg.ValidatorAddress.Hex(), "proposer", cfg.Proposer.Hex()) - } else { - switch release { - case standard.ContractsV180Tag: - validator = validations.NewV180Validator(l1Client) - case standard.ContractsV200Tag: - validator = validations.NewV200Validator(l1Client) - case standard.ContractsV300Tag: - validator = validations.NewV300Validator(l1Client) - case standard.ContractsV400Tag: - validator = validations.NewV400Validator(l1Client) - case standard.ContractsV410Tag: - validator = validations.NewV410Validator(l1Client) - default: - return nil, fmt.Errorf("invalid release: %s", release) - } - lgr.Info("Using BaseValidator", "version", release) + switch release { + case standard.ContractsV180Tag: + validator = validations.NewV180Validator(l1Client) + case standard.ContractsV200Tag: + validator = validations.NewV200Validator(l1Client) + case standard.ContractsV300Tag: + validator = validations.NewV300Validator(l1Client) + case standard.ContractsV400Tag: + validator = validations.NewV400Validator(l1Client) + case standard.ContractsV410Tag: + validator = validations.NewV410Validator(l1Client) + case standard.ContractsV500Tag: + validator = validations.NewV500Validator(l1Client) + default: + return nil, fmt.Errorf("invalid release: %s", release) } + lgr.Info("Using Validator", "version", release) return validator.Validate(ctx, validations.BaseValidatorInput{ ProxyAdminAddress: cfg.ProxyAdmin, diff --git a/op-validator/pkg/validations/validations.go b/op-validator/pkg/validations/validations.go index c82d918228a..cf253fb1f67 100644 --- a/op-validator/pkg/validations/validations.go +++ b/op-validator/pkg/validations/validations.go @@ -44,8 +44,8 @@ type BaseValidator struct { } type OPCMStandardValidator struct { - client *rpc.Client - validatorAddr common.Address + client *rpc.Client + release string } type BaseValidatorInput struct { @@ -60,6 +60,13 @@ func newBaseValidator(client *rpc.Client, release string) *BaseValidator { return &BaseValidator{client: client, release: release} } +func newOPCMStandardValidator(client *rpc.Client, release string) *OPCMStandardValidator { + return &OPCMStandardValidator{ + client: client, + release: release, + } +} + func (v *BaseValidator) Validate(ctx context.Context, input BaseValidatorInput) ([]string, error) { l1ChainID, err := ethclient.NewClient(v.client).ChainID(ctx) if err != nil { @@ -100,18 +107,24 @@ func (v *BaseValidator) Validate(ctx context.Context, input BaseValidatorInput) func (v *OPCMStandardValidator) Validate(ctx context.Context, input BaseValidatorInput) ([]string, error) { if input.Proposer == (common.Address{}) { - return nil, fmt.Errorf("proposer address is required for OPCMStandardValidator") + return nil, fmt.Errorf("proposer address is required for OPCM validation") } - if v.validatorAddr == (common.Address{}) { - return nil, fmt.Errorf("validator address is required for OPCMStandardValidator") + l1ChainID, err := ethclient.NewClient(v.client).ChainID(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get chain ID: %w", err) + } + + validatorAddr, err := ValidatorAddress(l1ChainID.Uint64(), v.release) + if err != nil { + return nil, fmt.Errorf("failed to get validator address: %w", err) } var rawOutput []byte if err := w3.NewClient(v.client).CallCtx( ctx, eth.Call(&w3types.Message{ - To: &v.validatorAddr, + To: &validatorAddr, Func: validateFuncOpcmValidator, Args: []any{ validateFuncArgsOpcmValidator{ @@ -134,13 +147,6 @@ func (v *OPCMStandardValidator) Validate(ctx context.Context, input BaseValidato return parseErrors(output), nil } -func NewOPCMStandardValidator(client *rpc.Client, validatorAddr common.Address) *OPCMStandardValidator { - return &OPCMStandardValidator{ - client: client, - validatorAddr: validatorAddr, - } -} - func NewV180Validator(client *rpc.Client) *BaseValidator { return newBaseValidator(client, standard.ContractsV180Tag) } @@ -161,8 +167,8 @@ func NewV410Validator(client *rpc.Client) *BaseValidator { return newBaseValidator(client, standard.ContractsV410Tag) } -func NewV500Validator(client *rpc.Client) *BaseValidator { - return newBaseValidator(client, standard.ContractsV500Tag) +func NewV500Validator(client *rpc.Client) *OPCMStandardValidator { + return newOPCMStandardValidator(client, standard.ContractsV500Tag) } func parseErrors(output string) []string { diff --git a/op-validator/pkg/validations/validations_test.go b/op-validator/pkg/validations/validations_test.go index 9ec05f14e92..bd25852f6d6 100644 --- a/op-validator/pkg/validations/validations_test.go +++ b/op-validator/pkg/validations/validations_test.go @@ -66,12 +66,6 @@ func TestValidate_Mocked(t *testing.T) { return NewV410Validator(rpcClient) }, }, - { - version: standard.ContractsV500Tag, - validator: func(rpcClient *rpc.Client) Validator { - return NewV500Validator(rpcClient) - }, - }, } for _, tt := range tests { t.Run(string(tt.version), func(t *testing.T) { @@ -101,3 +95,71 @@ func TestValidate_Mocked(t *testing.T) { }) } } + +func TestOPCMStandardValidator(t *testing.T) { + callResult := "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b504444472d34302c504444472d44574554482d33302c504444472d414e43484f52502d34302c504c44472d34302c504c44472d44574554482d33302c504c44472d414e43484f52502d3430000000000000000000000000000000000000000000" + + tests := []struct { + name string + input BaseValidatorInput + expectError bool + errorMsg string + }{ + { + name: "successful validation", + input: BaseValidatorInput{ + SystemConfigAddress: common.HexToAddress("0x034edD2A225f7f429A63E0f1D2084B9E0A93b538"), + AbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), + L2ChainID: big.NewInt(11155420), + Proposer: common.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"), + }, + expectError: false, + }, + { + name: "missing proposer address", + input: BaseValidatorInput{ + SystemConfigAddress: common.HexToAddress("0x034edD2A225f7f429A63E0f1D2084B9E0A93b538"), + AbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), + L2ChainID: big.NewInt(11155420), + Proposer: common.Address{}, + }, + expectError: true, + errorMsg: "proposer address is required", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var mockRPC *mockrpc.MockRPC + if !tt.expectError { + mockRPC = mockrpc.NewMockRPC( + t, + testlog.Logger(t, slog.LevelInfo), + mockrpc.WithOkCall("eth_chainId", mockrpc.NullMatcher(), "0xaa36a7"), // sepolia chain ID in hex + mockrpc.WithOkCall("eth_call", mockrpc.AnyParamsMatcher(), callResult), + ) + } else { + mockRPC = mockrpc.NewMockRPC(t, testlog.Logger(t, slog.LevelInfo)) + } + + rpcClient, err := rpc.Dial(mockRPC.Endpoint()) + require.NoError(t, err) + + validator := NewV500Validator(rpcClient) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + errCodes, err := validator.Validate(ctx, tt.input) + + if tt.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorMsg) + } else { + require.NoError(t, err) + require.Equal(t, []string{"PDDG-40", "PDDG-DWETH-30", "PDDG-ANCHORP-40", "PLDG-40", "PLDG-DWETH-30", "PLDG-ANCHORP-40"}, errCodes) + mockRPC.AssertExpectations(t) + } + }) + } +} From 6189deb95cc61232d03cd03a6865ad9d32f801d2 Mon Sep 17 00:00:00 2001 From: serpixel <5087962+serpixel@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:01:31 +0100 Subject: [PATCH 5/6] fix(op-validator): add comments --- .../pkg/validations/addresses_test.go | 2 +- op-validator/pkg/validations/validations.go | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/op-validator/pkg/validations/addresses_test.go b/op-validator/pkg/validations/addresses_test.go index b4728a28bc6..13245e67024 100644 --- a/op-validator/pkg/validations/addresses_test.go +++ b/op-validator/pkg/validations/addresses_test.go @@ -137,7 +137,7 @@ func testStandardVersion(t *testing.T, address common.Address, rpcClient *rpc.Cl w3c := w3.NewClient(rpcClient) - if semverTag == standard.ContractsV500Tag { + if semverTag >= standard.ContractsV500Tag { // For v5.0.0+ type implFieldDef struct { implGetter string diff --git a/op-validator/pkg/validations/validations.go b/op-validator/pkg/validations/validations.go index cf253fb1f67..891d42f3c2c 100644 --- a/op-validator/pkg/validations/validations.go +++ b/op-validator/pkg/validations/validations.go @@ -16,10 +16,13 @@ import ( "github.com/lmittmann/w3/w3types" ) +// validateFunc is used for 1.8.0-4.1.0 validation contracts var validateFunc = w3.MustNewFunc("validate((address proxyAdminAddress,address systemConfigAddress,bytes32 absolutePrestate,uint256 chainID) input,bool allowFailure)", "string") -var validateFuncOpcmValidator = w3.MustNewFunc("validate((address sysCfg,bytes32 absolutePrestate,uint256 l2ChainID,address proposer) input,bool allowFailure)", "string") +// validateFunc500Validator is used for 5.0.0+ validation contracts +var validateFunc500Validator = w3.MustNewFunc("validate((address sysCfg,bytes32 absolutePrestate,uint256 l2ChainID,address proposer) input,bool allowFailure)", "string") +// validateFuncArgs is used for 1.8.0-4.1.0 validation contracts type validateFuncArgs struct { ProxyAdminAddress common.Address SystemConfigAddress common.Address @@ -27,27 +30,32 @@ type validateFuncArgs struct { ChainID *big.Int } -type validateFuncArgsOpcmValidator struct { +// validateFuncArgs500Validator is used for 5.0.0+ validation contracts +type validateFuncArgs500Validator struct { SysCfg common.Address `w3:"sysCfg"` AbsolutePrestate common.Hash `w3:"absolutePrestate"` L2ChainID *big.Int `w3:"l2ChainID"` Proposer common.Address `w3:"proposer"` } +// Validator is used for all validation contracts type Validator interface { Validate(ctx context.Context, input BaseValidatorInput) ([]string, error) } +// BaseValidator is used for 1.8.0-4.1.0 validation contracts type BaseValidator struct { client *rpc.Client release string } +// OPCMStandardValidator is used for 5.0.0+ validation contracts type OPCMStandardValidator struct { client *rpc.Client release string } +// BaseValidatorInput is used for all validation contracts type BaseValidatorInput struct { ProxyAdminAddress common.Address SystemConfigAddress common.Address @@ -56,10 +64,12 @@ type BaseValidatorInput struct { Proposer common.Address } +// newBaseValidator is used for 1.8.0-4.1.0 validation contracts func newBaseValidator(client *rpc.Client, release string) *BaseValidator { return &BaseValidator{client: client, release: release} } +// newOPCMStandardValidator is used for 5.0.0+ validation contracts func newOPCMStandardValidator(client *rpc.Client, release string) *OPCMStandardValidator { return &OPCMStandardValidator{ client: client, @@ -67,6 +77,7 @@ func newOPCMStandardValidator(client *rpc.Client, release string) *OPCMStandardV } } +// Validate (BaseValidator) is used for 1.8.0-4.1.0 validation contracts func (v *BaseValidator) Validate(ctx context.Context, input BaseValidatorInput) ([]string, error) { l1ChainID, err := ethclient.NewClient(v.client).ChainID(ctx) if err != nil { @@ -105,6 +116,7 @@ func (v *BaseValidator) Validate(ctx context.Context, input BaseValidatorInput) return parseErrors(output), nil } +// Validate (OPCMStandardValidator) is used for 5.0.0+ validation contracts func (v *OPCMStandardValidator) Validate(ctx context.Context, input BaseValidatorInput) ([]string, error) { if input.Proposer == (common.Address{}) { return nil, fmt.Errorf("proposer address is required for OPCM validation") @@ -125,9 +137,9 @@ func (v *OPCMStandardValidator) Validate(ctx context.Context, input BaseValidato ctx, eth.Call(&w3types.Message{ To: &validatorAddr, - Func: validateFuncOpcmValidator, + Func: validateFunc500Validator, Args: []any{ - validateFuncArgsOpcmValidator{ + validateFuncArgs500Validator{ SysCfg: input.SystemConfigAddress, AbsolutePrestate: input.AbsolutePrestate, L2ChainID: input.L2ChainID, @@ -141,7 +153,7 @@ func (v *OPCMStandardValidator) Validate(ctx context.Context, input BaseValidato } var output string - if err := validateFuncOpcmValidator.DecodeReturns(rawOutput, &output); err != nil { + if err := validateFunc500Validator.DecodeReturns(rawOutput, &output); err != nil { return nil, fmt.Errorf("failed to unmarshal output: %w", err) } return parseErrors(output), nil From 704757cbe91c8d2ac6974b714822d996474f2ed2 Mon Sep 17 00:00:00 2001 From: serpixel <5087962+serpixel@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:04:54 +0100 Subject: [PATCH 6/6] fix(op-validator): use semver to compare --- op-validator/pkg/validations/addresses_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/op-validator/pkg/validations/addresses_test.go b/op-validator/pkg/validations/addresses_test.go index 13245e67024..d4f7e46b132 100644 --- a/op-validator/pkg/validations/addresses_test.go +++ b/op-validator/pkg/validations/addresses_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/Masterminds/semver/v3" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/superchain-registry/validation" @@ -137,7 +138,10 @@ func testStandardVersion(t *testing.T, address common.Address, rpcClient *rpc.Cl w3c := w3.NewClient(rpcClient) - if semverTag >= standard.ContractsV500Tag { + ver, err := semver.NewVersion(version.SystemConfig.Version) + require.NoError(t, err) + + if ver.Major() >= 5 { // For v5.0.0+ type implFieldDef struct { implGetter string