diff --git a/op-challenger/Makefile b/op-challenger/Makefile index 2c663ac00f6c..fa6d0dcae3c1 100644 --- a/op-challenger/Makefile +++ b/op-challenger/Makefile @@ -15,6 +15,9 @@ endif op-challenger: env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/op-challenger ./cmd +op-challenger-faultdisputegamen: + env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -tags faultdisputegamen -o ./bin/op-challenger ./cmd + fuzz: go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzKeccak ./game/keccak/matrix diff --git a/op-challenger/game/fault/contracts/abis/FaultDisputeGameN-1.1.0.json b/op-challenger/game/fault/contracts/abis/FaultDisputeGameN-1.1.0.json new file mode 100644 index 000000000000..e76bb6f23804 --- /dev/null +++ b/op-challenger/game/fault/contracts/abis/FaultDisputeGameN-1.1.0.json @@ -0,0 +1,1292 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_gameType", + "type": "uint32", + "internalType": "GameType" + }, + { + "name": "_absolutePrestate", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_maxGameDepth", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_splitDepth", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_clockExtension", + "type": "uint64", + "internalType": "Duration" + }, + { + "name": "_maxClockDuration", + "type": "uint64", + "internalType": "Duration" + }, + { + "name": "_vm", + "type": "address", + "internalType": "contract IBigStepper" + }, + { + "name": "_weth", + "type": "address", + "internalType": "contract IDelayedWETH" + }, + { + "name": "_anchorStateRegistry", + "type": "address", + "internalType": "contract IAnchorStateRegistry" + }, + { + "name": "_l2ChainId", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "MAX_ATTACK_BRANCH", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "N_BITS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "absolutePrestate", + "inputs": [], + "outputs": [ + { + "name": "absolutePrestate_", + "type": "bytes32", + "internalType": "Claim" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addLocalData", + "inputs": [ + { + "name": "_ident", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_execLeafIdx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_partOffset", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_daItem", + "type": "tuple", + "internalType": "struct LibDA.DAItem", + "components": [ + { + "name": "daType", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "dataHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "proof", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "uuid_", + "type": "bytes32", + "internalType": "Hash" + }, + { + "name": "value_", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "addLocalData", + "inputs": [ + { + "name": "_ident", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_execLeafIdx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_partOffset", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "anchorStateRegistry", + "inputs": [], + "outputs": [ + { + "name": "registry_", + "type": "address", + "internalType": "contract IAnchorStateRegistry" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "attack", + "inputs": [ + { + "name": "_disputed", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_parentIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_claim", + "type": "bytes32", + "internalType": "Claim" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "attackV2", + "inputs": [ + { + "name": "_disputed", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_parentIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_attackBranch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_daType", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_claims", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "challengeRootL2Block", + "inputs": [ + { + "name": "_outputRootProof", + "type": "tuple", + "internalType": "struct Types.OutputRootProof", + "components": [ + { + "name": "version", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "stateRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "messagePasserStorageRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "latestBlockhash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "name": "_headerRLP", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "claimCredit", + "inputs": [ + { + "name": "_recipient", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "claimData", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "parentIndex", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "counteredBy", + "type": "address", + "internalType": "address" + }, + { + "name": "claimant", + "type": "address", + "internalType": "address" + }, + { + "name": "bond", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "claim", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "position", + "type": "uint128", + "internalType": "Position" + }, + { + "name": "clock", + "type": "uint128", + "internalType": "Clock" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "claimDataLen", + "inputs": [], + "outputs": [ + { + "name": "len_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "claims", + "inputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "Hash" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "clockExtension", + "inputs": [], + "outputs": [ + { + "name": "clockExtension_", + "type": "uint64", + "internalType": "Duration" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "createdAt", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "Timestamp" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "credit", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "defend", + "inputs": [ + { + "name": "_disputed", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_parentIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_claim", + "type": "bytes32", + "internalType": "Claim" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "extraData", + "inputs": [], + "outputs": [ + { + "name": "extraData_", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "gameCreator", + "inputs": [], + "outputs": [ + { + "name": "creator_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "gameData", + "inputs": [], + "outputs": [ + { + "name": "gameType_", + "type": "uint32", + "internalType": "GameType" + }, + { + "name": "rootClaim_", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "extraData_", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "gameType", + "inputs": [], + "outputs": [ + { + "name": "gameType_", + "type": "uint32", + "internalType": "GameType" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getChallengerDuration", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "duration_", + "type": "uint64", + "internalType": "Duration" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getNumToResolve", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "numRemainingChildren_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRequiredBond", + "inputs": [ + { + "name": "_position", + "type": "uint128", + "internalType": "Position" + } + ], + "outputs": [ + { + "name": "requiredBond_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "l1Head", + "inputs": [], + "outputs": [ + { + "name": "l1Head_", + "type": "bytes32", + "internalType": "Hash" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "l2BlockNumber", + "inputs": [], + "outputs": [ + { + "name": "l2BlockNumber_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "l2BlockNumberChallenged", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "l2BlockNumberChallenger", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "l2ChainId", + "inputs": [], + "outputs": [ + { + "name": "l2ChainId_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxAttackBranch", + "inputs": [], + "outputs": [ + { + "name": "maxAttackBranch_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxClockDuration", + "inputs": [], + "outputs": [ + { + "name": "maxClockDuration_", + "type": "uint64", + "internalType": "Duration" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxGameDepth", + "inputs": [], + "outputs": [ + { + "name": "maxGameDepth_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "move", + "inputs": [ + { + "name": "_disputed", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_challengeIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_claim", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_isAttack", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "nBits", + "inputs": [], + "outputs": [ + { + "name": "nBits_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "resolutionCheckpoints", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "initialCheckpointComplete", + "type": "bool", + "internalType": "bool" + }, + { + "name": "subgameIndex", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "leftmostPosition", + "type": "uint128", + "internalType": "Position" + }, + { + "name": "counteredBy", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "resolve", + "inputs": [], + "outputs": [ + { + "name": "status_", + "type": "uint8", + "internalType": "enum GameStatus" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "resolveClaim", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_numToResolve", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "resolvedAt", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "Timestamp" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "resolvedSubgames", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "rootClaim", + "inputs": [], + "outputs": [ + { + "name": "rootClaim_", + "type": "bytes32", + "internalType": "Claim" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "splitDepth", + "inputs": [], + "outputs": [ + { + "name": "splitDepth_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "startingBlockNumber", + "inputs": [], + "outputs": [ + { + "name": "startingBlockNumber_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "startingOutputRoot", + "inputs": [], + "outputs": [ + { + "name": "root", + "type": "bytes32", + "internalType": "Hash" + }, + { + "name": "l2BlockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "startingRootHash", + "inputs": [], + "outputs": [ + { + "name": "startingRootHash_", + "type": "bytes32", + "internalType": "Hash" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "status", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum GameStatus" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "step", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_isAttack", + "type": "bool", + "internalType": "bool" + }, + { + "name": "_stateData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "stepV2", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_attackBranch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_stateData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proof", + "type": "tuple", + "internalType": "struct FaultDisputeGame.StepProof", + "components": [ + { + "name": "preStateItem", + "type": "tuple", + "internalType": "struct LibDA.DAItem", + "components": [ + { + "name": "daType", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "dataHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "proof", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "postStateItem", + "type": "tuple", + "internalType": "struct LibDA.DAItem", + "components": [ + { + "name": "daType", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "dataHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "proof", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "vmProof", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "subgames", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "version", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "vm", + "inputs": [], + "outputs": [ + { + "name": "vm_", + "type": "address", + "internalType": "contract IBigStepper" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "weth", + "inputs": [], + "outputs": [ + { + "name": "weth_", + "type": "address", + "internalType": "contract IDelayedWETH" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "Move", + "inputs": [ + { + "name": "parentIndex", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "claim", + "type": "bytes32", + "indexed": true, + "internalType": "Claim" + }, + { + "name": "claimant", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Resolved", + "inputs": [ + { + "name": "status", + "type": "uint8", + "indexed": true, + "internalType": "enum GameStatus" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AlreadyInitialized", + "inputs": [] + }, + { + "type": "error", + "name": "AnchorRootNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "BlockNumberMatches", + "inputs": [] + }, + { + "type": "error", + "name": "BondTransferFailed", + "inputs": [] + }, + { + "type": "error", + "name": "CannotDefendRootClaim", + "inputs": [] + }, + { + "type": "error", + "name": "ClaimAboveSplit", + "inputs": [] + }, + { + "type": "error", + "name": "ClaimAlreadyExists", + "inputs": [] + }, + { + "type": "error", + "name": "ClaimAlreadyResolved", + "inputs": [] + }, + { + "type": "error", + "name": "ClockNotExpired", + "inputs": [] + }, + { + "type": "error", + "name": "ClockTimeExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "ContentLengthMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "DuplicateStep", + "inputs": [] + }, + { + "type": "error", + "name": "EmptyItem", + "inputs": [] + }, + { + "type": "error", + "name": "GameDepthExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "GameNotInProgress", + "inputs": [] + }, + { + "type": "error", + "name": "IncorrectBondAmount", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidClockExtension", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidDataRemainder", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidDisputedClaimIndex", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidHeader", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidHeaderRLP", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidLocalIdent", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidOutputRootProof", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidParent", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidPosition", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidPrestate", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSplitDepth", + "inputs": [] + }, + { + "type": "error", + "name": "L2BlockNumberChallenged", + "inputs": [] + }, + { + "type": "error", + "name": "MaxDepthTooLarge", + "inputs": [] + }, + { + "type": "error", + "name": "NoCreditToClaim", + "inputs": [] + }, + { + "type": "error", + "name": "NotSupported", + "inputs": [] + }, + { + "type": "error", + "name": "OutOfOrderResolution", + "inputs": [] + }, + { + "type": "error", + "name": "UnexpectedList", + "inputs": [] + }, + { + "type": "error", + "name": "UnexpectedRootClaim", + "inputs": [ + { + "name": "rootClaim", + "type": "bytes32", + "internalType": "Claim" + } + ] + }, + { + "type": "error", + "name": "UnexpectedString", + "inputs": [] + }, + { + "type": "error", + "name": "ValidStep", + "inputs": [] + } +] diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index e48f06549dfb..2bde285fd952 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -1,3 +1,5 @@ +// +build !faultdisputegamen + package contracts import ( diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index 4ea7d9c3d0bf..266797b71b04 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -1,3 +1,5 @@ +// +build !faultdisputegamen + package contracts import ( diff --git a/op-challenger/game/fault/contracts/faultdisputegamen.go b/op-challenger/game/fault/contracts/faultdisputegamen.go new file mode 100644 index 000000000000..6f13d690ed2d --- /dev/null +++ b/op-challenger/game/fault/contracts/faultdisputegamen.go @@ -0,0 +1,681 @@ +// +build faultdisputegamen + +package contracts + +import ( + "bytes" + "context" + "errors" + "fmt" + "math" + "math/big" + "strings" + "time" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +// The maximum number of children that will be processed during a call to `resolveClaim` +var maxChildChecks = big.NewInt(512) + +var ( + methodVersion = "version" + methodMaxClockDuration = "maxClockDuration" + methodMaxGameDepth = "maxGameDepth" + methodAbsolutePrestate = "absolutePrestate" + methodNBits = "nBits" + methodMaxAttackBranch = "maxAttackBranch" + methodStatus = "status" + methodRootClaim = "rootClaim" + methodClaimCount = "claimDataLen" + methodClaim = "claimData" + methodL1Head = "l1Head" + methodResolvedSubgames = "resolvedSubgames" + methodResolve = "resolve" + methodResolveClaim = "resolveClaim" + methodAttack = "attack" + methodDefend = "defend" + methodStep = "step" + methodAddLocalData = "addLocalData" + methodVM = "vm" + methodStartingBlockNumber = "startingBlockNumber" + methodStartingRootHash = "startingRootHash" + methodSplitDepth = "splitDepth" + methodL2BlockNumber = "l2BlockNumber" + methodRequiredBond = "getRequiredBond" + methodClaimCredit = "claimCredit" + methodCredit = "credit" + methodWETH = "weth" + methodL2BlockNumberChallenged = "l2BlockNumberChallenged" + methodL2BlockNumberChallenger = "l2BlockNumberChallenger" + methodChallengeRootL2Block = "challengeRootL2Block" + methodAttackV2 = "attackV2" //todo + methodStepV2 = "stepV2" //todo +) + +var ( + ErrSimulationFailed = errors.New("tx simulation failed") + ErrChallengeL2BlockNotSupported = errors.New("contract version does not support challenging L2 block number") +) + +type FaultDisputeGameContractLatest struct { + metrics metrics.ContractMetricer + multiCaller *batching.MultiCaller + contract *batching.BoundContract +} + +type Proposal struct { + L2BlockNumber *big.Int + OutputRoot common.Hash +} + +type DAItem struct { + DaType *big.Int + DataHash common.Hash + Proof []byte +} + +type StepProof struct { + PreStateItem DAItem + PostStateItem DAItem + VmProof []byte +} + +// outputRootProof is designed to match the solidity OutputRootProof struct. +type outputRootProof struct { + Version [32]byte + StateRoot [32]byte + MessagePasserStorageRoot [32]byte + LatestBlockhash [32]byte +} + +func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { + contractAbi := snapshots.LoadFaultDisputeGameNABI() + + 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) + + // example: 1.1.0 + if strings.HasPrefix(version, "1.1.") { + // Detected an older version of contracts, use a compatibility shim. + legacyAbi := mustParseAbi(faultDisputeGameNAbi110) + return &FaultDisputeGameContractN110{ + 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 *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 { + return nil, common.Address{}, fmt.Errorf("failed to load weth address: %w", err) + } + wethAddr := result.GetAddress(0) + result, err = f.multiCaller.SingleCall(ctx, block, batching.NewBalanceCall(wethAddr)) + if err != nil { + return nil, common.Address{}, fmt.Errorf("failed to retrieve game balance: %w", err) + } + return result.GetBigInt(0), wethAddr, nil +} + +// 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 *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), + f.contract.Call(methodL2BlockNumber)) + if err != nil { + retErr = fmt.Errorf("failed to retrieve game block range: %w", err) + return + } + if len(results) != 2 { + retErr = fmt.Errorf("expected 2 results but got %v", len(results)) + return + } + prestateBlock = results[0].GetBigInt(0).Uint64() + poststateBlock = results[1].GetBigInt(0).Uint64() + return +} + +type GameMetadata struct { + L1Head common.Hash + L2BlockNum uint64 + RootClaim common.Hash + Status gameTypes.GameStatus + MaxClockDuration uint64 + L2BlockNumberChallenged bool + L2BlockNumberChallenger common.Address +} + +// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, max clock duration, and is l2 block number challenged. +func (f *FaultDisputeGameContractLatest) GetGameMetadata(ctx context.Context, block rpcblock.Block) (GameMetadata, error) { + defer f.metrics.StartContractRequest("GetGameMetadata")() + results, err := f.multiCaller.Call(ctx, block, + f.contract.Call(methodL1Head), + f.contract.Call(methodL2BlockNumber), + f.contract.Call(methodRootClaim), + f.contract.Call(methodStatus), + f.contract.Call(methodMaxClockDuration), + f.contract.Call(methodL2BlockNumberChallenged), + f.contract.Call(methodL2BlockNumberChallenger), + ) + if err != nil { + return GameMetadata{}, fmt.Errorf("failed to retrieve game metadata: %w", err) + } + if len(results) != 7 { + return GameMetadata{}, fmt.Errorf("expected 6 results but got %v", len(results)) + } + l1Head := results[0].GetHash(0) + l2BlockNumber := results[1].GetBigInt(0).Uint64() + rootClaim := results[2].GetHash(0) + status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0)) + if err != nil { + return GameMetadata{}, fmt.Errorf("failed to convert game status: %w", err) + } + duration := results[4].GetUint64(0) + blockChallenged := results[5].GetBool(0) + blockChallenger := results[6].GetAddress(0) + return GameMetadata{ + L1Head: l1Head, + L2BlockNum: l2BlockNumber, + RootClaim: rootClaim, + Status: status, + MaxClockDuration: duration, + L2BlockNumberChallenged: blockChallenged, + L2BlockNumberChallenger: blockChallenger, + }, nil +} + +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 { + return common.Hash{}, fmt.Errorf("failed to retrieve genesis output root: %w", err) + } + return startingRootHash.GetHash(0), nil +} + +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 { + return 0, fmt.Errorf("failed to retrieve split depth: %w", err) + } + return types.Depth(splitDepth.GetBigInt(0).Uint64()), nil +} + +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), + f.contract.Call(methodStatus)) + if err != nil { + return nil, gameTypes.GameStatusInProgress, err + } + if len(results) != 2 { + return nil, gameTypes.GameStatusInProgress, fmt.Errorf("expected 2 results but got %v", len(results)) + } + credit := results[0].GetBigInt(0) + status, err := gameTypes.GameStatusFromUint8(results[1].GetUint8(0)) + if err != nil { + return nil, gameTypes.GameStatusInProgress, fmt.Errorf("invalid game status %v: %w", status, err) + } + return credit, status, nil +} + +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)) + } + results, err := f.multiCaller.Call(ctx, block, calls...) + if err != nil { + return nil, fmt.Errorf("failed to retrieve required bonds: %w", err) + } + requiredBonds := make([]*big.Int, 0, len(positions)) + for _, result := range results { + requiredBonds = append(requiredBonds, result.GetBigInt(0)) + } + return requiredBonds, nil +} + +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 { + calls = append(calls, f.contract.Call(methodCredit, recipient)) + } + results, err := f.multiCaller.Call(ctx, block, calls...) + if err != nil { + return nil, fmt.Errorf("failed to retrieve credit: %w", err) + } + credits := make([]*big.Int, 0, len(recipients)) + for _, result := range results { + credits = append(credits, result.GetBigInt(0)) + } + return credits, nil +} + +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) + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("%w: %v", ErrSimulationFailed, err.Error()) + } + return call.ToTxCandidate() +} + +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 { + return nil, fmt.Errorf("failed to retrieve required bond: %w", err) + } + return bond.GetBigInt(0), nil +} + +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 addLocalData(ctx context.Context, ident uint64, execLeafIdx uint64, partOffset uint64, daItem DAItem) (txmgr.TxCandidate, error)) { + * call := f.contract.Call( + * methodAddLocalData, + * data.GetIdent(), + * new(big.Int).SetUint64(claimIdx), + * new(big.Int).SetUint64(uint64(data.OracleOffset)), + * ) + * return call.ToTxCandidate() + *} + */ + +func (f *FaultDisputeGameContractLatest) addLocalDataTx(claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { + call := f.contract.Call( + methodAddLocalData, + data.GetIdent(), + new(big.Int).SetUint64(claimIdx), + new(big.Int).SetUint64(uint64(data.OracleOffset)), + ) + return call.ToTxCandidate() +} + +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 + } + return oracle.AddGlobalDataTx(data) +} + +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 { + return nil, err + } + return delayedWETH.GetWithdrawals(ctx, block, gameAddr, recipients...) +} + +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 { + return nil, fmt.Errorf("failed to fetch WETH addr: %w", err) + } + return NewDelayedWETHContract(f.metrics, result.GetAddress(0), f.multiCaller), nil +} + +func (f *FaultDisputeGameContractLatest) GetOracle(ctx context.Context) (*PreimageOracleContract, error) { + defer f.metrics.StartContractRequest("GetOracle")() + vm, err := f.vm(ctx) + if err != nil { + return nil, err + } + return vm.Oracle(ctx) +} + +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 { + return 0, fmt.Errorf("failed to fetch max clock duration: %w", err) + } + return time.Duration(result.GetUint64(0)) * time.Second, nil +} + +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 { + return 0, fmt.Errorf("failed to fetch max game depth: %w", err) + } + return types.Depth(result.GetBigInt(0).Uint64()), nil +} + +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 { + return common.Hash{}, fmt.Errorf("failed to fetch absolute prestate hash: %w", err) + } + return result.GetHash(0), nil +} + +func (f *FaultDisputeGameContractLatest) GetNBits(ctx context.Context) (uint64, error) { + defer f.metrics.StartContractRequest("GetNBits")() + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodNBits)) + if err != nil { + return 0, fmt.Errorf("failed to fetch nbits: %w", err) + } + return result.GetBigInt(0).Uint64(), nil +} + +func (f *FaultDisputeGameContractLatest) GetMaxAttackBranch(ctx context.Context) (uint64, error) { + defer f.metrics.StartContractRequest("GetMaxAttackBranch")() + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodMaxAttackBranch)) + if err != nil { + return 0, fmt.Errorf("failed to fetch max attack branch: %w", err) + } + return result.GetBigInt(0).Uint64(), nil +} + +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 { + return common.Hash{}, fmt.Errorf("failed to fetch L1 head: %w", err) + } + return result.GetHash(0), nil +} + +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 { + return 0, fmt.Errorf("failed to fetch status: %w", err) + } + return gameTypes.GameStatusFromUint8(result.GetUint8(0)) +} + +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 { + return 0, fmt.Errorf("failed to fetch claim count: %w", err) + } + return result.GetBigInt(0).Uint64(), nil +} + +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 { + return types.Claim{}, fmt.Errorf("failed to fetch claim %v: %w", idx, err) + } + return f.decodeClaim(result, int(idx)), nil +} + +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) + }) + if err != nil { + return nil, fmt.Errorf("failed to load claims: %w", err) + } + + var claims []types.Claim + for idx, result := range results { + claims = append(claims, f.decodeClaim(result, idx)) + } + return claims, nil +} + +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 { + calls = append(calls, f.contract.Call(methodResolvedSubgames, big.NewInt(int64(claim.ContractIndex)))) + } + results, err := f.multiCaller.Call(ctx, block, calls...) + if err != nil { + return nil, fmt.Errorf("failed to retrieve resolved subgames: %w", err) + } + resolved := make([]bool, 0, len(claims)) + for _, result := range results { + resolved = append(resolved, result.GetBool(0)) + } + return resolved, nil +} + +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) + } + vmAddr := result.GetAddress(0) + return NewVMContract(vmAddr, f.multiCaller), nil +} + +func (f *FaultDisputeGameContractLatest) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) { + defer f.metrics.StartContractRequest("IsL2BlockNumberChallenged")() + result, err := f.multiCaller.SingleCall(ctx, block, f.contract.Call(methodL2BlockNumberChallenged)) + if err != nil { + return false, fmt.Errorf("failed to fetch block number challenged: %w", err) + } + return result.GetBool(0), nil +} + +func (f *FaultDisputeGameContractLatest) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) { + headerRlp, err := rlp.EncodeToBytes(challenge.Header) + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to serialize header: %w", err) + } + return f.contract.Call(methodChallengeRootL2Block, outputRootProof{ + Version: challenge.Output.Version, + StateRoot: challenge.Output.StateRoot, + MessagePasserStorageRoot: challenge.Output.WithdrawalStorageRoot, + LatestBlockhash: challenge.Output.BlockRef.Hash, + }, headerRlp).ToTxCandidate() +} + +func (f *FaultDisputeGameContractLatest) AttackTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) { + call := f.contract.Call(methodAttack, parent.Value, big.NewInt(int64(parent.ContractIndex)), pivot) + return f.txWithBond(ctx, parent.Position.Attack(), call) +} + +func (f *FaultDisputeGameContractLatest) DefendTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) { + call := f.contract.Call(methodDefend, parent.Value, big.NewInt(int64(parent.ContractIndex)), pivot) + return f.txWithBond(ctx, parent.Position.Defend(), call) +} + +func (f *FaultDisputeGameContractLatest) txWithBond(ctx context.Context, position types.Position, call *batching.ContractCall) (txmgr.TxCandidate, error) { + tx, err := call.ToTxCandidate() + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to create transaction: %w", err) + } + tx.Value, err = f.GetRequiredBond(ctx, position) + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to fetch required bond: %w", err) + } + return tx, nil +} + +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 *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) + if err != nil { + return fmt.Errorf("failed to call resolve claim: %w", err) + } + return nil +} + +func (f *FaultDisputeGameContractLatest) ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) { + call := f.resolveClaimCall(claimIdx) + return call.ToTxCandidate() +} + +func (f *FaultDisputeGameContractLatest) resolveClaimCall(claimIdx uint64) *batching.ContractCall { + return f.contract.Call(methodResolveClaim, new(big.Int).SetUint64(claimIdx), maxChildChecks) +} + +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) + if err != nil { + return gameTypes.GameStatusInProgress, fmt.Errorf("failed to call resolve: %w", err) + } + return gameTypes.GameStatusFromUint8(result.GetUint8(0)) +} + +func (f *FaultDisputeGameContractLatest) ResolveTx() (txmgr.TxCandidate, error) { + call := f.resolveCall() + return call.ToTxCandidate() +} + +func (f *FaultDisputeGameContractLatest) resolveCall() *batching.ContractCall { + return f.contract.Call(methodResolve) +} + +// decodeClock decodes a uint128 into a Clock duration and timestamp. +func decodeClock(clock *big.Int) types.Clock { + maxUint64 := new(big.Int).Add(new(big.Int).SetUint64(math.MaxUint64), big.NewInt(1)) + remainder := new(big.Int) + quotient, _ := new(big.Int).QuoRem(clock, maxUint64, remainder) + return types.NewClock(time.Duration(quotient.Int64())*time.Second, time.Unix(remainder.Int64(), 0)) +} + +// packClock packs the Clock duration and timestamp into a uint128. +func packClock(c types.Clock) *big.Int { + duration := big.NewInt(int64(c.Duration.Seconds())) + encoded := new(big.Int).Lsh(duration, 64) + return new(big.Int).Or(encoded, big.NewInt(c.Timestamp.Unix())) +} + +func (f *FaultDisputeGameContractLatest) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim { + parentIndex := result.GetUint32(0) + counteredBy := result.GetAddress(1) + claimant := result.GetAddress(2) + bond := result.GetBigInt(3) + claim := result.GetHash(4) + position := result.GetBigInt(5) + clock := result.GetBigInt(6) + return types.Claim{ + ClaimData: types.ClaimData{ + Value: claim, + Position: types.NewPositionFromGIndex(position), + Bond: bond, + }, + CounteredBy: counteredBy, + Claimant: claimant, + Clock: decodeClock(clock), + ContractIndex: contractIndex, + ParentContractIndex: int(parentIndex), + } +} + +func (f *FaultDisputeGameContractLatest) AttackV2Tx(ctx context.Context, parent types.Claim, attackBranch uint64, daType uint64, claims []byte) (txmgr.TxCandidate, error) { + nBits, err := f.GetNBits(ctx) + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to retrieve nbits: %w", err) + } + call := f.contract.Call(methodAttackV2, + parent.Value, + big.NewInt(int64(parent.ContractIndex)), + new(big.Int).SetUint64(attackBranch), + new(big.Int).SetUint64(daType), + claims) + return f.txWithBond(ctx, parent.Position.MoveN(nBits, attackBranch), call) +} + +func (f *FaultDisputeGameContractLatest) StepV2Tx(claimIdx uint64, attackBranch uint64, stateData []byte, proof StepProof) (txmgr.TxCandidate, error) { + call := f.contract.Call(methodStepV2, new(big.Int).SetUint64(claimIdx), new(big.Int).SetUint64(attackBranch), stateData, proof) + return call.ToTxCandidate() +} + +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) (GameMetadata, 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) + IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) + ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) + AttackTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) + DefendTx(ctx context.Context, parent types.Claim, 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) + AttackV2Tx(ctx context.Context, parent types.Claim, attackBranch uint64, daType uint64, claims []byte) (txmgr.TxCandidate, error) + GetNBits(ctx context.Context) (uint64, error) + GetMaxAttackBranch(ctx context.Context) (uint64, error) + StepV2Tx(claimIdx uint64, attackBranch uint64, stateData []byte, proof StepProof) (txmgr.TxCandidate, error) +} diff --git a/op-challenger/game/fault/contracts/faultdisputegamen110.go b/op-challenger/game/fault/contracts/faultdisputegamen110.go new file mode 100644 index 000000000000..574d087b15ce --- /dev/null +++ b/op-challenger/game/fault/contracts/faultdisputegamen110.go @@ -0,0 +1,28 @@ +package contracts + +import ( + "context" + _ "embed" + "math/big" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/common" +) + +//go:embed abis/FaultDisputeGameN-1.1.0.json +var faultDisputeGameNAbi110 []byte + +type FaultDisputeGameContractN110 struct { + FaultDisputeGameContractLatest +} + +func (f *FaultDisputeGameContractN110) AttackTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) { + call := f.contract.Call(methodAttack, big.NewInt(int64(parent.ContractIndex)), pivot) + return f.txWithBond(ctx, parent.Position.Attack(), call) +} + +func (f *FaultDisputeGameContractN110) DefendTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) { + call := f.contract.Call(methodDefend, big.NewInt(int64(parent.ContractIndex)), pivot) + return f.txWithBond(ctx, parent.Position.Defend(), call) +} diff --git a/op-challenger/game/fault/contracts/faultdisputegamen_test.go b/op-challenger/game/fault/contracts/faultdisputegamen_test.go new file mode 100644 index 000000000000..b70b47bb1d71 --- /dev/null +++ b/op-challenger/game/fault/contracts/faultdisputegamen_test.go @@ -0,0 +1,847 @@ +// +build faultdisputegamen + +package contracts + +import ( + "context" + "errors" + "fmt" + "math" + "math/big" + "math/rand" + "slices" + "testing" + "time" + + contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" + faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" + "github.com/ethereum-optimism/optimism/op-service/testutils" + "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/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" +) + +var ( + fdgAddr = common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB") + vmAddr = common.HexToAddress("0x33332842371dFC380576ebb09Ae16Cb6B6c3333") + oracleAddr = common.HexToAddress("0x44442842371dFC380576ebb09Ae16Cb6B6ca4444") +) + +type contractVersion struct { + version string + loadAbi func() *abi.ABI +} + +func (c contractVersion) Is(versions ...string) bool { + return slices.Contains(versions, c.version) +} + +const ( + vers080 = "0.8.0" + vers0180 = "0.18.0" + vers111 = "1.1.1" + versLatest = "1.2.0" +) + +var versions = []contractVersion{ + { + version: versLatest, + loadAbi: snapshots.LoadFaultDisputeGameNABI, + }, +} + +func TestSimpleGetters(t *testing.T) { + tests := []struct { + methodAlias string + method string + args []interface{} + result interface{} + expected interface{} // Defaults to expecting the same as result + call func(game FaultDisputeGameContract) (any, error) + applies func(version contractVersion) bool + }{ + { + methodAlias: "status", + method: methodStatus, + result: types.GameStatusChallengerWon, + call: func(game FaultDisputeGameContract) (any, error) { + return game.GetStatus(context.Background()) + }, + }, + { + methodAlias: "maxClockDuration", + method: methodMaxClockDuration, + result: uint64(5566), + 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: "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) { + return game.GetMaxGameDepth(context.Background()) + }, + }, + { + methodAlias: "nBits", + method: methodNBits, + result: big.NewInt(2), + expected: uint64(2), + call: func(game FaultDisputeGameContract) (any, error) { + return game.GetNBits(context.Background()) + }, + }, + { + methodAlias: "maxAttackBranch", + method: methodMaxAttackBranch, + result: big.NewInt(3), + expected: uint64(3), + call: func(game FaultDisputeGameContract) (any, error) { + return game.GetMaxAttackBranch(context.Background()) + }, + }, + { + methodAlias: "absolutePrestate", + method: methodAbsolutePrestate, + result: common.Hash{0xab}, + call: func(game FaultDisputeGameContract) (any, error) { + return game.GetAbsolutePrestateHash(context.Background()) + }, + }, + { + methodAlias: "claimCount", + method: methodClaimCount, + result: big.NewInt(9876), + expected: uint64(9876), + call: func(game FaultDisputeGameContract) (any, error) { + return game.GetClaimCount(context.Background()) + }, + }, + { + methodAlias: "l1Head", + method: methodL1Head, + result: common.Hash{0xdd, 0xbb}, + call: func(game FaultDisputeGameContract) (any, error) { + return game.GetL1Head(context.Background()) + }, + }, + { + methodAlias: "resolve", + method: methodResolve, + result: types.GameStatusInProgress, + call: func(game FaultDisputeGameContract) (any, error) { + return game.CallResolve(context.Background()) + }, + }, + } + 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) + }) + } + }) + } +} + +func TestClock_EncodingDecoding(t *testing.T) { + t.Run("DurationAndTimestamp", func(t *testing.T) { + by := common.FromHex("00000000000000050000000000000002") + encoded := new(big.Int).SetBytes(by) + clock := decodeClock(encoded) + require.Equal(t, 5*time.Second, clock.Duration) + require.Equal(t, time.Unix(2, 0), clock.Timestamp) + require.Equal(t, encoded, packClock(clock)) + }) + + t.Run("ZeroDuration", func(t *testing.T) { + by := common.FromHex("00000000000000000000000000000002") + encoded := new(big.Int).SetBytes(by) + clock := decodeClock(encoded) + require.Equal(t, 0*time.Second, clock.Duration) + require.Equal(t, time.Unix(2, 0), clock.Timestamp) + require.Equal(t, encoded, packClock(clock)) + }) + + t.Run("ZeroTimestamp", func(t *testing.T) { + by := common.FromHex("00000000000000050000000000000000") + encoded := new(big.Int).SetBytes(by) + clock := decodeClock(encoded) + require.Equal(t, 5*time.Second, clock.Duration) + require.Equal(t, time.Unix(0, 0), clock.Timestamp) + require.Equal(t, encoded, packClock(clock)) + }) + + t.Run("ZeroClock", func(t *testing.T) { + by := common.FromHex("00000000000000000000000000000000") + encoded := new(big.Int).SetBytes(by) + clock := decodeClock(encoded) + require.Equal(t, 0*time.Second, clock.Duration) + require.Equal(t, time.Unix(0, 0), clock.Timestamp) + require.Equal(t, encoded.Uint64(), packClock(clock).Uint64()) + }) +} + +func TestGetOracleAddr(t *testing.T) { + 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) { + 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) { + 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) + }) + } +} + +func TestGetBalance(t *testing.T) { + 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) { + 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) { + 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) { + 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) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + bond := big.NewInt(1044) + value := common.Hash{0xaa} + parent := faultTypes.Claim{ClaimData: faultTypes.ClaimData{Value: common.Hash{0xbb}}, ContractIndex: 111} + stubRpc.SetResponse(fdgAddr, methodRequiredBond, rpcblock.Latest, []interface{}{parent.Position.Attack().ToGIndex()}, []interface{}{bond}) + if version.Is(vers080, vers0180, vers111) { + stubRpc.SetResponse(fdgAddr, methodAttack, rpcblock.Latest, []interface{}{big.NewInt(111), value}, nil) + } else { + stubRpc.SetResponse(fdgAddr, methodAttack, rpcblock.Latest, []interface{}{parent.Value, big.NewInt(111), value}, nil) + } + tx, err := game.AttackTx(context.Background(), parent, value) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + require.Equal(t, bond, tx.Value) + }) + } +} + +func TestDefendTx(t *testing.T) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + bond := big.NewInt(1044) + value := common.Hash{0xaa} + parent := faultTypes.Claim{ClaimData: faultTypes.ClaimData{Value: common.Hash{0xbb}}, ContractIndex: 111} + stubRpc.SetResponse(fdgAddr, methodRequiredBond, rpcblock.Latest, []interface{}{parent.Position.Defend().ToGIndex()}, []interface{}{bond}) + if version.Is(vers080, vers0180, vers111) { + stubRpc.SetResponse(fdgAddr, methodDefend, rpcblock.Latest, []interface{}{big.NewInt(111), value}, nil) + } else { + stubRpc.SetResponse(fdgAddr, methodDefend, rpcblock.Latest, []interface{}{parent.Value, big.NewInt(111), value}, nil) + } + tx, err := game.DefendTx(context.Background(), parent, value) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + require.Equal(t, bond, tx.Value) + }) + } +} + +func TestStepTx(t *testing.T) { + 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) { + stubRpc.SetResponse( + fdgAddr, + methodClaim, + block, + []interface{}{big.NewInt(int64(claim.ContractIndex))}, + []interface{}{ + uint32(claim.ParentContractIndex), + claim.CounteredBy, + claim.Claimant, + claim.Bond, + claim.Value, + claim.Position.ToGIndex(), + packClock(claim.Clock), + }) +} + +func TestGetBlockRange(t *testing.T) { + 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) { + 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) { + 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 + expectedL2BlockNumberChallenged := true + expectedL2BlockNumberChallenger := common.Address{0xee} + 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 { + expectedL2BlockNumberChallenged = false + expectedL2BlockNumberChallenger = common.Address{} + stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedMaxClockDuration * 2}) + } else if version.version == vers0180 { + expectedL2BlockNumberChallenged = false + expectedL2BlockNumberChallenger = common.Address{} + stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) + } else { + stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) + stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenged, block, nil, []interface{}{expectedL2BlockNumberChallenged}) + stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenger, block, nil, []interface{}{expectedL2BlockNumberChallenger}) + } + actual, err := contract.GetGameMetadata(context.Background(), block) + expected := GameMetadata{ + L1Head: expectedL1Head, + L2BlockNum: expectedL2BlockNumber, + RootClaim: expectedRootClaim, + Status: expectedStatus, + MaxClockDuration: expectedMaxClockDuration, + L2BlockNumberChallenged: expectedL2BlockNumberChallenged, + L2BlockNumberChallenger: expectedL2BlockNumberChallenger, + } + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + } +} + +func TestGetStartingRootHash(t *testing.T) { + 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) { + 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) { + 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) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + + block := rpcblock.ByNumber(482) + + 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]}) + } + + 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) { + 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) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + + 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} + + 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]) + } + }) + } +} + +func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) { + for _, version := range versions { + version := version + for _, expected := range []bool{true, false} { + expected := expected + t.Run(fmt.Sprintf("%v-%v", version.version, expected), func(t *testing.T) { + block := rpcblock.ByHash(common.Hash{0x43}) + stubRpc, game := setupFaultDisputeGameTest(t, version) + supportsL2BlockNumChallenge := version.version != vers080 && version.version != vers0180 + if supportsL2BlockNumChallenge { + stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenged, block, nil, []interface{}{expected}) + } else if expected { + t.Skip("Can't have challenged L2 block number on this contract version") + } + challenged, err := game.IsL2BlockNumberChallenged(context.Background(), block) + require.NoError(t, err) + require.Equal(t, expected, challenged) + }) + } + } +} + +func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + rng := rand.New(rand.NewSource(0)) + stubRpc, game := setupFaultDisputeGameTest(t, version) + challenge := &faultTypes.InvalidL2BlockNumberChallenge{ + Output: ð.OutputResponse{ + Version: eth.Bytes32{}, + OutputRoot: eth.Bytes32{0xaa}, + BlockRef: eth.L2BlockRef{Hash: common.Hash{0xbb}}, + WithdrawalStorageRoot: common.Hash{0xcc}, + StateRoot: common.Hash{0xdd}, + }, + Header: testutils.RandomHeader(rng), + } + supportsL2BlockNumChallenge := version.version != vers080 && version.version != vers0180 + if supportsL2BlockNumChallenge { + headerRlp, err := rlp.EncodeToBytes(challenge.Header) + require.NoError(t, err) + stubRpc.SetResponse(fdgAddr, methodChallengeRootL2Block, rpcblock.Latest, []interface{}{ + outputRootProof{ + Version: challenge.Output.Version, + StateRoot: challenge.Output.StateRoot, + MessagePasserStorageRoot: challenge.Output.WithdrawalStorageRoot, + LatestBlockhash: challenge.Output.BlockRef.Hash, + }, + headerRlp, + }, nil) + } + tx, err := game.ChallengeL2BlockNumberTx(challenge) + if supportsL2BlockNumChallenge { + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + } else { + require.ErrorIs(t, err, ErrChallengeL2BlockNotSupported) + require.Equal(t, txmgr.TxCandidate{}, tx) + } + }) + } +} + +func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, FaultDisputeGameContract) { + fdgAbi := version.loadAbi() + + vmAbi := snapshots.LoadMIPSABI() + oracleAbi := snapshots.LoadPreimageOracleABI() + + stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi) + stubRpc.AddContract(vmAddr, vmAbi) + stubRpc.AddContract(oracleAddr, oracleAbi) + caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) + + 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 +} + +func TestAttackV2Tx(t *testing.T) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + bond := big.NewInt(1044) + value := common.Hash{0xaa} + var claims [96]byte + copy(claims[0:32], value[:]) + copy(claims[32:64], value[:]) + copy(claims[64:96], value[:]) + nBits := uint64(2) + attackBranch := big.NewInt(0) + daType := big.NewInt(1) + parent := faultTypes.Claim{ClaimData: faultTypes.ClaimData{Value: common.Hash{0xbb}}, ContractIndex: 111} + stubRpc.SetResponse(fdgAddr, methodNBits, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(nBits)}) + stubRpc.SetResponse(fdgAddr, methodRequiredBond, rpcblock.Latest, []interface{}{parent.Position.MoveN(nBits, attackBranch.Uint64()).ToGIndex()}, []interface{}{bond}) + stubRpc.SetResponse(fdgAddr, methodAttackV2, rpcblock.Latest, []interface{}{parent.Value, big.NewInt(111), attackBranch, daType, claims[:]}, nil) + tx, err := game.AttackV2Tx(context.Background(), parent, attackBranch.Uint64(), daType.Uint64(), claims[:]) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + require.Equal(t, bond, tx.Value) + }) + } +} + +func TestStepV2Tx(t *testing.T) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + stateData := []byte{1, 2, 3} + vmProof := []byte{4, 5, 6, 7, 8, 9} + preStateItem := DAItem{ + DaType: big.NewInt(0), + DataHash: [32]byte{0x01, 0x02, 0x03}, + Proof: []byte("pre state proof"), + } + postStateItem := DAItem{ + DaType: big.NewInt(0), + DataHash: [32]byte{0x04, 0x05, 0x06}, + Proof: []byte("post state proof"), + } + proofData := StepProof{ + PreStateItem: preStateItem, + PostStateItem: postStateItem, + VmProof: vmProof, + } + stubRpc.SetResponse(fdgAddr, methodStepV2, rpcblock.Latest, []interface{}{big.NewInt(111), big.NewInt(1), stateData, proofData}, nil) + tx, err := game.StepV2Tx(111, 1, stateData, proofData) + require.NoError(t, err) + stubRpc.VerifyTxCandidate(tx) + }) + } +} diff --git a/op-challenger/game/fault/types/position.go b/op-challenger/game/fault/types/position.go index 86d10730f721..c72e0a187601 100644 --- a/op-challenger/game/fault/types/position.go +++ b/op-challenger/game/fault/types/position.go @@ -154,3 +154,10 @@ func bigMSB(x *big.Int) Depth { } return Depth(x.BitLen() - 1) } + +func (p Position) MoveN(depth uint64, branch uint64) Position { + return Position{ + depth: p.depth + Depth(depth), + indexAtDepth: new(big.Int).Add(p.IndexAtDepth(), new(big.Int).Lsh(big.NewInt(int64(branch)), uint(depth))), + } +} diff --git a/op-challenger/game/fault/types/position_test.go b/op-challenger/game/fault/types/position_test.go index 535c01a7e6bf..f74ac085c10a 100644 --- a/op-challenger/game/fault/types/position_test.go +++ b/op-challenger/game/fault/types/position_test.go @@ -314,3 +314,52 @@ func TestRelativeMoves(t *testing.T) { }) } } + +func TestMoveN(t *testing.T) { + tests0 := []struct { + startGIndex *big.Int + defendGIndex *big.Int + }{ + {bi(1), bi(4)}, + {bi(2), bi(8)}, + {bi(4), bi(16)}, + } + depth := uint64(2) + branch := uint64(0) + for _, test := range tests0 { + pos := NewPositionFromGIndex(test.startGIndex) + result := pos.MoveN(depth, branch) + require.Equalf(t, test.defendGIndex, result.ToGIndex(), "move GIndex %s, expected=%s, got=%s", test.startGIndex, test.defendGIndex, result.ToGIndex()) + } + + tests1 := []struct { + startGIndex *big.Int + defendGIndex *big.Int + }{ + {bi(2), bi(12)}, + {bi(4), bi(20)}, + {bi(8), bi(36)}, + } + depth = uint64(2) + branch = uint64(1) + for _, test := range tests1 { + pos := NewPositionFromGIndex(test.startGIndex) + result := pos.MoveN(depth, branch) + require.Equalf(t, test.defendGIndex, result.ToGIndex(), "move GIndex %s, expected=%s, got=%s", test.startGIndex, test.defendGIndex, result.ToGIndex()) + } + + tests3 := []struct { + startGIndex *big.Int + defendGIndex *big.Int + }{ + {bi(4), bi(28)}, + {bi(8), bi(44)}, + } + depth = uint64(2) + branch = uint64(3) + for _, test := range tests3 { + pos := NewPositionFromGIndex(test.startGIndex) + result := pos.MoveN(depth, branch) + require.Equalf(t, test.defendGIndex, result.ToGIndex(), "move GIndex %s, expected=%s, got=%s", test.startGIndex, test.defendGIndex, result.ToGIndex()) + } +} diff --git a/packages/contracts-bedrock/snapshots/abi/FaultDisputeGameN.json b/packages/contracts-bedrock/snapshots/abi/FaultDisputeGameN.json new file mode 100644 index 000000000000..e76bb6f23804 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/FaultDisputeGameN.json @@ -0,0 +1,1292 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_gameType", + "type": "uint32", + "internalType": "GameType" + }, + { + "name": "_absolutePrestate", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_maxGameDepth", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_splitDepth", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_clockExtension", + "type": "uint64", + "internalType": "Duration" + }, + { + "name": "_maxClockDuration", + "type": "uint64", + "internalType": "Duration" + }, + { + "name": "_vm", + "type": "address", + "internalType": "contract IBigStepper" + }, + { + "name": "_weth", + "type": "address", + "internalType": "contract IDelayedWETH" + }, + { + "name": "_anchorStateRegistry", + "type": "address", + "internalType": "contract IAnchorStateRegistry" + }, + { + "name": "_l2ChainId", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "MAX_ATTACK_BRANCH", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "N_BITS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "absolutePrestate", + "inputs": [], + "outputs": [ + { + "name": "absolutePrestate_", + "type": "bytes32", + "internalType": "Claim" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addLocalData", + "inputs": [ + { + "name": "_ident", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_execLeafIdx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_partOffset", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_daItem", + "type": "tuple", + "internalType": "struct LibDA.DAItem", + "components": [ + { + "name": "daType", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "dataHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "proof", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [ + { + "name": "uuid_", + "type": "bytes32", + "internalType": "Hash" + }, + { + "name": "value_", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "addLocalData", + "inputs": [ + { + "name": "_ident", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_execLeafIdx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_partOffset", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "anchorStateRegistry", + "inputs": [], + "outputs": [ + { + "name": "registry_", + "type": "address", + "internalType": "contract IAnchorStateRegistry" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "attack", + "inputs": [ + { + "name": "_disputed", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_parentIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_claim", + "type": "bytes32", + "internalType": "Claim" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "attackV2", + "inputs": [ + { + "name": "_disputed", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_parentIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_attackBranch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_daType", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_claims", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "challengeRootL2Block", + "inputs": [ + { + "name": "_outputRootProof", + "type": "tuple", + "internalType": "struct Types.OutputRootProof", + "components": [ + { + "name": "version", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "stateRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "messagePasserStorageRoot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "latestBlockhash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "name": "_headerRLP", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "claimCredit", + "inputs": [ + { + "name": "_recipient", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "claimData", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "parentIndex", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "counteredBy", + "type": "address", + "internalType": "address" + }, + { + "name": "claimant", + "type": "address", + "internalType": "address" + }, + { + "name": "bond", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "claim", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "position", + "type": "uint128", + "internalType": "Position" + }, + { + "name": "clock", + "type": "uint128", + "internalType": "Clock" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "claimDataLen", + "inputs": [], + "outputs": [ + { + "name": "len_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "claims", + "inputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "Hash" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "clockExtension", + "inputs": [], + "outputs": [ + { + "name": "clockExtension_", + "type": "uint64", + "internalType": "Duration" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "createdAt", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "Timestamp" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "credit", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "defend", + "inputs": [ + { + "name": "_disputed", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_parentIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_claim", + "type": "bytes32", + "internalType": "Claim" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "extraData", + "inputs": [], + "outputs": [ + { + "name": "extraData_", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "gameCreator", + "inputs": [], + "outputs": [ + { + "name": "creator_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "gameData", + "inputs": [], + "outputs": [ + { + "name": "gameType_", + "type": "uint32", + "internalType": "GameType" + }, + { + "name": "rootClaim_", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "extraData_", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "gameType", + "inputs": [], + "outputs": [ + { + "name": "gameType_", + "type": "uint32", + "internalType": "GameType" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getChallengerDuration", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "duration_", + "type": "uint64", + "internalType": "Duration" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getNumToResolve", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "numRemainingChildren_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRequiredBond", + "inputs": [ + { + "name": "_position", + "type": "uint128", + "internalType": "Position" + } + ], + "outputs": [ + { + "name": "requiredBond_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "l1Head", + "inputs": [], + "outputs": [ + { + "name": "l1Head_", + "type": "bytes32", + "internalType": "Hash" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "l2BlockNumber", + "inputs": [], + "outputs": [ + { + "name": "l2BlockNumber_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "l2BlockNumberChallenged", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "l2BlockNumberChallenger", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "l2ChainId", + "inputs": [], + "outputs": [ + { + "name": "l2ChainId_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxAttackBranch", + "inputs": [], + "outputs": [ + { + "name": "maxAttackBranch_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxClockDuration", + "inputs": [], + "outputs": [ + { + "name": "maxClockDuration_", + "type": "uint64", + "internalType": "Duration" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxGameDepth", + "inputs": [], + "outputs": [ + { + "name": "maxGameDepth_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "move", + "inputs": [ + { + "name": "_disputed", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_challengeIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_claim", + "type": "bytes32", + "internalType": "Claim" + }, + { + "name": "_isAttack", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "nBits", + "inputs": [], + "outputs": [ + { + "name": "nBits_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "resolutionCheckpoints", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "initialCheckpointComplete", + "type": "bool", + "internalType": "bool" + }, + { + "name": "subgameIndex", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "leftmostPosition", + "type": "uint128", + "internalType": "Position" + }, + { + "name": "counteredBy", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "resolve", + "inputs": [], + "outputs": [ + { + "name": "status_", + "type": "uint8", + "internalType": "enum GameStatus" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "resolveClaim", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_numToResolve", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "resolvedAt", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "Timestamp" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "resolvedSubgames", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "rootClaim", + "inputs": [], + "outputs": [ + { + "name": "rootClaim_", + "type": "bytes32", + "internalType": "Claim" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "splitDepth", + "inputs": [], + "outputs": [ + { + "name": "splitDepth_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "startingBlockNumber", + "inputs": [], + "outputs": [ + { + "name": "startingBlockNumber_", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "startingOutputRoot", + "inputs": [], + "outputs": [ + { + "name": "root", + "type": "bytes32", + "internalType": "Hash" + }, + { + "name": "l2BlockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "startingRootHash", + "inputs": [], + "outputs": [ + { + "name": "startingRootHash_", + "type": "bytes32", + "internalType": "Hash" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "status", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum GameStatus" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "step", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_isAttack", + "type": "bool", + "internalType": "bool" + }, + { + "name": "_stateData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proof", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "stepV2", + "inputs": [ + { + "name": "_claimIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_attackBranch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_stateData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_proof", + "type": "tuple", + "internalType": "struct FaultDisputeGame.StepProof", + "components": [ + { + "name": "preStateItem", + "type": "tuple", + "internalType": "struct LibDA.DAItem", + "components": [ + { + "name": "daType", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "dataHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "proof", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "postStateItem", + "type": "tuple", + "internalType": "struct LibDA.DAItem", + "components": [ + { + "name": "daType", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "dataHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "proof", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "vmProof", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "subgames", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "version", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "vm", + "inputs": [], + "outputs": [ + { + "name": "vm_", + "type": "address", + "internalType": "contract IBigStepper" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "weth", + "inputs": [], + "outputs": [ + { + "name": "weth_", + "type": "address", + "internalType": "contract IDelayedWETH" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "Move", + "inputs": [ + { + "name": "parentIndex", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "claim", + "type": "bytes32", + "indexed": true, + "internalType": "Claim" + }, + { + "name": "claimant", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Resolved", + "inputs": [ + { + "name": "status", + "type": "uint8", + "indexed": true, + "internalType": "enum GameStatus" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AlreadyInitialized", + "inputs": [] + }, + { + "type": "error", + "name": "AnchorRootNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "BlockNumberMatches", + "inputs": [] + }, + { + "type": "error", + "name": "BondTransferFailed", + "inputs": [] + }, + { + "type": "error", + "name": "CannotDefendRootClaim", + "inputs": [] + }, + { + "type": "error", + "name": "ClaimAboveSplit", + "inputs": [] + }, + { + "type": "error", + "name": "ClaimAlreadyExists", + "inputs": [] + }, + { + "type": "error", + "name": "ClaimAlreadyResolved", + "inputs": [] + }, + { + "type": "error", + "name": "ClockNotExpired", + "inputs": [] + }, + { + "type": "error", + "name": "ClockTimeExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "ContentLengthMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "DuplicateStep", + "inputs": [] + }, + { + "type": "error", + "name": "EmptyItem", + "inputs": [] + }, + { + "type": "error", + "name": "GameDepthExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "GameNotInProgress", + "inputs": [] + }, + { + "type": "error", + "name": "IncorrectBondAmount", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidClockExtension", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidDataRemainder", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidDisputedClaimIndex", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidHeader", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidHeaderRLP", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidLocalIdent", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidOutputRootProof", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidParent", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidPosition", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidPrestate", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSplitDepth", + "inputs": [] + }, + { + "type": "error", + "name": "L2BlockNumberChallenged", + "inputs": [] + }, + { + "type": "error", + "name": "MaxDepthTooLarge", + "inputs": [] + }, + { + "type": "error", + "name": "NoCreditToClaim", + "inputs": [] + }, + { + "type": "error", + "name": "NotSupported", + "inputs": [] + }, + { + "type": "error", + "name": "OutOfOrderResolution", + "inputs": [] + }, + { + "type": "error", + "name": "UnexpectedList", + "inputs": [] + }, + { + "type": "error", + "name": "UnexpectedRootClaim", + "inputs": [ + { + "name": "rootClaim", + "type": "bytes32", + "internalType": "Claim" + } + ] + }, + { + "type": "error", + "name": "UnexpectedString", + "inputs": [] + }, + { + "type": "error", + "name": "ValidStep", + "inputs": [] + } +] diff --git a/packages/contracts-bedrock/snapshots/abi_loader_fdgn.go b/packages/contracts-bedrock/snapshots/abi_loader_fdgn.go new file mode 100644 index 000000000000..7b8bcaaf97e8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi_loader_fdgn.go @@ -0,0 +1,16 @@ +// +build faultdisputegamen + +package snapshots + +import ( + _ "embed" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +//go:embed abi/FaultDisputeGameN.json +var faultDisputeGameN []byte + +func LoadFaultDisputeGameNABI() *abi.ABI { + return loadABI(faultDisputeGameN) +}