diff --git a/op-challenger/cmd/create_game.go b/op-challenger/cmd/create_game.go index fc305f3bfc97..0ef1a83d8404 100644 --- a/op-challenger/cmd/create_game.go +++ b/op-challenger/cmd/create_game.go @@ -1,14 +1,17 @@ package main import ( + "context" "fmt" "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/flags" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger/tools" opservice "github.com/ethereum-optimism/optimism/op-service" oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" "github.com/urfave/cli/v2" @@ -38,7 +41,10 @@ func CreateGame(ctx *cli.Context) error { traceType := ctx.Uint64(TraceTypeFlag.Name) l2BlockNum := ctx.Uint64(L2BlockNumFlag.Name) - contract, txMgr, err := NewContractWithTxMgr[*contracts.DisputeGameFactoryContract](ctx, flags.FactoryAddressFlag.Name, contracts.NewDisputeGameFactoryContract) + contract, txMgr, err := NewContractWithTxMgr[*contracts.DisputeGameFactoryContract](ctx, flags.FactoryAddressFlag.Name, + func(ctx context.Context, metricer contractMetrics.ContractMetricer, address common.Address, caller *batching.MultiCaller) (*contracts.DisputeGameFactoryContract, error) { + return contracts.NewDisputeGameFactoryContract(metricer, address, caller), nil + }) if err != nil { return fmt.Errorf("failed to create dispute game factory bindings: %w", err) } diff --git a/op-challenger/cmd/list_claims.go b/op-challenger/cmd/list_claims.go index 2e7afa95006f..548b05828e66 100644 --- a/op-challenger/cmd/list_claims.go +++ b/op-challenger/cmd/list_claims.go @@ -48,11 +48,14 @@ func ListClaims(ctx *cli.Context) error { defer l1Client.Close() caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize) - contract := contracts.NewFaultDisputeGameContract(metrics.NoopContractMetrics, gameAddr, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx.Context, metrics.NoopContractMetrics, gameAddr, caller) + if err != nil { + return err + } return listClaims(ctx.Context, contract) } -func listClaims(ctx context.Context, game *contracts.FaultDisputeGameContract) error { +func listClaims(ctx context.Context, game contracts.FaultDisputeGameContract) error { maxDepth, err := game.GetMaxGameDepth(ctx) if err != nil { return fmt.Errorf("failed to retrieve max depth: %w", err) diff --git a/op-challenger/cmd/list_games.go b/op-challenger/cmd/list_games.go index e172f59823cf..2ab282ad343f 100644 --- a/op-challenger/cmd/list_games.go +++ b/op-challenger/cmd/list_games.go @@ -66,7 +66,10 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr infos := make([]*gameInfo, len(games)) var wg sync.WaitGroup for idx, game := range games { - gameContract := contracts.NewFaultDisputeGameContract(metrics.NoopContractMetrics, game.Proxy, caller) + gameContract, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, game.Proxy, caller) + if err != nil { + return fmt.Errorf("failed to create dispute game contract: %w", err) + } info := gameInfo{GameMetadata: game} infos[idx] = &info gameProxy := game.Proxy diff --git a/op-challenger/cmd/move.go b/op-challenger/cmd/move.go index 50d325974f0b..5e24d065b23c 100644 --- a/op-challenger/cmd/move.go +++ b/op-challenger/cmd/move.go @@ -46,7 +46,7 @@ func Move(ctx *cli.Context) error { return fmt.Errorf("both attack and defense flags cannot be set") } - contract, txMgr, err := NewContractWithTxMgr[*contracts.FaultDisputeGameContract](ctx, GameAddressFlag.Name, contracts.NewFaultDisputeGameContract) + contract, txMgr, err := NewContractWithTxMgr[contracts.FaultDisputeGameContract](ctx, GameAddressFlag.Name, contracts.NewFaultDisputeGameContract) if err != nil { return fmt.Errorf("failed to create dispute game bindings: %w", err) } diff --git a/op-challenger/cmd/resolve.go b/op-challenger/cmd/resolve.go index cc4c18ab11f3..0e5dfb8f3878 100644 --- a/op-challenger/cmd/resolve.go +++ b/op-challenger/cmd/resolve.go @@ -12,7 +12,7 @@ import ( ) func Resolve(ctx *cli.Context) error { - contract, txMgr, err := NewContractWithTxMgr[*contracts.FaultDisputeGameContract](ctx, GameAddressFlag.Name, contracts.NewFaultDisputeGameContract) + contract, txMgr, err := NewContractWithTxMgr[contracts.FaultDisputeGameContract](ctx, GameAddressFlag.Name, contracts.NewFaultDisputeGameContract) if err != nil { return fmt.Errorf("failed to create dispute game bindings: %w", err) } diff --git a/op-challenger/cmd/utils.go b/op-challenger/cmd/utils.go index 135da14d6a3d..ad5d30a8d41d 100644 --- a/op-challenger/cmd/utils.go +++ b/op-challenger/cmd/utils.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/ethereum-optimism/optimism/op-challenger/flags" @@ -14,7 +15,7 @@ import ( "github.com/urfave/cli/v2" ) -type ContractCreator[T any] func(contractMetrics.ContractMetricer, common.Address, *batching.MultiCaller) T +type ContractCreator[T any] func(context.Context, contractMetrics.ContractMetricer, common.Address, *batching.MultiCaller) (T, error) // NewContractWithTxMgr creates a new contract and a transaction manager. func NewContractWithTxMgr[T any](ctx *cli.Context, flagName string, creator ContractCreator[T]) (T, txmgr.TxManager, error) { @@ -40,7 +41,10 @@ func newContractFromCLI[T any](ctx *cli.Context, flagName string, caller *batchi return contract, err } - created := creator(contractMetrics.NoopContractMetrics, gameAddr, caller) + created, err := creator(ctx.Context, contractMetrics.NoopContractMetrics, gameAddr, caller) + if err != nil { + return contract, fmt.Errorf("failed to create contract bindings: %w", err) + } return created, nil } diff --git a/op-challenger/game/fault/contracts/abis/FaultDisputeGame-0.8.0.json b/op-challenger/game/fault/contracts/abis/FaultDisputeGame-0.8.0.json new file mode 100644 index 000000000000..8bd94969b07c --- /dev/null +++ b/op-challenger/game/fault/contracts/abis/FaultDisputeGame-0.8.0.json @@ -0,0 +1,741 @@ +[ + { + "inputs": [ + { + "internalType": "GameType", + "name": "_gameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "_absolutePrestate", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_maxGameDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_splitDepth", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "_gameDuration", + "type": "uint64" + }, + { + "internalType": "contract IBigStepper", + "name": "_vm", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "_weth", + "type": "address" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [], + "name": "absolutePrestate", + "outputs": [ + { + "internalType": "Claim", + "name": "absolutePrestate_", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_ident", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_execLeafIdx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_partOffset", + "type": "uint256" + } + ], + "name": "addLocalData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_parentIndex", + "type": "uint256" + }, + { + "internalType": "Claim", + "name": "_claim", + "type": "bytes32" + } + ], + "name": "attack", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + } + ], + "name": "claimCredit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "claimData", + "outputs": [ + { + "internalType": "uint32", + "name": "parentIndex", + "type": "uint32" + }, + { + "internalType": "address", + "name": "counteredBy", + "type": "address" + }, + { + "internalType": "address", + "name": "claimant", + "type": "address" + }, + { + "internalType": "uint128", + "name": "bond", + "type": "uint128" + }, + { + "internalType": "Claim", + "name": "claim", + "type": "bytes32" + }, + { + "internalType": "Position", + "name": "position", + "type": "uint128" + }, + { + "internalType": "Clock", + "name": "clock", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimDataLen", + "outputs": [ + { + "internalType": "uint256", + "name": "len_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "createdAt", + "outputs": [ + { + "internalType": "Timestamp", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "credit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_parentIndex", + "type": "uint256" + }, + { + "internalType": "Claim", + "name": "_claim", + "type": "bytes32" + } + ], + "name": "defend", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "extraData", + "outputs": [ + { + "internalType": "bytes", + "name": "extraData_", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "gameData", + "outputs": [ + { + "internalType": "GameType", + "name": "gameType_", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "rootClaim_", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "extraData_", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gameDuration", + "outputs": [ + { + "internalType": "Duration", + "name": "gameDuration_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gameType", + "outputs": [ + { + "internalType": "GameType", + "name": "gameType_", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Position", + "name": "_position", + "type": "uint128" + } + ], + "name": "getRequiredBond", + "outputs": [ + { + "internalType": "uint256", + "name": "requiredBond_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "l1Head", + "outputs": [ + { + "internalType": "Hash", + "name": "l1Head_", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "l2BlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "l2BlockNumber_", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "l2ChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "l2ChainId_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxGameDepth", + "outputs": [ + { + "internalType": "uint256", + "name": "maxGameDepth_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_challengeIndex", + "type": "uint256" + }, + { + "internalType": "Claim", + "name": "_claim", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "_isAttack", + "type": "bool" + } + ], + "name": "move", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "resolve", + "outputs": [ + { + "internalType": "enum GameStatus", + "name": "status_", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_claimIndex", + "type": "uint256" + } + ], + "name": "resolveClaim", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "resolvedAt", + "outputs": [ + { + "internalType": "Timestamp", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rootClaim", + "outputs": [ + { + "internalType": "Claim", + "name": "rootClaim_", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "splitDepth", + "outputs": [ + { + "internalType": "uint256", + "name": "splitDepth_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "startingBlockNumber_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingOutputRoot", + "outputs": [ + { + "internalType": "Hash", + "name": "root", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "l2BlockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingRootHash", + "outputs": [ + { + "internalType": "Hash", + "name": "startingRootHash_", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "enum GameStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_claimIndex", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "_isAttack", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "_stateData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "_proof", + "type": "bytes" + } + ], + "name": "step", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vm", + "outputs": [ + { + "internalType": "contract IBigStepper", + "name": "vm_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "weth", + "outputs": [ + { + "internalType": "contract IDelayedWETH", + "name": "weth_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "parentIndex", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "Claim", + "name": "claim", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "claimant", + "type": "address" + } + ], + "name": "Move", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum GameStatus", + "name": "status", + "type": "uint8" + } + ], + "name": "Resolved", + "type": "event" + }, + { + "inputs": [], + "name": "AlreadyInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "AnchorRootNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "BondTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "CannotDefendRootClaim", + "type": "error" + }, + { + "inputs": [], + "name": "ClaimAboveSplit", + "type": "error" + }, + { + "inputs": [], + "name": "ClaimAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "ClaimAlreadyResolved", + "type": "error" + }, + { + "inputs": [], + "name": "ClockNotExpired", + "type": "error" + }, + { + "inputs": [], + "name": "ClockTimeExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "DuplicateStep", + "type": "error" + }, + { + "inputs": [], + "name": "GameDepthExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotInProgress", + "type": "error" + }, + { + "inputs": [], + "name": "IncorrectBondAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLocalIdent", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidParent", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPrestate", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSplitDepth", + "type": "error" + }, + { + "inputs": [], + "name": "NoCreditToClaim", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfOrderResolution", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Claim", + "name": "rootClaim", + "type": "bytes32" + } + ], + "name": "UnexpectedRootClaim", + "type": "error" + }, + { + "inputs": [], + "name": "ValidStep", + "type": "error" + } +] \ No newline at end of file diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index acdf7d3fd022..30c0ff663f3d 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -1,11 +1,13 @@ package contracts import ( + "bytes" "context" "errors" "fmt" "math" "math/big" + "strings" "time" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" @@ -15,6 +17,7 @@ import ( "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/accounts/abi" "github.com/ethereum/go-ethereum/common" ) @@ -22,6 +25,7 @@ import ( var maxChildChecks = big.NewInt(512) var ( + methodVersion = "version" methodMaxClockDuration = "maxClockDuration" methodMaxGameDepth = "maxGameDepth" methodAbsolutePrestate = "absolutePrestate" @@ -50,7 +54,7 @@ var ( var ErrSimulationFailed = errors.New("tx simulation failed") -type FaultDisputeGameContract struct { +type FaultDisputeGameContractLatest struct { metrics metrics.ContractMetricer multiCaller *batching.MultiCaller contract *batching.BoundContract @@ -61,20 +65,46 @@ type Proposal struct { OutputRoot common.Hash } -func NewFaultDisputeGameContract(metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) *FaultDisputeGameContract { +func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { contractAbi := snapshots.LoadFaultDisputeGameABI() - return &FaultDisputeGameContract{ - metrics: metrics, - multiCaller: caller, - contract: batching.NewBoundContract(contractAbi, addr), + result, err := caller.SingleCall(ctx, rpcblock.Latest, batching.NewContractCall(contractAbi, addr, methodVersion)) + if err != nil { + return nil, fmt.Errorf("failed to retrieve version of dispute game %v: %w", addr, err) + } + version := result.GetString(0) + + if strings.HasPrefix(version, "0.8.") { + // Detected an older version of contracts, use a compatibility shim. + legacyAbi := mustParseAbi(faultDisputeGameAbi020) + return &FaultDisputeGameContract080{ + FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ + metrics: metrics, + multiCaller: caller, + contract: batching.NewBoundContract(legacyAbi, addr), + }, + }, nil + } else { + return &FaultDisputeGameContractLatest{ + metrics: metrics, + multiCaller: caller, + contract: batching.NewBoundContract(contractAbi, addr), + }, nil + } +} + +func mustParseAbi(json []byte) *abi.ABI { + loaded, err := abi.JSON(bytes.NewReader(json)) + if err != nil { + panic(err) } + return &loaded } // GetBalance returns the total amount of ETH controlled by this contract. // Note that the ETH is actually held by the DelayedWETH contract which may be shared by multiple games. // Returns the balance and the address of the contract that actually holds the balance. -func (f *FaultDisputeGameContract) GetBalance(ctx context.Context, block rpcblock.Block) (*big.Int, common.Address, error) { +func (f *FaultDisputeGameContractLatest) GetBalance(ctx context.Context, block rpcblock.Block) (*big.Int, common.Address, error) { defer f.metrics.StartContractRequest("GetBalance")() result, err := f.multiCaller.SingleCall(ctx, block, f.contract.Call(methodWETH)) if err != nil { @@ -90,7 +120,7 @@ func (f *FaultDisputeGameContract) GetBalance(ctx context.Context, block rpcbloc // GetBlockRange returns the block numbers of the absolute pre-state block (typically genesis or the bedrock activation block) // and the post-state block (that the proposed output root is for). -func (f *FaultDisputeGameContract) GetBlockRange(ctx context.Context) (prestateBlock uint64, poststateBlock uint64, retErr error) { +func (f *FaultDisputeGameContractLatest) GetBlockRange(ctx context.Context) (prestateBlock uint64, poststateBlock uint64, retErr error) { defer f.metrics.StartContractRequest("GetBlockRange")() results, err := f.multiCaller.Call(ctx, rpcblock.Latest, f.contract.Call(methodStartingBlockNumber), @@ -109,7 +139,7 @@ func (f *FaultDisputeGameContract) GetBlockRange(ctx context.Context) (prestateB } // GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and max clock duration. -func (f *FaultDisputeGameContract) GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) { +func (f *FaultDisputeGameContractLatest) GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) { defer f.metrics.StartContractRequest("GetGameMetadata")() results, err := f.multiCaller.Call(ctx, block, f.contract.Call(methodL1Head), @@ -134,7 +164,7 @@ func (f *FaultDisputeGameContract) GetGameMetadata(ctx context.Context, block rp return l1Head, l2BlockNumber, rootClaim, status, duration, nil } -func (f *FaultDisputeGameContract) GetStartingRootHash(ctx context.Context) (common.Hash, error) { +func (f *FaultDisputeGameContractLatest) GetStartingRootHash(ctx context.Context) (common.Hash, error) { defer f.metrics.StartContractRequest("GetStartingRootHash")() startingRootHash, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodStartingRootHash)) if err != nil { @@ -143,7 +173,7 @@ func (f *FaultDisputeGameContract) GetStartingRootHash(ctx context.Context) (com return startingRootHash.GetHash(0), nil } -func (f *FaultDisputeGameContract) GetSplitDepth(ctx context.Context) (types.Depth, error) { +func (f *FaultDisputeGameContractLatest) GetSplitDepth(ctx context.Context) (types.Depth, error) { defer f.metrics.StartContractRequest("GetSplitDepth")() splitDepth, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodSplitDepth)) if err != nil { @@ -152,7 +182,7 @@ func (f *FaultDisputeGameContract) GetSplitDepth(ctx context.Context) (types.Dep return types.Depth(splitDepth.GetBigInt(0).Uint64()), nil } -func (f *FaultDisputeGameContract) GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error) { +func (f *FaultDisputeGameContractLatest) GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error) { defer f.metrics.StartContractRequest("GetCredit")() results, err := f.multiCaller.Call(ctx, rpcblock.Latest, f.contract.Call(methodCredit, recipient), @@ -171,7 +201,7 @@ func (f *FaultDisputeGameContract) GetCredit(ctx context.Context, recipient comm return credit, status, nil } -func (f *FaultDisputeGameContract) GetRequiredBonds(ctx context.Context, block rpcblock.Block, positions ...*big.Int) ([]*big.Int, error) { +func (f *FaultDisputeGameContractLatest) GetRequiredBonds(ctx context.Context, block rpcblock.Block, positions ...*big.Int) ([]*big.Int, error) { calls := make([]batching.Call, 0, len(positions)) for _, position := range positions { calls = append(calls, f.contract.Call(methodRequiredBond, position)) @@ -187,7 +217,7 @@ func (f *FaultDisputeGameContract) GetRequiredBonds(ctx context.Context, block r return requiredBonds, nil } -func (f *FaultDisputeGameContract) GetCredits(ctx context.Context, block rpcblock.Block, recipients ...common.Address) ([]*big.Int, error) { +func (f *FaultDisputeGameContractLatest) GetCredits(ctx context.Context, block rpcblock.Block, recipients ...common.Address) ([]*big.Int, error) { defer f.metrics.StartContractRequest("GetCredits")() calls := make([]batching.Call, 0, len(recipients)) for _, recipient := range recipients { @@ -204,7 +234,7 @@ func (f *FaultDisputeGameContract) GetCredits(ctx context.Context, block rpcbloc return credits, nil } -func (f *FaultDisputeGameContract) ClaimCreditTx(ctx context.Context, recipient common.Address) (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) ClaimCreditTx(ctx context.Context, recipient common.Address) (txmgr.TxCandidate, error) { defer f.metrics.StartContractRequest("ClaimCredit")() call := f.contract.Call(methodClaimCredit, recipient) _, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, call) @@ -214,7 +244,7 @@ func (f *FaultDisputeGameContract) ClaimCreditTx(ctx context.Context, recipient return call.ToTxCandidate() } -func (f *FaultDisputeGameContract) GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error) { +func (f *FaultDisputeGameContractLatest) GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error) { defer f.metrics.StartContractRequest("GetRequiredBond")() bond, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodRequiredBond, position.ToGIndex())) if err != nil { @@ -223,14 +253,14 @@ func (f *FaultDisputeGameContract) GetRequiredBond(ctx context.Context, position return bond.GetBigInt(0), nil } -func (f *FaultDisputeGameContract) UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { if data.IsLocal { return f.addLocalDataTx(claimIdx, data) } return f.addGlobalDataTx(ctx, data) } -func (f *FaultDisputeGameContract) addLocalDataTx(claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) addLocalDataTx(claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { call := f.contract.Call( methodAddLocalData, data.GetIdent(), @@ -240,7 +270,7 @@ func (f *FaultDisputeGameContract) addLocalDataTx(claimIdx uint64, data *types.P return call.ToTxCandidate() } -func (f *FaultDisputeGameContract) addGlobalDataTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) addGlobalDataTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { oracle, err := f.GetOracle(ctx) if err != nil { return txmgr.TxCandidate{}, err @@ -248,7 +278,7 @@ func (f *FaultDisputeGameContract) addGlobalDataTx(ctx context.Context, data *ty return oracle.AddGlobalDataTx(data) } -func (f *FaultDisputeGameContract) GetWithdrawals(ctx context.Context, block rpcblock.Block, gameAddr common.Address, recipients ...common.Address) ([]*WithdrawalRequest, error) { +func (f *FaultDisputeGameContractLatest) GetWithdrawals(ctx context.Context, block rpcblock.Block, gameAddr common.Address, recipients ...common.Address) ([]*WithdrawalRequest, error) { defer f.metrics.StartContractRequest("GetWithdrawals")() delayedWETH, err := f.getDelayedWETH(ctx) if err != nil { @@ -257,7 +287,7 @@ func (f *FaultDisputeGameContract) GetWithdrawals(ctx context.Context, block rpc return delayedWETH.GetWithdrawals(ctx, block, gameAddr, recipients...) } -func (f *FaultDisputeGameContract) getDelayedWETH(ctx context.Context) (*DelayedWETHContract, error) { +func (f *FaultDisputeGameContractLatest) getDelayedWETH(ctx context.Context) (*DelayedWETHContract, error) { defer f.metrics.StartContractRequest("GetDelayedWETH")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodWETH)) if err != nil { @@ -266,7 +296,7 @@ func (f *FaultDisputeGameContract) getDelayedWETH(ctx context.Context) (*Delayed return NewDelayedWETHContract(f.metrics, result.GetAddress(0), f.multiCaller), nil } -func (f *FaultDisputeGameContract) GetOracle(ctx context.Context) (*PreimageOracleContract, error) { +func (f *FaultDisputeGameContractLatest) GetOracle(ctx context.Context) (*PreimageOracleContract, error) { defer f.metrics.StartContractRequest("GetOracle")() vm, err := f.vm(ctx) if err != nil { @@ -275,7 +305,7 @@ func (f *FaultDisputeGameContract) GetOracle(ctx context.Context) (*PreimageOrac return vm.Oracle(ctx) } -func (f *FaultDisputeGameContract) GetMaxClockDuration(ctx context.Context) (time.Duration, error) { +func (f *FaultDisputeGameContractLatest) GetMaxClockDuration(ctx context.Context) (time.Duration, error) { defer f.metrics.StartContractRequest("GetMaxClockDuration")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodMaxClockDuration)) if err != nil { @@ -284,7 +314,7 @@ func (f *FaultDisputeGameContract) GetMaxClockDuration(ctx context.Context) (tim return time.Duration(result.GetUint64(0)) * time.Second, nil } -func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (types.Depth, error) { +func (f *FaultDisputeGameContractLatest) GetMaxGameDepth(ctx context.Context) (types.Depth, error) { defer f.metrics.StartContractRequest("GetMaxGameDepth")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodMaxGameDepth)) if err != nil { @@ -293,7 +323,7 @@ func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (types.D return types.Depth(result.GetBigInt(0).Uint64()), nil } -func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) { +func (f *FaultDisputeGameContractLatest) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) { defer f.metrics.StartContractRequest("GetAbsolutePrestateHash")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodAbsolutePrestate)) if err != nil { @@ -302,7 +332,7 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) return result.GetHash(0), nil } -func (f *FaultDisputeGameContract) GetL1Head(ctx context.Context) (common.Hash, error) { +func (f *FaultDisputeGameContractLatest) GetL1Head(ctx context.Context) (common.Hash, error) { defer f.metrics.StartContractRequest("GetL1Head")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodL1Head)) if err != nil { @@ -311,7 +341,7 @@ func (f *FaultDisputeGameContract) GetL1Head(ctx context.Context) (common.Hash, return result.GetHash(0), nil } -func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) { +func (f *FaultDisputeGameContractLatest) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) { defer f.metrics.StartContractRequest("GetStatus")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodStatus)) if err != nil { @@ -320,7 +350,7 @@ func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.Gam return gameTypes.GameStatusFromUint8(result.GetUint8(0)) } -func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, error) { +func (f *FaultDisputeGameContractLatest) GetClaimCount(ctx context.Context) (uint64, error) { defer f.metrics.StartContractRequest("GetClaimCount")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodClaimCount)) if err != nil { @@ -329,7 +359,7 @@ func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, e return result.GetBigInt(0).Uint64(), nil } -func (f *FaultDisputeGameContract) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) { +func (f *FaultDisputeGameContractLatest) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) { defer f.metrics.StartContractRequest("GetClaim")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodClaim, new(big.Int).SetUint64(idx))) if err != nil { @@ -338,7 +368,7 @@ func (f *FaultDisputeGameContract) GetClaim(ctx context.Context, idx uint64) (ty return f.decodeClaim(result, int(idx)), nil } -func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) { +func (f *FaultDisputeGameContractLatest) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) { defer f.metrics.StartContractRequest("GetAllClaims")() results, err := batching.ReadArray(ctx, f.multiCaller, block, f.contract.Call(methodClaimCount), func(i *big.Int) *batching.ContractCall { return f.contract.Call(methodClaim, i) @@ -354,7 +384,7 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context, block rpcbl return claims, nil } -func (f *FaultDisputeGameContract) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) { +func (f *FaultDisputeGameContractLatest) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) { defer f.metrics.StartContractRequest("IsResolved")() calls := make([]batching.Call, 0, len(claims)) for _, claim := range claims { @@ -371,7 +401,7 @@ func (f *FaultDisputeGameContract) IsResolved(ctx context.Context, block rpcbloc return resolved, nil } -func (f *FaultDisputeGameContract) vm(ctx context.Context) (*VMContract, error) { +func (f *FaultDisputeGameContractLatest) vm(ctx context.Context) (*VMContract, error) { result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodVM)) if err != nil { return nil, fmt.Errorf("failed to fetch VM addr: %w", err) @@ -380,22 +410,22 @@ func (f *FaultDisputeGameContract) vm(ctx context.Context) (*VMContract, error) return NewVMContract(vmAddr, f.multiCaller), nil } -func (f *FaultDisputeGameContract) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) { call := f.contract.Call(methodAttack, new(big.Int).SetUint64(parentContractIndex), pivot) return call.ToTxCandidate() } -func (f *FaultDisputeGameContract) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) { call := f.contract.Call(methodDefend, new(big.Int).SetUint64(parentContractIndex), pivot) return call.ToTxCandidate() } -func (f *FaultDisputeGameContract) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) { call := f.contract.Call(methodStep, new(big.Int).SetUint64(claimIdx), isAttack, stateData, proof) return call.ToTxCandidate() } -func (f *FaultDisputeGameContract) CallResolveClaim(ctx context.Context, claimIdx uint64) error { +func (f *FaultDisputeGameContractLatest) CallResolveClaim(ctx context.Context, claimIdx uint64) error { defer f.metrics.StartContractRequest("CallResolveClaim")() call := f.resolveClaimCall(claimIdx) _, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, call) @@ -405,16 +435,16 @@ func (f *FaultDisputeGameContract) CallResolveClaim(ctx context.Context, claimId return nil } -func (f *FaultDisputeGameContract) ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) { call := f.resolveClaimCall(claimIdx) return call.ToTxCandidate() } -func (f *FaultDisputeGameContract) resolveClaimCall(claimIdx uint64) *batching.ContractCall { +func (f *FaultDisputeGameContractLatest) resolveClaimCall(claimIdx uint64) *batching.ContractCall { return f.contract.Call(methodResolveClaim, new(big.Int).SetUint64(claimIdx), maxChildChecks) } -func (f *FaultDisputeGameContract) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) { +func (f *FaultDisputeGameContractLatest) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) { defer f.metrics.StartContractRequest("CallResolve")() call := f.resolveCall() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, call) @@ -424,12 +454,12 @@ func (f *FaultDisputeGameContract) CallResolve(ctx context.Context) (gameTypes.G return gameTypes.GameStatusFromUint8(result.GetUint8(0)) } -func (f *FaultDisputeGameContract) ResolveTx() (txmgr.TxCandidate, error) { +func (f *FaultDisputeGameContractLatest) ResolveTx() (txmgr.TxCandidate, error) { call := f.resolveCall() return call.ToTxCandidate() } -func (f *FaultDisputeGameContract) resolveCall() *batching.ContractCall { +func (f *FaultDisputeGameContractLatest) resolveCall() *batching.ContractCall { return f.contract.Call(methodResolve) } @@ -448,7 +478,7 @@ func packClock(c types.Clock) *big.Int { return new(big.Int).Or(encoded, big.NewInt(c.Timestamp.Unix())) } -func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim { +func (f *FaultDisputeGameContractLatest) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim { parentIndex := result.GetUint32(0) counteredBy := result.GetAddress(1) claimant := result.GetAddress(2) @@ -469,3 +499,35 @@ func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, cont ParentContractIndex: int(parentIndex), } } + +type FaultDisputeGameContract interface { + GetBalance(ctx context.Context, block rpcblock.Block) (*big.Int, common.Address, error) + GetBlockRange(ctx context.Context) (prestateBlock uint64, poststateBlock uint64, retErr error) + GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) + GetStartingRootHash(ctx context.Context) (common.Hash, error) + GetSplitDepth(ctx context.Context) (types.Depth, error) + GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error) + GetRequiredBonds(ctx context.Context, block rpcblock.Block, positions ...*big.Int) ([]*big.Int, error) + GetCredits(ctx context.Context, block rpcblock.Block, recipients ...common.Address) ([]*big.Int, error) + ClaimCreditTx(ctx context.Context, recipient common.Address) (txmgr.TxCandidate, error) + GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error) + UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error) + GetWithdrawals(ctx context.Context, block rpcblock.Block, gameAddr common.Address, recipients ...common.Address) ([]*WithdrawalRequest, error) + GetOracle(ctx context.Context) (*PreimageOracleContract, error) + GetMaxClockDuration(ctx context.Context) (time.Duration, error) + GetMaxGameDepth(ctx context.Context) (types.Depth, error) + GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) + GetL1Head(ctx context.Context) (common.Hash, error) + GetStatus(ctx context.Context) (gameTypes.GameStatus, error) + GetClaimCount(ctx context.Context) (uint64, error) + GetClaim(ctx context.Context, idx uint64) (types.Claim, error) + GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) + IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) + AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) + DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) + StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) + CallResolveClaim(ctx context.Context, claimIdx uint64) error + ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) + CallResolve(ctx context.Context) (gameTypes.GameStatus, error) + ResolveTx() (txmgr.TxCandidate, error) +} diff --git a/op-challenger/game/fault/contracts/faultdisputegame080.go b/op-challenger/game/fault/contracts/faultdisputegame080.go new file mode 100644 index 000000000000..18ed05d20c02 --- /dev/null +++ b/op-challenger/game/fault/contracts/faultdisputegame080.go @@ -0,0 +1,134 @@ +package contracts + +import ( + "context" + _ "embed" + "fmt" + "math/big" + "time" + + "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/go-ethereum/common" +) + +//go:embed abis/FaultDisputeGame-0.8.0.json +var faultDisputeGameAbi020 []byte + +var resolvedBondAmount = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1)) + +var ( + methodGameDuration = "gameDuration" +) + +type FaultDisputeGameContract080 struct { + FaultDisputeGameContractLatest +} + +// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and max clock duration. +func (f *FaultDisputeGameContract080) GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, 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(methodGameDuration)) + if err != nil { + return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to retrieve game metadata: %w", err) + } + if len(results) != 5 { + return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("expected 3 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 common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to convert game status: %w", err) + } + duration := results[4].GetUint64(0) + return l1Head, l2BlockNumber, rootClaim, status, duration / 2, nil +} + +func (f *FaultDisputeGameContract080) GetMaxClockDuration(ctx context.Context) (time.Duration, error) { + defer f.metrics.StartContractRequest("GetMaxClockDuration")() + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodGameDuration)) + if err != nil { + return 0, fmt.Errorf("failed to fetch game duration: %w", err) + } + return time.Duration(result.GetUint64(0)) * time.Second / 2, nil +} + +func (f *FaultDisputeGameContract080) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) { + claim, err := f.FaultDisputeGameContractLatest.GetClaim(ctx, idx) + if err != nil { + return types.Claim{}, err + } + // Replace the resolved sentinel with what the bond would have been + if claim.Bond.Cmp(resolvedBondAmount) == 0 { + bond, err := f.GetRequiredBond(ctx, claim.Position) + if err != nil { + return types.Claim{}, err + } + claim.Bond = bond + } + return claim, nil +} + +func (f *FaultDisputeGameContract080) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) { + claims, err := f.FaultDisputeGameContractLatest.GetAllClaims(ctx, block) + if err != nil { + return nil, err + } + resolvedClaims := make([]*types.Claim, 0, len(claims)) + positions := make([]*big.Int, 0, len(claims)) + for i, claim := range claims { + if claim.Bond.Cmp(resolvedBondAmount) == 0 { + resolvedClaims = append(resolvedClaims, &claims[i]) + positions = append(positions, claim.Position.ToGIndex()) + } + } + bonds, err := f.GetRequiredBonds(ctx, block, positions...) + if err != nil { + return nil, fmt.Errorf("failed to get required bonds for resolved claims: %w", err) + } + for i, bond := range bonds { + resolvedClaims[i].Bond = bond + } + return claims, nil +} + +func (f *FaultDisputeGameContract080) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) { + rawClaims, err := f.FaultDisputeGameContractLatest.GetAllClaims(ctx, block) + if err != nil { + return nil, fmt.Errorf("failed to get raw claim data: %w", err) + } + results := make([]bool, len(claims)) + for i, claim := range claims { + results[i] = rawClaims[claim.ContractIndex].Bond.Cmp(resolvedBondAmount) == 0 + } + return results, nil +} + +func (f *FaultDisputeGameContract080) CallResolveClaim(ctx context.Context, claimIdx uint64) error { + defer f.metrics.StartContractRequest("CallResolveClaim")() + call := f.resolveClaimCall(claimIdx) + _, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, call) + if err != nil { + return fmt.Errorf("failed to call resolve claim: %w", err) + } + return nil +} + +func (f *FaultDisputeGameContract080) ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) { + call := f.resolveClaimCall(claimIdx) + return call.ToTxCandidate() +} + +func (f *FaultDisputeGameContract080) resolveClaimCall(claimIdx uint64) *batching.ContractCall { + return f.contract.Call(methodResolveClaim, new(big.Int).SetUint64(claimIdx)) +} diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index 592025be2269..670442029dac 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -16,6 +16,7 @@ import ( batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -26,6 +27,29 @@ var ( oracleAddr = common.HexToAddress("0x44442842371dFC380576ebb09Ae16Cb6B6ca4444") ) +type contractVersion struct { + version string + loadAbi func() *abi.ABI +} + +const ( + vers080 = "0.8.0" + versLatest = "0.18.0" +) + +var versions = []contractVersion{ + { + version: vers080, + loadAbi: func() *abi.ABI { + return mustParseAbi(faultDisputeGameAbi020) + }, + }, + { + version: versLatest, + loadAbi: snapshots.LoadFaultDisputeGameABI, + }, +} + func TestSimpleGetters(t *testing.T) { tests := []struct { methodAlias string @@ -33,13 +57,14 @@ func TestSimpleGetters(t *testing.T) { args []interface{} result interface{} expected interface{} // Defaults to expecting the same as result - call func(game *FaultDisputeGameContract) (any, error) + call func(game FaultDisputeGameContract) (any, error) + applies func(version contractVersion) bool }{ { methodAlias: "status", method: methodStatus, result: types.GameStatusChallengerWon, - call: func(game *FaultDisputeGameContract) (any, error) { + call: func(game FaultDisputeGameContract) (any, error) { return game.GetStatus(context.Background()) }, }, @@ -48,16 +73,31 @@ func TestSimpleGetters(t *testing.T) { method: methodMaxClockDuration, result: uint64(5566), expected: 5566 * time.Second, - call: func(game *FaultDisputeGameContract) (any, error) { + call: func(game FaultDisputeGameContract) (any, error) { return game.GetMaxClockDuration(context.Background()) }, + applies: func(version contractVersion) bool { + return version.version != vers080 + }, + }, + { + methodAlias: "gameDuration", + method: methodGameDuration, + result: uint64(5566) * 2, + expected: 5566 * time.Second, + call: func(game FaultDisputeGameContract) (any, error) { + return game.GetMaxClockDuration(context.Background()) + }, + applies: func(version contractVersion) bool { + return version.version == vers080 + }, }, { methodAlias: "maxGameDepth", method: methodMaxGameDepth, result: big.NewInt(128), expected: faultTypes.Depth(128), - call: func(game *FaultDisputeGameContract) (any, error) { + call: func(game FaultDisputeGameContract) (any, error) { return game.GetMaxGameDepth(context.Background()) }, }, @@ -65,7 +105,7 @@ func TestSimpleGetters(t *testing.T) { methodAlias: "absolutePrestate", method: methodAbsolutePrestate, result: common.Hash{0xab}, - call: func(game *FaultDisputeGameContract) (any, error) { + call: func(game FaultDisputeGameContract) (any, error) { return game.GetAbsolutePrestateHash(context.Background()) }, }, @@ -74,7 +114,7 @@ func TestSimpleGetters(t *testing.T) { method: methodClaimCount, result: big.NewInt(9876), expected: uint64(9876), - call: func(game *FaultDisputeGameContract) (any, error) { + call: func(game FaultDisputeGameContract) (any, error) { return game.GetClaimCount(context.Background()) }, }, @@ -82,7 +122,7 @@ func TestSimpleGetters(t *testing.T) { methodAlias: "l1Head", method: methodL1Head, result: common.Hash{0xdd, 0xbb}, - call: func(game *FaultDisputeGameContract) (any, error) { + call: func(game FaultDisputeGameContract) (any, error) { return game.GetL1Head(context.Background()) }, }, @@ -90,23 +130,31 @@ func TestSimpleGetters(t *testing.T) { methodAlias: "resolve", method: methodResolve, result: types.GameStatusInProgress, - call: func(game *FaultDisputeGameContract) (any, error) { + call: func(game FaultDisputeGameContract) (any, error) { return game.CallResolve(context.Background()) }, }, } - for _, test := range tests { - test := test - t.Run(test.methodAlias, func(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - stubRpc.SetResponse(fdgAddr, test.method, rpcblock.Latest, nil, []interface{}{test.result}) - status, err := test.call(game) - require.NoError(t, err) - expected := test.expected - if expected == nil { - expected = test.result + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + for _, test := range tests { + test := test + t.Run(test.methodAlias, func(t *testing.T) { + if test.applies != nil && !test.applies(version) { + t.Skip("Skipping for this version") + } + stubRpc, game := setupFaultDisputeGameTest(t, version) + stubRpc.SetResponse(fdgAddr, test.method, rpcblock.Latest, nil, []interface{}{test.result}) + status, err := test.call(game) + require.NoError(t, err) + expected := test.expected + if expected == nil { + expected = test.result + } + require.Equal(t, expected, status) + }) } - require.Equal(t, expected, status) }) } } @@ -150,153 +198,211 @@ func TestClock_EncodingDecoding(t *testing.T) { } func TestGetOracleAddr(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - stubRpc.SetResponse(fdgAddr, methodVM, rpcblock.Latest, nil, []interface{}{vmAddr}) - stubRpc.SetResponse(vmAddr, methodOracle, rpcblock.Latest, nil, []interface{}{oracleAddr}) - - actual, err := game.GetOracle(context.Background()) - require.NoError(t, err) - require.Equal(t, oracleAddr, actual.Addr()) + for _, version := range versions { + version := version + t.Run(version.version, 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}) + + actual, err := game.GetOracle(context.Background()) + require.NoError(t, err) + require.Equal(t, oracleAddr, actual.Addr()) + }) + } } func TestGetClaim(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - idx := big.NewInt(2) - parentIndex := uint32(1) - counteredBy := common.Address{0x01} - claimant := common.Address{0x02} - bond := big.NewInt(5) - value := common.Hash{0xab} - position := big.NewInt(2) - clock := big.NewInt(1234) - stubRpc.SetResponse(fdgAddr, methodClaim, rpcblock.Latest, []interface{}{idx}, []interface{}{parentIndex, counteredBy, claimant, bond, value, position, clock}) - status, err := game.GetClaim(context.Background(), idx.Uint64()) - require.NoError(t, err) - require.Equal(t, faultTypes.Claim{ - ClaimData: faultTypes.ClaimData{ - Value: value, - Position: faultTypes.NewPositionFromGIndex(position), - Bond: bond, - }, - CounteredBy: counteredBy, - Claimant: claimant, - Clock: decodeClock(big.NewInt(1234)), - ContractIndex: int(idx.Uint64()), - ParentContractIndex: 1, - }, status) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + idx := big.NewInt(2) + parentIndex := uint32(1) + counteredBy := common.Address{0x01} + claimant := common.Address{0x02} + bond := big.NewInt(5) + value := common.Hash{0xab} + position := big.NewInt(2) + clock := big.NewInt(1234) + stubRpc.SetResponse(fdgAddr, methodClaim, rpcblock.Latest, []interface{}{idx}, []interface{}{parentIndex, counteredBy, claimant, bond, value, position, clock}) + status, err := game.GetClaim(context.Background(), idx.Uint64()) + require.NoError(t, err) + require.Equal(t, faultTypes.Claim{ + ClaimData: faultTypes.ClaimData{ + Value: value, + Position: faultTypes.NewPositionFromGIndex(position), + Bond: bond, + }, + CounteredBy: counteredBy, + Claimant: claimant, + Clock: decodeClock(big.NewInt(1234)), + ContractIndex: int(idx.Uint64()), + ParentContractIndex: 1, + }, status) + }) + } } func TestGetAllClaims(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - claim0 := faultTypes.Claim{ - ClaimData: faultTypes.ClaimData{ - Value: common.Hash{0xaa}, - Position: faultTypes.NewPositionFromGIndex(big.NewInt(1)), - Bond: big.NewInt(5), - }, - CounteredBy: common.Address{0x01}, - Claimant: common.Address{0x02}, - Clock: decodeClock(big.NewInt(1234)), - ContractIndex: 0, - ParentContractIndex: math.MaxUint32, - } - claim1 := faultTypes.Claim{ - ClaimData: faultTypes.ClaimData{ - Value: common.Hash{0xab}, - Position: faultTypes.NewPositionFromGIndex(big.NewInt(2)), - Bond: big.NewInt(5), - }, - CounteredBy: common.Address{0x02}, - Claimant: common.Address{0x01}, - Clock: decodeClock(big.NewInt(4455)), - ContractIndex: 1, - ParentContractIndex: 0, - } - claim2 := faultTypes.Claim{ - ClaimData: faultTypes.ClaimData{ - Value: common.Hash{0xbb}, - Position: faultTypes.NewPositionFromGIndex(big.NewInt(6)), - Bond: big.NewInt(5), - }, - Claimant: common.Address{0x02}, - Clock: decodeClock(big.NewInt(7777)), - ContractIndex: 2, - ParentContractIndex: 1, - } - expectedClaims := []faultTypes.Claim{claim0, claim1, claim2} - block := rpcblock.ByNumber(42) - stubRpc.SetResponse(fdgAddr, methodClaimCount, block, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))}) - for _, claim := range expectedClaims { - expectGetClaim(stubRpc, block, claim) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + claim0 := faultTypes.Claim{ + ClaimData: faultTypes.ClaimData{ + Value: common.Hash{0xaa}, + Position: faultTypes.NewPositionFromGIndex(big.NewInt(1)), + Bond: big.NewInt(5), + }, + CounteredBy: common.Address{0x01}, + Claimant: common.Address{0x02}, + Clock: decodeClock(big.NewInt(1234)), + ContractIndex: 0, + ParentContractIndex: math.MaxUint32, + } + claim1 := faultTypes.Claim{ + ClaimData: faultTypes.ClaimData{ + Value: common.Hash{0xab}, + Position: faultTypes.NewPositionFromGIndex(big.NewInt(2)), + Bond: big.NewInt(5), + }, + CounteredBy: common.Address{0x02}, + Claimant: common.Address{0x01}, + Clock: decodeClock(big.NewInt(4455)), + ContractIndex: 1, + ParentContractIndex: 0, + } + claim2 := faultTypes.Claim{ + ClaimData: faultTypes.ClaimData{ + Value: common.Hash{0xbb}, + Position: faultTypes.NewPositionFromGIndex(big.NewInt(6)), + Bond: big.NewInt(5), + }, + Claimant: common.Address{0x02}, + Clock: decodeClock(big.NewInt(7777)), + ContractIndex: 2, + ParentContractIndex: 1, + } + expectedClaims := []faultTypes.Claim{claim0, claim1, claim2} + block := rpcblock.ByNumber(42) + stubRpc.SetResponse(fdgAddr, methodClaimCount, block, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))}) + for _, claim := range expectedClaims { + expectGetClaim(stubRpc, block, claim) + } + claims, err := game.GetAllClaims(context.Background(), block) + require.NoError(t, err) + require.Equal(t, expectedClaims, claims) + }) } - claims, err := game.GetAllClaims(context.Background(), block) - require.NoError(t, err) - require.Equal(t, expectedClaims, claims) } func TestGetBalance(t *testing.T) { - wethAddr := common.Address{0x11, 0x55, 0x66} - balance := big.NewInt(9995877) - block := rpcblock.ByNumber(424) - stubRpc, game := setupFaultDisputeGameTest(t) - stubRpc.SetResponse(fdgAddr, methodWETH, block, nil, []interface{}{wethAddr}) - stubRpc.AddExpectedCall(batchingTest.NewGetBalanceCall(wethAddr, block, balance)) - - actualBalance, actualAddr, err := game.GetBalance(context.Background(), block) - require.NoError(t, err) - require.Equal(t, wethAddr, actualAddr) - require.Truef(t, balance.Cmp(actualBalance) == 0, "Expected balance %v but was %v", balance, actualBalance) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + wethAddr := common.Address{0x11, 0x55, 0x66} + balance := big.NewInt(9995877) + block := rpcblock.ByNumber(424) + stubRpc, game := setupFaultDisputeGameTest(t, version) + stubRpc.SetResponse(fdgAddr, methodWETH, block, nil, []interface{}{wethAddr}) + stubRpc.AddExpectedCall(batchingTest.NewGetBalanceCall(wethAddr, block, balance)) + + actualBalance, actualAddr, err := game.GetBalance(context.Background(), block) + require.NoError(t, err) + require.Equal(t, wethAddr, actualAddr) + require.Truef(t, balance.Cmp(actualBalance) == 0, "Expected balance %v but was %v", balance, actualBalance) + }) + } } func TestCallResolveClaim(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123), maxChildChecks}, nil) - err := game.CallResolveClaim(context.Background(), 123) - require.NoError(t, err) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + if version.version == vers080 { + stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123)}, nil) + } else { + stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123), maxChildChecks}, nil) + } + err := game.CallResolveClaim(context.Background(), 123) + require.NoError(t, err) + }) + } } func TestResolveClaimTxTest(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123), maxChildChecks}, nil) - tx, err := game.ResolveClaimTx(123) - require.NoError(t, err) - stubRpc.VerifyTxCandidate(tx) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + if version.version == vers080 { + stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123)}, nil) + } else { + stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123), maxChildChecks}, nil) + } + tx, err := game.ResolveClaimTx(123) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + } } func TestResolveTx(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - stubRpc.SetResponse(fdgAddr, methodResolve, rpcblock.Latest, nil, nil) - tx, err := game.ResolveTx() - require.NoError(t, err) - stubRpc.VerifyTxCandidate(tx) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + stubRpc.SetResponse(fdgAddr, methodResolve, rpcblock.Latest, nil, nil) + tx, err := game.ResolveTx() + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + } } func TestAttackTx(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - value := common.Hash{0xaa} - stubRpc.SetResponse(fdgAddr, methodAttack, rpcblock.Latest, []interface{}{big.NewInt(111), value}, nil) - tx, err := game.AttackTx(111, value) - require.NoError(t, err) - stubRpc.VerifyTxCandidate(tx) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + value := common.Hash{0xaa} + stubRpc.SetResponse(fdgAddr, methodAttack, rpcblock.Latest, []interface{}{big.NewInt(111), value}, nil) + tx, err := game.AttackTx(111, value) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + } } func TestDefendTx(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - value := common.Hash{0xaa} - stubRpc.SetResponse(fdgAddr, methodDefend, rpcblock.Latest, []interface{}{big.NewInt(111), value}, nil) - tx, err := game.DefendTx(111, value) - require.NoError(t, err) - stubRpc.VerifyTxCandidate(tx) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + value := common.Hash{0xaa} + stubRpc.SetResponse(fdgAddr, methodDefend, rpcblock.Latest, []interface{}{big.NewInt(111), value}, nil) + tx, err := game.DefendTx(111, value) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + } } func TestStepTx(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - stateData := []byte{1, 2, 3} - proofData := []byte{4, 5, 6, 7, 8, 9} - stubRpc.SetResponse(fdgAddr, methodStep, rpcblock.Latest, []interface{}{big.NewInt(111), true, stateData, proofData}, nil) - tx, err := game.StepTx(111, true, stateData, proofData) - require.NoError(t, err) - stubRpc.VerifyTxCandidate(tx) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + stateData := []byte{1, 2, 3} + proofData := []byte{4, 5, 6, 7, 8, 9} + stubRpc.SetResponse(fdgAddr, methodStep, rpcblock.Latest, []interface{}{big.NewInt(111), true, stateData, proofData}, nil) + tx, err := game.StepTx(111, true, stateData, proofData) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + } } func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, block rpcblock.Block, claim faultTypes.Claim) { @@ -317,171 +423,237 @@ func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, block rpcblock.Block, cla } func TestGetBlockRange(t *testing.T) { - stubRpc, contract := setupFaultDisputeGameTest(t) - expectedStart := uint64(65) - expectedEnd := uint64(102) - stubRpc.SetResponse(fdgAddr, methodStartingBlockNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedStart)}) - stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedEnd)}) - start, end, err := contract.GetBlockRange(context.Background()) - require.NoError(t, err) - require.Equal(t, expectedStart, start) - require.Equal(t, expectedEnd, end) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, contract := setupFaultDisputeGameTest(t, version) + expectedStart := uint64(65) + expectedEnd := uint64(102) + stubRpc.SetResponse(fdgAddr, methodStartingBlockNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedStart)}) + stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedEnd)}) + start, end, err := contract.GetBlockRange(context.Background()) + require.NoError(t, err) + require.Equal(t, expectedStart, start) + require.Equal(t, expectedEnd, end) + }) + } } func TestGetSplitDepth(t *testing.T) { - stubRpc, contract := setupFaultDisputeGameTest(t) - expectedSplitDepth := faultTypes.Depth(15) - stubRpc.SetResponse(fdgAddr, methodSplitDepth, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(uint64(expectedSplitDepth))}) - splitDepth, err := contract.GetSplitDepth(context.Background()) - require.NoError(t, err) - require.Equal(t, expectedSplitDepth, splitDepth) + for _, version := range versions { + version := version + t.Run(version.version, 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))}) + splitDepth, err := contract.GetSplitDepth(context.Background()) + require.NoError(t, err) + require.Equal(t, expectedSplitDepth, splitDepth) + }) + } } func TestGetGameMetadata(t *testing.T) { - stubRpc, contract := setupFaultDisputeGameTest(t) - expectedL1Head := common.Hash{0x0a, 0x0b} - expectedL2BlockNumber := uint64(123) - expectedMaxClockDuration := uint64(456) - expectedRootClaim := common.Hash{0x01, 0x02} - expectedStatus := types.GameStatusChallengerWon - block := rpcblock.ByNumber(889) - stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head}) - 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}) - stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) - l1Head, l2BlockNumber, rootClaim, status, duration, err := contract.GetGameMetadata(context.Background(), block) - require.NoError(t, err) - require.Equal(t, expectedL1Head, l1Head) - require.Equal(t, expectedL2BlockNumber, l2BlockNumber) - require.Equal(t, expectedRootClaim, rootClaim) - require.Equal(t, expectedStatus, status) - require.Equal(t, expectedMaxClockDuration, duration) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, contract := setupFaultDisputeGameTest(t, version) + expectedL1Head := common.Hash{0x0a, 0x0b} + expectedL2BlockNumber := uint64(123) + expectedMaxClockDuration := uint64(456) + expectedRootClaim := common.Hash{0x01, 0x02} + expectedStatus := types.GameStatusChallengerWon + block := rpcblock.ByNumber(889) + stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head}) + 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 { + stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedMaxClockDuration * 2}) + } else { + stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) + } + l1Head, l2BlockNumber, rootClaim, status, duration, err := contract.GetGameMetadata(context.Background(), block) + require.NoError(t, err) + require.Equal(t, expectedL1Head, l1Head) + require.Equal(t, expectedL2BlockNumber, l2BlockNumber) + require.Equal(t, expectedRootClaim, rootClaim) + require.Equal(t, expectedStatus, status) + require.Equal(t, expectedMaxClockDuration, duration) + }) + } } func TestGetStartingRootHash(t *testing.T) { - stubRpc, contract := setupFaultDisputeGameTest(t) - expectedOutputRoot := common.HexToHash("0x1234") - stubRpc.SetResponse(fdgAddr, methodStartingRootHash, rpcblock.Latest, nil, []interface{}{expectedOutputRoot}) - startingOutputRoot, err := contract.GetStartingRootHash(context.Background()) - require.NoError(t, err) - require.Equal(t, expectedOutputRoot, startingOutputRoot) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, contract := setupFaultDisputeGameTest(t, version) + expectedOutputRoot := common.HexToHash("0x1234") + stubRpc.SetResponse(fdgAddr, methodStartingRootHash, rpcblock.Latest, nil, []interface{}{expectedOutputRoot}) + startingOutputRoot, err := contract.GetStartingRootHash(context.Background()) + require.NoError(t, err) + require.Equal(t, expectedOutputRoot, startingOutputRoot) + }) + } } func TestFaultDisputeGame_UpdateOracleTx(t *testing.T) { - t.Run("Local", func(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - data := faultTypes.NewPreimageOracleData(common.Hash{0x01, 0xbc}.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7}, 16) - claimIdx := uint64(6) - stubRpc.SetResponse(fdgAddr, methodAddLocalData, rpcblock.Latest, []interface{}{ - data.GetIdent(), - new(big.Int).SetUint64(claimIdx), - new(big.Int).SetUint64(uint64(data.OracleOffset)), - }, nil) - tx, err := game.UpdateOracleTx(context.Background(), claimIdx, data) - require.NoError(t, err) - stubRpc.VerifyTxCandidate(tx) - }) - - t.Run("Global", func(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - data := faultTypes.NewPreimageOracleData(common.Hash{0x02, 0xbc}.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15}, 16) - claimIdx := uint64(6) - stubRpc.SetResponse(fdgAddr, methodVM, rpcblock.Latest, nil, []interface{}{vmAddr}) - stubRpc.SetResponse(vmAddr, methodOracle, rpcblock.Latest, nil, []interface{}{oracleAddr}) - stubRpc.SetResponse(oracleAddr, methodLoadKeccak256PreimagePart, rpcblock.Latest, []interface{}{ - new(big.Int).SetUint64(uint64(data.OracleOffset)), - data.GetPreimageWithoutSize(), - }, nil) - tx, err := game.UpdateOracleTx(context.Background(), claimIdx, data) - require.NoError(t, err) - stubRpc.VerifyTxCandidate(tx) - }) + for _, version := range versions { + version := version + t.Run(version.version, 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) + claimIdx := uint64(6) + stubRpc.SetResponse(fdgAddr, methodAddLocalData, rpcblock.Latest, []interface{}{ + data.GetIdent(), + new(big.Int).SetUint64(claimIdx), + new(big.Int).SetUint64(uint64(data.OracleOffset)), + }, nil) + tx, err := game.UpdateOracleTx(context.Background(), claimIdx, data) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + + t.Run("Global", func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + data := faultTypes.NewPreimageOracleData(common.Hash{0x02, 0xbc}.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15}, 16) + claimIdx := uint64(6) + stubRpc.SetResponse(fdgAddr, methodVM, rpcblock.Latest, nil, []interface{}{vmAddr}) + stubRpc.SetResponse(vmAddr, methodOracle, rpcblock.Latest, nil, []interface{}{oracleAddr}) + stubRpc.SetResponse(oracleAddr, methodLoadKeccak256PreimagePart, rpcblock.Latest, []interface{}{ + new(big.Int).SetUint64(uint64(data.OracleOffset)), + data.GetPreimageWithoutSize(), + }, nil) + tx, err := game.UpdateOracleTx(context.Background(), claimIdx, data) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + }) + } } func TestFaultDisputeGame_GetCredit(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - addr := common.Address{0x01} - expectedCredit := big.NewInt(4284) - expectedStatus := types.GameStatusChallengerWon - stubRpc.SetResponse(fdgAddr, methodCredit, rpcblock.Latest, []interface{}{addr}, []interface{}{expectedCredit}) - stubRpc.SetResponse(fdgAddr, methodStatus, rpcblock.Latest, nil, []interface{}{expectedStatus}) - - actualCredit, actualStatus, err := game.GetCredit(context.Background(), addr) - require.NoError(t, err) - require.Equal(t, expectedCredit, actualCredit) - require.Equal(t, expectedStatus, actualStatus) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + addr := common.Address{0x01} + expectedCredit := big.NewInt(4284) + expectedStatus := types.GameStatusChallengerWon + stubRpc.SetResponse(fdgAddr, methodCredit, rpcblock.Latest, []interface{}{addr}, []interface{}{expectedCredit}) + stubRpc.SetResponse(fdgAddr, methodStatus, rpcblock.Latest, nil, []interface{}{expectedStatus}) + + actualCredit, actualStatus, err := game.GetCredit(context.Background(), addr) + require.NoError(t, err) + require.Equal(t, expectedCredit, actualCredit) + require.Equal(t, expectedStatus, actualStatus) + }) + } } func TestFaultDisputeGame_GetCredits(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) - block := rpcblock.ByNumber(482) + block := rpcblock.ByNumber(482) - addrs := []common.Address{{0x01}, {0x02}, {0x03}} - expected := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(0)} + addrs := []common.Address{{0x01}, {0x02}, {0x03}} + expected := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(0)} - for i, addr := range addrs { - stubRpc.SetResponse(fdgAddr, methodCredit, block, []interface{}{addr}, []interface{}{expected[i]}) - } + for i, addr := range addrs { + stubRpc.SetResponse(fdgAddr, methodCredit, block, []interface{}{addr}, []interface{}{expected[i]}) + } - actual, err := game.GetCredits(context.Background(), block, addrs...) - require.NoError(t, err) - require.Equal(t, len(expected), len(actual)) - for i := range expected { - require.Zerof(t, expected[i].Cmp(actual[i]), "expected: %v actual: %v", expected[i], actual[i]) + actual, err := game.GetCredits(context.Background(), block, addrs...) + require.NoError(t, err) + require.Equal(t, len(expected), len(actual)) + for i := range expected { + require.Zerof(t, expected[i].Cmp(actual[i]), "expected: %v actual: %v", expected[i], actual[i]) + } + }) } } func TestFaultDisputeGame_ClaimCreditTx(t *testing.T) { - t.Run("Success", func(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - addr := common.Address{0xaa} - - stubRpc.SetResponse(fdgAddr, methodClaimCredit, rpcblock.Latest, []interface{}{addr}, nil) - tx, err := game.ClaimCreditTx(context.Background(), addr) - require.NoError(t, err) - stubRpc.VerifyTxCandidate(tx) - }) - - t.Run("SimulationFails", func(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) - addr := common.Address{0xaa} - - stubRpc.SetError(fdgAddr, methodClaimCredit, rpcblock.Latest, []interface{}{addr}, errors.New("still locked")) - tx, err := game.ClaimCreditTx(context.Background(), addr) - require.ErrorIs(t, err, ErrSimulationFailed) - require.Equal(t, txmgr.TxCandidate{}, tx) - }) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + t.Run("Success", func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + addr := common.Address{0xaa} + + stubRpc.SetResponse(fdgAddr, methodClaimCredit, rpcblock.Latest, []interface{}{addr}, nil) + tx, err := game.ClaimCreditTx(context.Background(), addr) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + + t.Run("SimulationFails", func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + addr := common.Address{0xaa} + + stubRpc.SetError(fdgAddr, methodClaimCredit, rpcblock.Latest, []interface{}{addr}, errors.New("still locked")) + tx, err := game.ClaimCreditTx(context.Background(), addr) + require.ErrorIs(t, err, ErrSimulationFailed) + require.Equal(t, txmgr.TxCandidate{}, tx) + }) + }) + } } func TestFaultDisputeGame_IsResolved(t *testing.T) { - stubRpc, game := setupFaultDisputeGameTest(t) + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) - block := rpcblock.ByNumber(482) + block := rpcblock.ByNumber(482) - claims := []faultTypes.Claim{ - {ContractIndex: 1}, - {ContractIndex: 5}, - {ContractIndex: 13}, - } - claimIdxs := []*big.Int{big.NewInt(1), big.NewInt(5), big.NewInt(13)} - expected := []bool{false, true, true} - - for i, idx := range claimIdxs { - stubRpc.SetResponse(fdgAddr, methodResolvedSubgames, block, []interface{}{idx}, []interface{}{expected[i]}) - } + claims := []faultTypes.Claim{ + {ContractIndex: 1}, + {ContractIndex: 5}, + {ContractIndex: 13}, + } + claimIdxs := []*big.Int{big.NewInt(1), big.NewInt(5), big.NewInt(13)} + expected := []bool{false, true, true} + + if version.version == vers080 { + claimCount := 14 + stubRpc.SetResponse(fdgAddr, methodClaimCount, block, nil, []interface{}{big.NewInt(int64(claimCount))}) + for idx := 0; idx < claimCount; idx++ { + bond := big.NewInt(42) + if idx == 5 || idx == 13 { // The two claims expected to be resolved + bond = resolvedBondAmount + } + expectGetClaim(stubRpc, block, faultTypes.Claim{ + ContractIndex: idx, + ClaimData: faultTypes.ClaimData{ + Bond: bond, + }, + }) + } + } else { + for i, idx := range claimIdxs { + stubRpc.SetResponse(fdgAddr, methodResolvedSubgames, block, []interface{}{idx}, []interface{}{expected[i]}) + } + } - actual, err := game.IsResolved(context.Background(), block, claims...) - require.NoError(t, err) - require.Equal(t, len(expected), len(actual)) - for i := range expected { - require.Equal(t, expected[i], actual[i]) + actual, err := game.IsResolved(context.Background(), block, claims...) + require.NoError(t, err) + require.Equal(t, len(expected), len(actual)) + for i := range expected { + require.Equal(t, expected[i], actual[i]) + } + }) } } -func setupFaultDisputeGameTest(t *testing.T) (*batchingTest.AbiBasedRpc, *FaultDisputeGameContract) { - fdgAbi := snapshots.LoadFaultDisputeGameABI() +func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, FaultDisputeGameContract) { + fdgAbi := version.loadAbi() vmAbi := snapshots.LoadMIPSABI() oracleAbi := snapshots.LoadPreimageOracleABI() @@ -490,6 +662,9 @@ func setupFaultDisputeGameTest(t *testing.T) (*batchingTest.AbiBasedRpc, *FaultD stubRpc.AddContract(vmAddr, vmAbi) stubRpc.AddContract(oracleAddr, oracleAbi) caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) - game := NewFaultDisputeGameContract(contractMetrics.NoopContractMetrics, fdgAddr, caller) + + stubRpc.SetResponse(fdgAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version}) + game, err := NewFaultDisputeGameContract(context.Background(), contractMetrics.NoopContractMetrics, fdgAddr, caller) + require.NoError(t, err) return stubRpc, game } diff --git a/op-challenger/game/fault/register.go b/op-challenger/game/fault/register.go index b2f6c977b4c7..d4cc6a7fad01 100644 --- a/op-challenger/game/fault/register.go +++ b/op-challenger/game/fault/register.go @@ -120,7 +120,10 @@ func registerAlphabet( claimants []common.Address, ) error { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { - contract := contracts.NewFaultDisputeGameContract(m, game.Proxy, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx, m, game.Proxy, caller) + if err != nil { + return nil, fmt.Errorf("failed to create fault dispute game contract: %w", err) + } oracle, err := contract.GetOracle(ctx) if err != nil { return nil, fmt.Errorf("failed to load oracle for game %v: %w", game.Proxy, err) @@ -157,7 +160,7 @@ func registerAlphabet( registry.RegisterGameType(faultTypes.AlphabetGameType, playerCreator) contractCreator := func(game types.GameMetadata) (claims.BondContract, error) { - return contracts.NewFaultDisputeGameContract(m, game.Proxy, caller), nil + return contracts.NewFaultDisputeGameContract(ctx, m, game.Proxy, caller) } registry.RegisterBondContract(faultTypes.AlphabetGameType, contractCreator) return nil @@ -168,7 +171,10 @@ func registerOracle(ctx context.Context, m metrics.Metricer, oracles OracleRegis if err != nil { return fmt.Errorf("failed to load implementation for game type %v: %w", gameType, err) } - contract := contracts.NewFaultDisputeGameContract(m, implAddr, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx, m, implAddr, caller) + if err != nil { + return fmt.Errorf("failed to create fault dispute game contracts: %w", err) + } oracle, err := contract.GetOracle(ctx) if err != nil { return fmt.Errorf("failed to load oracle address: %w", err) @@ -199,7 +205,10 @@ func registerAsterisc( ) error { asteriscPrestateProvider := asterisc.NewPrestateProvider(cfg.AsteriscAbsolutePreState) playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { - contract := contracts.NewFaultDisputeGameContract(m, game.Proxy, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx, m, game.Proxy, caller) + if err != nil { + return nil, fmt.Errorf("failed to create fault dispute game contracts: %w", err) + } oracle, err := contract.GetOracle(ctx) if err != nil { return nil, fmt.Errorf("failed to load oracle for game %v: %w", game.Proxy, err) @@ -236,7 +245,7 @@ func registerAsterisc( registry.RegisterGameType(gameType, playerCreator) contractCreator := func(game types.GameMetadata) (claims.BondContract, error) { - return contracts.NewFaultDisputeGameContract(m, game.Proxy, caller), nil + return contracts.NewFaultDisputeGameContract(ctx, m, game.Proxy, caller) } registry.RegisterBondContract(gameType, contractCreator) return nil @@ -276,7 +285,10 @@ func registerCannon( return cannon.NewPrestateProvider(prestatePath), nil }) playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { - contract := contracts.NewFaultDisputeGameContract(m, game.Proxy, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx, m, game.Proxy, caller) + if err != nil { + return nil, fmt.Errorf("failed to create fault dispute game contracts: %w", err) + } requiredPrestatehash, err := contract.GetAbsolutePrestateHash(ctx) if err != nil { return nil, fmt.Errorf("failed to load prestate hash for game %v: %w", game.Proxy, err) @@ -324,13 +336,13 @@ func registerCannon( registry.RegisterGameType(gameType, playerCreator) contractCreator := func(game types.GameMetadata) (claims.BondContract, error) { - return contracts.NewFaultDisputeGameContract(m, game.Proxy, caller), nil + return contracts.NewFaultDisputeGameContract(ctx, m, game.Proxy, caller) } registry.RegisterBondContract(gameType, contractCreator) return nil } -func loadL1Head(contract *contracts.FaultDisputeGameContract, ctx context.Context, l1HeaderSource L1HeaderSource) (eth.BlockID, error) { +func loadL1Head(contract contracts.FaultDisputeGameContract, ctx context.Context, l1HeaderSource L1HeaderSource) (eth.BlockID, error) { l1Head, err := contract.GetL1Head(ctx) if err != nil { return eth.BlockID{}, fmt.Errorf("failed to load L1 head: %w", err) diff --git a/op-dispute-mon/mon/extract/caller.go b/op-dispute-mon/mon/extract/caller.go index ed75d6e94e93..c96eb668c25a 100644 --- a/op-dispute-mon/mon/extract/caller.go +++ b/op-dispute-mon/mon/extract/caller.go @@ -34,7 +34,7 @@ type GameCaller interface { type GameCallerCreator struct { m GameCallerMetrics - cache *caching.LRUCache[common.Address, *contracts.FaultDisputeGameContract] + cache *caching.LRUCache[common.Address, contracts.FaultDisputeGameContract] caller *batching.MultiCaller } @@ -42,17 +42,20 @@ func NewGameCallerCreator(m GameCallerMetrics, caller *batching.MultiCaller) *Ga return &GameCallerCreator{ m: m, caller: caller, - cache: caching.NewLRUCache[common.Address, *contracts.FaultDisputeGameContract](m, metricsLabel, 100), + cache: caching.NewLRUCache[common.Address, contracts.FaultDisputeGameContract](m, metricsLabel, 100), } } -func (g *GameCallerCreator) CreateContract(game gameTypes.GameMetadata) (GameCaller, error) { +func (g *GameCallerCreator) CreateContract(ctx context.Context, game gameTypes.GameMetadata) (GameCaller, error) { if fdg, ok := g.cache.Get(game.Proxy); ok { return fdg, nil } switch game.GameType { case faultTypes.CannonGameType, faultTypes.AsteriscGameType, faultTypes.AlphabetGameType: - fdg := contracts.NewFaultDisputeGameContract(g.m, game.Proxy, g.caller) + 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) + } g.cache.Add(game.Proxy, fdg) return fdg, nil default: diff --git a/op-dispute-mon/mon/extract/caller_test.go b/op-dispute-mon/mon/extract/caller_test.go index 69cc470f55d6..810fd67425c8 100644 --- a/op-dispute-mon/mon/extract/caller_test.go +++ b/op-dispute-mon/mon/extract/caller_test.go @@ -1,10 +1,12 @@ package extract import ( + "context" "fmt" "testing" contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/common" "github.com/ethereum-optimism/optimism/op-bindings/bindings" @@ -49,13 +51,13 @@ func TestMetadataCreator_CreateContract(t *testing.T) { t.Run(test.name, func(t *testing.T) { caller, metrics := setupMetadataLoaderTest(t) creator := NewGameCallerCreator(metrics, caller) - _, err := creator.CreateContract(test.game) + _, err := creator.CreateContract(context.Background(), test.game) require.Equal(t, test.expectedErr, err) if test.expectedErr == nil { require.Equal(t, 1, metrics.cacheAddCalls) require.Equal(t, 1, metrics.cacheGetCalls) } - _, err = creator.CreateContract(test.game) + _, err = creator.CreateContract(context.Background(), test.game) require.Equal(t, test.expectedErr, err) if test.expectedErr == nil { require.Equal(t, 1, metrics.cacheAddCalls) @@ -70,6 +72,7 @@ func setupMetadataLoaderTest(t *testing.T) (*batching.MultiCaller, *mockCacheMet require.NoError(t, err) stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi) caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) + stubRpc.SetResponse(fdgAddr, "version", rpcblock.Latest, nil, []interface{}{"0.18.0"}) return caller, &mockCacheMetrics{} } diff --git a/op-dispute-mon/mon/extract/extractor.go b/op-dispute-mon/mon/extract/extractor.go index 11caa2277c39..cc3529d09ece 100644 --- a/op-dispute-mon/mon/extract/extractor.go +++ b/op-dispute-mon/mon/extract/extractor.go @@ -13,7 +13,7 @@ import ( ) type ( - CreateGameCaller func(game gameTypes.GameMetadata) (GameCaller, error) + CreateGameCaller func(ctx context.Context, game gameTypes.GameMetadata) (GameCaller, error) FactoryGameFetcher func(ctx context.Context, blockHash common.Hash, earliestTimestamp uint64) ([]gameTypes.GameMetadata, error) ) @@ -48,7 +48,7 @@ func (e *Extractor) Extract(ctx context.Context, blockHash common.Hash, minTimes func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, games []gameTypes.GameMetadata) []*monTypes.EnrichedGameData { var enrichedGames []*monTypes.EnrichedGameData for _, game := range games { - caller, err := e.createContract(game) + caller, err := e.createContract(ctx, game) if err != nil { e.logger.Error("Failed to create game caller", "err", err) continue diff --git a/op-dispute-mon/mon/extract/extractor_test.go b/op-dispute-mon/mon/extract/extractor_test.go index 76d19f81a50c..629770dd46e0 100644 --- a/op-dispute-mon/mon/extract/extractor_test.go +++ b/op-dispute-mon/mon/extract/extractor_test.go @@ -167,7 +167,7 @@ type mockGameCallerCreator struct { caller *mockGameCaller } -func (m *mockGameCallerCreator) CreateGameCaller(_ gameTypes.GameMetadata) (GameCaller, error) { +func (m *mockGameCallerCreator) CreateGameCaller(_ context.Context, _ gameTypes.GameMetadata) (GameCaller, error) { m.calls++ if m.err != nil { return nil, m.err diff --git a/op-e2e/e2eutils/disputegame/output_alphabet_helper.go b/op-e2e/e2eutils/disputegame/output_alphabet_helper.go index 96221d4a227b..bcdcc68970cb 100644 --- a/op-e2e/e2eutils/disputegame/output_alphabet_helper.go +++ b/op-e2e/e2eutils/disputegame/output_alphabet_helper.go @@ -39,7 +39,8 @@ func (g *OutputAlphabetGameHelper) StartChallenger( func (g *OutputAlphabetGameHelper) CreateHonestActor(ctx context.Context, l2Node string) *OutputHonestHelper { logger := testlog.Logger(g.T, log.LevelInfo).New("role", "HonestHelper", "game", g.Addr) caller := batching.NewMultiCaller(g.System.NodeClient("l1").Client(), batching.DefaultBatchSize) - contract := contracts.NewFaultDisputeGameContract(contractMetrics.NoopContractMetrics, g.Addr, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx, contractMetrics.NoopContractMetrics, g.Addr, caller) + g.Require.NoError(err) prestateBlock, poststateBlock, err := contract.GetBlockRange(ctx) g.Require.NoError(err, "Get block range") splitDepth := g.SplitDepth(ctx) diff --git a/op-e2e/e2eutils/disputegame/output_cannon_helper.go b/op-e2e/e2eutils/disputegame/output_cannon_helper.go index a87a2276eb9a..e0eedcadf632 100644 --- a/op-e2e/e2eutils/disputegame/output_cannon_helper.go +++ b/op-e2e/e2eutils/disputegame/output_cannon_helper.go @@ -63,7 +63,8 @@ func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node s logger := testlog.Logger(g.T, log.LevelInfo).New("role", "HonestHelper", "game", g.Addr) l2Client := g.System.NodeClient(l2Node) caller := batching.NewMultiCaller(g.System.NodeClient("l1").Client(), batching.DefaultBatchSize) - contract := contracts.NewFaultDisputeGameContract(contractMetrics.NoopContractMetrics, g.Addr, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx, contractMetrics.NoopContractMetrics, g.Addr, caller) + g.Require.NoError(err) prestateBlock, poststateBlock, err := contract.GetBlockRange(ctx) g.Require.NoError(err, "Failed to load block range") @@ -288,7 +289,8 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, caller := batching.NewMultiCaller(g.System.NodeClient("l1").Client(), batching.DefaultBatchSize) l2Client := g.System.NodeClient(l2Node) - contract := contracts.NewFaultDisputeGameContract(contractMetrics.NoopContractMetrics, g.Addr, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx, contractMetrics.NoopContractMetrics, g.Addr, caller) + g.Require.NoError(err) prestateBlock, poststateBlock, err := contract.GetBlockRange(ctx) g.Require.NoError(err, "Failed to load block range") diff --git a/op-e2e/e2eutils/disputegame/output_game_helper.go b/op-e2e/e2eutils/disputegame/output_game_helper.go index 44e948cd378b..b15db138be5a 100644 --- a/op-e2e/e2eutils/disputegame/output_game_helper.go +++ b/op-e2e/e2eutils/disputegame/output_game_helper.go @@ -713,7 +713,8 @@ func (g *OutputGameHelper) UploadPreimage(ctx context.Context, data *types.Preim func (g *OutputGameHelper) oracle(ctx context.Context) *contracts.PreimageOracleContract { caller := batching.NewMultiCaller(g.System.NodeClient("l1").Client(), batching.DefaultBatchSize) - contract := contracts.NewFaultDisputeGameContract(contractMetrics.NoopContractMetrics, g.Addr, caller) + contract, err := contracts.NewFaultDisputeGameContract(ctx, contractMetrics.NoopContractMetrics, g.Addr, caller) + g.Require.NoError(err) oracle, err := contract.GetOracle(ctx) g.Require.NoError(err, "Failed to create oracle contract") return oracle diff --git a/op-e2e/e2eutils/disputegame/output_honest_helper.go b/op-e2e/e2eutils/disputegame/output_honest_helper.go index 1a5d4e9dc328..e2de7c4ec743 100644 --- a/op-e2e/e2eutils/disputegame/output_honest_helper.go +++ b/op-e2e/e2eutils/disputegame/output_honest_helper.go @@ -17,11 +17,11 @@ type OutputHonestHelper struct { t *testing.T require *require.Assertions game *OutputGameHelper - contract *contracts.FaultDisputeGameContract + contract contracts.FaultDisputeGameContract correctTrace types.TraceAccessor } -func NewOutputHonestHelper(t *testing.T, require *require.Assertions, game *OutputGameHelper, contract *contracts.FaultDisputeGameContract, correctTrace types.TraceAccessor) *OutputHonestHelper { +func NewOutputHonestHelper(t *testing.T, require *require.Assertions, game *OutputGameHelper, contract contracts.FaultDisputeGameContract, correctTrace types.TraceAccessor) *OutputHonestHelper { return &OutputHonestHelper{ t: t, require: require, diff --git a/op-service/sources/batching/call.go b/op-service/sources/batching/call.go index d84932fac6ee..db4563b64258 100644 --- a/op-service/sources/batching/call.go +++ b/op-service/sources/batching/call.go @@ -63,3 +63,7 @@ func (c *CallResult) GetBytes32(i int) [32]byte { func (c *CallResult) GetBytes32Slice(i int) [][32]byte { return *abi.ConvertType(c.out[i], new([][32]byte)).(*[][32]byte) } + +func (c *CallResult) GetString(i int) string { + return *abi.ConvertType(c.out[i], new(string)).(*string) +}