diff --git a/op-e2e/faultproofs/output_cannon_test.go b/op-e2e/faultproofs/output_cannon_test.go index 9373a1ac9335..e26719e05a92 100644 --- a/op-e2e/faultproofs/output_cannon_test.go +++ b/op-e2e/faultproofs/output_cannon_test.go @@ -70,6 +70,35 @@ func TestOutputCannonGame(t *testing.T) { game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins) } +func TestOutputCannon_ChallengeAllZeroClaim(t *testing.T) { + // The dishonest actor always posts claims with all zeros. + op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(outputCannonTestExecutor)) + ctx := context.Background() + sys, l1Client := startFaultDisputeSystem(t) + t.Cleanup(sys.Close) + + disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) + game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 3, common.Hash{}) + game.LogGameData(ctx) + + claim := game.DisputeLastBlock(ctx) + game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + + game.DefendClaim(ctx, claim, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { + if parent.IsBottomGameRoot(ctx) { + return parent.Attack(ctx, common.Hash{}) + } + return parent.Defend(ctx, common.Hash{}) + }) + + game.LogGameData(ctx) + + sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx)) + require.NoError(t, wait.ForNextBlock(ctx, l1Client)) + game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins) + game.LogGameData(ctx) +} + func TestOutputCannon_PublishCannonRootClaim(t *testing.T) { op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(outputCannonTestExecutor)) tests := []struct { diff --git a/op-program/host/cmd/main_test.go b/op-program/host/cmd/main_test.go index 081136af117b..38cf29e6a4b6 100644 --- a/op-program/host/cmd/main_test.go +++ b/op-program/host/cmd/main_test.go @@ -239,6 +239,16 @@ func TestL2Claim(t *testing.T) { t.Run("Invalid", func(t *testing.T) { verifyArgsInvalid(t, config.ErrInvalidL2Claim.Error(), replaceRequiredArg("--l2.claim", "something")) }) + + t.Run("Allows all zero without prefix", func(t *testing.T) { + cfg := configForArgs(t, replaceRequiredArg("--l2.claim", "0000000000000000000000000000000000000000000000000000000000000000")) + require.EqualValues(t, common.Hash{}, cfg.L2Claim) + }) + + t.Run("Allows all zero with prefix", func(t *testing.T) { + cfg := configForArgs(t, replaceRequiredArg("--l2.claim", "0x0000000000000000000000000000000000000000000000000000000000000000")) + require.EqualValues(t, common.Hash{}, cfg.L2Claim) + }) } func TestL2BlockNumber(t *testing.T) { diff --git a/op-program/host/config/config.go b/op-program/host/config/config.go index 84d7c2e32908..117cdeadc9fd 100644 --- a/op-program/host/config/config.go +++ b/op-program/host/config/config.go @@ -85,9 +85,6 @@ func (c *Config) Check() error { if c.L2OutputRoot == (common.Hash{}) { return ErrInvalidL2OutputRoot } - if c.L2Claim == (common.Hash{}) { - return ErrInvalidL2Claim - } if c.L2ClaimBlockNumber == 0 { return ErrInvalidL2ClaimBlock } @@ -151,9 +148,13 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { if l2OutputRoot == (common.Hash{}) { return nil, ErrInvalidL2OutputRoot } - l2Claim := common.HexToHash(ctx.String(flags.L2Claim.Name)) - if l2Claim == (common.Hash{}) { - return nil, ErrInvalidL2Claim + strClaim := ctx.String(flags.L2Claim.Name) + l2Claim := common.HexToHash(strClaim) + // Require a valid hash, with the zero hash explicitly allowed. + if l2Claim == (common.Hash{}) && + strClaim != "0x0000000000000000000000000000000000000000000000000000000000000000" && + strClaim != "0000000000000000000000000000000000000000000000000000000000000000" { + return nil, fmt.Errorf("%w: %v", ErrInvalidL2Claim, strClaim) } l2ClaimBlockNum := ctx.Uint64(flags.L2BlockNumber.Name) l1Head := common.HexToHash(ctx.String(flags.L1Head.Name)) diff --git a/op-program/host/config/config_test.go b/op-program/host/config/config_test.go index 13f7cdcb657f..2b85da110778 100644 --- a/op-program/host/config/config_test.go +++ b/op-program/host/config/config_test.go @@ -65,11 +65,11 @@ func TestL2OutputRootRequired(t *testing.T) { require.ErrorIs(t, err, ErrInvalidL2OutputRoot) } -func TestL2ClaimRequired(t *testing.T) { +// The L2 claim may be provided by a dishonest actor so we must treat 0x00...00 as a real value. +func TestL2ClaimMayBeDefaultValue(t *testing.T) { config := validConfig() config.L2Claim = common.Hash{} - err := config.Check() - require.ErrorIs(t, err, ErrInvalidL2Claim) + require.NoError(t, config.Check()) } func TestL2ClaimBlockNumberRequired(t *testing.T) {