From 205e0b9ef5948bebe24b0a3dd9f6702a0469325b Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Fri, 12 Dec 2025 11:57:53 +0800 Subject: [PATCH] op-proposer: avoid claimData calls for non-matching game types --- op-proposer/contracts/disputegamefactory.go | 26 ++++++++++++++++--- .../contracts/disputegamefactory_test.go | 19 ++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/op-proposer/contracts/disputegamefactory.go b/op-proposer/contracts/disputegamefactory.go index 527d0e501603f..b60ab3798f759 100644 --- a/op-proposer/contracts/disputegamefactory.go +++ b/op-proposer/contracts/disputegamefactory.go @@ -75,17 +75,23 @@ func (f *DisputeGameFactory) HasProposedSince(ctx context.Context, proposer comm return false, time.Time{}, common.Hash{}, nil } for idx := gameCount - 1; ; idx-- { - game, err := f.gameAtIndex(ctx, idx) + gt, ts, err := f.gameBasicInfoAtIndex(ctx, idx) if err != nil { return false, time.Time{}, common.Hash{}, fmt.Errorf("failed to get dispute game %d: %w", idx, err) } - if game.Timestamp.Before(cutoff) { + if ts.Before(cutoff) { // Reached a game that is before the expected cutoff, so we haven't found a suitable proposal return false, time.Time{}, common.Hash{}, nil } - if game.GameType == gameType && game.Proposer == proposer { + if gt == gameType { + game, err := f.gameAtIndex(ctx, idx) + if err != nil { + return false, time.Time{}, common.Hash{}, fmt.Errorf("failed to get dispute game %d: %w", idx, err) + } // Found a matching proposal - return true, game.Timestamp, game.Claim, nil + if game.Proposer == proposer { + return true, game.Timestamp, game.Claim, nil + } } if idx == 0 { // Need to check here rather than in the for condition to avoid underflow // Checked every game and didn't find a match @@ -151,3 +157,15 @@ func (f *DisputeGameFactory) gameAtIndex(ctx context.Context, idx uint64) (gameM Claim: claim, }, nil } + +// gameBasicInfoAtIndex returns basic game info without loading claimData +// This avoids reverts when iterating over games with incompatible ABIs +func (f *DisputeGameFactory) gameBasicInfoAtIndex(ctx context.Context, idx uint64) (gameType uint32, timestamp time.Time, err error) { + cCtx, cancel := context.WithTimeout(ctx, f.networkTimeout) + defer cancel() + result, err := f.caller.SingleCall(cCtx, rpcblock.Latest, f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx))) + if err != nil { + return 0, time.Time{}, fmt.Errorf("failed to load game %v: %w", idx, err) + } + return result.GetUint32(0), time.Unix(int64(result.GetUint64(1)), 0), nil +} diff --git a/op-proposer/contracts/disputegamefactory_test.go b/op-proposer/contracts/disputegamefactory_test.go index dfd89a10900ff..ed4a8f82797fd 100644 --- a/op-proposer/contracts/disputegamefactory_test.go +++ b/op-proposer/contracts/disputegamefactory_test.go @@ -199,6 +199,25 @@ func TestHasProposedSince(t *testing.T) { require.Equal(t, expectedProposalTime, proposalTime) require.Equal(t, common.Hash{0xdd}, claim) }) + + t.Run("SkipsClaimForDifferentType-"+contractType.name, func(t *testing.T) { + stubRpc, factory := setupDisputeGameFactoryTest(t) + // idx 0: matching game type with claim data + withClaims(stubRpc, contractType.abi, + gameMetadata{GameType: 0, Timestamp: time.Unix(1400, 0), Address: common.Address{0x66}, Proposer: proposerAddr}, + ) + // Append idx 1: different game type, no claim data (would revert if called) + stubRpc.SetResponse(factoryAddr, methodGameCount, rpcblock.Latest, nil, []interface{}{big.NewInt(2)}) + stubRpc.SetResponse(factoryAddr, methodGameAtIndex, rpcblock.Latest, []interface{}{big.NewInt(1)}, []interface{}{ + uint32(1), uint64(1500), common.Address{0x55}, + }) + + proposed, proposalTime, claim, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0) + require.NoError(t, err) + require.True(t, proposed) + require.Equal(t, time.Unix(1400, 0), proposalTime) + require.Equal(t, common.Hash{0xdd}, claim) + }) } }