diff --git a/.gitmodules b/.gitmodules index f2b84783c4..aeb1e28300 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,3 +26,6 @@ [submodule "packages/contracts-bedrock/lib/automate"] path = packages/contracts-bedrock/lib/automate url = https://github.com/gelatodigital/automate +[submodule "packages/contracts-bedrock/lib/sp1-contracts"] + path = packages/contracts-bedrock/lib/sp1-contracts + url = https://github.com/lightning-li/sp1-contracts diff --git a/packages/contracts-bedrock/deploy-config/hardhat.json b/packages/contracts-bedrock/deploy-config/hardhat.json index 30a89a4ff4..53815cea33 100644 --- a/packages/contracts-bedrock/deploy-config/hardhat.json +++ b/packages/contracts-bedrock/deploy-config/hardhat.json @@ -62,5 +62,10 @@ "daChallengeWindow": 100, "daResolveWindow": 100, "daBondSize": 1000, - "daResolverRefundPercentage": 50 + "daResolverRefundPercentage": 50, + "blockDistance": 3, + "aggregationVkey": "0x0006a81df67f2d5e48048edd5c051a5be0ef9720a2ce130f12f7021256160e73", + "rangeVkeyCommitment": "0x5030974a2d74c494158e4af45836d72e2e0acae55f0f22d73c22bde90c1d6d98", + "sp1VerifierGateway": "0x51d3960c929B27Db3f041eA3c3aD4fF3c2A121C7", + "rollupConfigHash": "0x0b9b35ba1f4265979a10dea49f5501f81b3729b2856165574b1661323678e778" } diff --git a/packages/contracts-bedrock/deployments/97-deploy.json b/packages/contracts-bedrock/deployments/97-deploy.json new file mode 100644 index 0000000000..d963394043 --- /dev/null +++ b/packages/contracts-bedrock/deployments/97-deploy.json @@ -0,0 +1,36 @@ +{ + "AddressManager": "0x2e234DAe75C793f67A35089C9d99245E1C58470b", + "AnchorStateRegistry": "0x03D97F75CF5cE8986dc4fF531047AF2D52631aBE", + "AnchorStateRegistryProxy": "0x1aF7f588A501EA2B5bB3feeFA744892aA2CF00e6", + "DelayedWETH": "0x7dB36fC81A5197f84BDc88C9B1237A0fca492Cf4", + "DelayedWETHProxy": "0x756e0562323ADcDA4430d6cb456d9151f605290B", + "DisputeGameFactory": "0xc1B724FAe275423BE62006d7711b4435C31a3Fa2", + "DisputeGameFactoryProxy": "0xDB25A7b768311dE128BBDa7B8426c3f9C74f3240", + "L1CrossDomainMessenger": "0x9c0A33f6b1c5d574a6327101c597b0e792a4DC05", + "L1CrossDomainMessengerProxy": "0xD16d567549A2a2a2005aEACf7fB193851603dd70", + "L1ERC721Bridge": "0xF9A2bc3F5DA471245c57da74cfCF6Cdf5fb8040F", + "L1ERC721BridgeProxy": "0x13aa49bAc059d709dd0a18D6bb63290076a702D7", + "L1StandardBridge": "0x7ad421ef511CB1D0060fC90BBccCcf5735632f74", + "L1StandardBridgeProxy": "0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7", + "L2OutputOracle": "0x451CEA9b04c58DBC5770c1b1455a67440111b35f", + "L2OutputOracleProxy": "0x3381cD18e2Fb4dB236BF0525938AB6E43Db0440f", + "Mips": "0x71Fb6e45FA9D2396ffc32cd8605141d1af7ab957", + "OptimismMintableERC20Factory": "0x13E136063f4699eBf276c66125f8eA02adaB4F77", + "OptimismMintableERC20FactoryProxy": "0x96d3F6c20EEd2697647F543fE6C08bC2Fbf39758", + "OptimismPortal": "0x1fF359e524EB80A26D1D63D580B223f53120c65B", + "OptimismPortal2": "0x956486516159D5fDfAC9ee63759676A033A5bC88", + "OptimismPortalProxy": "0x212224D2F2d262cd093eE13240ca4873fcCBbA3C", + "PreimageOracle": "0x194C0E304fCA9281698112f40438bcb0D9F82544", + "ProtocolVersions": "0x3cea74C40CB33ae630e130516097cDd0cd06Cb5E", + "ProtocolVersionsProxy": "0x03A6a84cD762D9707A21605b548aaaB891562aAb", + "ProxyAdmin": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a", + "SafeProxyFactory": "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", + "SafeSingleton": "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552", + "SuperchainConfig": "0x9Fd48Cb6F987Cc0CbEC82A870C51Ec54cA16e599", + "SuperchainConfigProxy": "0xa0Cb889707d426A7A386870A03bc70d1b0697598", + "SystemConfig": "0xcC02cfD0D02e17d2805485f6878aFeE5da00A028", + "SystemConfigProxy": "0x2a07706473244BC757E10F2a9E86fB532828afe3", + "SystemOwnerSafe": "0xf82F7e298c13195A73c702625D49a1BD739cFaaF", + "ZkFaultProofConfig": "0xD384139842Cfff55fB7dC930e782cc09139a1cEe", + "ZkFaultProofConfigProxy": "0xe8dc788818033232EF9772CB2e6622F1Ec8bc840" +} \ No newline at end of file diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index b408087239..d586c4f645 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -16,6 +16,7 @@ remappings = [ '@rari-capital/solmate/=lib/solmate', '@lib-keccak/=lib/lib-keccak/contracts/lib', '@solady/=lib/solady/src', + "@sp1-contracts/=lib/sp1-contracts/contracts/", 'forge-std/=lib/forge-std/src', 'ds-test/=lib/forge-std/lib/ds-test/src', 'safe-contracts/=lib/safe-contracts/contracts', diff --git a/packages/contracts-bedrock/lib/forge-std b/packages/contracts-bedrock/lib/forge-std index 2d8b7b876a..5f1b1c6f60 160000 --- a/packages/contracts-bedrock/lib/forge-std +++ b/packages/contracts-bedrock/lib/forge-std @@ -1 +1 @@ -Subproject commit 2d8b7b876a5b328d6a73e13c4740ed7a0d72d5f4 +Subproject commit 5f1b1c6f607c34c76d5cefa33b41337b0d4699b4 diff --git a/packages/contracts-bedrock/lib/sp1-contracts b/packages/contracts-bedrock/lib/sp1-contracts new file mode 160000 index 0000000000..25b1596416 --- /dev/null +++ b/packages/contracts-bedrock/lib/sp1-contracts @@ -0,0 +1 @@ +Subproject commit 25b15964162ab31792fca1cd3028f75bbb4ebc51 diff --git a/packages/contracts-bedrock/scripts/Deploy.s.sol b/packages/contracts-bedrock/scripts/Deploy.s.sol index f445ae9781..95fc23e228 100644 --- a/packages/contracts-bedrock/scripts/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/Deploy.s.sol @@ -38,6 +38,7 @@ import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol"; import { PermissionedDisputeGame } from "src/dispute/PermissionedDisputeGame.sol"; import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; +import { ZkFaultProofConfig } from "src/dispute/ZkFaultProofConfig.sol"; import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; import { MIPS } from "src/cannon/MIPS.sol"; import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; @@ -58,6 +59,9 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol"; import { Process } from "scripts/libraries/Process.sol"; +import { SP1VerifierGateway } from "sp1-contracts/src/SP1VerifierGateway.sol"; +import { SP1Verifier } from "sp1-contracts/src/v3.0.0-rc4/SP1VerifierPlonk.sol"; + /// @title Deploy /// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function. /// To add a new contract to the system, add a public function that deploys that individual contract. @@ -152,6 +156,7 @@ contract Deploy is Deployer { DisputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"), DelayedWETH: mustGetAddress("DelayedWETHProxy"), AnchorStateRegistry: mustGetAddress("AnchorStateRegistryProxy"), + ZkFaultProofConfig: mustGetAddress("ZkFaultProofConfigProxy"), OptimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"), OptimismPortal: mustGetAddress("OptimismPortalProxy"), OptimismPortal2: mustGetAddress("OptimismPortalProxy"), @@ -171,6 +176,7 @@ contract Deploy is Deployer { DisputeGameFactory: getAddress("DisputeGameFactoryProxy"), DelayedWETH: getAddress("DelayedWETHProxy"), AnchorStateRegistry: getAddress("AnchorStateRegistryProxy"), + ZkFaultProofConfig: getAddress("ZkFaultProofConfigProxy"), OptimismMintableERC20Factory: getAddress("OptimismMintableERC20FactoryProxy"), OptimismPortal: getAddress("OptimismPortalProxy"), OptimismPortal2: getAddress("OptimismPortalProxy"), @@ -370,6 +376,7 @@ contract Deploy is Deployer { deployERC1967Proxy("L2OutputOracleProxy"); deployERC1967Proxy("DelayedWETHProxy"); deployERC1967Proxy("AnchorStateRegistryProxy"); + deployERC1967Proxy("ZkFaultProofConfigProxy"); transferAddressManagerOwnership(); // to the ProxyAdmin } @@ -392,6 +399,8 @@ contract Deploy is Deployer { deployPreimageOracle(); deployMips(); deployAnchorStateRegistry(); + deployZkFaultProofConfig(); + deploySp1VerifierSuite(); } /// @notice Initialize all of the implementations @@ -415,6 +424,7 @@ contract Deploy is Deployer { initializeDisputeGameFactory(); initializeDelayedWETH(); initializeAnchorStateRegistry(); + initializeZkFaultProofConfig(); } /// @notice Add Plasma setup to the OP chain @@ -813,6 +823,29 @@ contract Deploy is Deployer { addr_ = address(anchorStateRegistry); } + function deployZkFaultProofConfig() public broadcast returns (address addr_) { + console.log("Deploying ZkFaultProofConfig implementation"); + ZkFaultProofConfig config = new ZkFaultProofConfig{ salt: _implSalt() }(); + save("ZkFaultProofConfig", address(config)); + console.log("ZkFaultProofConfig deployed at %s", address(config)); + + addr_ = address(config); + } + + function deploySp1VerifierSuite() public broadcast returns (address addr_) { + console.log("Deploying SP1VerifierGateway implementation"); + SP1VerifierGateway suite = new SP1VerifierGateway(); + save("SP1VerifierGateway", address(suite)); + console.log("SP1VerifierGateway deployed at %s", address(suite)); + + + addr_ = address(suite); + console.log("Deploying PlonkVerifier implementation"); + + address verifier = address(new SP1Verifier()); + suite.addRoute(verifier); + } + /// @notice Deploy the SystemConfig function deploySystemConfig() public broadcast returns (address addr_) { console.log("Deploying SystemConfig implementation"); @@ -957,7 +990,7 @@ contract Deploy is Deployer { address anchorStateRegistryProxy = mustGetAddress("AnchorStateRegistryProxy"); address anchorStateRegistry = mustGetAddress("AnchorStateRegistry"); - AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](5); + AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](6); roots[0] = AnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.CANNON, outputRoot: OutputRoot({ @@ -987,6 +1020,13 @@ contract Deploy is Deployer { }) }); roots[4] = AnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.ZKFAULT, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + roots[5] = AnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.FAST, outputRoot: OutputRoot({ root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), @@ -1004,6 +1044,30 @@ contract Deploy is Deployer { console.log("AnchorStateRegistry version: %s", version); } + function initializeZkFaultProofConfig() public broadcast { + console.log("Upgrading and initializing ZkFaultProofConfig proxy"); + address zkFaultProofConfigProxy = mustGetAddress("ZkFaultProofConfigProxy"); + address zkFaultProofConfig = mustGetAddress("ZkFaultProofConfig"); + address sp1VerifierGateway = mustGetAddress("SP1VerifierGateway"); + + _upgradeAndCallViaSafe({ + _proxy: payable(zkFaultProofConfigProxy), + _implementation: zkFaultProofConfig, + _innerCallData: abi.encodeCall(ZkFaultProofConfig.initialize, ( + cfg.finalSystemOwner(), + cfg.blockDistance(), + cfg.l2ChainID(), + cfg.aggregationVkey(), + cfg.rangeVkeyCommitment(), + sp1VerifierGateway, + cfg.rollupConfigHash() + )) + }); + + string memory version = ZkFaultProofConfig(payable(zkFaultProofConfigProxy)).version(); + console.log("ZkFaultProofConfig version: %s", version); + } + /// @notice Initialize the SystemConfig function initializeSystemConfig() public broadcast { console.log("Upgrading and initializing SystemConfig proxy"); diff --git a/packages/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/DeployConfig.s.sol index 38f58688ac..fbbc72ece3 100644 --- a/packages/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/DeployConfig.s.sol @@ -75,6 +75,11 @@ contract DeployConfig is Script { uint256 public daResolveWindow; uint256 public daBondSize; uint256 public daResolverRefundPercentage; + uint256 public blockDistance; + bytes32 public aggregationVkey; + bytes32 public rangeVkeyCommitment; + address public sp1VerifierGateway; + bytes32 public rollupConfigHash; bool public useCustomGasToken; address public customGasTokenAddress; @@ -155,6 +160,12 @@ contract DeployConfig is Script { daBondSize = _readOr(_json, "$.daBondSize", 1000000000); daResolverRefundPercentage = _readOr(_json, "$.daResolverRefundPercentage", 0); + blockDistance = stdJson.readUint(_json, "$.blockDistance"); + aggregationVkey = stdJson.readBytes32(_json, "$.aggregationVkey"); + rangeVkeyCommitment = stdJson.readBytes32(_json, "$.rangeVkeyCommitment"); + sp1VerifierGateway = stdJson.readAddress(_json, "$.sp1VerifierGateway"); + rollupConfigHash = stdJson.readBytes32(_json, "$.rollupConfigHash"); + useCustomGasToken = _readOr(_json, "$.useCustomGasToken", false); customGasTokenAddress = _readOr(_json, "$.customGasTokenAddress", address(0)); diff --git a/packages/contracts-bedrock/scripts/Types.sol b/packages/contracts-bedrock/scripts/Types.sol index c8b48dc1e7..792d2df88e 100644 --- a/packages/contracts-bedrock/scripts/Types.sol +++ b/packages/contracts-bedrock/scripts/Types.sol @@ -10,6 +10,7 @@ library Types { address DisputeGameFactory; address DelayedWETH; address AnchorStateRegistry; + address ZkFaultProofConfig; address OptimismMintableERC20Factory; address OptimismPortal; address OptimismPortal2; diff --git a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol index 55d25abb92..120a5ac840 100644 --- a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol +++ b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol @@ -131,11 +131,113 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver emit DisputeGameCreated(address(proxy_), _gameType, _rootClaim); } + function _prepareExtraData( + uint256 l2BlockNumber, + Claim[] memory claims, + address parentProxy, + bytes memory extraData + ) private pure returns (bytes memory) { + // calculate the hash of all _claims + bytes32 claimsHash = keccak256(abi.encodePacked(claims)); + return abi.encodePacked( + l2BlockNumber, + claimsHash, + uint32(claims.length), + parentProxy, + extraData + ); + } + + function _validateGameCreation( + GameType _gameType, + Claim[] calldata _claims, + uint64 _parentGameIndex, + IDisputeGame impl + ) private view returns (IDisputeGame parentProxy) { + // If there is no implementation to clone for the given `GameType`, revert. + if (address(impl) == address(0)) revert NoImplementation(_gameType); + // If the required initialization bond is not met, revert. + if (msg.value != initBonds[_gameType]) revert IncorrectBondAmount(); + if (_claims.length == 0) revert NoClaims(); + // When the parent game index is the maximum value of a uint64, it means that there is no parent game. + // And use the state from anchor state registry. + if (_parentGameIndex != type(uint64).max && _parentGameIndex >= _disputeGameList.length) { + revert InvalidParentGameIndex(); + } + if (_parentGameIndex != type(uint64).max) { + (GameType parentGameType, , address proxy) = _disputeGameList[_parentGameIndex].unpack(); + parentProxy = IDisputeGame(proxy); + // The parent game must be of the same type as the child game. + if (parentGameType.raw() != _gameType.raw()) revert InvalidParentGameType(); + if (parentProxy.status() == GameStatus.CHALLENGER_WINS) { + revert InvalidParentGameStatus(); + } + } else { + parentProxy = IDisputeGame(address(0)); + } + } + + function createZkFaultDisputeGame( + GameType _gameType, + Claim[] calldata _claims, + uint64 _parentGameIndex, + uint64 _l2BlockNumber, + bytes calldata _extraData + ) external + payable + returns (IDisputeGame proxy_) + { + // Grab the implementation contract for the given `GameType`. + IDisputeGame impl = gameImpls[_gameType]; + IDisputeGame parentProxy = _validateGameCreation(_gameType, _claims, _parentGameIndex, impl); + // Clone the implementation contract and initialize it with the given parameters. + // + // CWIA Calldata Layout: + // ┌───────────────────────────┬────────────────────────────────────┐ + // │ Bytes │ Description │ + // ├───────────────────────────┼────────────────────────────────────┤ + // │ [0, 20) │ Game creator address │ + // | [20, 52) | Root claim | + // │ [52, 84) │ Parent block hash at creation time │ + // │ [84, 84+n) │ Extra data │ + // | [84, 116) | L2 block number | + // | [116, 148) | Hash of all claims | + // | [148, 152) | The length of claims | + // | [152, 172) | Parent game address | + // └───────────────────────────┴────────────────────────────────────┘ + Claim rootClaim = _claims[_claims.length-1]; + bytes memory extraData = _prepareExtraData( + _l2BlockNumber, + _claims, + address(parentProxy), + _extraData + ); + + proxy_ = IDisputeGame(address(impl).clone(abi.encodePacked(msg.sender, rootClaim, blockhash(block.number - 1), extraData))); + + // Compute the unique identifier for the dispute game. + Hash uuid = getGameUUID(_gameType, rootClaim, extraData); + // If a dispute game with the same UUID already exists, revert. + if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid); + + // Pack the game ID. + GameId id = LibGameId.pack(_gameType, Timestamp.wrap(uint64(block.timestamp)), address(proxy_)); + + // Store the dispute game id in the mapping & emit the `DisputeGameCreated` event. + _disputeGames[uuid] = id; + _disputeGameList.push(id); + + proxy_.initialize{ value: msg.value }(); + + emit DisputeGameCreated(address(proxy_), _gameType, rootClaim); + emit ZkDisputeGameIndexUpdated(_disputeGameList.length - 1); + + } /// @inheritdoc IDisputeGameFactory function getGameUUID( GameType _gameType, Claim _rootClaim, - bytes calldata _extraData + bytes memory _extraData ) public pure diff --git a/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol b/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol index ab559147ed..395ccbb211 100644 --- a/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol +++ b/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol @@ -470,7 +470,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver { numRemainingChildren_ = challengeIndicesLen - checkpoint.subgameIndex; } - /// @inheritdoc IFaultDisputeGame + /// @inheritdoc IDisputeGame function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) { l2BlockNumber_ = _getArgUint256(0x54); } diff --git a/packages/contracts-bedrock/src/dispute/ZkFaultDisputeGame.sol b/packages/contracts-bedrock/src/dispute/ZkFaultDisputeGame.sol new file mode 100644 index 0000000000..c769885a85 --- /dev/null +++ b/packages/contracts-bedrock/src/dispute/ZkFaultDisputeGame.sol @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { FixedPointMathLib } from "@solady/utils/FixedPointMathLib.sol"; + +import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; +import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; +import { IZkFaultDisputeGame } from "src/dispute/interfaces/IZkFaultDisputeGame.sol"; +import { ZkFaultProofConfig } from "src/dispute/ZkFaultProofConfig.sol"; +import { SP1VerifierGateway } from "@sp1-contracts/src/SP1VerifierGateway.sol"; +import { IInitializable } from "src/dispute/interfaces/IInitializable.sol"; +import { IBigStepper, IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol"; +import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; +import { Clone } from "@solady/utils/Clone.sol"; +import { ISemver } from "src/universal/ISemver.sol"; +import "src/dispute/lib/Types.sol"; +import "src/dispute/lib/Errors.sol"; + +/// @title FaultDisputeGame +/// @notice An implementation of the `IFaultDisputeGame` interface. +contract ZkFaultDisputeGame is IZkFaultDisputeGame, Clone, ISemver { + //////////////////////////////////////////////////////////////// + // State Vars // + //////////////////////////////////////////////////////////////// + + /// @notice The maximum duration that may accumulate on a team's chess clock before they may no longer respond. + Duration internal immutable MAX_CLOCK_DURATION; + + Duration internal immutable MAX_GENERATE_PROOF_DURATION; + Duration internal immutable MAX_DETECT_FAULT_DURATION; + + /// @notice The game type ID. + GameType internal immutable GAME_TYPE; + + /// @notice WETH contract for holding ETH. + IDelayedWETH internal immutable WETH; + + /// @notice The anchor state registry. + IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; + + /// @notice The configuration contract for the zk fault proof system. + ZkFaultProofConfig internal immutable CONFIG; + + /// @notice The chain ID of the L2 network this contract argues about. + uint256 internal immutable L2_CHAIN_ID; + + /// @notice Semantic version. + /// @custom:semver 1.0.0 + string public constant version = "1.0.0"; + + /// @notice The starting timestamp of the game + Timestamp public createdAt; + + /// @notice The timestamp of the game's global resolution. + Timestamp public resolvedAt; + + /// @inheritdoc IDisputeGame + GameStatus public status; + + /// @notice Flag for the `initialize` function to prevent re-initialization. + bool internal initialized; + + /// @notice Credited balances for winning participants. + mapping(address => uint256) public credit; + + /// @notice The mapping of claims which have been challenged by signal. + mapping(uint256 => bool) public challengedClaims; + /// @notice The indexes list of the challenged claims. + uint256[] public challengedClaimIndexes; + /// @notice The mapping of challenged claims and their timestamp. + mapping(uint256 => uint64) public challengedClaimsTimestamp; + /// @notice The mapping of claims and their challengers. + mapping(uint256 => address payable) public challengers; + /// @notice The fault prover who submitted the valid fault proof. + address payable public faultProofProver; + + /// @notice The mapping of claims and their validity proof provers. + mapping(uint256 => address payable) public validityProofProvers; + /// @notice The mapping of claims which have been challenged by signal and proven to be invalid. + mapping(uint256 => bool) public invalidChallengeClaims; + /// @notice The indexes list of the invalid challenged claims. + uint256[] public invalidChallengeClaimIndexes; + + /// @notice The flag for the challenge success. + bool public isChallengeSuccess; + /// @notice The index of the successful challenge. + uint256 public successfulChallengeIndex; + + /// @notice The latest finalized output root, serving as the anchor for output bisection. + OutputRoot public startingOutputRoot; + + uint256 public constant PERCENTAGE_DIVISOR = 10000; + uint256 public immutable PROPOSER_BOND; + uint256 public immutable CHALLENGER_BOND; + address payable public immutable FEE_VAULT_ADDRESS; + uint256 public immutable CHALLENGER_REWARD_PERCENTAGE; + uint256 public immutable PROVER_REWARD_PERCENTAGE; + + /// @param _gameType The type ID of the game. + /// @param _maxGenerateProofDuration The maximum amount of time that a validity prover has to generate a proof. + /// @param _maxDetectFaultDuration The maximum amount of time that a challenger has to detect a fault and sunmit a signal. + /// @param _weth WETH contract for holding ETH. + /// @param _anchorStateRegistry The contract that stores the anchor state for each game type. + /// @param _config The configuration contract for the zk fault proof system. + /// @param _l2ChainId Chain ID of the L2 network this contract argues about. + constructor( + GameType _gameType, + Duration _maxGenerateProofDuration, + Duration _maxDetectFaultDuration, + uint256 _PROPOSER_BOND, + uint256 _CHALLENGER_BOND, + address payable _FEE_VAULT_ADDRESS, + uint256 _CHALLENGER_REWARD_PERCENTAGE, + uint256 _PROVER_REWARD_PERCENTAGE, + IDelayedWETH _weth, + IAnchorStateRegistry _anchorStateRegistry, + ZkFaultProofConfig _config, + uint256 _l2ChainId + + ) { + GAME_TYPE = _gameType; + MAX_GENERATE_PROOF_DURATION = _maxGenerateProofDuration; + MAX_DETECT_FAULT_DURATION = _maxDetectFaultDuration; + PROPOSER_BOND = _PROPOSER_BOND; + CHALLENGER_BOND = _CHALLENGER_BOND; + FEE_VAULT_ADDRESS = _FEE_VAULT_ADDRESS; + CHALLENGER_REWARD_PERCENTAGE = _CHALLENGER_REWARD_PERCENTAGE; + PROVER_REWARD_PERCENTAGE = _PROVER_REWARD_PERCENTAGE; + if (CHALLENGER_REWARD_PERCENTAGE + PROVER_REWARD_PERCENTAGE > PERCENTAGE_DIVISOR) { + revert InvalidRewardPercentage(); + } + MAX_CLOCK_DURATION = Duration.wrap(MAX_GENERATE_PROOF_DURATION.raw() + MAX_DETECT_FAULT_DURATION.raw()); + WETH = _weth; + ANCHOR_STATE_REGISTRY = _anchorStateRegistry; + CONFIG = _config; + L2_CHAIN_ID = _l2ChainId; + } + + /// @inheritdoc IInitializable + function initialize() public payable virtual { + // SAFETY: Any revert in this function will bubble up to the DisputeGameFactory and + // prevent the game from being created. + // + // Implicit assumptions: + // - The `gameStatus` state variable defaults to 0, which is `GameStatus.IN_PROGRESS` + // - The dispute game factory will enforce the required bond to initialize the game. + // + // Explicit checks: + // - The game must not have already been initialized. + // - An output root cannot be proposed at or before the starting block number. + + // INVARIANT: The game must not have already been initialized. + if (initialized) revert AlreadyInitialized(); + // if (msg.value != PROPOSER_BOND) revert IncorrectBondAmount(); + IZkFaultDisputeGame parentGameContract = parentGameProxy(); + // Grab the latest anchor root. + (Hash root, uint256 rootBlockNumber) = ANCHOR_STATE_REGISTRY.anchors(GAME_TYPE); + + if (address(parentGameContract) != address(0)) { + root = Hash.wrap(parentGameContract.rootClaim().raw()); + rootBlockNumber = parentGameContract.l2BlockNumber(); + if (parentGameStatus() == GameStatus.CHALLENGER_WINS) { + revert ParentGameIsInvalid(); + } + } + + // Should only happen if this is a new game type that hasn't been set up yet. + if (root.raw() == bytes32(0)) revert AnchorRootNotFound(); + + // Set the starting output root. + startingOutputRoot = OutputRoot({ l2BlockNumber: rootBlockNumber, root: root }); + + // Revert if the calldata size is not the expected length. + // + // This is to prevent adding extra or omitting bytes from to `extraData` that result in a different game UUID + // in the factory, but are not used by the game, which would allow for multiple dispute games for the same + // output proposal to be created. + // + // Expected length: 0xb2 + // - 0x04 selector + // - 0x14 creator address + // - 0x20 root claim + // - 0x20 l1 head + // - 0x58 extraData + // - 0x20 l2 block number + // - 0x20 claims hash + // - 0x04 claims length + // - 0x14 parent game contract address + // - 0x02 CWIA bytes + assembly { + if iszero(eq(calldatasize(), 0xb2)) { + // Store the selector for `BadExtraData()` & revert + mstore(0x00, 0x9824bdab) + revert(0x1C, 0x04) + } + } + + // Do not allow the game to be initialized if the root claim corresponds to a block at or before the + // configured starting block number. + uint256 currentL2BlockNumber = l2BlockNumber(); + if (currentL2BlockNumber <= rootBlockNumber) revert UnexpectedRootClaim(rootClaim()); + + // This statement also ensures the correctness of currentL2BlockNumber, The reason is as follows: + // If the currentL2BlockNumber is wrong, then the proposer must provide a wrong list of claims. + // The challenger can detect the mismatch between the l2 block number and claims. + // This mismatch can be proved in the zk proof. + if (CONFIG.blockDistance() * claimsLength() != currentL2BlockNumber - rootBlockNumber) { + revert InvalidClaimsLength(); + } + + // Set the game as initialized. + initialized = true; + + // Deposit the bond. + WETH.deposit{ value: msg.value }(); + + // Set the game's starting timestamp + createdAt = Timestamp.wrap(uint64(block.timestamp)); + } + + function requireNotExpired(Duration _maxDuration, Timestamp _startDuration) internal view { + if (block.timestamp - _startDuration.raw() > _maxDuration.raw()) { + revert ClockTimeExceeded(); + } + } + + function challengeByProof(uint256 _disputeClaimIndex, Claim _expectedClaim, Claim[] calldata _originalClaims, bytes calldata _proof) external override { + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + if (parentGameStatus() == GameStatus.CHALLENGER_WINS) revert ParentGameIsInvalid(); + requireNotExpired(MAX_CLOCK_DURATION, createdAt); + if (isChallengeSuccess) revert GameChallengeSucceeded(); + + // allow direct challenge even there is a signal challenge + // check the validity of origin claims + if (_originalClaims.length != claimsLength()) { + revert InvalidClaimsLength(); + } + if (keccak256(abi.encodePacked(_originalClaims)) != claimsHash().raw()) { + revert InvalidOriginClaims(); + } + + // check the validity of _disputeClaimIndex + if (_disputeClaimIndex >= _originalClaims.length) { + revert InvalidDisputeClaimIndex(); + } + + if (_expectedClaim.raw() == _originalClaims[_disputeClaimIndex].raw()) { + revert InvalidExpectedClaim(); + } + Claim agreedClaim; + if (_disputeClaimIndex == 0) { + agreedClaim = Claim.wrap(startingOutputRoot.root.raw()); + } else { + agreedClaim = _originalClaims[_disputeClaimIndex - 1]; + } + + uint256 claimBlockNumber = startingOutputRoot.l2BlockNumber + (_disputeClaimIndex+1) * CONFIG.blockDistance(); + AggregationOutputs memory publicValues = AggregationOutputs({ + l1Head: l1Head().raw(), + l2PreRoot: agreedClaim.raw(), + claimRoot: _expectedClaim.raw(), + claimBlockNum: claimBlockNumber, + chainId: L2_CHAIN_ID, + rollupConfigHash: CONFIG.rollupConfigHash(), + rangeVkeyCommitment: CONFIG.rangeVkeyCommitment() + }); + + SP1VerifierGateway verifierGateway = SP1VerifierGateway(CONFIG.verifierGateway()); + verifierGateway.verifyProof(CONFIG.aggregationVkey(), abi.encode(publicValues), _proof); + + // update dispute claim index status + isChallengeSuccess = true; + successfulChallengeIndex = _disputeClaimIndex; + faultProofProver = payable(msg.sender); + } + + function challengeBySignal(uint256 _disputeClaimIndex) payable external override { + if (msg.value != CHALLENGER_BOND) revert IncorrectBondAmount(); + WETH.deposit{ value: msg.value }(); + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + if (parentGameStatus() == GameStatus.CHALLENGER_WINS) revert ParentGameIsInvalid(); + requireNotExpired(MAX_DETECT_FAULT_DURATION, createdAt); + if (isChallengeSuccess) revert GameChallengeSucceeded(); + if (challengedClaims[_disputeClaimIndex]) revert ClaimAlreadyChallenged(); + if (_disputeClaimIndex >= claimsLength()) { + revert InvalidDisputeClaimIndex(); + } + challengedClaims[_disputeClaimIndex] = true; + challengedClaimsTimestamp[_disputeClaimIndex] = uint64(block.timestamp); + challengers[_disputeClaimIndex] = payable(msg.sender); + challengedClaimIndexes.push(_disputeClaimIndex); + emit ChallengeBySignalCreated(_disputeClaimIndex); + } + + function submitProofForSignal(uint256 _disputeClaimIndex, Claim[] calldata _originalClaims, bytes calldata _proof) external override { + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + if (parentGameStatus() == GameStatus.CHALLENGER_WINS) revert ParentGameIsInvalid(); + // if the dispute claim index is already proven to be valid, revert + if (isChallengeSuccess) revert GameChallengeSucceeded(); + // if there is no signal challenge, revert + if (!challengedClaims[_disputeClaimIndex]) revert ClaimNotChallenged(); + // if the dispute claim index is already proven to be invalid, revert + if (invalidChallengeClaims[_disputeClaimIndex]) revert ChallengeAlreadyInvalid(); + // if the challenge signal is submitted after the MAX_GENERATE_PROOF_DURATION, revert + requireNotExpired(MAX_GENERATE_PROOF_DURATION, Timestamp.wrap(challengedClaimsTimestamp[_disputeClaimIndex])); + + // check the validity of origin claims + if (_originalClaims.length != claimsLength()) { + revert InvalidClaimsLength(); + } + if (keccak256(abi.encodePacked(_originalClaims)) != claimsHash().raw()) { + revert InvalidOriginClaims(); + } + + // check the validity of _disputeClaimIndex + if (_disputeClaimIndex >= _originalClaims.length) { + revert InvalidDisputeClaimIndex(); + } + + Claim agreedClaim; + if (_disputeClaimIndex == 0) { + agreedClaim = Claim.wrap(startingOutputRoot.root.raw()); + } else { + agreedClaim = _originalClaims[_disputeClaimIndex - 1]; + } + uint256 claimBlockNumber = startingOutputRoot.l2BlockNumber + (_disputeClaimIndex+1) * CONFIG.blockDistance(); + AggregationOutputs memory publicValues = AggregationOutputs({ + l1Head: l1Head().raw(), + l2PreRoot: agreedClaim.raw(), + claimRoot: _originalClaims[_disputeClaimIndex].raw(), + claimBlockNum: claimBlockNumber, + chainId: L2_CHAIN_ID, + rollupConfigHash: CONFIG.rollupConfigHash(), + rangeVkeyCommitment: CONFIG.rangeVkeyCommitment() + }); + + SP1VerifierGateway verifierGateway = SP1VerifierGateway(CONFIG.verifierGateway()); + verifierGateway.verifyProof(CONFIG.aggregationVkey(), abi.encode(publicValues), _proof); + + validityProofProvers[_disputeClaimIndex] = payable(msg.sender); + invalidChallengeClaims[_disputeClaimIndex] = true; + invalidChallengeClaimIndexes.push(_disputeClaimIndex); + emit SubmitProofForSignalCreated(_disputeClaimIndex); + } + + function resolveClaim() external override { + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + if (parentGameStatus() == GameStatus.CHALLENGER_WINS) revert ParentGameIsInvalid(); + if (isChallengeSuccess) revert GameChallengeSucceeded(); + + bool findValidChallenge = false; + for (uint256 i = 0; i < challengedClaimIndexes.length; i++) { + if (invalidChallengeClaims[challengedClaimIndexes[i]]) { + continue; + } + if (block.timestamp - challengedClaimsTimestamp[challengedClaimIndexes[i]] > MAX_GENERATE_PROOF_DURATION.raw()) { + isChallengeSuccess = true; + successfulChallengeIndex = challengedClaimIndexes[i]; + faultProofProver = challengers[successfulChallengeIndex]; + findValidChallenge = true; + break; + } + } + if (!findValidChallenge) { + revert NoExpiredChallenges(); + } + return; + } + + /// @inheritdoc IZkFaultDisputeGame + function startingBlockNumber() external view returns (uint256 startingBlockNumber_) { + startingBlockNumber_ = startingOutputRoot.l2BlockNumber; + } + + /// @inheritdoc IZkFaultDisputeGame + function startingRootHash() external view returns (Hash startingRootHash_) { + startingRootHash_ = startingOutputRoot.root; + } + + //////////////////////////////////////////////////////////////// + // `IDisputeGame` impl // + //////////////////////////////////////////////////////////////// + + /// @inheritdoc IDisputeGame + function resolve() external returns (GameStatus status_) { + // INVARIANT: Resolution cannot occur unless the game is currently in progress. + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + GameStatus parentStatus = parentGameStatus(); + // parent game must be resolved + if (parentStatus == GameStatus.IN_PROGRESS) { + revert ParentGameNotResolved(); + } + if (parentStatus == GameStatus.CHALLENGER_WINS) { + status_ = GameStatus.CHALLENGER_WINS; + // re-update fault proof prover + faultProofProver = payable(parentGameProxy().gameWinner()); + } + if (isChallengeSuccess) { + status_ = GameStatus.CHALLENGER_WINS; + } + + if (status_ != GameStatus.CHALLENGER_WINS) { + // first check if the challenge window is expired + if (block.timestamp - createdAt.raw() <= MAX_CLOCK_DURATION.raw()) { + revert ClockNotExpired(); + } + // then check if there is any remaining challenges + for (uint256 i = 0; i < challengedClaimIndexes.length; i++) { + if (!invalidChallengeClaims[challengedClaimIndexes[i]]) { + revert UnresolvedChallenges(); + } + } + status_ = GameStatus.DEFENDER_WINS; + } + + uint256 currentContractBalance = WETH.balanceOf(address(this)); + if (status_ == GameStatus.CHALLENGER_WINS) { + // refund valid challengers if there is any + for (uint256 i = 0; i < challengedClaimIndexes.length; i++) { + if (!invalidChallengeClaims[challengedClaimIndexes[i]]) { + // refund the bond + _distributeBond(challengers[challengedClaimIndexes[i]], CHALLENGER_BOND); + currentContractBalance = currentContractBalance - CHALLENGER_BOND; + } + } + // TODO reward part of challengers bond to validity provers, current reward is zero + uint256 initialBalance = currentContractBalance; + // reward the special challenger who submitted the signal which is proven to be valid + // 1. someone submitted a valid fault proof corresponding to the challenge index; or + // 2. the generate proof window is expired and no one submitted a validity proof + // If isChallengeSuccess is true, then the challenger exists; + // If isChallengeSuccess is false, then it indicates that the parent game is CHALLENGER_WINS and + // there is no successful challenge in the current game. + if (isChallengeSuccess && parentStatus != GameStatus.CHALLENGER_WINS) { + // there is a challenger who submmitted the dispute claim index by `challengeBySignal` + uint256 challengerBond = (currentContractBalance * CHALLENGER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR; + if (challengedClaims[successfulChallengeIndex]) { + _distributeBond(challengers[successfulChallengeIndex], challengerBond); + } else { + // if there is no challenger, then the challenger is the fault proof prover self + _distributeBond(faultProofProver, challengerBond); + } + currentContractBalance = currentContractBalance - challengerBond; + } + // reward the fault proof prover + uint256 proverBond = (initialBalance * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR; + _distributeBond(faultProofProver, proverBond); + currentContractBalance = currentContractBalance - proverBond; + } else if (status_ == GameStatus.DEFENDER_WINS) { + // reward part of challengers bond to validity provers + for (uint256 i = 0; i < invalidChallengeClaimIndexes.length; i++) { + uint256 proverBond = (CHALLENGER_BOND * PROVER_REWARD_PERCENTAGE) / PERCENTAGE_DIVISOR; + _distributeBond(validityProofProvers[invalidChallengeClaimIndexes[i]], proverBond); + currentContractBalance = currentContractBalance - proverBond; + } + // refund the bond to proposer + _distributeBond(gameCreator(), PROPOSER_BOND); + currentContractBalance = currentContractBalance - PROPOSER_BOND; + } else { + // sanity check + revert InvalidGameStatus(); + } + // transfer the rest + _distributeBond(FEE_VAULT_ADDRESS, currentContractBalance); + + resolvedAt = Timestamp.wrap(uint64(block.timestamp)); + + // Update the status and emit the resolved event, note that we're performing an assignment here. + emit Resolved(status = status_); + + // Try to update the anchor state, this should not revert. + ANCHOR_STATE_REGISTRY.tryUpdateAnchorState(); + } + + /// @inheritdoc IDisputeGame + function gameType() public view override returns (GameType gameType_) { + gameType_ = GAME_TYPE; + } + + /// @inheritdoc IDisputeGame + function gameCreator() public pure returns (address creator_) { + creator_ = _getArgAddress(0x00); + } + + /// @inheritdoc IDisputeGame + function rootClaim() public pure returns (Claim rootClaim_) { + rootClaim_ = Claim.wrap(_getArgBytes32(0x14)); + } + + /// @inheritdoc IDisputeGame + function l1Head() public pure returns (Hash l1Head_) { + l1Head_ = Hash.wrap(_getArgBytes32(0x34)); + } + + function claimsHash() public pure returns (Hash claimsHash_) { + claimsHash_ = Hash.wrap(_getArgBytes32(0x74)); + } + + function claimsLength() public pure returns (uint256 claimsLength_) { + claimsLength_ = uint256(_getArgUint32(0x94)); + } + + function parentGameProxy() public pure returns (IZkFaultDisputeGame parentGameProxy_) { + parentGameProxy_ = IZkFaultDisputeGame(_getArgAddress(0x98)); + } + + /// @inheritdoc IDisputeGame + function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) { + l2BlockNumber_ = uint256(_getArgUint256(0x54)); + } + + /// @inheritdoc IDisputeGame + function extraData() public pure returns (bytes memory extraData_) { + // The extra data starts at the second word within the cwia calldata and + // is 60 bytes long. + extraData_ = _getArgBytes(0x54, 0x58); + } + + /// @inheritdoc IDisputeGame + function gameData() external view returns (GameType gameType_, Claim rootClaim_, bytes memory extraData_) { + gameType_ = gameType(); + rootClaim_ = rootClaim(); + extraData_ = extraData(); + } + + //////////////////////////////////////////////////////////////// + // HELPERS // + //////////////////////////////////////////////////////////////// + + /// @notice Pays out the bond of a claim to a given recipient. + /// @param _recipient The recipient of the bond. + /// @param _bond The bond to pay out. + function _distributeBond(address _recipient, uint256 _bond) internal { + // Increase the recipient's credit. + credit[_recipient] += _bond; + + // Unlock the bond. + WETH.unlock(_recipient, _bond); + } + + //////////////////////////////////////////////////////////////// + // MISC EXTERNAL // + //////////////////////////////////////////////////////////////// + + /// @notice Claim the credit belonging to the recipient address. + /// @param _recipient The owner and recipient of the credit. + function claimCredit(address _recipient) external { + // Remove the credit from the recipient prior to performing the external call. + uint256 recipientCredit = credit[_recipient]; + credit[_recipient] = 0; + + // Revert if the recipient has no credit to claim. + if (recipientCredit == 0) revert NoCreditToClaim(); + + // Try to withdraw the WETH amount so it can be used here. + WETH.withdraw(_recipient, recipientCredit); + + // Transfer the credit to the recipient. + (bool success,) = _recipient.call{ value: recipientCredit }(hex""); + if (!success) revert BondTransferFailed(); + } + + /// @notice Returns the max clock duration. + function maxClockDuration() external view returns (Duration maxClockDuration_) { + maxClockDuration_ = MAX_CLOCK_DURATION; + } + + /// @notice Returns the maximum duration allowed for generating a proof. + function maxGenerateProofDuration() external view returns (Duration maxGenerateProofDuration_) { + maxGenerateProofDuration_ = MAX_GENERATE_PROOF_DURATION; + } + + /// @notice Returns the maximum duration allowed for detecting a fault. + function maxDetectFaultDuration() external view returns (Duration maxDetectFaultDuration_) { + maxDetectFaultDuration_ = MAX_DETECT_FAULT_DURATION; + } + + /// @notice Returns the WETH contract for holding ETH. + function weth() external view returns (IDelayedWETH weth_) { + weth_ = WETH; + } + + /// @notice Returns the anchor state registry contract. + function anchorStateRegistry() external view returns (IAnchorStateRegistry registry_) { + registry_ = ANCHOR_STATE_REGISTRY; + } + + /// @notice Returns Config contract for holding zk fault proof configuration. + function config() external view returns (ZkFaultProofConfig config_) { + config_ = CONFIG; + } + + /// @notice Returns the chain ID of the L2 network this contract argues about. + function l2ChainId() external view returns (uint256 l2ChainId_) { + l2ChainId_ = L2_CHAIN_ID; + } + + /// @notice Returns the fault prover for the game + function gameWinner() external view returns (address gameWinner_) { + if (status != GameStatus.CHALLENGER_WINS) { + gameWinner_ = address(0); + } else { + gameWinner_ = faultProofProver; + } + } + + /// @notice Returns the length of the `challengedClaimIndexes` array. + function challengedClaimIndexesLen() external view returns (uint256 len_) { + len_ = challengedClaimIndexes.length; + } + + /// @notice Returns the parent game status + /// if the parent game is 0x00...00, then the parent game is from anchor state registry + /// and its status is always DEFENDER_WINS + function parentGameStatus() internal view returns (GameStatus parentGameStatus_) { + if (address(parentGameProxy()) == address(0)) { + parentGameStatus_ = GameStatus.DEFENDER_WINS; + } else { + parentGameStatus_ = parentGameProxy().status(); + } + } + +} diff --git a/packages/contracts-bedrock/src/dispute/ZkFaultProofConfig.sol b/packages/contracts-bedrock/src/dispute/ZkFaultProofConfig.sol new file mode 100644 index 0000000000..2c0d7b6c42 --- /dev/null +++ b/packages/contracts-bedrock/src/dispute/ZkFaultProofConfig.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { SP1VerifierGateway } from "@sp1-contracts/src/SP1VerifierGateway.sol"; +import { ISemver } from "src/universal/ISemver.sol"; + +contract ZkFaultProofConfig is OwnableUpgradeable, ISemver { + + /// @notice Semantic version. + /// @custom:semver 1.0.0 + string public constant version = "1.0.0"; + + /// @notice the block interval of claims proposed by proposers + uint256 public blockDistance; + + /// @notice the hash of the rollup configuration + bytes32 public rollupConfigHash; + + /// @notice The chain ID of the L2 chain. + uint256 public chainId; + + /// @notice The verification key of the aggregation SP1 program. + bytes32 public aggregationVkey; + + /// @notice The 32 byte commitment to the BabyBear representation of the verification key of the range SP1 program. Specifically, + /// this verification is the output of converting the [u32; 8] range BabyBear verification key to a [u8; 32] array. + bytes32 public rangeVkeyCommitment; + + /// @notice The deployed SP1VerifierGateway contract to request proofs from. + SP1VerifierGateway public verifierGateway; + + /// @notice A trusted mapping of block numbers to block hashes. + mapping(uint256 => bytes32) public historicBlockHashes; + + /// @notice Emitted when the aggregation vkey is updated. + /// @param oldVkey The old aggregation vkey. + /// @param newVkey The new aggregation vkey. + event UpdatedAggregationVKey(bytes32 indexed oldVkey, bytes32 indexed newVkey); + + /// @notice Emitted when the range vkey commitment is updated. + /// @param oldRangeVkeyCommitment The old range vkey commitment. + /// @param newRangeVkeyCommitment The new range vkey commitment. + event UpdatedRangeVkeyCommitment(bytes32 indexed oldRangeVkeyCommitment, bytes32 indexed newRangeVkeyCommitment); + + /// @notice Emitted when the verifier gateway is updated. + /// @param oldVerifierGateway The old verifier gateway. + /// @param newVerifierGateway The new verifier gateway. + event UpdatedVerifierGateway(address indexed oldVerifierGateway, address indexed newVerifierGateway); + + /// @notice Emitted when the rollup config hash is updated. + /// @param oldRollupConfigHash The old rollup config hash. + /// @param newRollupConfigHash The new rollup config hash. + event UpdatedRollupConfigHash(bytes32 indexed oldRollupConfigHash, bytes32 indexed newRollupConfigHash); + + constructor() {} + + function initialize ( + address _owner, + uint256 _blockDistance, + uint256 _chainId, + bytes32 _aggregationVkey, + bytes32 _rangeVkeyCommitment, + address _verifierGateway, + bytes32 _rollupConfigHash + ) public initializer { + __Ownable_init(); + transferOwnership(_owner); + + blockDistance = _blockDistance; + chainId = _chainId; + aggregationVkey = _aggregationVkey; + rangeVkeyCommitment = _rangeVkeyCommitment; + verifierGateway = SP1VerifierGateway(_verifierGateway); + rollupConfigHash = _rollupConfigHash; + } + + function updateAggregationVKey(bytes32 _aggregationVKey) external onlyOwner { + aggregationVkey = _aggregationVKey; + emit UpdatedAggregationVKey(aggregationVkey, _aggregationVKey); + } + + function updateRangeVkeyCommitment(bytes32 _rangeVkeyCommitment) external onlyOwner { + rangeVkeyCommitment = _rangeVkeyCommitment; + emit UpdatedRangeVkeyCommitment(rangeVkeyCommitment, _rangeVkeyCommitment); + } + + function updateVerifierGateway(address _verifierGateway) external onlyOwner { + verifierGateway = SP1VerifierGateway(_verifierGateway); + emit UpdatedVerifierGateway(address(verifierGateway), _verifierGateway); + } + + function updateRollupConfigHash(bytes32 _rollupConfigHash) external onlyOwner { + rollupConfigHash = _rollupConfigHash; + emit UpdatedRollupConfigHash(rollupConfigHash, _rollupConfigHash); + } + +} diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGame.sol b/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGame.sol index 7e9389b4dd..bb1a8f241d 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGame.sol +++ b/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGame.sol @@ -45,6 +45,9 @@ interface IDisputeGame is IInitializable { /// @return l1Head_ The parent hash of the L1 block when the dispute game was created. function l1Head() external pure returns (Hash l1Head_); + /// @notice The l2BlockNumber of the disputed output root in the `L2OutputOracle`. + function l2BlockNumber() external view returns (uint256 l2BlockNumber_); + /// @notice Getter for the extra data. /// @dev `clones-with-immutable-args` argument #4 /// @return extraData_ Any extra data supplied to the dispute game contract by the creator. diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGameFactory.sol b/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGameFactory.sol index 5021de04d6..3d555e7455 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGameFactory.sol +++ b/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGameFactory.sol @@ -24,6 +24,10 @@ interface IDisputeGameFactory { /// @param newBond The new bond (in wei) for initializing the game type. event InitBondUpdated(GameType indexed gameType, uint256 indexed newBond); + /// @notice Emitted when a new dispute game is created, index updated + /// @param gameIndex The index of the DisputeGame. + event ZkDisputeGameIndexUpdated(uint256 indexed gameIndex); + /// @notice Information about a dispute game found in a `findLatestGames` search. struct GameSearchResult { uint256 index; @@ -93,6 +97,16 @@ interface IDisputeGameFactory { payable returns (IDisputeGame proxy_); + function createZkFaultDisputeGame( + GameType _gameType, + Claim[] calldata _claims, + uint64 _parentGameIndex, + uint64 _l2BlockNumber, + bytes calldata _extraData + ) external + payable + returns (IDisputeGame proxy_); + /// @notice Sets the implementation contract for a specific `GameType`. /// @dev May only be called by the `owner`. /// @param _gameType The type of the DisputeGame. diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IFaultDisputeGame.sol b/packages/contracts-bedrock/src/dispute/interfaces/IFaultDisputeGame.sol index 43de378ac0..7a79e58070 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IFaultDisputeGame.sol +++ b/packages/contracts-bedrock/src/dispute/interfaces/IFaultDisputeGame.sol @@ -85,9 +85,6 @@ interface IFaultDisputeGame is IDisputeGame { /// @return numRemainingChildren_ The number of children that still need to be checked to resolve the subgame. function getNumToResolve(uint256 _claimIndex) external view returns (uint256 numRemainingChildren_); - /// @notice The l2BlockNumber of the disputed output root in the `L2OutputOracle`. - function l2BlockNumber() external view returns (uint256 l2BlockNumber_); - /// @notice Starting output root and block number of the game. function startingOutputRoot() external view returns (Hash startingRoot_, uint256 l2BlockNumber_); diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IZkFaultDisputeGame.sol b/packages/contracts-bedrock/src/dispute/interfaces/IZkFaultDisputeGame.sol new file mode 100644 index 0000000000..d9cbf381b0 --- /dev/null +++ b/packages/contracts-bedrock/src/dispute/interfaces/IZkFaultDisputeGame.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IDisputeGame } from "./IDisputeGame.sol"; + +import "src/dispute/lib/Types.sol"; + +/// @title IZkFaultDisputeGame +/// @notice The interface for a zk fault proof backed dispute game. +interface IZkFaultDisputeGame is IDisputeGame { + + /// @notice Emitted when a new ChallengeBySignal is created + /// @param _disputeClaimIndex The index of the dispute claim + event ChallengeBySignalCreated(uint256 indexed _disputeClaimIndex); + + // @notice Emitted when a new SubmitProofForSignal is created + /// @param _disputeClaimIndex The index of the dispute claim + event SubmitProofForSignalCreated(uint256 indexed _disputeClaimIndex); + + /// @notice Parameters to initialize the contract. + struct InitParams { + uint256 chainId; + bytes32 aggregationVkey; + bytes32 rangeVkeyCommitment; + address verifierGateway; + bytes32 startingOutputRoot; + address owner; + bytes32 rollupConfigHash; + } + + /// @notice The public values committed to for an OP Succinct aggregation program. + struct AggregationOutputs { + bytes32 l1Head; + bytes32 l2PreRoot; + bytes32 claimRoot; + uint256 claimBlockNum; + uint256 chainId; + bytes32 rollupConfigHash; + bytes32 rangeVkeyCommitment; + } + + /// @notice Challengers use this function to challenge a claim by providing a proof. + function challengeByProof(uint256 _disputeClaimIndex, Claim _expectedClaim, + Claim[] calldata _originalClaims, bytes calldata _proof) external; + + /// @notice Challengers use this function with a bond to signal that they want to challenge a claim. + function challengeBySignal(uint256 _disputeClaimIndex) payable external; + + /// @notice Proposers use this function to submit a proof for a signal. + function submitProofForSignal(uint256 _disputeClaimIndex, Claim[] calldata _originalClaims, + bytes calldata _proof) external; + + /// @notice Challengers user this function to resolve challenge after the generation proof period has passed. + function resolveClaim() external; + + /// @notice Starting output root and block number of the game. + function startingOutputRoot() external view returns (Hash startingRoot_, uint256 l2BlockNumber_); + + /// @notice Only the starting block number of the game. + function startingBlockNumber() external view returns (uint256 startingBlockNumber_); + + /// @notice Only the starting output root of the game. + function startingRootHash() external view returns (Hash startingRootHash_); + + function gameWinner() external view returns (address gameWinner_); +} diff --git a/packages/contracts-bedrock/src/dispute/lib/Errors.sol b/packages/contracts-bedrock/src/dispute/lib/Errors.sol index 9a3237b32d..116f964ae9 100644 --- a/packages/contracts-bedrock/src/dispute/lib/Errors.sol +++ b/packages/contracts-bedrock/src/dispute/lib/Errors.sol @@ -11,6 +11,26 @@ import "src/dispute/lib/LibUDT.sol"; /// @param gameType The unsupported game type. error NoImplementation(GameType gameType); +/// @notice the following errors are related to zk fault dispute games +error NoClaims(); +error InvalidParentGameIndex(); +error InvalidParentGameType(); +error InvalidParentGameStatus(); +error InvalidOriginClaims(); +error InvalidDisputeClaimIndex(); +error InvalidExpectedClaim(); +error ClaimAlreadyChallenged(); +error ClaimNotChallenged(); +error InvalidClaimsLength(); +error GameChallengeSucceeded(); +error ChallengeAlreadyInvalid(); +error UnresolvedChallenges(); +error ParentGameNotResolved(); +error ParentGameIsInvalid(); +error InvalidGameStatus(); +error NoExpiredChallenges(); +error InvalidRewardPercentage(); + /// @notice Thrown when a dispute game that already exists is attempted to be created. /// @param uuid The UUID of the dispute game that already exists. error GameAlreadyExists(Hash uuid); diff --git a/packages/contracts-bedrock/src/dispute/lib/Types.sol b/packages/contracts-bedrock/src/dispute/lib/Types.sol index 8d86e6e253..a7a4bc037d 100644 --- a/packages/contracts-bedrock/src/dispute/lib/Types.sol +++ b/packages/contracts-bedrock/src/dispute/lib/Types.sol @@ -33,6 +33,9 @@ library GameTypes { /// @notice A dispute game type the uses the asterisc VM GameType internal constant ASTERISC = GameType.wrap(2); + /// @notice A dispute game type that uses zk fault proof + GameType internal constant ZKFAULT = GameType.wrap(3); + /// @notice A dispute game type with short game duration for testing withdrawals. /// Not intended for production use. GameType internal constant FAST = GameType.wrap(254); diff --git a/packages/contracts-bedrock/test/dispute/ZkFaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/ZkFaultDisputeGame.t.sol new file mode 100644 index 0000000000..60ee826582 --- /dev/null +++ b/packages/contracts-bedrock/test/dispute/ZkFaultDisputeGame.t.sol @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { Test } from "forge-std/Test.sol"; +import { Vm } from "forge-std/Vm.sol"; + +import { DisputeGameFactory_Init } from "test/dispute/DisputeGameFactory.t.sol"; +import { ZkFaultDisputeGame, IDisputeGame} from "src/dispute/ZkFaultDisputeGame.sol"; +import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; + +import "src/dispute/lib/Types.sol"; +import "src/dispute/lib/Errors.sol"; +import { console2 as console} from "forge-std/Test.sol"; + + +contract ZkFaultDisputeGame_Init is DisputeGameFactory_Init { + /// @dev The type of the game being tested. + GameType internal constant GAME_TYPE = GameType.wrap(3); + Duration internal maxGenerateProofDuration = Duration.wrap(100); + Duration internal maxDetectFaultDuration = Duration.wrap(100); + uint256 internal constant challengerRewardPercentage = 1000; + uint256 internal constant proverRewardPercentage = 5000; + uint256 internal constant percentageDivisor = 10000; + uint256 internal constant challengerBond = 1 ether; + uint256 internal constant proposerBond = 1 ether; + address payable internal constant feeVaultAddress = payable(address(0)); + /// @dev The implementation of the game being tested. + ZkFaultDisputeGame internal gameImpl; + + // address immutable sp1VerifierGateway = 0x51d3960c929B27Db3f041eA3c3aD4fF3c2A121C7; + // bytes32 immutable rollupConfigHash = 0x0b9b35ba1f4265979a10dea49f5501f81b3729b2856165574b1661323678e778; + // bytes32 immutable aggregationVkey = 0x0006a81df67f2d5e48048edd5c051a5be0ef9720a2ce130f12f7021256160e73; + // bytes32 immutable rangeVkeyCommitment = 0x5030974a2d74c494158e4af45836d72e2e0acae55f0f22d73c22bde90c1d6d98; + + // This hash is the block 46562356 hash of BSC testnet + bytes32 immutable l1BlockHash = 0x08837589aa817404bd09e35dd23b9efca9f3021809fa73c861d5eb9c2f41a06d; + /// @dev The extra data passed to the game for initialization. + bytes internal extraData; + + function init() public { + + gameImpl = new ZkFaultDisputeGame({ + _gameType: GAME_TYPE, + _maxGenerateProofDuration: maxGenerateProofDuration, + _maxDetectFaultDuration: maxDetectFaultDuration, + _PROPOSER_BOND: proposerBond, + _CHALLENGER_BOND: challengerBond, + _FEE_VAULT_ADDRESS: feeVaultAddress, + _CHALLENGER_REWARD_PERCENTAGE: challengerRewardPercentage, + _PROVER_REWARD_PERCENTAGE: proverRewardPercentage, + _weth: delayedWeth, + _anchorStateRegistry: anchorStateRegistry, + _config: zkFaultProofConfig, + _l2ChainId: 5611 // opbnb testnet chain id + }); + + // Register the game implementation. + disputeGameFactory.setImplementation(GAME_TYPE, gameImpl); + disputeGameFactory.setInitBond(GAME_TYPE, proposerBond); + } + +} + +/* +*** Use opbnb testnet block 47528965-47528980 to generate zk proof +*** Two Games: +*** 1. Parent Game: 47528965-47528971 +*** 2. Child Game : 47528971-47528980 +*** https://testnet.opbnbscan.com/tx?block=47528956 +*** (47528965, 47528968] proof: 8653506623244edee8024efd99f19b806a8527b78291d9728516364ff8ee36728cd582500c68ca9edb5681591fb4ad0796ed6b983f8796c3b75f8bd5e360d61baa65191a1d63351599586537a801f816ae02b809be4a7b4076df97d36e48390b0ccce0e700dfe1e2be1ce304ed5e91b84f4914ee7b4a9c1f0cbb28b5dbb7ab7e1efaa5e4264449cf0efcf92eeeb51763f76c0232c47853e2d929a6af64754b0d23b90d791931dec9b23c02c0671d872a47d97c4c99050f78a48be7ab65aad140356b850210f70475b1388d112a18d7be71c39fba19efc10310d46f5d3f8ac1d4d2c118fe210d6483910c4ed03381ac18211238fe59f00b57d37c2e7146436dab82c544442c8188f22adba2be9fc50316a4a8888f03493b6e63a917c7742fbc53b764aca00e22058ecc7e4865eeac88007aa1d387a630522d5d1333e64023a76b71c8635d304fa2d0b95e5cb3aab490e215c086103290fd62ae9c3599bf244a15212f45fd0ff30621ac7a1d311695ec737e2fb306332a69fe73d9db110cf73a1d23630caf09c649fea0964cdd2855239803bb0421843eac61073090bbf456f7ad315ab6ea274fa6aa6375cada850c0081f30163c8ba95b433c6f53148a656fb934870ec192052b7c898af154eb1e51fa77361765c0244c285f667ce2c3297e7c67920060f2e823f1e94b5919cb386362ad0b752ba232fe734c982f83cfd6e1234d11dbe5b2cc612f3a915721531c4c5ae327605d73a999c43867d6aae6fc955f5de40b78f0bf6f436f3908dd11de20030558502d0f84be4abaf8058067f4ac439410751b22fdc89bcb749833f5ac39baaa29de3b8a58d660f19e5885775a693596f2adfec0b5f33b5113e56bf1409dc12ffc2698aae41a9515c98e5660de179ba463046951ea791ea82af0135da8f759b1c41b6098e07e9563a0263969472a7abb3f5f38424a44a135fe04846da2609ce77fedea0aa11bef7cbc1d505728df5d010b365890d507299095b152518fd4b00f7315d011aebb65b0893392b49c64d7fb782df9c1f9f04fbb757cfb9e0b72306e8162a45246b5f947093f99f4122ad64fc87d9131e7b6a212d7b8f19479ff8f0dd3edd8b7961783d0fcf009a085dab597504182e11610ca08ee877d657a768584dcbf8a366052a7d418703aa8e4744b14d8b905b1e2552638af0be86f0500d4a8bc563482d22c2b8fd9c4121898fc54415c6773b +*** (47528968, 47528971] proof: 865350662799a0f21694ab08e1b809bc2a4e29bfced51b8c874c15854824230bb654e8090c79aad0adb9933f7bf7a9ae25835dc9f23036eeb91d2f07ac2b9db7bd0da148155d54c08125cd34d56cc3400fdfabe4869aa99f6ea55ce81abbbe960c88ee78290b65352cdfe26641ea2a473cb5034ba117b8a1771ccc60cd9ce7b2efc6eb5422f204c12f24beae751cc14214d485b16bb637416d98a4092726ddc47c7276f6215713698141591b79d3c978753442e6c8019c5e224d8a6d63bf7853a072d8a71871b64bf3f96508dfa74b4dd5416f1c4acd0a9780a907e00feec9b494e300e00fa3e79754d4574a279104e6f652afd4f329a467f5a4bfcd47ee8df07ef87eea19bcced91d1f4ddd4fa71af478c9aba42e219e3b27fbd3fe3dfd2eabbe703dd21a60fbeacccca280425c9cf9793d729db4023c7b5c58e23bc4b923789806fac82d272f19950ae0eccc3bc4e993b4319c15f69b69809ede167d1fb03b3c2e8629126e55f56fedddfbff41eab7b03365f1302afda1921a20b69778c1df0eb2987d0d11d70a01305b249a1806c0ebd2aca0b22b91ceb6a2cb4dc363a97edf216eac1d32e95fb9ab56349e82bccca0ae9ce6b7fb4aadfcab024e08d7585ddd875b782445f35f345683324c9a27db07f2e2fe0a55b4a187272f45aa76838e007ad8ac2ae51a6eb3448a6396d5420c2667f5d2f06e92107e894cb767c800ac1260972728870b1c346be18921b7c8768a71a759a09dcaa9f57a5a484f2b04afee6689570364d3285028eb7deebaac404cdf9ef700dcb7c50e3c7284222929ff881121ff1f419490b47d63403854b628483f51d649ef6ce8cef27a004eebcf04cc6f675c17fdee4478a23a95d8e38f429b2aa2fe24f44e0ad8d87c26a899e3f2757281782e51658e5a3724a2393dd715038bd0876ca78fe0474d730454e99d7f5f33780d1d85c555d6ed79a6cc645bc70968f81bedb7fa9ceea85a96b7229dd417e802d82271fae8f776bc05bb72f73f4e6a0256cacde3f9aa7e3ea3a9665b2c34abf0a80ef13896de49152e936346be7f8dd1536d8fe57878ae8864890323dbe79ff58a29d7f2632dd5a05d9d2589d28e2c0f99b3793533869b355f404f98c12a8bf84f1777426713c0e79ef5772b7416cf144c1a80b915b8da94d6b9dff3ee15dfbe1505a31c41ee062bb6b15a3324cf2fb929735178e59c29f65a16fda981204d4bb3 +*** (47528971, 47528974] proof: 8653506626d363b85c9a5d9ca3ff305f93621efcad98edb97278bf91e3965f79221758e6305469b9f769d5b4f1af6fc36d9b906e125a6bbf0ca8429fc22875f98b12402d2d57ad93b081e9bdab5aea15d86d98077b833fa2527b0d71650520d56db9039f1a62897864cd400ec08b1cfe3f5e55cc1eb28f86f971e296dd4358bcb374683b232e1cc975181cf2d3e224095edf916f35d37922fa7e62a4af5ae564929feec62700f8435383ae63e5a2f4c8996d3578277251f6dd29978a10297b8f5b3b4bcb1e32a58fc3ed0b76092ed72e5c4c51deb31839555798a248e86175457840eb9f14226aa60c4380991dedaaa3026b8165b216e4d6a6b7ff330dbd906c200bc95c0296f130f46504cbd4f6fad92df66564b251482fcd7fefa37949e76d554163131e1f22cc5926ae46d652f545ad95520c0c92f7e36bacb03223fda191ee67d1432fb918dc85f0bc8ee7c6af391e95bbeea6df71161ef4153b74ea9bbbf9fdcf9927dedfca2d5896531b669966af2f351b1ad5b20260d94106b014b58ca37534cf1cd38d37f4765830b84a9b9460104555813decea22fe06d2100078708f5d54a00f6101a1a7c8dea39d6e5d97fa23c88c77919d0ad9999d5e8adf63c2d6a396290833ccd6d754481abbba5fa9c08506679bf9e38361e2ab3cd9e2986e2d9c9ec803c57a7d357535339fcebfac5fe320cf05ed86a61858b880005f4bd68fb5433207e97b0309d712116e80dafcac389670a3cc02d2012191ecaa459c030f96840a12f02fc9b06c9d4c8d4cb5fd3b0e7863f55db5862faf1e9e86040a5028541ac227b7e932794832756cc293efc8ee35feb66f25bcbf1201b3adfe03b4770de18d0c9040ec5e989285f9fe53761496b497a5dfbe822fc57ff24b13d4f1bc11712d0938a63efe5985bcdbfbae3b5f9d5f929655f24271b5523682814faeeb5a5dbf089a02906313ec148e4c91e0ab65e55f9b93e7ee244739d733b685461231eb46152c72f2cdd5dec91bfe510110c97bbf2f0ae9e61f6f4b707c894b6ba1c09224010f54aebd98da4473e93d91aef8aad009699c3307db44a3e465654567eeb2cc0fdb561bc244019b2cd044b1e04cc34f936ae1df9a396c1315f8307c1ed88df218572d4a53493b02286cd9ede0b59a5720b62706d4d7dcfa7b4730f099d314cd1142131a9daa5750e9c85b25f76f4f1b722ae62a457ceeb7e9cccb06d9b7f314 +*** (47528974, 47528977] proof: 86535066010ba13283ec6fa24f4441ad332130b3ed51e587618909945a0b30d0313142b5168d69b26926f3d44b451ccc52f07f3bd7378ecb3c8fcf01d4a22e7f1813104e1c40aa55e8efcd32b7ada67eed9a6f1e140df00f4a71c9f04ab4b816147c9d75053be92c211dd62f3377faa5b043f8c62c13f18ab2109ea3b96fecc02b935990165bd5bf9970add163ed805137592182220f43d3b4c70233d8afe68151acef32154fd1d79b12ba7052316ef203a6a60eb9a7dc38084f220667def722f697ea582e8902c29ab8d5aa34d31e3e5c2c928e7dbb966af95af939b5ca855513def9062ea4ea31a831f552eb73a3e0d765f3e7f21e26f5ef484e6f4746d963dd24744506ff063b48dd2695f5d275fc0071dec47573b65ff291bea5d6e4f98b82d65eb821e1c0ee113cf95a8165518d9edc9f2590dad503bd04d86ca637b2f8742b02710fe99faa8879ca9035a64c6dfd9d5dc0d077ead0076d0844842b517aaac43ccb2257df9c2a90dfe3d580b661d0b3ea4b37ee77ab3138bd2d3d1ea8f026b24de51393de27de7a5715f8cee55ddab20713674191f7982b47399dc23520c7d36b98166bfdeb11347fc275a411d84618353e125631cfa2a7da21142b4a964b705f39222d66a0804574cd0734dfde380c42a1002f6133cca715e2ffe964dacd95839404d05be96d16a2c3a71c9c47de68bd4c378eb2ee0acd79e57e7206e5ee7ec01011ce2bd7e4f58c6777cdec5184688428e947808cac761c09897e0cd8c357766d28f7213e8cb4f7bcde040050617a08215343611fb30bcf05ddbec2c4d9bf6cd5181a3d3819353399cea935c1d6c21afb6925c9e57f945e886d8220183d1d3b562ef5838efead233faccb0665ec13bbb36d27c3497a6d52f8565b61d759315e072d233237a60c0cfafb822a3eeba2d1811aacf7188c6200b90b675bfd8b1fedcd2d32094de1a5c2509c2b67d3196edbedce7f2882d2af3467ce679588edc095231fa286b955f629b54846ee2d3e4a642a48d5715acc4f5462d140b237d4ab9449144e7e1ff6e2d07ffcb3a128a579aba7299e7a396735f7cc6e37e542f645cc3e152023dc3ac23f9d3aeaa048ae89b803ed1849d14efa6363d554931fddb0d61c14a302f38cb419d5cbaaa1145c379856910ec40e8bd67d1b617082988e01c48f270a6672603fd93ffbd92e18beba7145dfd29545db35e834d41a77bf7463da23 +*** (47528977, 47528980] proof: 8653506606e0d6cd8bea8879f9eeecf333ee4f3ba9dfdef37e2f262a2ad58f2c616b54fd24da0dd7f6a9bec26ea8d1e8fb73af4efae6def6c9fb49d6e4900cad51b0c7ba21507acc7a5c1f680299c82812ec0d0f047fbdd2adbf39d3a24632e6dd18cef91e9b318af91f7ce31fba5391af8a9b2d37c897eb336de1e444662f96e5b419f00c3f494ea32d8d631c2bb032e06cfaf840745294f7ad71c4a0e31a0bc46e1b4d073978db4fc150b592c227ad36f651699f038b3558cbf8d195dfc541eb051f5618f209f176b4b0d76f184dc0a26c333ddc88d9705c504c9a91ed0745130d9ad2270f26db6a25bfd1e8d32003511f2e41b6eae60dd5a0aa5d014e137877187bef1fedca4f3bd1b7faa40a159ab2ead1dc44fc19aa97852fef867fd70ea1484c46053a3a86ecf11876875648c6bec10fad0074b47f8f8678f2934c49632e53624714b0dad48d4295c38e69fce3a6b75a17ff294acccf555444483e5ddd6f9ca59201fdf4528a2bec9efae36a4f918410cd142d5745286c22037d1259f41a90e3302f5c41e89a830fe9646dfd510b4324eeca5a574d59cad37043f436f8974c4c250dff99a6217df65135081b4cde00a52d334f575109976061cba4b662d34c08a317563edd216829caa5ff7c3a81beb236c794ca07dc8aff32826f0a6b27da6bd50737a94b0596047bf07f6d68c33f45b97fb90ded3395bcc6979d23392b62ddcb03026aacaf52035a157228c6b04c91c1ec3bc46d5647fede5dd390b95c4095002e8766cc9c205c7464ae68164015d0e962f2837ce827c6e4779e96c8f586c5252d20092677eb6afc9464a9d35ba6859289d06c2416189fd1ec67f9c6bf9195742c34d759fbcab908da1d30620cd30371f69ca2ae2d4fb0b03d518e8d31ed90231eefcc085ea31c34af5cf6a976e81f4d512a3cd71a64524e97949511c7d2e0cc00e99c5d6f93233506c38f85500b6bc1ad9b9acf413f2a232e469b38dd7fd1af00dfa8bd318a22945da83bb4bda7a08c5cd52b3172f5388077c49f3df7471ad01d90204ec131348a2996f628bc7c568a12b1488bf99653b12e6d72365303589a08bfb045101ba8c4b204e58895a5f92a8332811dff8edf51f33a86a549077c8a0ecbf0a099f8a910d906e7c753deb521e1cd9d16a1a030ecf09658ddacd7d12a0f8e7693f8fb76b23af6b1ed25a9373e09b935b298cfb51411170acb7956e3fe +*/ + +contract ZkFaultDisputeGame_Test is ZkFaultDisputeGame_Init { + Claim[] internal parentClaims; + uint64 parentClaimBlockNumber = 47528971; + uint256 parentCreateTimestamp = 1734663238; + uint256 faultGameWithdrawalDelay = 604800; + Claim[] internal childClaims; + uint64 childClaimBlockNumber = 47528980; + uint256 childCreateTimestamp = 1734666838; + /// @dev The `Clone` proxy of the game. + ZkFaultDisputeGame internal parentGameProxy; + ZkFaultDisputeGame internal childGameProxy; + bytes[] parentProofs; + bytes[] childProofs; + + // used address + address parentFaultProver; + address parentValidityProver; + address parentChallenger; + address childValidityProver; + address childFaultProver; + address childChallenger; + + function setUp() public override { + // uint256 id = vm.createFork("https://ancient-responsive-seed.bsc-testnet.quiknode.pro/989bbd40f106bddfe2e7f9c74e49644f28a6451e", 46571120); + // vm.selectFork(id); + super.setUp(); + OutputRoot memory outputRoot = OutputRoot({ + root: Hash.wrap(bytes32(0xeb184356c1e393bf4ab709254068cbb11ed723e45a5ff832b7394636786f52e2)), + l2BlockNumber: 47528965 + }); + AnchorStateRegistry.StartingAnchorRoot[] memory _startingAnchorRoots = new AnchorStateRegistry.StartingAnchorRoot[](1); + _startingAnchorRoots[0] = AnchorStateRegistry.StartingAnchorRoot({ + gameType: GAME_TYPE, + outputRoot: outputRoot + }); + vm.store(address(anchorStateRegistry), + bytes32(0x0000000000000000000000000000000000000000000000000000000000000000), + bytes32(0x0000000000000000000000000000000000000000000000000000000000000000)); + // anchorStateRegistry.setAnchorState(GAME_TYPE, outputRoot); + anchorStateRegistry.initialize(_startingAnchorRoots); + // block 47528965 + parentClaims.push(Claim.wrap(bytes32(0xab7649c15939b35ace364954fd6b3859363784d3751d735c40581ade07808dc5))); + parentClaims.push(Claim.wrap(bytes32(0xb9b9a9fe5b39804effc888ec9acb23b2f3c5772c368a6297966df44e53c2ebae))); + parentProofs.push(hex"8653506623244edee8024efd99f19b806a8527b78291d9728516364ff8ee36728cd582500c68ca9edb5681591fb4ad0796ed6b983f8796c3b75f8bd5e360d61baa65191a1d63351599586537a801f816ae02b809be4a7b4076df97d36e48390b0ccce0e700dfe1e2be1ce304ed5e91b84f4914ee7b4a9c1f0cbb28b5dbb7ab7e1efaa5e4264449cf0efcf92eeeb51763f76c0232c47853e2d929a6af64754b0d23b90d791931dec9b23c02c0671d872a47d97c4c99050f78a48be7ab65aad140356b850210f70475b1388d112a18d7be71c39fba19efc10310d46f5d3f8ac1d4d2c118fe210d6483910c4ed03381ac18211238fe59f00b57d37c2e7146436dab82c544442c8188f22adba2be9fc50316a4a8888f03493b6e63a917c7742fbc53b764aca00e22058ecc7e4865eeac88007aa1d387a630522d5d1333e64023a76b71c8635d304fa2d0b95e5cb3aab490e215c086103290fd62ae9c3599bf244a15212f45fd0ff30621ac7a1d311695ec737e2fb306332a69fe73d9db110cf73a1d23630caf09c649fea0964cdd2855239803bb0421843eac61073090bbf456f7ad315ab6ea274fa6aa6375cada850c0081f30163c8ba95b433c6f53148a656fb934870ec192052b7c898af154eb1e51fa77361765c0244c285f667ce2c3297e7c67920060f2e823f1e94b5919cb386362ad0b752ba232fe734c982f83cfd6e1234d11dbe5b2cc612f3a915721531c4c5ae327605d73a999c43867d6aae6fc955f5de40b78f0bf6f436f3908dd11de20030558502d0f84be4abaf8058067f4ac439410751b22fdc89bcb749833f5ac39baaa29de3b8a58d660f19e5885775a693596f2adfec0b5f33b5113e56bf1409dc12ffc2698aae41a9515c98e5660de179ba463046951ea791ea82af0135da8f759b1c41b6098e07e9563a0263969472a7abb3f5f38424a44a135fe04846da2609ce77fedea0aa11bef7cbc1d505728df5d010b365890d507299095b152518fd4b00f7315d011aebb65b0893392b49c64d7fb782df9c1f9f04fbb757cfb9e0b72306e8162a45246b5f947093f99f4122ad64fc87d9131e7b6a212d7b8f19479ff8f0dd3edd8b7961783d0fcf009a085dab597504182e11610ca08ee877d657a768584dcbf8a366052a7d418703aa8e4744b14d8b905b1e2552638af0be86f0500d4a8bc563482d22c2b8fd9c4121898fc54415c6773b"); + parentProofs.push(hex"865350662799a0f21694ab08e1b809bc2a4e29bfced51b8c874c15854824230bb654e8090c79aad0adb9933f7bf7a9ae25835dc9f23036eeb91d2f07ac2b9db7bd0da148155d54c08125cd34d56cc3400fdfabe4869aa99f6ea55ce81abbbe960c88ee78290b65352cdfe26641ea2a473cb5034ba117b8a1771ccc60cd9ce7b2efc6eb5422f204c12f24beae751cc14214d485b16bb637416d98a4092726ddc47c7276f6215713698141591b79d3c978753442e6c8019c5e224d8a6d63bf7853a072d8a71871b64bf3f96508dfa74b4dd5416f1c4acd0a9780a907e00feec9b494e300e00fa3e79754d4574a279104e6f652afd4f329a467f5a4bfcd47ee8df07ef87eea19bcced91d1f4ddd4fa71af478c9aba42e219e3b27fbd3fe3dfd2eabbe703dd21a60fbeacccca280425c9cf9793d729db4023c7b5c58e23bc4b923789806fac82d272f19950ae0eccc3bc4e993b4319c15f69b69809ede167d1fb03b3c2e8629126e55f56fedddfbff41eab7b03365f1302afda1921a20b69778c1df0eb2987d0d11d70a01305b249a1806c0ebd2aca0b22b91ceb6a2cb4dc363a97edf216eac1d32e95fb9ab56349e82bccca0ae9ce6b7fb4aadfcab024e08d7585ddd875b782445f35f345683324c9a27db07f2e2fe0a55b4a187272f45aa76838e007ad8ac2ae51a6eb3448a6396d5420c2667f5d2f06e92107e894cb767c800ac1260972728870b1c346be18921b7c8768a71a759a09dcaa9f57a5a484f2b04afee6689570364d3285028eb7deebaac404cdf9ef700dcb7c50e3c7284222929ff881121ff1f419490b47d63403854b628483f51d649ef6ce8cef27a004eebcf04cc6f675c17fdee4478a23a95d8e38f429b2aa2fe24f44e0ad8d87c26a899e3f2757281782e51658e5a3724a2393dd715038bd0876ca78fe0474d730454e99d7f5f33780d1d85c555d6ed79a6cc645bc70968f81bedb7fa9ceea85a96b7229dd417e802d82271fae8f776bc05bb72f73f4e6a0256cacde3f9aa7e3ea3a9665b2c34abf0a80ef13896de49152e936346be7f8dd1536d8fe57878ae8864890323dbe79ff58a29d7f2632dd5a05d9d2589d28e2c0f99b3793533869b355f404f98c12a8bf84f1777426713c0e79ef5772b7416cf144c1a80b915b8da94d6b9dff3ee15dfbe1505a31c41ee062bb6b15a3324cf2fb929735178e59c29f65a16fda981204d4bb3"); + + childClaims.push(Claim.wrap(bytes32(0xd6c969048a04b51295d8bab9e4739e6684e0cb2c3ca8cc04ec21fb7b7e55dcc2))); + childClaims.push(Claim.wrap(bytes32(0xc9797bac31da11798dd5b7c1b3b8e00c5b59d19fc830011dcc527532745a23ca))); + childClaims.push(Claim.wrap(bytes32(0xcebef2969b201f8ec59652ca3fc07691251d685d3a6eeed153cd8473b92040e3))); + childProofs.push(hex"8653506626d363b85c9a5d9ca3ff305f93621efcad98edb97278bf91e3965f79221758e6305469b9f769d5b4f1af6fc36d9b906e125a6bbf0ca8429fc22875f98b12402d2d57ad93b081e9bdab5aea15d86d98077b833fa2527b0d71650520d56db9039f1a62897864cd400ec08b1cfe3f5e55cc1eb28f86f971e296dd4358bcb374683b232e1cc975181cf2d3e224095edf916f35d37922fa7e62a4af5ae564929feec62700f8435383ae63e5a2f4c8996d3578277251f6dd29978a10297b8f5b3b4bcb1e32a58fc3ed0b76092ed72e5c4c51deb31839555798a248e86175457840eb9f14226aa60c4380991dedaaa3026b8165b216e4d6a6b7ff330dbd906c200bc95c0296f130f46504cbd4f6fad92df66564b251482fcd7fefa37949e76d554163131e1f22cc5926ae46d652f545ad95520c0c92f7e36bacb03223fda191ee67d1432fb918dc85f0bc8ee7c6af391e95bbeea6df71161ef4153b74ea9bbbf9fdcf9927dedfca2d5896531b669966af2f351b1ad5b20260d94106b014b58ca37534cf1cd38d37f4765830b84a9b9460104555813decea22fe06d2100078708f5d54a00f6101a1a7c8dea39d6e5d97fa23c88c77919d0ad9999d5e8adf63c2d6a396290833ccd6d754481abbba5fa9c08506679bf9e38361e2ab3cd9e2986e2d9c9ec803c57a7d357535339fcebfac5fe320cf05ed86a61858b880005f4bd68fb5433207e97b0309d712116e80dafcac389670a3cc02d2012191ecaa459c030f96840a12f02fc9b06c9d4c8d4cb5fd3b0e7863f55db5862faf1e9e86040a5028541ac227b7e932794832756cc293efc8ee35feb66f25bcbf1201b3adfe03b4770de18d0c9040ec5e989285f9fe53761496b497a5dfbe822fc57ff24b13d4f1bc11712d0938a63efe5985bcdbfbae3b5f9d5f929655f24271b5523682814faeeb5a5dbf089a02906313ec148e4c91e0ab65e55f9b93e7ee244739d733b685461231eb46152c72f2cdd5dec91bfe510110c97bbf2f0ae9e61f6f4b707c894b6ba1c09224010f54aebd98da4473e93d91aef8aad009699c3307db44a3e465654567eeb2cc0fdb561bc244019b2cd044b1e04cc34f936ae1df9a396c1315f8307c1ed88df218572d4a53493b02286cd9ede0b59a5720b62706d4d7dcfa7b4730f099d314cd1142131a9daa5750e9c85b25f76f4f1b722ae62a457ceeb7e9cccb06d9b7f314"); + childProofs.push(hex"86535066010ba13283ec6fa24f4441ad332130b3ed51e587618909945a0b30d0313142b5168d69b26926f3d44b451ccc52f07f3bd7378ecb3c8fcf01d4a22e7f1813104e1c40aa55e8efcd32b7ada67eed9a6f1e140df00f4a71c9f04ab4b816147c9d75053be92c211dd62f3377faa5b043f8c62c13f18ab2109ea3b96fecc02b935990165bd5bf9970add163ed805137592182220f43d3b4c70233d8afe68151acef32154fd1d79b12ba7052316ef203a6a60eb9a7dc38084f220667def722f697ea582e8902c29ab8d5aa34d31e3e5c2c928e7dbb966af95af939b5ca855513def9062ea4ea31a831f552eb73a3e0d765f3e7f21e26f5ef484e6f4746d963dd24744506ff063b48dd2695f5d275fc0071dec47573b65ff291bea5d6e4f98b82d65eb821e1c0ee113cf95a8165518d9edc9f2590dad503bd04d86ca637b2f8742b02710fe99faa8879ca9035a64c6dfd9d5dc0d077ead0076d0844842b517aaac43ccb2257df9c2a90dfe3d580b661d0b3ea4b37ee77ab3138bd2d3d1ea8f026b24de51393de27de7a5715f8cee55ddab20713674191f7982b47399dc23520c7d36b98166bfdeb11347fc275a411d84618353e125631cfa2a7da21142b4a964b705f39222d66a0804574cd0734dfde380c42a1002f6133cca715e2ffe964dacd95839404d05be96d16a2c3a71c9c47de68bd4c378eb2ee0acd79e57e7206e5ee7ec01011ce2bd7e4f58c6777cdec5184688428e947808cac761c09897e0cd8c357766d28f7213e8cb4f7bcde040050617a08215343611fb30bcf05ddbec2c4d9bf6cd5181a3d3819353399cea935c1d6c21afb6925c9e57f945e886d8220183d1d3b562ef5838efead233faccb0665ec13bbb36d27c3497a6d52f8565b61d759315e072d233237a60c0cfafb822a3eeba2d1811aacf7188c6200b90b675bfd8b1fedcd2d32094de1a5c2509c2b67d3196edbedce7f2882d2af3467ce679588edc095231fa286b955f629b54846ee2d3e4a642a48d5715acc4f5462d140b237d4ab9449144e7e1ff6e2d07ffcb3a128a579aba7299e7a396735f7cc6e37e542f645cc3e152023dc3ac23f9d3aeaa048ae89b803ed1849d14efa6363d554931fddb0d61c14a302f38cb419d5cbaaa1145c379856910ec40e8bd67d1b617082988e01c48f270a6672603fd93ffbd92e18beba7145dfd29545db35e834d41a77bf7463da23"); + childProofs.push(hex"8653506606e0d6cd8bea8879f9eeecf333ee4f3ba9dfdef37e2f262a2ad58f2c616b54fd24da0dd7f6a9bec26ea8d1e8fb73af4efae6def6c9fb49d6e4900cad51b0c7ba21507acc7a5c1f680299c82812ec0d0f047fbdd2adbf39d3a24632e6dd18cef91e9b318af91f7ce31fba5391af8a9b2d37c897eb336de1e444662f96e5b419f00c3f494ea32d8d631c2bb032e06cfaf840745294f7ad71c4a0e31a0bc46e1b4d073978db4fc150b592c227ad36f651699f038b3558cbf8d195dfc541eb051f5618f209f176b4b0d76f184dc0a26c333ddc88d9705c504c9a91ed0745130d9ad2270f26db6a25bfd1e8d32003511f2e41b6eae60dd5a0aa5d014e137877187bef1fedca4f3bd1b7faa40a159ab2ead1dc44fc19aa97852fef867fd70ea1484c46053a3a86ecf11876875648c6bec10fad0074b47f8f8678f2934c49632e53624714b0dad48d4295c38e69fce3a6b75a17ff294acccf555444483e5ddd6f9ca59201fdf4528a2bec9efae36a4f918410cd142d5745286c22037d1259f41a90e3302f5c41e89a830fe9646dfd510b4324eeca5a574d59cad37043f436f8974c4c250dff99a6217df65135081b4cde00a52d334f575109976061cba4b662d34c08a317563edd216829caa5ff7c3a81beb236c794ca07dc8aff32826f0a6b27da6bd50737a94b0596047bf07f6d68c33f45b97fb90ded3395bcc6979d23392b62ddcb03026aacaf52035a157228c6b04c91c1ec3bc46d5647fede5dd390b95c4095002e8766cc9c205c7464ae68164015d0e962f2837ce827c6e4779e96c8f586c5252d20092677eb6afc9464a9d35ba6859289d06c2416189fd1ec67f9c6bf9195742c34d759fbcab908da1d30620cd30371f69ca2ae2d4fb0b03d518e8d31ed90231eefcc085ea31c34af5cf6a976e81f4d512a3cd71a64524e97949511c7d2e0cc00e99c5d6f93233506c38f85500b6bc1ad9b9acf413f2a232e469b38dd7fd1af00dfa8bd318a22945da83bb4bda7a08c5cd52b3172f5388077c49f3df7471ad01d90204ec131348a2996f628bc7c568a12b1488bf99653b12e6d72365303589a08bfb045101ba8c4b204e58895a5f92a8332811dff8edf51f33a86a549077c8a0ecbf0a099f8a910d906e7c753deb521e1cd9d16a1a030ecf09658ddacd7d12a0f8e7693f8fb76b23af6b1ed25a9373e09b935b298cfb51411170acb7956e3fe"); + + bytes memory extraData; + super.init(); + // Create a new game + address proposer = makeAddr("proposer"); + vm.deal(proposer, 100 ether); + vm.startPrank(proposer); + vm.setBlockhash(vm.getBlockNumber()-1, l1BlockHash); + vm.warp(parentCreateTimestamp); + parentGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond} ( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + vm.stopPrank(); + // Check immutables + assertEq(parentGameProxy.gameType().raw(), GAME_TYPE.raw()); + assertEq(parentGameProxy.maxGenerateProofDuration().raw(), 100); + assertEq(parentGameProxy.maxDetectFaultDuration().raw(), 100); + assertEq(parentGameProxy.maxClockDuration().raw(), 200); + assertEq(parentGameProxy.CHALLENGER_BOND(), 1 ether); + assertEq(parentGameProxy.PROPOSER_BOND(), 1 ether); + assertEq(parentGameProxy.FEE_VAULT_ADDRESS(), address(0)); + assertEq(parentGameProxy.CHALLENGER_REWARD_PERCENTAGE(), 1000); + assertEq(parentGameProxy.PROVER_REWARD_PERCENTAGE(), 5000); + assertEq(address(parentGameProxy.anchorStateRegistry()), address(anchorStateRegistry)); + assertEq(address(parentGameProxy.config()), address(zkFaultProofConfig)); + // Label the proxy + vm.label(address(parentGameProxy), "ParentZkFaultDisputeGame_Clone"); + + // Create a new game + vm.startPrank(proposer); + vm.warp(childCreateTimestamp); + childGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond} ( + GAME_TYPE, childClaims, 0, childClaimBlockNumber, extraData))); + vm.label(address(childGameProxy), "ChildZkFaultDisputeGame_Clone"); + vm.stopPrank(); + + // used address + parentFaultProver = makeAddr("parentFaultProver"); + parentChallenger = makeAddr("parentChallenger"); + parentValidityProver = makeAddr("parentValidityProver"); + childValidityProver = makeAddr("childValidityProver"); + childFaultProver = makeAddr("childFaultProver"); + childChallenger = makeAddr("challenger"); + + vm.deal(parentFaultProver, 100 ether); + vm.deal(parentChallenger, 100 ether); + vm.deal(parentValidityProver, 100 ether); + vm.deal(childValidityProver, 100 ether); + vm.deal(childFaultProver, 100 ether); + vm.deal(childChallenger, 100 ether); + } + + function testChallengeBySignal() public { + vm.warp(parentCreateTimestamp + 1); + // Challenge the game + uint256 disputeClaimIndex = 1; + parentGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + // Check the game status + assertEq(parentGameProxy.challengedClaimIndexes(0), disputeClaimIndex); + + // proof for block 47528968(agreed claim)-47528971(dispute claim) + bytes memory proof = parentProofs[disputeClaimIndex]; + parentGameProxy.submitProofForSignal(disputeClaimIndex, parentClaims, proof); + // Check the game status + assertEq(parentGameProxy.validityProofProvers(disputeClaimIndex), address(this)); + assertEq(parentGameProxy.invalidChallengeClaims(disputeClaimIndex), true); + assertEq(parentGameProxy.invalidChallengeClaimIndexes(0), disputeClaimIndex); + + vm.warp(childCreateTimestamp + 1); + childGameProxy.challengeBySignal{ value: challengerBond}(disputeClaimIndex); + // Check the game status + assertEq(childGameProxy.challengedClaimIndexes(0), disputeClaimIndex); + + } + + function testChallengeBySignal_FailCases() public { + uint256 disputeClaimIndex = 1; + // Challenge the game with not enough bond + vm.expectRevert(abi.encodeWithSelector(IncorrectBondAmount.selector)); + parentGameProxy.challengeBySignal{ value: challengerBond - 1 }(disputeClaimIndex); + + // Challenge the game after the max detect fault duration + vm.warp(parentCreateTimestamp + parentGameProxy.maxDetectFaultDuration().raw() + 1); + vm.expectRevert(abi.encodeWithSelector(ClockTimeExceeded.selector)); + parentGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + + // Challenge the same dispute claim index again + vm.warp(parentCreateTimestamp + 1); + parentGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + vm.expectRevert(abi.encodeWithSelector(ClaimAlreadyChallenged.selector)); + parentGameProxy.challengeBySignal{ value: challengerBond}(disputeClaimIndex); + + // Challenge the game with invalid claim index + vm.expectRevert(abi.encodeWithSelector(InvalidDisputeClaimIndex.selector)); + parentGameProxy.challengeBySignal{ value: challengerBond }(parentClaims.length); + + // construct invalid parent game + Claim expectedClaim = parentClaims[disputeClaimIndex]; + bytes memory extraData; + parentClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000003)); + ZkFaultDisputeGame invalidParentGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + + ZkFaultDisputeGame invalidChildGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, childClaims, uint64(disputeGameFactory.gameCount()-1), childClaimBlockNumber, extraData))); + // parent game is resolved to CHALLENGER_WINS + vm.startPrank(parentFaultProver); + invalidParentGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, parentProofs[disputeClaimIndex]); + invalidParentGameProxy.resolve(); + vm.stopPrank(); + + // Challenge the game which is already resolved + vm.expectRevert(abi.encodeWithSelector(GameNotInProgress.selector)); + invalidParentGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + + // Challenge the game with invalid parent game + vm.expectRevert(abi.encodeWithSelector(ParentGameIsInvalid.selector)); + invalidChildGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + + } + + function testChallengeByProof() public { + // construct invalid games + uint256 disputeClaimIndex = 1; + Claim expectedClaim = parentClaims[disputeClaimIndex]; + parentClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000001)); + bytes memory extraData; + ZkFaultDisputeGame invalidGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + // Challenge the game + bytes memory proof = parentProofs[disputeClaimIndex]; + vm.prank(msg.sender); + invalidGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, proof); + assertEq(invalidGameProxy.isChallengeSuccess(), true); + assertEq(invalidGameProxy.successfulChallengeIndex(), disputeClaimIndex); + assertEq(invalidGameProxy.faultProofProver(), msg.sender); + + uint64 parentGameIndex = uint64(disputeGameFactory.gameCount()-1); + console.log("parent game index is %d", parentGameIndex); + // create a new child game + ZkFaultDisputeGame invalidChildGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, childClaims, parentGameIndex, childClaimBlockNumber, extraData))); + + // resolve the parent game + invalidGameProxy.resolve(); + assertEq(uint256(invalidGameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + vm.expectRevert(abi.encodeWithSelector(ParentGameIsInvalid.selector)); + invalidChildGameProxy.resolveClaim(); + } + + function testChallengeByProof_Child_FirstClaim() public { + uint256 disputeClaimIndex = 0; + // The valid parent game index which is created in the setup function + uint64 parentGameIndex = 0; + Claim expectedClaim = childClaims[disputeClaimIndex]; + childClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000001)); + ZkFaultDisputeGame invalidChildProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, childClaims, parentGameIndex, childClaimBlockNumber, new bytes(0)))); + // block (47528974, 47528977] proof + bytes memory proof = childProofs[disputeClaimIndex]; + vm.startPrank(childFaultProver); + invalidChildProxy.challengeByProof(disputeClaimIndex, expectedClaim, childClaims, proof); + assertEq(invalidChildProxy.isChallengeSuccess(), true); + assertEq(invalidChildProxy.successfulChallengeIndex(), disputeClaimIndex); + assertEq(invalidChildProxy.faultProofProver(), childFaultProver); + + // another dispute claim index: first challenge by signal, then challenge by proof + uint256 secondDisputeClaimIndex = 2; + Claim secondExpectedClaim = childClaims[secondDisputeClaimIndex]; + childClaims[secondDisputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000001)); + ZkFaultDisputeGame secondInvalidChildProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, childClaims, parentGameIndex, childClaimBlockNumber, extraData))); + secondInvalidChildProxy.challengeBySignal{ value: challengerBond }(secondDisputeClaimIndex); + secondInvalidChildProxy.challengeByProof(secondDisputeClaimIndex, secondExpectedClaim, childClaims, childProofs[secondDisputeClaimIndex]); + assertEq(secondInvalidChildProxy.isChallengeSuccess(), true); + assertEq(secondInvalidChildProxy.successfulChallengeIndex(), secondDisputeClaimIndex); + assertEq(secondInvalidChildProxy.faultProofProver(), childFaultProver); + vm.stopPrank(); + } + + function testChallengeByProof_FailCases() public { + uint256 disputeClaimIndex = 1; + Claim expectedClaim = parentClaims[disputeClaimIndex]; + Claim[] memory invalidClaims = new Claim[](parentClaims.length + 1); + for (uint256 i = 0; i < parentClaims.length; i++) { + invalidClaims[i] = parentClaims[i]; + } + bytes memory extraData; + vm.warp(parentCreateTimestamp + 1); + vm.expectRevert(abi.encodeWithSelector(InvalidClaimsLength.selector)); + parentGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, invalidClaims, parentProofs[disputeClaimIndex]); + + Claim[] memory invalidClaims2 = parentClaims; + invalidClaims2[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000005)); + vm.expectRevert(abi.encodeWithSelector(InvalidOriginClaims.selector)); + parentGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, invalidClaims2, parentProofs[disputeClaimIndex]); + + vm.expectRevert(abi.encodeWithSelector(InvalidDisputeClaimIndex.selector)); + parentGameProxy.challengeByProof(parentClaims.length, expectedClaim, parentClaims, parentProofs[disputeClaimIndex]); + + vm.expectRevert(abi.encodeWithSelector(InvalidExpectedClaim.selector)); + parentGameProxy.challengeByProof(disputeClaimIndex, parentClaims[disputeClaimIndex], parentClaims, parentProofs[disputeClaimIndex]); + + vm.warp(parentCreateTimestamp + parentGameProxy.maxClockDuration().raw() + 1); + vm.expectRevert(abi.encodeWithSelector(ClockTimeExceeded.selector)); + parentGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, parentProofs[disputeClaimIndex]); + + vm.warp(parentCreateTimestamp + 1); + // 0x09bde339 is sig of InvalidProof() + vm.expectRevert(abi.encodeWithSelector(0x09bde339)); + parentGameProxy.challengeByProof(disputeClaimIndex, invalidClaims2[disputeClaimIndex], parentClaims, parentProofs[disputeClaimIndex]); + + // construct invalid parent game + parentClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000005)); + ZkFaultDisputeGame invalidGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + + ZkFaultDisputeGame invalidChildGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, childClaims, uint64(disputeGameFactory.gameCount()-1), childClaimBlockNumber, extraData))); + + vm.startPrank(parentFaultProver); + invalidGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, parentProofs[disputeClaimIndex]); + invalidGameProxy.resolve(); + vm.stopPrank(); + + vm.expectRevert(abi.encodeWithSelector(ParentGameIsInvalid.selector)); + invalidChildGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, childClaims, childProofs[disputeClaimIndex]); + } + + function testResolveClaim() public { + uint256 disputeClaimIndex = 1; + uint256 disputeTimestamp = parentCreateTimestamp + 10; + vm.warp(disputeTimestamp); + vm.prank(msg.sender); + parentGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + + uint256 secondDisputeClaimIndex = 0; + uint256 secondDisputeTimestamp = parentCreateTimestamp + 20; + vm.warp(secondDisputeTimestamp); + parentGameProxy.challengeBySignal{ value: challengerBond }(secondDisputeClaimIndex); + + Duration maxGenerateProofDuration = parentGameProxy.maxGenerateProofDuration(); + vm.warp(disputeTimestamp + maxGenerateProofDuration.raw()); + vm.expectRevert(abi.encodeWithSelector(NoExpiredChallenges.selector)); + parentGameProxy.resolveClaim(); + + vm.warp(disputeTimestamp + maxGenerateProofDuration.raw() + 1); + parentGameProxy.resolveClaim(); + assertEq(parentGameProxy.isChallengeSuccess(), true); + assertEq(parentGameProxy.successfulChallengeIndex(), disputeClaimIndex); + assertEq(parentGameProxy.faultProofProver(), msg.sender); + } + + function testResolveClaim_FailCases() public { + uint256 disputeClaimIndex = 1; + Claim expectedClaim = parentClaims[disputeClaimIndex]; + parentClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000005)); + vm.warp(parentCreateTimestamp); + ZkFaultDisputeGame invalidGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame { value: proposerBond } ( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + + uint256 disputeTimestamp = parentCreateTimestamp + 10; + vm.warp(disputeTimestamp); + vm.prank(msg.sender); + parentGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + + Duration maxGenerateProofDuration = parentGameProxy.maxGenerateProofDuration(); + vm.warp(disputeTimestamp + maxGenerateProofDuration.raw()); + vm.expectRevert(abi.encodeWithSelector(NoExpiredChallenges.selector)); + parentGameProxy.resolveClaim(); + + vm.startPrank(parentFaultProver); + invalidGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, parentProofs[disputeClaimIndex]); + vm.expectRevert(abi.encodeWithSelector(GameChallengeSucceeded.selector)); + invalidGameProxy.resolveClaim(); + vm.stopPrank(); + + invalidGameProxy.resolve(); + vm.expectRevert(abi.encodeWithSelector(GameNotInProgress.selector)); + invalidGameProxy.resolveClaim(); + } + + function testResolve_CHALLENGER_WINS_ByProof() public { + address faultProver = makeAddr("faultProver"); + // construct invalid games + uint256 disputeClaimIndex = 0; + Claim expectedClaim = parentClaims[disputeClaimIndex]; + parentClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000001)); + bytes memory extraData; + vm.warp(parentCreateTimestamp); + ZkFaultDisputeGame invalidGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame{ value: proposerBond }( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + // Challenge the game + // proof for block 47528971(agreed claim)-47528974(dispute claim) + bytes memory proof = parentProofs[disputeClaimIndex]; + vm.deal(faultProver, 100 ether); + vm.startPrank(faultProver); + uint256 proverInitialBalance = faultProver.balance; + uint256 contractBalance = delayedWeth.balanceOf(address(invalidGameProxy)); + assertEq(contractBalance, proposerBond); + invalidGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, proof); + invalidGameProxy.resolve(); + uint256 reward = contractBalance * (proverRewardPercentage + challengerRewardPercentage) / percentageDivisor; + vm.warp(parentCreateTimestamp + faultGameWithdrawalDelay + 1); + invalidGameProxy.claimCredit(faultProver); + uint256 proverAfterBalance = faultProver.balance; + assertEq(proverAfterBalance - proverInitialBalance, reward); + assertEq(uint256(invalidGameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + vm.stopPrank(); + } + + // The challenger and the prover are two different accounts + // two challengers and one prover + function testResolve_CHALLENGER_WINS_BySignalAndProof() public { + address faultProver = makeAddr("faultProver"); + address challenger = makeAddr("challenger"); + address challenger2 = makeAddr("challenger2"); + vm.deal(challenger, 100 ether); + vm.deal(challenger2, 100 ether); + vm.deal(faultProver, 100 ether); + uint256 disputeClaimIndex = 0; + Claim expectedClaim = parentClaims[disputeClaimIndex]; + parentClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000002)); + bytes memory proof = parentProofs[disputeClaimIndex]; + vm.warp(parentCreateTimestamp); + ZkFaultDisputeGame invalidGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame{ value: proposerBond }( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + + vm.startPrank(challenger); + invalidGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + vm.stopPrank(); + + vm.startPrank(challenger2); + uint256 challenger2DisputeIndex = 1; + invalidGameProxy.challengeBySignal{ value: challengerBond }(challenger2DisputeIndex); + vm.stopPrank(); + + vm.startPrank(faultProver); + invalidGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, proof); + vm.stopPrank(); + + uint256 contractBalance = delayedWeth.balanceOf(address(invalidGameProxy)); + assertEq(contractBalance, proposerBond + challengerBond * 2); + + uint256 challengerInitialBalance = challenger.balance; + uint256 challenger2InitialBalance = challenger2.balance; + uint256 proverInitialBalance = faultProver.balance; + invalidGameProxy.resolve(); + vm.warp(parentCreateTimestamp + faultGameWithdrawalDelay + 1); + invalidGameProxy.claimCredit(faultProver); + uint256 proverAfterBalance = faultProver.balance; + assertEq(uint256(invalidGameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + // First refund to challenger and challenger2 + contractBalance = contractBalance - challengerBond * 2; + invalidGameProxy.claimCredit(challenger2); + assertEq(challenger2.balance - challenger2InitialBalance, challengerBond); + // Then calculate the challenger reward and the prover reward + uint256 challengeReward = contractBalance * challengerRewardPercentage / percentageDivisor; + invalidGameProxy.claimCredit(challenger); + assertEq(challenger.balance - challengerInitialBalance, challengeReward + challengerBond); + uint256 proverReward = contractBalance * proverRewardPercentage / percentageDivisor; + assertEq(proverAfterBalance - proverInitialBalance, proverReward); + } + + // Two games: invalid parent game and one valid child game + // The valid child game has validity prover and two challengers + // expect result: + // a. the validity prover doesn't get any reward + // b. the challenger who submit the challenge which is proved invalid by validity prover get slashed; + // c. the challenger who submit the challenge which isn't proved invalid by validity prover get refund; + function testResolve_CHALLENGER_WINS_ParentGameIsInvalid_S1() public { + // first construct an invalid parent game and an invalid child game + uint256 disputeClaimIndex = 0; + Claim expectedClaim = parentClaims[disputeClaimIndex]; + parentClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000002)); + bytes memory proof = parentProofs[disputeClaimIndex]; + ZkFaultDisputeGame invalidGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame{ value: proposerBond }( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + + uint64 currentParentGameIndex = uint64(disputeGameFactory.gameCount() - 1); + // construct a valid child game whose parent game is invalid + ZkFaultDisputeGame childGameProxy2 = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame{ value: proposerBond }( + GAME_TYPE, childClaims, currentParentGameIndex, childClaimBlockNumber, extraData))); + + // Challenge the parent game + vm.startPrank(parentFaultProver); + invalidGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, proof); + vm.stopPrank(); + + vm.startPrank(childChallenger); + uint256 challengerInitialBalance = childChallenger.balance; + childGameProxy2.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + childGameProxy2.challengeBySignal{ value: challengerBond }(disputeClaimIndex + 1); + vm.stopPrank(); + + vm.startPrank(childValidityProver); + uint256 validityProverInitialBalance = childValidityProver.balance; + proof = childProofs[disputeClaimIndex+1]; + childGameProxy2.submitProofForSignal(disputeClaimIndex + 1, childClaims, proof); + vm.stopPrank(); + + uint256 proverInitialBalance = parentFaultProver.balance; + vm.warp(parentCreateTimestamp); + invalidGameProxy.resolve(); + assertEq(uint256(invalidGameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + vm.warp(parentCreateTimestamp + faultGameWithdrawalDelay + 1); + invalidGameProxy.claimCredit(parentFaultProver); + uint256 proverAfterBalance = parentFaultProver.balance; + assertEq(proverAfterBalance - proverInitialBalance, proposerBond * (proverRewardPercentage + challengerRewardPercentage) / percentageDivisor); + proverInitialBalance = proverAfterBalance; + + vm.warp(parentCreateTimestamp); + childGameProxy2.resolve(); + vm.warp(parentCreateTimestamp + faultGameWithdrawalDelay + 1); + + vm.expectRevert(abi.encodeWithSelector(NoCreditToClaim.selector)); + childGameProxy2.claimCredit(childValidityProver); + childGameProxy2.claimCredit(childChallenger); + childGameProxy2.claimCredit(parentFaultProver); + assertEq(uint256(childGameProxy2.status()), uint256(GameStatus.CHALLENGER_WINS)); + uint256 challengerAfterBalance = childChallenger.balance; + uint256 validityProverAfterBalance = childValidityProver.balance; + proverAfterBalance = parentFaultProver.balance; + // the validity prover can't get the reward because of the parent game is invalid + assertEq(validityProverAfterBalance, validityProverInitialBalance); + // the challenger can only get one bond refund, because one of the two challenges is invalid + assertEq(challengerInitialBalance - challengerAfterBalance, challengerBond); + // the total reward is proposerBond + challengerBond + assertEq(proverAfterBalance - proverInitialBalance, (proposerBond + challengerBond) * proverRewardPercentage / percentageDivisor); + } + + // Two games: invalid parent game and one inValid child game + // The invalid child game has two challengers, one child fault prover and one validity prover + // expect result: + // a. the child fault prover doesn't get any reward + // b. the challenger who submit the challenger which is proved valid by fault prover get reward; + // c. the child validity prover doesn't get any reward + // d. the challenger who submit the challenge which is proved invalid by validity prover get slashed; + function testResolve_CHALLENGER_WINS_ParentGameIsInvalid_S2() public { + // first construct an invalid parent game and an invalid child game + uint256 disputeClaimIndex = 0; + Claim expectedClaim = parentClaims[disputeClaimIndex]; + parentClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000002)); + bytes memory proof = parentProofs[disputeClaimIndex]; + ZkFaultDisputeGame invalidGameProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame{ value: proposerBond }( + GAME_TYPE, parentClaims, type(uint64).max, parentClaimBlockNumber, extraData))); + + Claim childExpectedClaim = childClaims[disputeClaimIndex]; + childClaims[disputeClaimIndex] = Claim.wrap(bytes32(0x0000000000000000000000000000000000000000000000000000000000000002)); + // construct a valid child game whose parent game is invalid + ZkFaultDisputeGame invalidChildProxy = ZkFaultDisputeGame(address(disputeGameFactory.createZkFaultDisputeGame{ value: proposerBond }( + GAME_TYPE, childClaims, uint64(disputeGameFactory.gameCount() - 1), childClaimBlockNumber, extraData))); + + // Challenge the parent game + + vm.startPrank(parentChallenger); + uint256 parentChallengerInitialBalance = parentChallenger.balance; + invalidGameProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + uint256 afterBalance = parentChallenger.balance; + assertEq(parentChallengerInitialBalance - afterBalance, challengerBond); + vm.stopPrank(); + + vm.startPrank(parentFaultProver); + uint256 parentFaultProverInitialBalance = parentFaultProver.balance; + invalidGameProxy.challengeByProof(disputeClaimIndex, expectedClaim, parentClaims, proof); + assertEq(invalidGameProxy.isChallengeSuccess(), true); + vm.stopPrank(); + + vm.startPrank(childChallenger); + uint256 childChallengerInitialBalance = childChallenger.balance; + invalidChildProxy.challengeBySignal{ value: challengerBond }(2); + invalidChildProxy.challengeBySignal{ value: challengerBond }(disputeClaimIndex); + vm.stopPrank(); + proof = childProofs[2]; + + vm.startPrank(childValidityProver); + uint256 childValidityProverInitialBalance = childValidityProver.balance; + invalidChildProxy.submitProofForSignal(2, childClaims, proof); + assertEq(invalidChildProxy.invalidChallengeClaims(2), true); + vm.stopPrank(); + + vm.startPrank(childFaultProver); + uint256 childFaultProverInitialBalance = childFaultProver.balance; + proof = childProofs[disputeClaimIndex]; + invalidChildProxy.challengeByProof(disputeClaimIndex, childExpectedClaim, childClaims, proof); + assertEq(invalidChildProxy.isChallengeSuccess(), true); + vm.stopPrank(); + + vm.warp(parentCreateTimestamp); + invalidGameProxy.resolve(); + vm.warp(parentCreateTimestamp + faultGameWithdrawalDelay + 1); + assertEq(uint256(invalidGameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + + invalidGameProxy.claimCredit(parentFaultProver); + afterBalance = parentFaultProver.balance; + assertEq(afterBalance - parentFaultProverInitialBalance, proposerBond * proverRewardPercentage / percentageDivisor); + parentFaultProverInitialBalance = afterBalance; + + invalidGameProxy.claimCredit(parentChallenger); + afterBalance = parentChallenger.balance; + assertEq(afterBalance - parentChallengerInitialBalance, proposerBond * challengerRewardPercentage / percentageDivisor); + + vm.warp(parentCreateTimestamp); + invalidChildProxy.resolve(); + vm.warp(parentCreateTimestamp + faultGameWithdrawalDelay + 1); + assertEq(uint256(invalidChildProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + + vm.expectRevert(abi.encodeWithSelector(NoCreditToClaim.selector)); + invalidChildProxy.claimCredit(childFaultProver); + afterBalance = childFaultProver.balance; + assertEq(afterBalance - childFaultProverInitialBalance, 0); + + vm.expectRevert(abi.encodeWithSelector(NoCreditToClaim.selector)); + invalidChildProxy.claimCredit(childValidityProver); + afterBalance = childValidityProver.balance; + assertEq(afterBalance - childValidityProverInitialBalance, 0); + + invalidChildProxy.claimCredit(childChallenger); + afterBalance = childChallenger.balance; + assertEq(childChallengerInitialBalance - afterBalance, challengerBond); + + invalidChildProxy.claimCredit(parentFaultProver); + afterBalance = parentFaultProver.balance; + assertEq(afterBalance - parentFaultProverInitialBalance, (proposerBond + challengerBond) * proverRewardPercentage / percentageDivisor); + } + + // + function testResolve_DEFENDER_WINS() public { + vm.startPrank(parentChallenger); + vm.warp(parentCreateTimestamp + 1); + uint256 parentChallengerInitialBalance = parentChallenger.balance; + for (uint256 i = 0; i < parentClaims.length; i++) { + parentGameProxy.challengeBySignal{ value: challengerBond }(i); + } + vm.stopPrank(); + vm.startPrank(parentValidityProver); + uint256 validityProverInitialBalance = parentValidityProver.balance; + for (uint256 i = 0; i < parentClaims.length; i++) { + parentGameProxy.submitProofForSignal(i, parentClaims, parentProofs[i]); + } + + uint256 proposerInitialBalance = parentGameProxy.gameCreator().balance; + + assertEq(delayedWeth.balanceOf(address(parentGameProxy)), proposerBond + challengerBond * parentClaims.length); + vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(ClockNotExpired.selector)); + parentGameProxy.resolve(); + vm.warp(parentCreateTimestamp + parentGameProxy.maxClockDuration().raw() + 1); + parentGameProxy.resolve(); + assertEq(uint256(parentGameProxy.status()), uint256(GameStatus.DEFENDER_WINS)); + + vm.warp(parentCreateTimestamp + parentGameProxy.maxClockDuration().raw() + faultGameWithdrawalDelay + 2); + vm.expectRevert(abi.encodeWithSelector(NoCreditToClaim.selector)); + parentGameProxy.claimCredit(parentChallenger); + uint256 parentChallengerAfterBalance = parentChallenger.balance; + assertEq(parentChallengerInitialBalance - parentChallengerAfterBalance, challengerBond * parentClaims.length); + + parentGameProxy.claimCredit(parentGameProxy.gameCreator()); + uint256 proposerAfterBalance = parentGameProxy.gameCreator().balance; + assertEq(proposerAfterBalance - proposerInitialBalance, proposerBond); + + parentGameProxy.claimCredit(parentValidityProver); + uint256 validityProverAfterBalance = parentValidityProver.balance; + assertEq(validityProverAfterBalance - validityProverInitialBalance, challengerBond * parentClaims.length * proverRewardPercentage / percentageDivisor); + } + +} diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 9844e6ddd7..e8da765a39 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -23,6 +23,7 @@ import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; +import { ZkFaultProofConfig } from "src/dispute/ZkFaultProofConfig.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; import { DeployConfig } from "scripts/DeployConfig.s.sol"; import { Deploy } from "scripts/Deploy.s.sol"; @@ -74,6 +75,7 @@ contract Setup { SuperchainConfig superchainConfig; DataAvailabilityChallenge dataAvailabilityChallenge; AnchorStateRegistry anchorStateRegistry; + ZkFaultProofConfig zkFaultProofConfig; L2CrossDomainMessenger l2CrossDomainMessenger = L2CrossDomainMessenger(payable(Predeploys.L2_CROSS_DOMAIN_MESSENGER)); @@ -138,6 +140,7 @@ contract Setup { protocolVersions = ProtocolVersions(deploy.mustGetAddress("ProtocolVersionsProxy")); superchainConfig = SuperchainConfig(deploy.mustGetAddress("SuperchainConfigProxy")); anchorStateRegistry = AnchorStateRegistry(deploy.mustGetAddress("AnchorStateRegistryProxy")); + zkFaultProofConfig = ZkFaultProofConfig(deploy.mustGetAddress("ZkFaultProofConfigProxy")); vm.label(address(l2OutputOracle), "L2OutputOracle"); vm.label(deploy.mustGetAddress("L2OutputOracleProxy"), "L2OutputOracleProxy");