diff --git a/op-challenger/game/fault/contracts/detect.go b/op-challenger/game/fault/contracts/detect.go new file mode 100644 index 0000000000000..91abca153f230 --- /dev/null +++ b/op-challenger/game/fault/contracts/detect.go @@ -0,0 +1,44 @@ +package contracts + +import ( + "context" + "fmt" + + faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum/common" +) + +var ( + methodGameType = "gameType" + + gameTypeABI = mustParseAbi([]byte(`[{ + "inputs": [], + "name": "gameType", + "outputs": [{"type": "uint32"}], + "stateMutability": "view", + "type": "function" + }]`)) +) + +func DetectGameType(ctx context.Context, addr common.Address, caller *batching.MultiCaller) (faultTypes.GameType, error) { + result, err := caller.SingleCall(ctx, rpcblock.Latest, batching.NewContractCall(gameTypeABI, addr, methodGameType)) + if err != nil { + return faultTypes.UnknownGameType, fmt.Errorf("failed to detect game type: %w", err) + } + gameType := faultTypes.GameType(result.GetUint32(0)) + switch gameType { + case faultTypes.CannonGameType, + faultTypes.PermissionedGameType, + faultTypes.AsteriscGameType, + faultTypes.AlphabetGameType, + faultTypes.FastGameType, + faultTypes.AsteriscKonaGameType, + faultTypes.SuperCannonGameType, + faultTypes.SuperPermissionedGameType: + return gameType, nil + default: + return faultTypes.UnknownGameType, fmt.Errorf("unsupported game type: %d", gameType) + } +} diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index ebb5af9d0a0bd..f0385cd50133a 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -76,6 +76,19 @@ type outputRootProof struct { } func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { + gameType, err := DetectGameType(ctx, addr, caller) + if err != nil { + return nil, fmt.Errorf("failed to detect game type: %w", err) + } + switch gameType { + case types.SuperCannonGameType, types.SuperPermissionedGameType: + return NewSuperFaultDisputeGameContract(ctx, metrics, addr, caller) + default: + return NewPreInteropFaultDisputeGameContract(ctx, metrics, addr, caller) + } +} + +func NewPreInteropFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { contractAbi := snapshots.LoadFaultDisputeGameABI() var builder VersionedBuilder[FaultDisputeGameContract] @@ -211,7 +224,7 @@ func (f *FaultDisputeGameContractLatest) GetGameMetadata(ctx context.Context, bl return GameMetadata{}, fmt.Errorf("failed to retrieve game metadata: %w", err) } if len(results) != 7 { - return GameMetadata{}, fmt.Errorf("expected 6 results but got %v", len(results)) + return GameMetadata{}, fmt.Errorf("expected 7 results but got %v", len(results)) } l1Head := results[0].GetHash(0) l2BlockNumber := results[1].GetBigInt(0).Uint64() diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index e3e1901d375e5..683dd06551a0d 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -38,57 +38,78 @@ var ( ) type contractVersion struct { - version string - loadAbi func() *abi.ABI + version string + gameType faultTypes.GameType + loadAbi func() *abi.ABI } func (c contractVersion) Is(versions ...string) bool { return slices.Contains(versions, c.version) } +func (c contractVersion) String() string { + return fmt.Sprintf("%s (%s)", c.version, c.gameType) +} + +func (c contractVersion) IsSuperCannon() bool { + return c.gameType == faultTypes.SuperCannonGameType || c.gameType == faultTypes.SuperPermissionedGameType +} + const ( - vers080 = "0.8.0" - vers0180 = "0.18.0" - vers111 = "1.1.1" - vers120 = "1.2.0" - vers131 = "1.3.1" - versLatest = "1.4.0" + vers080 = "0.8.0" + vers0180 = "0.18.0" + vers111 = "1.1.1" + vers120 = "1.2.0" + vers131 = "1.3.1" + versLatest = "1.4.0" + verSuperCannon = "0.1.0" ) var versions = []contractVersion{ { - version: vers080, + version: vers080, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi020) }, }, { - version: vers0180, + version: vers0180, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi0180) }, }, { - version: vers111, + version: vers111, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi111) }, }, { - version: vers120, + version: vers120, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi120) }, }, { - version: vers131, + version: vers131, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi131) }, }, { - version: versLatest, - loadAbi: snapshots.LoadFaultDisputeGameABI, + version: versLatest, + gameType: faultTypes.CannonGameType, + loadAbi: snapshots.LoadFaultDisputeGameABI, + }, + { + version: verSuperCannon, + gameType: faultTypes.SuperCannonGameType, + loadAbi: snapshots.LoadFaultDisputeGameABI, }, } @@ -188,7 +209,7 @@ func TestSimpleGetters(t *testing.T) { } for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { for _, test := range tests { test := test t.Run(test.methodAlias, func(t *testing.T) { @@ -214,7 +235,7 @@ func TestBondDistributionMode(t *testing.T) { unsupportedVersions := []string{vers080, vers0180, vers111, vers120, vers131} for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { supported := !slices.Contains(unsupportedVersions, version.version) stubRpc, game := setupFaultDisputeGameTest(t, version) if supported { @@ -272,7 +293,7 @@ func TestClock_EncodingDecoding(t *testing.T) { func TestGetOracleAddr(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) stubRpc.SetResponse(fdgAddr, methodVM, rpcblock.Latest, nil, []interface{}{vmAddr}) stubRpc.SetResponse(vmAddr, methodOracle, rpcblock.Latest, nil, []interface{}{oracleAddr}) @@ -287,7 +308,7 @@ func TestGetOracleAddr(t *testing.T) { func TestGetClaim(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) idx := big.NewInt(2) parentIndex := uint32(1) @@ -319,7 +340,7 @@ func TestGetClaim(t *testing.T) { func TestGetAllClaims(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) claim0 := faultTypes.Claim{ ClaimData: faultTypes.ClaimData{ @@ -372,7 +393,7 @@ func TestGetAllClaims(t *testing.T) { func TestGetBalance(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { wethAddr := common.Address{0x11, 0x55, 0x66} balance := big.NewInt(9995877) delaySeconds := big.NewInt(429829) @@ -396,7 +417,7 @@ func TestGetBalance(t *testing.T) { func TestCallResolveClaim(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) if version.version == vers080 { stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123)}, nil) @@ -412,7 +433,7 @@ func TestCallResolveClaim(t *testing.T) { func TestResolveClaimTxTest(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) if version.version == vers080 { stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123)}, nil) @@ -429,7 +450,7 @@ func TestResolveClaimTxTest(t *testing.T) { func TestResolveTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) stubRpc.SetResponse(fdgAddr, methodResolve, rpcblock.Latest, nil, nil) tx, err := game.ResolveTx() @@ -442,7 +463,7 @@ func TestResolveTx(t *testing.T) { func TestAttackTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) bond := big.NewInt(1044) value := common.Hash{0xaa} @@ -464,7 +485,7 @@ func TestAttackTx(t *testing.T) { func TestDefendTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) bond := big.NewInt(1044) value := common.Hash{0xaa} @@ -486,7 +507,7 @@ func TestDefendTx(t *testing.T) { func TestStepTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) stateData := []byte{1, 2, 3} proofData := []byte{4, 5, 6, 7, 8, 9} @@ -518,7 +539,7 @@ func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, block rpcblock.Block, cla func TestGetBlockRange(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, contract := setupFaultDisputeGameTest(t, version) expectedStart := uint64(65) expectedEnd := uint64(102) @@ -535,7 +556,7 @@ func TestGetBlockRange(t *testing.T) { func TestGetSplitDepth(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, contract := setupFaultDisputeGameTest(t, version) expectedSplitDepth := faultTypes.Depth(15) stubRpc.SetResponse(fdgAddr, methodSplitDepth, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(uint64(expectedSplitDepth))}) @@ -549,7 +570,7 @@ func TestGetSplitDepth(t *testing.T) { func TestGetGameMetadata(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, contract := setupFaultDisputeGameTest(t, version) expectedL1Head := common.Hash{0x0a, 0x0b} expectedL2BlockNumber := uint64(123) @@ -563,18 +584,13 @@ func TestGetGameMetadata(t *testing.T) { stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)}) stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim}) stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus}) - if version.version == vers080 { - expectedL2BlockNumberChallenged = false - expectedL2BlockNumberChallenger = common.Address{} - stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedMaxClockDuration * 2}) - } else if version.version == vers0180 { - expectedL2BlockNumberChallenged = false - expectedL2BlockNumberChallenger = common.Address{} - stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) - } else { + supportsL2BlockNumChallenge := (version.version != vers080 && version.version != vers0180) && !version.IsSuperCannon() + if supportsL2BlockNumChallenge { stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenged, block, nil, []interface{}{expectedL2BlockNumberChallenged}) stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenger, block, nil, []interface{}{expectedL2BlockNumberChallenger}) + } else if expectedL2BlockNumberChallenged { + t.Skip("Can't have challenged L2 block number on this contract version") } actual, err := contract.GetGameMetadata(context.Background(), block) expected := GameMetadata{ @@ -595,7 +611,7 @@ func TestGetGameMetadata(t *testing.T) { func TestGetStartingRootHash(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, contract := setupFaultDisputeGameTest(t, version) expectedOutputRoot := common.HexToHash("0x1234") stubRpc.SetResponse(fdgAddr, methodStartingRootHash, rpcblock.Latest, nil, []interface{}{expectedOutputRoot}) @@ -609,7 +625,7 @@ func TestGetStartingRootHash(t *testing.T) { func TestFaultDisputeGame_UpdateOracleTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { t.Run("Local", func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) data := faultTypes.NewPreimageOracleData(common.Hash{0x01, 0xbc}.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7}, 16) @@ -645,7 +661,7 @@ func TestFaultDisputeGame_UpdateOracleTx(t *testing.T) { func TestFaultDisputeGame_GetCredit(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) addr := common.Address{0x01} expectedCredit := big.NewInt(4284) @@ -664,7 +680,7 @@ func TestFaultDisputeGame_GetCredit(t *testing.T) { func TestFaultDisputeGame_GetCredits(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) block := rpcblock.ByNumber(482) @@ -689,7 +705,7 @@ func TestFaultDisputeGame_GetCredits(t *testing.T) { func TestFaultDisputeGame_ClaimCreditTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { t.Run("Success", func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) addr := common.Address{0xaa} @@ -716,7 +732,7 @@ func TestFaultDisputeGame_ClaimCreditTx(t *testing.T) { func TestFaultDisputeGame_IsResolved(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) block := rpcblock.ByNumber(482) @@ -763,12 +779,16 @@ func TestFaultDisputeGame_IsResolved(t *testing.T) { func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) { for _, version := range versions { version := version - for _, expected := range []bool{true, false} { + var expectations = []bool{true, false} + if version.IsSuperCannon() { + expectations = []bool{false} + } + for _, expected := range expectations { expected := expected - t.Run(fmt.Sprintf("%v-%v", version.version, expected), func(t *testing.T) { + t.Run(fmt.Sprintf("%v-%v", version.String(), expected), func(t *testing.T) { block := rpcblock.ByHash(common.Hash{0x43}) stubRpc, game := setupFaultDisputeGameTest(t, version) - supportsL2BlockNumChallenge := version.version != vers080 && version.version != vers0180 + supportsL2BlockNumChallenge := (version.version != vers080 && version.version != vers0180) && !version.IsSuperCannon() if supportsL2BlockNumChallenge { stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenged, block, nil, []interface{}{expected}) } else if expected { @@ -785,7 +805,7 @@ func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { rng := rand.New(rand.NewSource(0)) stubRpc, game := setupFaultDisputeGameTest(t, version) challenge := &faultTypes.InvalidL2BlockNumberChallenge{ @@ -798,7 +818,7 @@ func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) { }, Header: testutils.RandomHeader(rng), } - supportsL2BlockNumChallenge := version.version != vers080 && version.version != vers0180 + supportsL2BlockNumChallenge := (version.version != vers080 && version.version != vers0180) && !version.IsSuperCannon() if supportsL2BlockNumChallenge { headerRlp, err := rlp.EncodeToBytes(challenge.Header) require.NoError(t, err) @@ -835,6 +855,7 @@ func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batching stubRpc.AddContract(oracleAddr, oracleAbi) caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) + stubRpc.SetResponse(fdgAddr, methodGameType, rpcblock.Latest, nil, []interface{}{uint32(version.gameType)}) stubRpc.SetResponse(fdgAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version}) stubRpc.SetResponse(oracleAddr, methodVersion, rpcblock.Latest, nil, []interface{}{oracleLatest}) game, err := NewFaultDisputeGameContract(context.Background(), contractMetrics.NoopContractMetrics, fdgAddr, caller) diff --git a/op-challenger/game/fault/contracts/superfaultdisputegame.go b/op-challenger/game/fault/contracts/superfaultdisputegame.go new file mode 100644 index 0000000000000..5add719a453d9 --- /dev/null +++ b/op-challenger/game/fault/contracts/superfaultdisputegame.go @@ -0,0 +1,71 @@ +package contracts + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" + "github.com/ethereum/go-ethereum/common" +) + +type SuperFaultDisputeGameContractLatest struct { + FaultDisputeGameContractLatest +} + +func NewSuperFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { + contractAbi := snapshots.LoadFaultDisputeGameABI() + return &SuperFaultDisputeGameContractLatest{ + FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ + metrics: metrics, + multiCaller: caller, + contract: batching.NewBoundContract(contractAbi, addr), + }, + }, nil +} + +// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, max clock duration, and is l2 block number challenged. +func (f *SuperFaultDisputeGameContractLatest) GetGameMetadata(ctx context.Context, block rpcblock.Block) (GameMetadata, error) { + defer f.metrics.StartContractRequest("GetGameMetadata")() + results, err := f.multiCaller.Call(ctx, block, + f.contract.Call(methodL1Head), + f.contract.Call(methodL2BlockNumber), + f.contract.Call(methodRootClaim), + f.contract.Call(methodStatus), + f.contract.Call(methodMaxClockDuration), + ) + if err != nil { + return GameMetadata{}, fmt.Errorf("failed to retrieve game metadata: %w", err) + } + if len(results) != 5 { + return GameMetadata{}, fmt.Errorf("expected 5 results but got %v", len(results)) + } + l1Head := results[0].GetHash(0) + l2BlockNumber := results[1].GetBigInt(0).Uint64() + rootClaim := results[2].GetHash(0) + status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0)) + if err != nil { + return GameMetadata{}, fmt.Errorf("failed to convert game status: %w", err) + } + duration := results[4].GetUint64(0) + return GameMetadata{ + L1Head: l1Head, + L2BlockNum: l2BlockNumber, + RootClaim: rootClaim, + Status: status, + MaxClockDuration: duration, + }, nil +} + +func (f *SuperFaultDisputeGameContractLatest) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) { + return false, nil +} + +func (f *SuperFaultDisputeGameContractLatest) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) { + return txmgr.TxCandidate{}, ErrChallengeL2BlockNotSupported +} diff --git a/op-challenger/game/fault/register_task_test.go b/op-challenger/game/fault/register_task_test.go index f09c3e79fe34c..2e58fadf74ae6 100644 --- a/op-challenger/game/fault/register_task_test.go +++ b/op-challenger/game/fault/register_task_test.go @@ -2,6 +2,7 @@ package fault import ( "context" + "fmt" "testing" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" @@ -39,33 +40,38 @@ func TestRegisterOracle_MissingGameImpl(t *testing.T) { } func TestRegisterOracle_AddsOracle(t *testing.T) { - gameFactoryAddr := common.Address{0xaa} - gameImplAddr := common.Address{0xbb} - vmAddr := common.Address{0xcc} - oracleAddr := common.Address{0xdd} - rpc := test.NewAbiBasedRpc(t, gameFactoryAddr, snapshots.LoadDisputeGameFactoryABI()) - rpc.AddContract(gameImplAddr, snapshots.LoadFaultDisputeGameABI()) - rpc.AddContract(vmAddr, snapshots.LoadMIPSABI()) - rpc.AddContract(oracleAddr, snapshots.LoadPreimageOracleABI()) - m := metrics.NoopMetrics - caller := batching.NewMultiCaller(rpc, batching.DefaultBatchSize) - gameFactory := contracts.NewDisputeGameFactoryContract(m, gameFactoryAddr, caller) + for _, gameType := range []faultTypes.GameType{faultTypes.CannonGameType, faultTypes.SuperCannonGameType} { + t.Run(fmt.Sprintf("%v", gameType), func(t *testing.T) { + gameFactoryAddr := common.Address{0xaa} + gameImplAddr := common.Address{0xbb} + vmAddr := common.Address{0xcc} + oracleAddr := common.Address{0xdd} + rpc := test.NewAbiBasedRpc(t, gameFactoryAddr, snapshots.LoadDisputeGameFactoryABI()) + rpc.AddContract(gameImplAddr, snapshots.LoadFaultDisputeGameABI()) + rpc.AddContract(vmAddr, snapshots.LoadMIPSABI()) + rpc.AddContract(oracleAddr, snapshots.LoadPreimageOracleABI()) + m := metrics.NoopMetrics + caller := batching.NewMultiCaller(rpc, batching.DefaultBatchSize) + gameFactory := contracts.NewDisputeGameFactoryContract(m, gameFactoryAddr, caller) - logger := testlog.Logger(t, log.LvlInfo) - oracles := registry.NewOracleRegistry() - gameType := faultTypes.CannonGameType + logger := testlog.Logger(t, log.LvlInfo) + oracles := registry.NewOracleRegistry() - // Use the latest v1 of these contracts. Doesn't have to be an exact match for the version. - rpc.SetResponse(gameImplAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) - rpc.SetResponse(oracleAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) + // Use the latest v1 of these contracts. Doesn't have to be an exact match for the version. + rpc.SetResponse(gameImplAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) + rpc.SetResponse(oracleAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) - rpc.SetResponse(gameFactoryAddr, "gameImpls", rpcblock.Latest, []interface{}{gameType}, []interface{}{gameImplAddr}) - rpc.SetResponse(gameImplAddr, "vm", rpcblock.Latest, []interface{}{}, []interface{}{vmAddr}) - rpc.SetResponse(vmAddr, "oracle", rpcblock.Latest, []interface{}{}, []interface{}{oracleAddr}) + rpc.SetResponse(gameFactoryAddr, "gameImpls", rpcblock.Latest, []interface{}{gameType}, []interface{}{gameImplAddr}) + rpc.SetResponse(gameImplAddr, "vm", rpcblock.Latest, []interface{}{}, []interface{}{vmAddr}) + rpc.SetResponse(vmAddr, "oracle", rpcblock.Latest, []interface{}{}, []interface{}{oracleAddr}) - err := registerOracle(context.Background(), logger, m, oracles, gameFactory, caller, gameType) - require.NoError(t, err) - registered := oracles.Oracles() - require.Len(t, registered, 1) - require.Equal(t, oracleAddr, registered[0].Addr()) + rpc.SetResponse(gameImplAddr, "gameType", rpcblock.Latest, []interface{}{}, []interface{}{uint32(gameType)}) + + err := registerOracle(context.Background(), logger, m, oracles, gameFactory, caller, gameType) + require.NoError(t, err) + registered := oracles.Oracles() + require.Len(t, registered, 1) + require.Equal(t, oracleAddr, registered[0].Addr()) + }) + } } diff --git a/op-dispute-mon/mon/extract/caller.go b/op-dispute-mon/mon/extract/caller.go index 0b88360b69aeb..cdd2d887d42ef 100644 --- a/op-dispute-mon/mon/extract/caller.go +++ b/op-dispute-mon/mon/extract/caller.go @@ -56,7 +56,9 @@ func (g *GameCallerCreator) CreateContract(ctx context.Context, game gameTypes.G faultTypes.AsteriscGameType, faultTypes.AlphabetGameType, faultTypes.FastGameType, - faultTypes.AsteriscKonaGameType: + faultTypes.AsteriscKonaGameType, + faultTypes.SuperCannonGameType, + faultTypes.SuperPermissionedGameType: fdg, err := contracts.NewFaultDisputeGameContract(ctx, g.m, game.Proxy, g.caller) if err != nil { return nil, fmt.Errorf("failed to create fault dispute game contract: %w", err) diff --git a/op-dispute-mon/mon/extract/caller_test.go b/op-dispute-mon/mon/extract/caller_test.go index 61c7f349c82b8..8892c2a794f29 100644 --- a/op-dispute-mon/mon/extract/caller_test.go +++ b/op-dispute-mon/mon/extract/caller_test.go @@ -51,17 +51,25 @@ func TestMetadataCreator_CreateContract(t *testing.T) { name: "validAsteriscKonaGameType", game: types.GameMetadata{GameType: uint32(faultTypes.AsteriscKonaGameType), Proxy: fdgAddr}, }, + { + name: "validSuperCannonGameType", + game: types.GameMetadata{GameType: uint32(faultTypes.SuperCannonGameType), Proxy: fdgAddr}, + }, + { + name: "validSuperPermissionedGameType", + game: types.GameMetadata{GameType: uint32(faultTypes.SuperPermissionedGameType), Proxy: fdgAddr}, + }, { name: "InvalidGameType", - game: types.GameMetadata{GameType: 4, Proxy: fdgAddr}, - expectedErr: fmt.Errorf("unsupported game type: 4"), + game: types.GameMetadata{GameType: 6, Proxy: fdgAddr}, + expectedErr: fmt.Errorf("unsupported game type: 6"), }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - caller, metrics := setupMetadataLoaderTest(t) + caller, metrics := setupMetadataLoaderTest(t, test.game.GameType) creator := NewGameCallerCreator(metrics, caller) _, err := creator.CreateContract(context.Background(), test.game) require.Equal(t, test.expectedErr, err) @@ -79,11 +87,12 @@ func TestMetadataCreator_CreateContract(t *testing.T) { } } -func setupMetadataLoaderTest(t *testing.T) (*batching.MultiCaller, *mockCacheMetrics) { +func setupMetadataLoaderTest(t *testing.T, gameType uint32) (*batching.MultiCaller, *mockCacheMetrics) { fdgAbi := snapshots.LoadFaultDisputeGameABI() stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi) caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) stubRpc.SetResponse(fdgAddr, "version", rpcblock.Latest, nil, []interface{}{"0.18.0"}) + stubRpc.SetResponse(fdgAddr, "gameType", rpcblock.Latest, nil, []interface{}{gameType}) return caller, &mockCacheMetrics{} }