diff --git a/.gitignore b/.gitignore index 73a2e81b4..afbd7e903 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,8 @@ deploy-config/getting-started.json # IDE /.idea/ +# Foundry +foundry.lock + # OS .DS_Store diff --git a/Makefile b/Makefile index 2372124ca..9fdfbdbe9 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,10 @@ deps: clean-lib github.com/OpenZeppelin/openzeppelin-contracts-upgradeable@0a2cb9a445c365870ed7a8ab461b12acf3e27d63 \ github.com/transmissions11/solmate@8f9b23f8838670afda0fd8983f2c41e8037ae6bc \ github.com/safe-global/safe-contracts@bf943f80fec5ac647159d26161446ac5d716a294 \ - github.com/Vectorized/solady@502cc1ea718e6fa73b380635ee0868b0740595f0 + github.com/Vectorized/solady@502cc1ea718e6fa73b380635ee0868b0740595f0 \ + github.com/base/nitro-validator@0f006d2075637dd9640e530c4a7065f5c8bb2132 \ + github.com/base/op-enclave@a2d5398f04c3a8e4df929d58ee638ba4a037bfec \ + github.com/automata-network/aws-nitro-enclave-attestation@10fe7be8d9840490f5655e4b2a2aba3a95ec88c1 forge install --no-git \ github.com/ethereum-optimism/superchain-registry@84bce73573f130008d84bae6e924163bab589a11 @# openzeppelin-contracts-v5 and solady-v0.0.245 use the same orgs as their diff --git a/deploy-config/hardhat.json b/deploy-config/hardhat.json index 8a0a3d4eb..c1c6714ea 100644 --- a/deploy-config/hardhat.json +++ b/deploy-config/hardhat.json @@ -72,5 +72,15 @@ "gasPayingTokenName": "", "gasPayingTokenSymbol": "", "nativeAssetLiquidityAmount": null, - "liquidityControllerOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" + "liquidityControllerOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "multiproofConfigHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "multiproofGameType": 621, + "teeProposer": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "nitroEnclaveVerifier": "0x0000000000000000000000000000000000000000", + "multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001", + "multiproofGenesisBlockNumber": 0, + "multiproofBlockInterval": 100, + "multiproofIntermediateBlockInterval": 10, + "multiproofProofThreshold": 1 } diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index f5d209036..9a85de7a2 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia.json @@ -64,5 +64,15 @@ "gasPayingTokenName": "", "gasPayingTokenSymbol": "", "nativeAssetLiquidityAmount": null, - "liquidityControllerOwner": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301" + "liquidityControllerOwner": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "multiproofConfigHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360", + "multiproofGameType": 621, + "teeProposer": "0xb28E6890Cffa969dA9851c1BF1Ac34B76EbFEe98", + "nitroEnclaveVerifier": "0x0000000000000000000000000000000000000000", + "multiproofGenesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", + "multiproofGenesisBlockNumber": 37223829, + "multiproofBlockInterval": 100, + "multiproofIntermediateBlockInterval": 10, + "multiproofProofThreshold": 1 } diff --git a/deployments/11155111-dev-no-nitro.json b/deployments/11155111-dev-no-nitro.json new file mode 100644 index 000000000..f98679b2f --- /dev/null +++ b/deployments/11155111-dev-no-nitro.json @@ -0,0 +1 @@ +{"SystemConfigGlobal":"0xf8293c0f3a36A746B559a1a51870339B20F60945","TEEVerifier":"0x82453dA61B397EE366fB2129502de9c216480aB6","DisputeGameFactory":"0xfEa8Cb315F75d838b6c76ae336a9255f81df0D50","AnchorStateRegistry":"0x556BD554854504BE2F2023F6531D25eF6f6Fe77D","DelayedWETH":"0xb1FB7f05711d2270cD658448562A29E8c5C95E9E","AggregateVerifier":"0xeeF18F1640fa79f919799B5D629908909e715f97"} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 058cad23c..44da9da93 100644 --- a/foundry.toml +++ b/foundry.toml @@ -21,6 +21,7 @@ additional_compiler_profiles = [ { name = "dispute", optimizer_runs = 5000 }, ] compilation_restrictions = [ + { paths = "src/dispute/DisputeGameFactory.sol", optimizer_runs = 5000 }, { paths = "src/dispute/FaultDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/dispute/v2/FaultDisputeGameV2.sol", optimizer_runs = 5000 }, { paths = "src/dispute/PermissionedDisputeGame.sol", optimizer_runs = 5000 }, @@ -29,7 +30,15 @@ compilation_restrictions = [ { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 5000 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, - { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 5000 } + { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 5000 }, + { paths = "src/L1/SystemConfig.sol", optimizer_runs = 5000 }, + { paths = "src/universal/OptimismMintableERC20Factory.sol", optimizer_runs = 5000 }, + { paths = "src/dispute/AnchorStateRegistry.sol", optimizer_runs = 5000 }, + { paths = "src/dispute/DelayedWETH.sol", optimizer_runs = 5000 }, + { paths = "src/universal/ProxyAdmin.sol", optimizer_runs = 5000 }, + { paths = "src/universal/Proxy.sol", optimizer_runs = 5000 }, + { paths = "src/L2/OptimismMintableERC721.sol", optimizer_runs = 5000 }, + { paths = "src/L2/OptimismMintableERC721Factory.sol", optimizer_runs = 5000 }, ] extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] @@ -43,6 +52,7 @@ remappings = [ '@openzeppelin/contracts-v5/=lib/openzeppelin-contracts-v5/contracts', '@rari-capital/solmate/=lib/solmate', '@lib-keccak/=lib/lib-keccak/contracts/lib', + 'solady/=lib/solady/src/', '@solady/=lib/solady/src', '@solady-v0.0.245/=lib/solady-v0.0.245/src', 'forge-std/=lib/forge-std/src', @@ -153,6 +163,7 @@ additional_compiler_profiles = [ { name = "dispute", optimizer_runs = 0 }, ] compilation_restrictions = [ + { paths = "src/dispute/DisputeGameFactory.sol", optimizer_runs = 0 }, { paths = "src/dispute/FaultDisputeGame.sol", optimizer_runs = 0 }, { paths = "src/dispute/v2/FaultDisputeGameV2.sol", optimizer_runs = 0 }, { paths = "src/dispute/PermissionedDisputeGame.sol", optimizer_runs = 0 }, @@ -162,6 +173,14 @@ compilation_restrictions = [ { paths = "src/L1/OPContractsManagerStandardValidator.sol", optimizer_runs = 0 }, { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, { paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 0 }, + { paths = "src/L1/SystemConfig.sol", optimizer_runs = 0 }, + { paths = "src/universal/OptimismMintableERC20Factory.sol", optimizer_runs = 0 }, + { paths = "src/dispute/AnchorStateRegistry.sol", optimizer_runs = 0 }, + { paths = "src/dispute/DelayedWETH.sol", optimizer_runs = 0 }, + { paths = "src/universal/ProxyAdmin.sol", optimizer_runs = 0 }, + { paths = "src/universal/Proxy.sol", optimizer_runs = 0 }, + { paths = "src/L2/OptimismMintableERC721.sol", optimizer_runs = 0 }, + { paths = "src/L2/OptimismMintableERC721Factory.sol", optimizer_runs = 0 }, ] ################################################################ diff --git a/interfaces/dispute/IDisputeGameFactory.sol b/interfaces/dispute/IDisputeGameFactory.sol index da99c869a..91cc2ce89 100644 --- a/interfaces/dispute/IDisputeGameFactory.sol +++ b/interfaces/dispute/IDisputeGameFactory.sol @@ -34,6 +34,15 @@ interface IDisputeGameFactory is IProxyAdminOwnedBase, IReinitializableBase { external payable returns (IDisputeGame proxy_); + function createWithInitData( + GameType _gameType, + Claim _rootClaim, + bytes memory _extraData, + bytes memory initData + ) + external + payable + returns (IDisputeGame proxy_); function findLatestGames( GameType _gameType, uint256 _start, diff --git a/interfaces/dispute/IInitializable.sol b/interfaces/dispute/IInitializable.sol index a3bb74b82..c66ec9f82 100644 --- a/interfaces/dispute/IInitializable.sol +++ b/interfaces/dispute/IInitializable.sol @@ -3,4 +3,5 @@ pragma solidity ^0.8.0; interface IInitializable { function initialize() external payable; + function initializeWithInitData(bytes calldata initData) external payable; } diff --git a/interfaces/multiproof/IVerifier.sol b/interfaces/multiproof/IVerifier.sol new file mode 100644 index 000000000..d6a6b8745 --- /dev/null +++ b/interfaces/multiproof/IVerifier.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IVerifier { + function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view returns (bool); +} diff --git a/scripts/autogen/generate-semver-lock/main.go b/scripts/autogen/generate-semver-lock/main.go index 9fb9f2103..44eb9d5ce 100644 --- a/scripts/autogen/generate-semver-lock/main.go +++ b/scripts/autogen/generate-semver-lock/main.go @@ -81,6 +81,16 @@ func processFile(file string) (*SemverLockResult, []error) { sourceFilePath = path contractName = name contractKey = sourceFilePath + ":" + name + if strings.HasSuffix(file, ".dispute.json") { + // We have an additional compiler profile called "dispute". + // This can produce different bytecode for certain contracts + // and the output will contain 2 jsons: .sol and + // .dispute.sol. These both produce the same contractKey + // since the CompilationTarget is the same. However, this leads to + // non-determinstic initCode hashes. Here, we make the contractKey + // unique thus guranteeing deterministic hashes. + contractKey += ":dispute" + } break } diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol index 1b029609f..77d2ddd47 100644 --- a/scripts/deploy/Deploy.s.sol +++ b/scripts/deploy/Deploy.s.sol @@ -283,6 +283,14 @@ contract Deploy is Deployer { faultGameV2SplitDepth: cfg.faultGameV2SplitDepth(), faultGameV2ClockExtension: cfg.faultGameV2ClockExtension(), faultGameV2MaxClockDuration: cfg.faultGameV2MaxClockDuration(), + teeImageHash: cfg.teeImageHash(), + multiproofConfigHash: cfg.multiproofConfigHash(), + multiproofGameType: cfg.multiproofGameType(), + nitroEnclaveVerifier: cfg.nitroEnclaveVerifier(), + l2ChainID: cfg.l2ChainID(), + multiproofBlockInterval: cfg.multiproofBlockInterval(), + multiproofIntermediateBlockInterval: cfg.multiproofIntermediateBlockInterval(), + multiproofProofThreshold: cfg.multiproofProofThreshold(), protocolVersionsProxy: IProtocolVersions(artifacts.mustGetAddress("ProtocolVersionsProxy")), superchainConfigProxy: superchainConfigProxy, superchainProxyAdmin: superchainProxyAdmin, @@ -300,6 +308,8 @@ contract Deploy is Deployer { artifacts.save("DelayedWETHImpl", address(dio.delayedWETHImpl)); artifacts.save("PreimageOracle", address(dio.preimageOracleSingleton)); artifacts.save("PermissionedDisputeGame", address(dio.permissionedDisputeGameV2Impl)); + artifacts.save("AggregateVerifier", address(dio.aggregateVerifierImpl)); + artifacts.save("SystemConfigGlobal", address(dio.systemConfigGlobalImpl)); // Get a contract set from the implementation addresses which were just deployed. Types.ContractSet memory impls = ChainAssertions.dioToContractSet(dio); diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index c1d9694c7..085194225 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -93,6 +93,18 @@ contract DeployConfig is Script { uint256 public faultGameV2ClockExtension; uint256 public faultGameV2MaxClockDuration; + // Multiproof Configuration + bytes32 public teeImageHash; + bytes32 public multiproofConfigHash; + uint256 public multiproofGameType; + address public teeProposer; + address public nitroEnclaveVerifier; + bytes32 public multiproofGenesisOutputRoot; + uint256 public multiproofGenesisBlockNumber; + uint256 public multiproofBlockInterval; + uint256 public multiproofIntermediateBlockInterval; + uint256 public multiproofProofThreshold; + bool public useInterop; bool public useUpgradedFork; bytes32 public devFeatureBitmap; @@ -192,6 +204,16 @@ contract DeployConfig is Script { faultGameV2SplitDepth = _readOr(_json, "$.faultGameV2SplitDepth", 30); faultGameV2ClockExtension = _readOr(_json, "$.faultGameV2ClockExtension", 10800); faultGameV2MaxClockDuration = _readOr(_json, "$.faultGameV2MaxClockDuration", 302400); + teeImageHash = bytes32(_readOr(_json, "$.teeImageHash", 0)); + multiproofConfigHash = bytes32(_readOr(_json, "$.multiproofConfigHash", 0)); + multiproofGameType = _readOr(_json, "$.multiproofGameType", 621); + teeProposer = _readOr(_json, "$.teeProposer", finalSystemOwner); + nitroEnclaveVerifier = stdJson.readAddress(_json, "$.nitroEnclaveVerifier"); + multiproofGenesisOutputRoot = bytes32(_readOr(_json, "$.multiproofGenesisOutputRoot", uint256(1))); + multiproofGenesisBlockNumber = _readOr(_json, "$.multiproofGenesisBlockNumber", 0); + multiproofBlockInterval = _readOr(_json, "$.multiproofBlockInterval", 100); + multiproofIntermediateBlockInterval = _readOr(_json, "$.multiproofIntermediateBlockInterval", 10); + multiproofProofThreshold = _readOr(_json, "$.multiproofProofThreshold", 1); } function fork() public view returns (Fork fork_) { diff --git a/scripts/deploy/DeployImplementations.s.sol b/scripts/deploy/DeployImplementations.s.sol index 7a45a2152..a4676f0d2 100644 --- a/scripts/deploy/DeployImplementations.s.sol +++ b/scripts/deploy/DeployImplementations.s.sol @@ -39,10 +39,19 @@ import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; import { ChainAssertions } from "scripts/deploy/ChainAssertions.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { GameType } from "src/dispute/lib/Types.sol"; contract DeployImplementations is Script { struct Input { @@ -58,6 +67,15 @@ contract DeployImplementations is Script { uint256 faultGameV2SplitDepth; uint256 faultGameV2ClockExtension; uint256 faultGameV2MaxClockDuration; + // Multiproof parameters + bytes32 teeImageHash; + bytes32 multiproofConfigHash; + uint256 multiproofGameType; + address nitroEnclaveVerifier; + uint256 l2ChainID; + uint256 multiproofBlockInterval; + uint256 multiproofIntermediateBlockInterval; + uint256 multiproofProofThreshold; // Outputs from DeploySuperchain.s.sol. ISuperchainConfig superchainConfigProxy; IProtocolVersions protocolVersionsProxy; @@ -95,6 +113,8 @@ contract DeployImplementations is Script { IPermissionedDisputeGameV2 permissionedDisputeGameV2Impl; ISuperFaultDisputeGame superFaultDisputeGameImpl; ISuperPermissionedDisputeGame superPermissionedDisputeGameImpl; + IVerifier aggregateVerifierImpl; + SystemConfigGlobal systemConfigGlobalImpl; } bytes32 internal _salt = DeployUtils.DEFAULT_SALT; @@ -128,6 +148,7 @@ contract DeployImplementations is Script { deployAnchorStateRegistryImpl(_input, output_); deployFaultDisputeGameV2Impl(_input, output_); deployPermissionedDisputeGameV2Impl(_input, output_); + deployAggregateVerifierImpl(_input, output_); if (DevFeatures.isDevFeatureEnabled(_input.devFeatureBitmap, DevFeatures.OPTIMISM_PORTAL_INTEROP)) { deploySuperFaultDisputeGameImpl(_input, output_); deploySuperPermissionedDisputeGameImpl(_input, output_); @@ -695,6 +716,38 @@ contract DeployImplementations is Script { _output.opcmStandardValidator = impl; } + function deployAggregateVerifierImpl(Input memory _input, Output memory _output) private { + address zkVerifier = address(new MockVerifier()); + + address teeVerifierImpl; + { + SystemConfigGlobal scgImpl = new SystemConfigGlobal(INitroEnclaveVerifier(_input.nitroEnclaveVerifier)); + vm.label(address(scgImpl), "SystemConfigGlobalImpl"); + _output.systemConfigGlobalImpl = scgImpl; + teeVerifierImpl = address(new TEEVerifier(scgImpl)); + } + + _output.aggregateVerifierImpl = IVerifier( + address( + new AggregateVerifier( + GameType.wrap(uint32(_input.multiproofGameType)), + _output.anchorStateRegistryImpl, + _output.delayedWETHImpl, + IVerifier(teeVerifierImpl), + IVerifier(zkVerifier), + _input.teeImageHash, + bytes32(0), + _input.multiproofConfigHash, + _input.l2ChainID, + _input.multiproofBlockInterval, + _input.multiproofIntermediateBlockInterval, + _input.multiproofProofThreshold + ) + ) + ); + vm.label(address(_output.aggregateVerifierImpl), "AggregateVerifierImpl"); + } + function assertValidInput(Input memory _input) private pure { // Validate V2 game depth parameters are sensible require( diff --git a/scripts/libraries/ForgeArtifacts.sol b/scripts/libraries/ForgeArtifacts.sol index f49faec8a..a15f799d7 100644 --- a/scripts/libraries/ForgeArtifacts.sol +++ b/scripts/libraries/ForgeArtifacts.sol @@ -152,10 +152,14 @@ library ForgeArtifacts { /// @notice Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract. function getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) { - // FaultDisputeGame and PermissionedDisputeGame use a different name for the initialized storage slot. + // FaultDisputeGame, PermissionedDisputeGame, and AggregateVerifier use a different name for the initialized + // storage slot. string memory slotName = "_initialized"; string memory slotType = "t_uint8"; - if (LibString.eq(_contractName, "FaultDisputeGame") || LibString.eq(_contractName, "PermissionedDisputeGame")) { + if ( + LibString.eq(_contractName, "FaultDisputeGame") || LibString.eq(_contractName, "PermissionedDisputeGame") + || LibString.eq(_contractName, "AggregateVerifier") + ) { slotName = "initialized"; slotType = "t_bool"; } diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol new file mode 100644 index 000000000..04f5cc9e3 --- /dev/null +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +/** + * @title DeployDevNoNitro + * @notice Development deployment WITHOUT AWS Nitro attestation validation. + * + * ══════════════════════════════════════════════════════════════════════════════════ + * DEPLOYMENT TYPE: DEV (NO NITRO) + * ══════════════════════════════════════════════════════════════════════════════════ + * + * This script deploys infrastructure using DevSystemConfigGlobal, which BYPASSES + * AWS Nitro attestation validation. Signers can be registered with a simple call + * to addDevSigner() without needing a real Nitro enclave or attestation document. + * + * USE THIS SCRIPT WHEN: + * - Running local development or testing + * - You don't have access to an AWS Nitro enclave + * - You want to quickly test the prover without attestation overhead + * + * DO NOT USE THIS SCRIPT FOR: + * - Production deployments + * - Security testing of the attestation flow + * + * ───────────────────────────────────────────────────────────────────────────────── + * SIGNER REGISTRATION (SIMPLIFIED) + * ───────────────────────────────────────────────────────────────────────────────── + * + * After deployment, register a signer with a single call: + * + * cast send $SYSTEM_CONFIG_GLOBAL \ + * "addDevSigner(address,bytes32)" $SIGNER_ADDRESS $TEE_IMAGE_HASH \ + * --private-key $OWNER_KEY --rpc-url $RPC_URL + * + * No attestation, PCR0 registration, or certificate validation required. + * + * ───────────────────────────────────────────────────────────────────────────────── + * COMPARISON WITH DeployDevWithNitro + * ───────────────────────────────────────────────────────────────────────────────── + * + * | Feature | DeployDevNoNitro | DeployDevWithNitro | + * |----------------------------|----------------------|------------------------| + * | SystemConfigGlobal | DevSystemConfigGlobal | SystemConfigGlobal | + * | Signer registration | addDevSigner() | registerSigner() | + * | Requires Nitro enclave | No | Yes | + * | Validates attestation (ZK) | No | Yes | + * | PCR0 pre-registration | No | Yes | + * | Attestation freshness | N/A | < 60 minutes | + * + * Both scripts use mocks for AnchorStateRegistry, DelayedWETH, and ZK Verifier. + * + * ══════════════════════════════════════════════════════════════════════════════════ + */ + +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { Script } from "forge-std/Script.sol"; +import { console2 as console } from "forge-std/console2.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; +import { GameType, Hash } from "src/dispute/lib/Types.sol"; + +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; +import { Config } from "scripts/libraries/Config.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; +import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; + +import { MinimalProxyAdmin } from "./mocks/MinimalProxyAdmin.sol"; +import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; +import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; + +/// @title DeployDevNoNitro +/// @notice Development deployment WITHOUT AWS Nitro attestation validation. +/// @dev Uses DevSystemConfigGlobal which allows addDevSigner() to bypass attestation. +contract DeployDevNoNitro is Script { + /// @notice Constant from Optimism's Constants.sol - the storage slot for proxy admin. + bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + uint256 public constant BLOCK_INTERVAL = 100; + uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 public constant PROOF_THRESHOLD = 1; + uint256 public constant INIT_BOND = 0.001 ether; + + DeployConfig public constant cfg = + DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig")))))); + + // Deployed addresses + address public systemConfigGlobalProxy; + address public teeVerifier; + address public disputeGameFactory; + address public mockAnchorRegistry; + address public mockDelayedWETH; + address public aggregateVerifier; + + function setUp() public { + DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" }); + cfg.read(Config.deployConfigPath()); + } + + function run() public { + GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); + + console.log("=== Deploying Dev Infrastructure (NO NITRO) ==="); + console.log("Chain ID:", block.chainid); + console.log("Owner:", cfg.finalSystemOwner()); + console.log("TEE Proposer:", cfg.teeProposer()); + console.log("Game Type:", cfg.multiproofGameType()); + console.log(""); + console.log("NOTE: Using DevSystemConfigGlobal - NO attestation required."); + + vm.startBroadcast(); + + _deployTEEContracts(cfg.finalSystemOwner()); + _registerProposer(cfg.teeProposer()); + _deployInfrastructure(gameType); + _deployAggregateVerifier(gameType); + + vm.stopBroadcast(); + + _printSummary(); + _writeOutput(); + } + + function _deployTEEContracts(address owner) internal { + address scgImpl = address(new DevSystemConfigGlobal(INitroEnclaveVerifier(address(0)))); + systemConfigGlobalProxy = address( + new TransparentUpgradeableProxy( + scgImpl, address(0xdead), abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) + ) + ); + console.log("DevSystemConfigGlobal:", systemConfigGlobalProxy); + + teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); + console.log("TEEVerifier:", teeVerifier); + } + + function _registerProposer(address teeProposer) internal { + SystemConfigGlobal(systemConfigGlobalProxy).setProposer(teeProposer, true); + console.log("Registered TEE proposer:", teeProposer); + } + + function _deployInfrastructure(GameType gameType) internal { + address factoryImpl = address(new DisputeGameFactory()); + MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.finalSystemOwner()); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(factoryImpl, address(proxyAdmin), ""); + + vm.store(address(proxy), PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(address(proxyAdmin))))); + DisputeGameFactory(address(proxy)).initialize(cfg.finalSystemOwner()); + + disputeGameFactory = address(proxy); + console.log("DisputeGameFactory:", disputeGameFactory); + + MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); + mockAnchorRegistry = address(asr); + asr.initialize( + disputeGameFactory, + Hash.wrap(cfg.multiproofGenesisOutputRoot()), + cfg.multiproofGenesisBlockNumber(), + gameType + ); + console.log("AnchorStateRegistry (mock):", mockAnchorRegistry); + } + + function _deployAggregateVerifier(GameType gameType) internal { + address zkVerifier = address(new MockVerifier()); + console.log("MockVerifier (ZK):", zkVerifier); + + mockDelayedWETH = address(new MockDelayedWETH()); + console.log("MockDelayedWETH:", mockDelayedWETH); + + aggregateVerifier = address( + new AggregateVerifier( + gameType, + IAnchorStateRegistry(mockAnchorRegistry), + IDelayedWETH(payable(mockDelayedWETH)), + IVerifier(teeVerifier), + IVerifier(zkVerifier), + cfg.teeImageHash(), + bytes32(0), + cfg.multiproofConfigHash(), + 8453, + BLOCK_INTERVAL, + INTERMEDIATE_BLOCK_INTERVAL, + PROOF_THRESHOLD + ) + ); + console.log("AggregateVerifier:", aggregateVerifier); + + DisputeGameFactory(disputeGameFactory).setImplementation(gameType, IDisputeGame(aggregateVerifier)); + DisputeGameFactory(disputeGameFactory).setInitBond(gameType, INIT_BOND); + console.log("Registered AggregateVerifier with factory"); + } + + function _printSummary() internal view { + console.log("\n========================================"); + console.log(" DEV DEPLOYMENT COMPLETE (NO NITRO)"); + console.log("========================================"); + console.log("\nTEE Contracts:"); + console.log(" DevSystemConfigGlobal:", systemConfigGlobalProxy); + console.log(" TEEVerifier:", teeVerifier); + console.log("\nInfrastructure:"); + console.log(" DisputeGameFactory:", disputeGameFactory); + console.log(" AnchorStateRegistry (mock):", mockAnchorRegistry); + console.log(" DelayedWETH (mock):", mockDelayedWETH); + console.log("\nGame:"); + console.log(" AggregateVerifier:", aggregateVerifier); + console.log(" Game Type:", cfg.multiproofGameType()); + console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash())); + console.log(" Config Hash:", vm.toString(cfg.multiproofConfigHash())); + console.log("========================================"); + console.log("\n>>> NEXT STEP - Register dev signer (NO ATTESTATION NEEDED) <<<"); + console.log("\ncast send", systemConfigGlobalProxy); + console.log(' "addDevSigner(address,bytes32)" '); + console.log(" ", vm.toString(cfg.teeImageHash())); + console.log(" --private-key --rpc-url "); + console.log("\n========================================\n"); + } + + function _writeOutput() internal { + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-dev-no-nitro.json"); + string memory output = string.concat( + '{"SystemConfigGlobal":"', + vm.toString(systemConfigGlobalProxy), + '","TEEVerifier":"', + vm.toString(teeVerifier), + '","DisputeGameFactory":"', + vm.toString(disputeGameFactory), + '","AnchorStateRegistry":"', + vm.toString(mockAnchorRegistry), + '","DelayedWETH":"', + vm.toString(mockDelayedWETH), + '","AggregateVerifier":"', + vm.toString(aggregateVerifier), + '"}' + ); + vm.writeFile(outPath, output); + console.log("Deployment saved to:", outPath); + } +} diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol new file mode 100644 index 000000000..875674ae3 --- /dev/null +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +/** + * @title DeployDevWithNitro + * @notice Development deployment WITH AWS Nitro attestation validation. + * + * ══════════════════════════════════════════════════════════════════════════════════ + * DEPLOYMENT TYPE: DEV (WITH NITRO) + * ══════════════════════════════════════════════════════════════════════════════════ + * + * This script deploys infrastructure using the REAL SystemConfigGlobal, which + * REQUIRES a ZK proof of a valid AWS Nitro attestation for signer registration. + * You cannot use addDevSigner() - you must go through the full registerSigner() flow. + * A pre-deployed NitroEnclaveVerifier contract address must be provided in the config. + * + * USE THIS SCRIPT WHEN: + * - Testing the full Nitro attestation flow end-to-end + * - You have access to a real AWS Nitro enclave + * - You want to validate the production registration process + * + * DO NOT USE THIS SCRIPT IF: + * - You don't have a Nitro enclave available + * - You just want quick local testing (use DeployDevNoNitro instead) + * + * NOTE: This still uses mocks for AnchorStateRegistry, DelayedWETH, and ZK Verifier, + * so it's not a full production deployment - just production-like for the + * Nitro attestation flow. + * + * ───────────────────────────────────────────────────────────────────────────────── + * STEP 1: Register the PCR0 (enclave image hash) - OWNER ONLY + * ───────────────────────────────────────────────────────────────────────────────── + * + * The PCR0 is the raw 48-byte hash of the enclave image. You must register it + * before any signers using that image can be registered. + * + * # Get raw PCR0 bytes from the enclave (48 bytes, hex-encoded) + * PCR0_RAW="0x<48_bytes_hex>" + * + * # Register it (only owner can do this) + * cast send $SYSTEM_CONFIG_GLOBAL "registerPCR0(bytes)" $PCR0_RAW \ + * --private-key $OWNER_KEY --rpc-url $RPC_URL + * + * Note: The teeImageHash in deploy-config is keccak256(PCR0_RAW), not the raw bytes. + * + * ───────────────────────────────────────────────────────────────────────────────── + * STEP 2: Generate ZK proof of the attestation and register the signer + * ───────────────────────────────────────────────────────────────────────────────── + * + * Generate a Risc0 ZK proof of the Nitro attestation offchain, then call: + * + * cast send $SYSTEM_CONFIG_GLOBAL \ + * "registerSigner(bytes,bytes)" $ZK_OUTPUT $ZK_PROOF_BYTES \ + * --private-key $OWNER_OR_MANAGER_KEY --rpc-url $RPC_URL + * + * IMPORTANT: The attestation is only valid for 60 minutes! Generate the proof + * and submit the transaction within that window. + * + * ───────────────────────────────────────────────────────────────────────────────── + * VERIFICATION + * ───────────────────────────────────────────────────────────────────────────────── + * + * After registration, verify the signer is registered: + * + * # Check if signer is valid + * cast call $SYSTEM_CONFIG_GLOBAL "isValidSigner(address)(bool)" $SIGNER_ADDRESS + * + * # Get the PCR0 hash associated with the signer + * cast call $SYSTEM_CONFIG_GLOBAL "signerPCR0(address)(bytes32)" $SIGNER_ADDRESS + * + * ───────────────────────────────────────────────────────────────────────────────── + * COMPARISON WITH DeployDevNoNitro + * ───────────────────────────────────────────────────────────────────────────────── + * + * | Feature | DeployDevNoNitro | DeployDevWithNitro | + * |----------------------------|----------------------|------------------------| + * | SystemConfigGlobal | DevSystemConfigGlobal | SystemConfigGlobal | + * | Signer registration | addDevSigner() | registerSigner() | + * | Requires Nitro enclave | No | Yes | + * | Validates attestation (ZK) | No | Yes | + * | PCR0 pre-registration | No | Yes | + * | Attestation freshness | N/A | < 60 minutes | + * + * Both scripts use mocks for AnchorStateRegistry, DelayedWETH, and ZK Verifier. + * + * ══════════════════════════════════════════════════════════════════════════════════ + */ + +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { Script } from "forge-std/Script.sol"; +import { console2 as console } from "forge-std/console2.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; +import { GameType, Hash } from "src/dispute/lib/Types.sol"; + +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; +import { Config } from "scripts/libraries/Config.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; + +import { MinimalProxyAdmin } from "./mocks/MinimalProxyAdmin.sol"; +import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; +import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; + +/// @title DeployDevWithNitro +/// @notice Development deployment WITH AWS Nitro attestation validation. +/// @dev Uses real SystemConfigGlobal which requires registerSigner() with valid attestation. +contract DeployDevWithNitro is Script { + /// @notice Constant from Optimism's Constants.sol - the storage slot for proxy admin. + bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + uint256 public constant BLOCK_INTERVAL = 100; + uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 public constant PROOF_THRESHOLD = 1; + uint256 public constant INIT_BOND = 0.001 ether; + + DeployConfig public constant cfg = + DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig")))))); + + // Deployed addresses + address public systemConfigGlobalProxy; + address public teeVerifier; + address public disputeGameFactory; + address public mockAnchorRegistry; + address public mockDelayedWETH; + address public aggregateVerifier; + + function setUp() public { + DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" }); + cfg.read(Config.deployConfigPath()); + } + + function run() public { + GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); + + console.log("=== Deploying Dev Infrastructure (WITH NITRO) ==="); + console.log("Chain ID:", block.chainid); + console.log("Owner:", cfg.finalSystemOwner()); + console.log("TEE Proposer:", cfg.teeProposer()); + console.log("Game Type:", cfg.multiproofGameType()); + console.log(""); + console.log("NOTE: Using REAL SystemConfigGlobal - ZK attestation proof REQUIRED."); + console.log("NitroEnclaveVerifier:", cfg.nitroEnclaveVerifier()); + + vm.startBroadcast(); + + _deployTEEContracts(cfg.finalSystemOwner(), cfg.nitroEnclaveVerifier()); + _registerProposer(cfg.teeProposer()); + _deployInfrastructure(gameType); + _deployAggregateVerifier(gameType); + + vm.stopBroadcast(); + + _printSummary(); + _writeOutput(); + } + + function _deployTEEContracts(address owner, address _nitroEnclaveVerifier) internal { + address scgImpl = address(new SystemConfigGlobal(INitroEnclaveVerifier(_nitroEnclaveVerifier))); + console.log("NitroEnclaveVerifier (external):", _nitroEnclaveVerifier); + systemConfigGlobalProxy = address( + new TransparentUpgradeableProxy( + scgImpl, address(0xdead), abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) + ) + ); + console.log("SystemConfigGlobal:", systemConfigGlobalProxy); + + teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); + console.log("TEEVerifier:", teeVerifier); + } + + function _registerProposer(address teeProposer) internal { + SystemConfigGlobal(systemConfigGlobalProxy).setProposer(teeProposer, true); + console.log("Registered TEE proposer:", teeProposer); + } + + function _deployInfrastructure(GameType gameType) internal { + address factoryImpl = address(new DisputeGameFactory()); + MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.finalSystemOwner()); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(factoryImpl, address(proxyAdmin), ""); + + vm.store(address(proxy), PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(address(proxyAdmin))))); + DisputeGameFactory(address(proxy)).initialize(cfg.finalSystemOwner()); + + disputeGameFactory = address(proxy); + console.log("DisputeGameFactory:", disputeGameFactory); + + MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); + mockAnchorRegistry = address(asr); + asr.initialize( + disputeGameFactory, + Hash.wrap(cfg.multiproofGenesisOutputRoot()), + cfg.multiproofGenesisBlockNumber(), + gameType + ); + console.log("AnchorStateRegistry (mock):", mockAnchorRegistry); + } + + function _deployAggregateVerifier(GameType gameType) internal { + address zkVerifier = address(new MockVerifier()); + console.log("MockVerifier (ZK):", zkVerifier); + + mockDelayedWETH = address(new MockDelayedWETH()); + console.log("MockDelayedWETH:", mockDelayedWETH); + + aggregateVerifier = address( + new AggregateVerifier( + gameType, + IAnchorStateRegistry(mockAnchorRegistry), + IDelayedWETH(payable(mockDelayedWETH)), + IVerifier(teeVerifier), + IVerifier(zkVerifier), + cfg.teeImageHash(), + bytes32(0), + cfg.multiproofConfigHash(), + 8453, + BLOCK_INTERVAL, + INTERMEDIATE_BLOCK_INTERVAL, + PROOF_THRESHOLD + ) + ); + console.log("AggregateVerifier:", aggregateVerifier); + + DisputeGameFactory(disputeGameFactory).setImplementation(gameType, IDisputeGame(aggregateVerifier)); + DisputeGameFactory(disputeGameFactory).setInitBond(gameType, INIT_BOND); + console.log("Registered AggregateVerifier with factory"); + } + + function _printSummary() internal view { + console.log("\n========================================"); + console.log(" DEV DEPLOYMENT COMPLETE (WITH NITRO)"); + console.log("========================================"); + console.log("\nTEE Contracts:"); + console.log(" NitroEnclaveVerifier (external):", cfg.nitroEnclaveVerifier()); + console.log(" SystemConfigGlobal:", systemConfigGlobalProxy); + console.log(" TEEVerifier:", teeVerifier); + console.log("\nInfrastructure:"); + console.log(" DisputeGameFactory:", disputeGameFactory); + console.log(" AnchorStateRegistry (mock):", mockAnchorRegistry); + console.log(" DelayedWETH (mock):", mockDelayedWETH); + console.log("\nGame:"); + console.log(" AggregateVerifier:", aggregateVerifier); + console.log(" Game Type:", cfg.multiproofGameType()); + console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash())); + console.log(" Config Hash:", vm.toString(cfg.multiproofConfigHash())); + console.log("========================================"); + console.log("\n>>> NEXT STEPS (ZK ATTESTATION PROOF REQUIRED) <<<"); + console.log("\n1. Register the PCR0 (raw 48-byte enclave image hash):"); + console.log(" cast send", systemConfigGlobalProxy); + console.log(' "registerPCR0(bytes)" '); + console.log(" --private-key --rpc-url "); + console.log("\n2. Generate a Risc0 ZK proof of the Nitro attestation offchain."); + console.log("\n3. Register signer with ZK proof:"); + console.log(" cast send", systemConfigGlobalProxy); + console.log(' "registerSigner(bytes,bytes)" '); + console.log(" --private-key --rpc-url "); + console.log("\nSee the comments at the top of this file for full details."); + console.log("========================================\n"); + } + + function _writeOutput() internal { + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-dev-with-nitro.json"); + string memory output = string.concat( + '{"SystemConfigGlobal":"', + vm.toString(systemConfigGlobalProxy), + '","TEEVerifier":"', + vm.toString(teeVerifier), + '","DisputeGameFactory":"', + vm.toString(disputeGameFactory), + '","AnchorStateRegistry":"', + vm.toString(mockAnchorRegistry), + '","DelayedWETH":"', + vm.toString(mockDelayedWETH), + '","AggregateVerifier":"', + vm.toString(aggregateVerifier), + '"}' + ); + vm.writeFile(outPath, output); + console.log("Deployment saved to:", outPath); + } +} diff --git a/scripts/multiproof/mocks/MinimalProxyAdmin.sol b/scripts/multiproof/mocks/MinimalProxyAdmin.sol new file mode 100644 index 000000000..ad46f7a4a --- /dev/null +++ b/scripts/multiproof/mocks/MinimalProxyAdmin.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +/// @title MinimalProxyAdmin +/// @notice Minimal contract to satisfy DisputeGameFactory's proxy admin check. +/// @dev DisputeGameFactory.initialize() requires msg.sender == proxyAdmin() or proxyAdminOwner(). +/// We deploy this minimal contract and set it as the proxy admin via vm.store. +contract MinimalProxyAdmin { + address public owner; + + constructor(address initialOwner) { + owner = initialOwner; + } +} diff --git a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol new file mode 100644 index 000000000..a4108dc4d --- /dev/null +++ b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { GameType, Hash } from "src/dispute/lib/Types.sol"; + +/// @title MockAnchorStateRegistry +/// @notice Minimal mock for testing - stores anchor state and factory reference. +/// @dev We use a mock instead of the real AnchorStateRegistry because: +/// 1. The real contract requires deploying the entire Optimism L1 stack +/// (SystemConfig, SuperchainConfig, ProxyAdmin, Guardian roles, etc.) +/// 2. The real contract has "stack too deep" compilation issues that require +/// special compiler settings (via-ir) which significantly slow builds +/// 3. For TEE prover testing, we only need getAnchorRoot() and setAnchorState() +contract MockAnchorStateRegistry { + Hash public anchorRoot; + uint256 public anchorL2BlockNumber; + address public factory; + GameType public respectedGameType; + + /// @notice Initializes the mock registry. + /// @param newFactory The dispute game factory address. + /// @param newAnchorRoot The initial anchor root. + /// @param newAnchorL2BlockNumber The initial anchor L2 block number. + /// @param gameType The respected game type. + function initialize( + address newFactory, + Hash newAnchorRoot, + uint256 newAnchorL2BlockNumber, + GameType gameType + ) + external + { + factory = newFactory; + anchorRoot = newAnchorRoot; + anchorL2BlockNumber = newAnchorL2BlockNumber; + respectedGameType = gameType; + } + + /// @notice Returns the anchor root and block number. + /// @return The anchor root hash and L2 block number. + function getAnchorRoot() external view returns (Hash, uint256) { + return (anchorRoot, anchorL2BlockNumber); + } + + /// @notice Returns the dispute game factory address. + /// @return The factory address. + function disputeGameFactory() external view returns (address) { + return factory; + } + + /// @notice Sets the respected game type. + /// @param gameType The new game type. + function setRespectedGameType(GameType gameType) external { + respectedGameType = gameType; + } + + /// @notice Updates the anchor state (for testing purposes). + /// @param newAnchorRoot The new anchor root. + /// @param newAnchorL2BlockNumber The new anchor L2 block number. + function setAnchorState(Hash newAnchorRoot, uint256 newAnchorL2BlockNumber) external { + anchorRoot = newAnchorRoot; + anchorL2BlockNumber = newAnchorL2BlockNumber; + } + + /// @notice Checks if a game is registered. + /// @return Always returns true for testing. + function isGameRegistered(IDisputeGame) external pure returns (bool) { + return true; + } + + /// @notice Checks if a game is blacklisted. + /// @return Always returns false for testing. + function isGameBlacklisted(IDisputeGame) external pure returns (bool) { + return false; + } + + /// @notice Checks if a game is retired. + /// @return Always returns false for testing. + function isGameRetired(IDisputeGame) external pure returns (bool) { + return false; + } + + /// @notice Checks if a game is respected. + /// @return Always returns true for testing. + function isGameRespected(IDisputeGame) external pure returns (bool) { + return true; + } +} diff --git a/scripts/multiproof/mocks/MockDelayedWETH.sol b/scripts/multiproof/mocks/MockDelayedWETH.sol new file mode 100644 index 000000000..ddffec0b7 --- /dev/null +++ b/scripts/multiproof/mocks/MockDelayedWETH.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +/// @title MockDelayedWETH +/// @notice Minimal mock for testing - implements the IDelayedWETH interface. +/// @dev For testing purposes only. The real DelayedWETH handles bond deposits and withdrawals. +contract MockDelayedWETH { + /// @notice Accepts ETH deposits (no-op for testing). + function deposit() external payable { } + + /// @notice Mock unlock - no-op for testing. + function unlock(address, uint256) external { } + + /// @notice Mock withdraw - transfers ETH back. + /// @param recipient The address to send ETH to. + /// @param amount The amount of ETH to withdraw. + function withdraw(address recipient, uint256 amount) external { + payable(recipient).transfer(amount); + } + + /// @notice Allows contract to receive ETH. + receive() external payable { } +} diff --git a/snapshots/abi/AggregateVerifier.json b/snapshots/abi/AggregateVerifier.json new file mode 100644 index 000000000..2a142bdd7 --- /dev/null +++ b/snapshots/abi/AggregateVerifier.json @@ -0,0 +1,1068 @@ +[ + { + "inputs": [ + { + "internalType": "GameType", + "name": "gameType_", + "type": "uint32" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistry_", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + }, + { + "internalType": "contract IVerifier", + "name": "teeVerifier", + "type": "address" + }, + { + "internalType": "contract IVerifier", + "name": "zkVerifier", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "teeImageHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "zkImageHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "configHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "intermediateBlockInterval", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BLOCKHASH_WINDOW", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BLOCK_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONFIG_HASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DELAYED_WETH", + "outputs": [ + { + "internalType": "contract IDelayedWETH", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DISPUTE_GAME_FACTORY", + "outputs": [ + { + "internalType": "contract IDisputeGameFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EIP2935_CONTRACT", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EIP2935_WINDOW", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "FAST_FINALIZATION_DELAY", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INTERMEDIATE_BLOCK_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "L2_CHAIN_ID", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SLOW_FINALIZATION_DELAY", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TEE_IMAGE_HASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TEE_VERIFIER", + "outputs": [ + { + "internalType": "contract IVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ZK_IMAGE_HASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ZK_VERIFIER", + "outputs": [ + { + "internalType": "contract IVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "anchorStateRegistry", + "outputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bondAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bondClaimed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bondRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bondUnlocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gameIndex", + "type": "uint256" + } + ], + "name": "challenge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimCredit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "closeGame", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "counteredByGameAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "createdAt", + "outputs": [ + { + "internalType": "Timestamp", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "expectedResolution", + "outputs": [ + { + "internalType": "Timestamp", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "extraData", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gameCreator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "gameData", + "outputs": [ + { + "internalType": "GameType", + "name": "", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gameOver", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gameType", + "outputs": [ + { + "internalType": "GameType", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "name": "initializeWithInitData", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "intermediateOutputRoot", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "intermediateOutputRoots", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "intermediateOutputRootsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1Head", + "outputs": [ + { + "internalType": "Hash", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "l2SequenceNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "intermediateRootIndex", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "intermediateRootToProve", + "type": "bytes32" + } + ], + "name": "nullify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "parentIndex", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "resolve", + "outputs": [ + { + "internalType": "enum GameStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resolvedAt", + "outputs": [ + { + "internalType": "Timestamp", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rootClaim", + "outputs": [ + { + "internalType": "Claim", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "startingBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingOutputRoot", + "outputs": [ + { + "internalType": "Hash", + "name": "root", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "l2SequenceNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingRootHash", + "outputs": [ + { + "internalType": "Hash", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "enum GameStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "teeProver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "verifyProposalProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "wasRespectedGameTypeWhenCreated", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "zkProver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "challenger", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IDisputeGame", + "name": "game", + "type": "address" + } + ], + "name": "Challenged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "CreditClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nullifier", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "intermediateRootIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "intermediateRoot", + "type": "bytes32" + } + ], + "name": "Nullified", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "prover", + "type": "address" + }, + { + "indexed": true, + "internalType": "enum AggregateVerifier.ProofType", + "name": "proofType", + "type": "uint8" + } + ], + "name": "Proved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "enum GameStatus", + "name": "status", + "type": "uint8" + } + ], + "name": "Resolved", + "type": "event" + }, + { + "inputs": [], + "name": "AlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum AggregateVerifier.ProofType", + "name": "proofType", + "type": "uint8" + } + ], + "name": "AlreadyProven", + "type": "error" + }, + { + "inputs": [], + "name": "BondRecipientEmpty", + "type": "error" + }, + { + "inputs": [], + "name": "BondTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "ClaimAlreadyResolved", + "type": "error" + }, + { + "inputs": [], + "name": "CounteredByGameNotResolved", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotFinalized", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotInProgress", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotOver", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotResolved", + "type": "error" + }, + { + "inputs": [], + "name": "GameOver", + "type": "error" + }, + { + "inputs": [], + "name": "GamePaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "intermediateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "claim", + "type": "bytes32" + } + ], + "name": "IntermediateRootMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "IntermediateRootSameAsProposed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "intermediateBlockInterval", + "type": "uint256" + } + ], + "name": "InvalidBlockInterval", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCounteredByGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIntermediateRootIndex", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidParentGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProof", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProofType", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "claimed", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "actual", + "type": "bytes32" + } + ], + "name": "L1OriginHashMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "l1OriginNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentBlock", + "type": "uint256" + } + ], + "name": "L1OriginInFuture", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "l1OriginNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentBlock", + "type": "uint256" + } + ], + "name": "L1OriginTooOld", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum AggregateVerifier.ProofType", + "name": "proofType", + "type": "uint8" + } + ], + "name": "MissingProof", + "type": "error" + }, + { + "inputs": [], + "name": "NoCreditToClaim", + "type": "error" + }, + { + "inputs": [], + "name": "NoProofProvided", + "type": "error" + }, + { + "inputs": [], + "name": "ParentGameNotResolved", + "type": "error" + }, + { + "inputs": [], + "name": "Reentrancy", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expectedBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actualBlockNumber", + "type": "uint256" + } + ], + "name": "UnexpectedBlockNumber", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/BalanceTracker.json b/snapshots/abi/BalanceTracker.json new file mode 100644 index 000000000..136ef890e --- /dev/null +++ b/snapshots/abi/BalanceTracker.json @@ -0,0 +1,194 @@ +[ + { + "inputs": [ + { + "internalType": "address payable", + "name": "profitWallet", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [], + "name": "MAX_SYSTEM_ADDRESS_COUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PROFIT_WALLET", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable[]", + "name": "systemAddresses_", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "targetBalances_", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "processFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "systemAddresses", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "targetBalances", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "systemAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "balanceNeeded", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "balanceSent", + "type": "uint256" + } + ], + "name": "ProcessedFunds", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ReceivedFunds", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "profitWallet", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "balanceSent", + "type": "uint256" + } + ], + "name": "SentProfit", + "type": "event" + } +] \ No newline at end of file diff --git a/snapshots/abi/CBMulticall.json b/snapshots/abi/CBMulticall.json new file mode 100644 index 000000000..9ac8ef7f6 --- /dev/null +++ b/snapshots/abi/CBMulticall.json @@ -0,0 +1,485 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "returnData", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Call3[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Call3Value[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3Value", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Call3[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregateDelegateCalls", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getBasefee", + "outputs": [ + { + "internalType": "uint256", + "name": "basefee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "chainid", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [ + { + "internalType": "address", + "name": "coinbase", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "gaslimit", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getEthBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "MustDelegateCall", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/DisputeGameFactory.json b/snapshots/abi/DisputeGameFactory.json index 016224be1..54f34da5f 100644 --- a/snapshots/abi/DisputeGameFactory.json +++ b/snapshots/abi/DisputeGameFactory.json @@ -33,6 +33,40 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "GameType", + "name": "_gameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "_rootClaim", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "_initData", + "type": "bytes" + } + ], + "name": "createWithInitData", + "outputs": [ + { + "internalType": "contract IDisputeGame", + "name": "proxy_", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { diff --git a/snapshots/abi/FeeDisburser.json b/snapshots/abi/FeeDisburser.json new file mode 100644 index 000000000..4dfa787a5 --- /dev/null +++ b/snapshots/abi/FeeDisburser.json @@ -0,0 +1,182 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "l1Wallet", + "type": "address" + }, + { + "internalType": "uint256", + "name": "feeDisbursementInterval", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [], + "name": "FEE_DISBURSEMENT_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "L1_WALLET", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWAL_MIN_GAS", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "disburseFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lastDisbursementTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "netFeeRevenue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "disbursementTime", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "deprecated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalFeesDisbursed", + "type": "uint256" + } + ], + "name": "FeesDisbursed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "FeesReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "NoFeesCollected", + "type": "event" + }, + { + "inputs": [], + "name": "FeeVaultMustWithdrawToFeeDisburser", + "type": "error" + }, + { + "inputs": [], + "name": "FeeVaultMustWithdrawToL2", + "type": "error" + }, + { + "inputs": [], + "name": "IntervalNotReached", + "type": "error" + }, + { + "inputs": [], + "name": "IntervalTooLow", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/OPSuccinctFaultDisputeGame.json b/snapshots/abi/OPSuccinctFaultDisputeGame.json index 9e60e0034..6eb3a95f1 100644 --- a/snapshots/abi/OPSuccinctFaultDisputeGame.json +++ b/snapshots/abi/OPSuccinctFaultDisputeGame.json @@ -318,6 +318,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "initializeWithInitData", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [], "name": "l1Head", diff --git a/snapshots/abi/Recovery.json b/snapshots/abi/Recovery.json new file mode 100644 index 000000000..32eda0212 --- /dev/null +++ b/snapshots/abi/Recovery.json @@ -0,0 +1,138 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "OWNER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "name": "withdrawETH", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/SmartEscrow.json b/snapshots/abi/SmartEscrow.json new file mode 100644 index 000000000..06bdc5288 --- /dev/null +++ b/snapshots/abi/SmartEscrow.json @@ -0,0 +1,977 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "benefactor_", + "type": "address" + }, + { + "internalType": "address", + "name": "beneficiary_", + "type": "address" + }, + { + "internalType": "address", + "name": "benefactorOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "beneficiaryOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "escrowOwner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "start_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cliffStart_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "end_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vestingPeriodSeconds_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "initialTokens_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vestingEventTokens_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BENEFACTOR_OWNER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BENEFICIARY_OWNER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OP_TOKEN", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TERMINATOR_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptDefaultAdminTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "beginDefaultAdminTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "benefactor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "beneficiary", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cancelDefaultAdminTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint48", + "name": "newDelay", + "type": "uint48" + } + ], + "name": "changeDefaultAdminDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cliffStart", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "contractTerminated", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "defaultAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "defaultAdminDelay", + "outputs": [ + { + "internalType": "uint48", + "name": "", + "type": "uint48" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "defaultAdminDelayIncreaseWait", + "outputs": [ + { + "internalType": "uint48", + "name": "", + "type": "uint48" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "end", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingDefaultAdmin", + "outputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + }, + { + "internalType": "uint48", + "name": "schedule", + "type": "uint48" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingDefaultAdminDelay", + "outputs": [ + { + "internalType": "uint48", + "name": "newDelay", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "schedule", + "type": "uint48" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "releasable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "release", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "released", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resume", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rollbackDefaultAdminDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "start", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "terminate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newBenefactor", + "type": "address" + } + ], + "name": "updateBenefactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newBeneficiary", + "type": "address" + } + ], + "name": "updateBeneficiary", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "vestedAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vestingEventTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vestingPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawUnvestedTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldBenefactor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newBenefactor", + "type": "address" + } + ], + "name": "BenefactorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldBeneficiary", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newBeneficiary", + "type": "address" + } + ], + "name": "BeneficiaryUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "ContractResumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "ContractTerminated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DefaultAdminDelayChangeCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint48", + "name": "newDelay", + "type": "uint48" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "effectSchedule", + "type": "uint48" + } + ], + "name": "DefaultAdminDelayChangeScheduled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DefaultAdminTransferCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "acceptSchedule", + "type": "uint48" + } + ], + "name": "DefaultAdminTransferScheduled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokensReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "benefactor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokensWithdrawn", + "type": "event" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint48", + "name": "schedule", + "type": "uint48" + } + ], + "name": "AccessControlEnforcedDefaultAdminDelay", + "type": "error" + }, + { + "inputs": [], + "name": "AccessControlEnforcedDefaultAdminRules", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "defaultAdmin", + "type": "address" + } + ], + "name": "AccessControlInvalidDefaultAdmin", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "AddressIsZeroAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "cliffStartTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTimestamp", + "type": "uint256" + } + ], + "name": "CliffStartTimeAfterEndTime", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "cliffStartTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + } + ], + "name": "CliffStartTimeInvalid", + "type": "error" + }, + { + "inputs": [], + "name": "ContractIsNotTerminated", + "type": "error" + }, + { + "inputs": [], + "name": "ContractIsTerminated", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "startTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTimestamp", + "type": "uint256" + } + ], + "name": "StartTimeAfterEndTime", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "vestingPeriodSeconds", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTimestamp", + "type": "uint256" + } + ], + "name": "UnevenVestingPeriod", + "type": "error" + }, + { + "inputs": [], + "name": "VestingEventTokensIsZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "vestingPeriodSeconds", + "type": "uint256" + } + ], + "name": "VestingPeriodExceedsContractDuration", + "type": "error" + }, + { + "inputs": [], + "name": "VestingPeriodIsZeroSeconds", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/SuperchainConfig.json b/snapshots/abi/SuperchainConfig.json index 67e886503..cfc27101f 100644 --- a/snapshots/abi/SuperchainConfig.json +++ b/snapshots/abi/SuperchainConfig.json @@ -1,9 +1,46 @@ [ { - "inputs": [], + "inputs": [ + { + "internalType": "address", + "name": "_guardian", + "type": "address" + }, + { + "internalType": "address", + "name": "_incidentResponder", + "type": "address" + } + ], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "GUARDIAN", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INCIDENT_RESPONDER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -51,28 +88,15 @@ }, { "inputs": [], - "name": "initVersion", + "name": "incidentResponder", "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", - "name": "_guardian", + "name": "", "type": "address" } ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -223,38 +247,6 @@ "stateMutability": "view", "type": "function" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "enum SuperchainConfig.UpdateType", - "name": "updateType", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "ConfigUpdate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -265,7 +257,7 @@ "type": "address" } ], - "name": "Paused", + "name": "PauseExtended", "type": "event" }, { @@ -278,7 +270,7 @@ "type": "address" } ], - "name": "PauseExtended", + "name": "Paused", "type": "event" }, { @@ -324,11 +316,6 @@ "name": "ProxyAdminOwnedBase_ProxyAdminNotFound", "type": "error" }, - { - "inputs": [], - "name": "ReinitializableBase_ZeroInitVersion", - "type": "error" - }, { "inputs": [ { @@ -355,5 +342,10 @@ "inputs": [], "name": "SuperchainConfig_OnlyGuardian", "type": "error" + }, + { + "inputs": [], + "name": "SuperchainConfig_OnlyGuardianOrIncidentResponder", + "type": "error" } ] \ No newline at end of file diff --git a/snapshots/abi/SystemConfigGlobal.json b/snapshots/abi/SystemConfigGlobal.json new file mode 100644 index 000000000..e2e1a3db9 --- /dev/null +++ b/snapshots/abi/SystemConfigGlobal.json @@ -0,0 +1,440 @@ +[ + { + "inputs": [ + { + "internalType": "contract INitroEnclaveVerifier", + "name": "nitroVerifier", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "MAX_AGE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NITRO_VERIFIER", + "outputs": [ + { + "internalType": "contract INitroEnclaveVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "pcr0", + "type": "bytes" + } + ], + "name": "deregisterPCR0", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "deregisterSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "initialManager", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isValidProposer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "isValidSigner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "manager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "pcr0", + "type": "bytes" + } + ], + "name": "registerPCR0", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "output", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "registerSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceManagement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "name": "setProposer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "signerPCR0", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newManager", + "type": "address" + } + ], + "name": "transferManagement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "validPCR0s", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousManager", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newManager", + "type": "address" + } + ], + "name": "ManagementTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "pcr0Hash", + "type": "bytes32" + } + ], + "name": "PCR0Deregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "pcr0Hash", + "type": "bytes32" + } + ], + "name": "PCR0Registered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "name": "ProposerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "SignerDeregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "pcr0", + "type": "bytes32" + } + ], + "name": "SignerRegistered", + "type": "event" + }, + { + "inputs": [], + "name": "AttestationTooOld", + "type": "error" + }, + { + "inputs": [], + "name": "AttestationVerificationFailed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPCR0", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPublicKey", + "type": "error" + }, + { + "inputs": [], + "name": "PCR0NotFound", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/TEEVerifier.json b/snapshots/abi/TEEVerifier.json new file mode 100644 index 000000000..b1bccadb7 --- /dev/null +++ b/snapshots/abi/TEEVerifier.json @@ -0,0 +1,116 @@ +[ + { + "inputs": [ + { + "internalType": "contract SystemConfigGlobal", + "name": "systemConfigGlobal", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "SYSTEM_CONFIG_GLOBAL", + "outputs": [ + { + "internalType": "contract SystemConfigGlobal", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "imageId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "journal", + "type": "bytes32" + } + ], + "name": "verify", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "signerPCR0", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "claimedImageId", + "type": "bytes32" + } + ], + "name": "ImageIdMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProofFormat", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposer", + "type": "address" + } + ], + "name": "InvalidProposer", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "InvalidSigner", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 5f3043e6d..08bc12a07 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -11,6 +11,10 @@ "initCodeHash": "0xe52c51805cfd55967d037173159f18aaf4344e32e5c8ad41f8d5d0025b1d36a8", "sourceCodeHash": "0xe5f2b1915a201c0b8a107f168f5b9bc8aec8e8e95f938082e42ba5b5c8ebbd11" }, + "src/L1/FeesDepositor.sol:FeesDepositor:dispute": { + "initCodeHash": "0xe52c51805cfd55967d037173159f18aaf4344e32e5c8ad41f8d5d0025b1d36a8", + "sourceCodeHash": "0xe5f2b1915a201c0b8a107f168f5b9bc8aec8e8e95f938082e42ba5b5c8ebbd11" + }, "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger": { "initCodeHash": "0x3dc659aafb03bd357f92abfc6794af89ee0ddd5212364551637422bf8d0b00f9", "sourceCodeHash": "0xfd67aae7ef1d684f3fccc036a80123e0ffa13de3e26c910cb7b927059c5a6289" @@ -47,8 +51,12 @@ "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" }, + "src/L1/SuperchainConfig.sol:SuperchainConfig:dispute": { + "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", + "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" + }, "src/L1/SystemConfig.sol:SystemConfig": { - "initCodeHash": "0x184e74fa4993271e35d477155689ceba54c083c71f0f93f2dea0ee6330c61693", + "initCodeHash": "0x3e8e52d96398a6de91d8922769cc5d0bc7acb2a692689ceb70f1de816e8d6b14", "sourceCodeHash": "0x1a24ffe154eddbe0088a4b0848e662bf5eb482fe031ba528f74c51804f6e0395" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { @@ -128,11 +136,11 @@ "sourceCodeHash": "0xd6e94bc9df025855916aa4184d0bc739b0fbe786dfd037b99dbb51d0d3e46918" }, "src/L2/OptimismMintableERC721.sol:OptimismMintableERC721": { - "initCodeHash": "0x316c6d716358f5b5284cd5457ea9fca4b5ad4a37835d4b4b300413dafbfa2159", + "initCodeHash": "0x363dcb59140ecfbcdff7b5f4e822f0fdc8492fc02b6d9abf3cb822ae75e8dcfb", "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" }, "src/L2/OptimismMintableERC721Factory.sol:OptimismMintableERC721Factory": { - "initCodeHash": "0xa692a3fc4a71eb3381a59d1ab655bbc02e8b507add7c3f560ee24b001d88ae6e", + "initCodeHash": "0x3f31209e30a79f367b59b66e3ee6f43f5916ec26e6d1c5a08846d636884f020d", "sourceCodeHash": "0xb0be3deac32956251adb37d3ca61f619ca4348a1355a41c856a3a95adde0e4ff" }, "src/L2/OptimismSuperchainERC20.sol:OptimismSuperchainERC20": { @@ -180,16 +188,16 @@ "sourceCodeHash": "0xd3fd2d07ea417c97590c5b1895890186faf7d1f12f1e2d368ba270d077c088fd" }, "src/dispute/AnchorStateRegistry.sol:AnchorStateRegistry": { - "initCodeHash": "0xc00fdb1a4ae0ec8d7a96ebaad38ffaee9d96b942ab2a56e0ce2f76639f79ae7c", + "initCodeHash": "0x4795cc818d507942e953f1ed412218f5cdbf5802a98e5fa1c73450dd6dfa39e9", "sourceCodeHash": "0x93fcf998d69b2a72273c4043c465010d53e338be95331cdd122ea55ff5bb0ef8" }, "src/dispute/DelayedWETH.sol:DelayedWETH": { - "initCodeHash": "0xa8f60e142108b33675a8f6b6979c73b96eea247884842d796f9f878904c0a906", + "initCodeHash": "0x592a6e22e1d02797eeb6fa622806e58f867bb6066944852e120b5871d3110825", "sourceCodeHash": "0xdebf2ab3af4d5549c40e9dd9db6b2458af286f323b6891f3b0c4e89f3c8928db" }, "src/dispute/DisputeGameFactory.sol:DisputeGameFactory": { - "initCodeHash": "0x820e876839f94564a9d5d1fa131781c40126aed76fbbd348f49f318ae3e12aae", - "sourceCodeHash": "0x870025c86292471724e2be703b181be8d71a7e1e96ebf181e0a81c005032a5db" + "initCodeHash": "0x727de8dbfd3432d95e19c39ece5ac7aec183dd67a09db769791d0e835e365d88", + "sourceCodeHash": "0x099527c6ecc13a98b6b9f09b7fcd4b44ffa351665e556df0bd0db344dd749da9" }, "src/dispute/FaultDisputeGame.sol:FaultDisputeGame": { "initCodeHash": "0xe7d3c982532946d196d7efadb9e2576c76b8f9e0d1f885ac36977d6f3fb72a65", @@ -216,8 +224,8 @@ "sourceCodeHash": "0xc0ff6e93b6e2b9111c11e81b5df8948ab71d02b9d2c4dfda982fcb615519f1f7" }, "src/dispute/zk/OPSuccinctFaultDisputeGame.sol:OPSuccinctFaultDisputeGame": { - "initCodeHash": "0xb9d0d9ca4df242f188b2d5be7d692459a12409a67a6504ef44ef589c6ca2c1a9", - "sourceCodeHash": "0x85f80adb845f59e9137d462e219c0cdba27058be77d855075e286aa316735aa0" + "initCodeHash": "0x70d20610e50b4eb713c33ce9b0a64200227f99efad88856f269316c6b789ecd1", + "sourceCodeHash": "0xce800b9bd50229b069793b44cf62823135034344a41dcd83e7a24428abe8483a" }, "src/legacy/DeployerWhitelist.sol:DeployerWhitelist": { "initCodeHash": "0x2e0ef4c341367eb59cc6c25190c64eff441d3fe130189da91d4d126f6bdbc9b5", @@ -231,6 +239,30 @@ "initCodeHash": "0x3a82e248129d19764bb975bb79b48a982f077f33bb508480bf8d2ec1c0c9810d", "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, + "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { + "initCodeHash": "0x6e30a9816642f1ea2887f16223868fcd04ac80c3a42a4583dfc611d8331f8674", + "sourceCodeHash": "0xb9786dc79b4b494d81905235f7bda044c5b1a98ad82416829f5f6948be1175cc" + }, + "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { + "initCodeHash": "0xc59c62a455533735aa9337d2db88b255713bd4dde20d3345265ec2fafe62af70", + "sourceCodeHash": "0xb9786dc79b4b494d81905235f7bda044c5b1a98ad82416829f5f6948be1175cc" + }, + "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { + "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", + "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" + }, + "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { + "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", + "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" + }, + "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { + "initCodeHash": "0x78317d9088a26523d938ce0d88ed0134abc60292c0cdb3f8a6a9530638fd9e9a", + "sourceCodeHash": "0x08e6d340b025c5a6c5997ef6fb69e6c14444f9e473b2c5337d6c10b6c89c0b4f" + }, + "src/multiproof/tee/TEEVerifier.sol:TEEVerifier:dispute": { + "initCodeHash": "0x78317d9088a26523d938ce0d88ed0134abc60292c0cdb3f8a6a9530638fd9e9a", + "sourceCodeHash": "0x08e6d340b025c5a6c5997ef6fb69e6c14444f9e473b2c5337d6c10b6c89c0b4f" + }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", "sourceCodeHash": "0xac49a0ecf22b8a7bb3ebef830a2d27b19050f9b08941186e8563d5113cf0ce9c" @@ -255,8 +287,12 @@ "initCodeHash": "0x3c85eed0d017dca8eda6396aa842ddc12492587b061e8c756a8d32c4610a9658", "sourceCodeHash": "0x7023665d461f173417d932b55010b8f6c34f2bbaf56cfe4e1b15862c08cbcaac" }, + "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20:dispute": { + "initCodeHash": "0xa26cd5b88cb1d4e5cef0422b98ff4c33a3bbfa33479ebd8632d095bb2c633ca7", + "sourceCodeHash": "0x7023665d461f173417d932b55010b8f6c34f2bbaf56cfe4e1b15862c08cbcaac" + }, "src/universal/OptimismMintableERC20Factory.sol:OptimismMintableERC20Factory": { - "initCodeHash": "0x747baf403205b900e1144038f2b807c84059229aedda8c91936798e1403eda39", + "initCodeHash": "0x75f5f8dad139fce79a2fed9c80bf653184c73fb7dc319715cdd07392f32b007a", "sourceCodeHash": "0xf71e16aaad1ec2459040ab8c93b7188b2c04c671c21b4d43fba75cab80ed1b21" }, "src/universal/StorageSetter.sol:StorageSetter": { diff --git a/snapshots/storageLayout/AggregateVerifier.json b/snapshots/storageLayout/AggregateVerifier.json new file mode 100644 index 000000000..1045c4170 --- /dev/null +++ b/snapshots/storageLayout/AggregateVerifier.json @@ -0,0 +1,93 @@ +[ + { + "bytes": "8", + "label": "createdAt", + "offset": 0, + "slot": "0", + "type": "Timestamp" + }, + { + "bytes": "8", + "label": "resolvedAt", + "offset": 8, + "slot": "0", + "type": "Timestamp" + }, + { + "bytes": "1", + "label": "status", + "offset": 16, + "slot": "0", + "type": "enum GameStatus" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 17, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1", + "label": "wasRespectedGameTypeWhenCreated", + "offset": 18, + "slot": "0", + "type": "bool" + }, + { + "bytes": "64", + "label": "startingOutputRoot", + "offset": 0, + "slot": "1", + "type": "struct Proposal" + }, + { + "bytes": "20", + "label": "bondRecipient", + "offset": 0, + "slot": "3", + "type": "address" + }, + { + "bytes": "1", + "label": "bondUnlocked", + "offset": 20, + "slot": "3", + "type": "bool" + }, + { + "bytes": "1", + "label": "bondClaimed", + "offset": 21, + "slot": "3", + "type": "bool" + }, + { + "bytes": "32", + "label": "bondAmount", + "offset": 0, + "slot": "4", + "type": "uint256" + }, + { + "bytes": "20", + "label": "counteredByGameAddress", + "offset": 0, + "slot": "5", + "type": "address" + }, + { + "bytes": "32", + "label": "proofTypeToProver", + "offset": 0, + "slot": "6", + "type": "mapping(enum AggregateVerifier.ProofType => address)" + }, + { + "bytes": "8", + "label": "expectedResolution", + "offset": 0, + "slot": "7", + "type": "Timestamp" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/BalanceTracker.json b/snapshots/storageLayout/BalanceTracker.json new file mode 100644 index 000000000..2fb7cb0a1 --- /dev/null +++ b/snapshots/storageLayout/BalanceTracker.json @@ -0,0 +1,44 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "32", + "label": "_status", + "offset": 0, + "slot": "1", + "type": "uint256" + }, + { + "bytes": "1568", + "label": "__gap", + "offset": 0, + "slot": "2", + "type": "uint256[49]" + }, + { + "bytes": "32", + "label": "systemAddresses", + "offset": 0, + "slot": "51", + "type": "address payable[]" + }, + { + "bytes": "32", + "label": "targetBalances", + "offset": 0, + "slot": "52", + "type": "uint256[]" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/CBMulticall.json b/snapshots/storageLayout/CBMulticall.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/snapshots/storageLayout/CBMulticall.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/snapshots/storageLayout/FeeDisburser.json b/snapshots/storageLayout/FeeDisburser.json new file mode 100644 index 000000000..53ffb02a4 --- /dev/null +++ b/snapshots/storageLayout/FeeDisburser.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "32", + "label": "lastDisbursementTime", + "offset": 0, + "slot": "0", + "type": "uint256" + }, + { + "bytes": "32", + "label": "netFeeRevenue", + "offset": 0, + "slot": "1", + "type": "uint256" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/Recovery.json b/snapshots/storageLayout/Recovery.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/snapshots/storageLayout/Recovery.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/snapshots/storageLayout/SmartEscrow.json b/snapshots/storageLayout/SmartEscrow.json new file mode 100644 index 000000000..1b1333a15 --- /dev/null +++ b/snapshots/storageLayout/SmartEscrow.json @@ -0,0 +1,79 @@ +[ + { + "bytes": "32", + "label": "_roles", + "offset": 0, + "slot": "0", + "type": "mapping(bytes32 => struct AccessControl.RoleData)" + }, + { + "bytes": "20", + "label": "_pendingDefaultAdmin", + "offset": 0, + "slot": "1", + "type": "address" + }, + { + "bytes": "6", + "label": "_pendingDefaultAdminSchedule", + "offset": 20, + "slot": "1", + "type": "uint48" + }, + { + "bytes": "6", + "label": "_currentDelay", + "offset": 26, + "slot": "1", + "type": "uint48" + }, + { + "bytes": "20", + "label": "_currentDefaultAdmin", + "offset": 0, + "slot": "2", + "type": "address" + }, + { + "bytes": "6", + "label": "_pendingDelay", + "offset": 20, + "slot": "2", + "type": "uint48" + }, + { + "bytes": "6", + "label": "_pendingDelaySchedule", + "offset": 26, + "slot": "2", + "type": "uint48" + }, + { + "bytes": "20", + "label": "benefactor", + "offset": 0, + "slot": "3", + "type": "address" + }, + { + "bytes": "20", + "label": "beneficiary", + "offset": 0, + "slot": "4", + "type": "address" + }, + { + "bytes": "32", + "label": "released", + "offset": 0, + "slot": "5", + "type": "uint256" + }, + { + "bytes": "1", + "label": "contractTerminated", + "offset": 0, + "slot": "6", + "type": "bool" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/SuperchainConfig.json b/snapshots/storageLayout/SuperchainConfig.json index b0aae64a5..57708ae70 100644 --- a/snapshots/storageLayout/SuperchainConfig.json +++ b/snapshots/storageLayout/SuperchainConfig.json @@ -1,30 +1,9 @@ [ - { - "bytes": "1", - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "uint8" - }, - { - "bytes": "1", - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "bool" - }, - { - "bytes": "20", - "label": "guardian", - "offset": 2, - "slot": "0", - "type": "address" - }, { "bytes": "32", "label": "pauseTimestamps", "offset": 0, - "slot": "1", + "slot": "0", "type": "mapping(address => uint256)" } ] \ No newline at end of file diff --git a/snapshots/storageLayout/SystemConfigGlobal.json b/snapshots/storageLayout/SystemConfigGlobal.json new file mode 100644 index 000000000..2bd347c54 --- /dev/null +++ b/snapshots/storageLayout/SystemConfigGlobal.json @@ -0,0 +1,65 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "address" + }, + { + "bytes": "20", + "label": "_manager", + "offset": 0, + "slot": "52", + "type": "address" + }, + { + "bytes": "1536", + "label": "__gap", + "offset": 0, + "slot": "53", + "type": "uint256[48]" + }, + { + "bytes": "32", + "label": "validPCR0s", + "offset": 0, + "slot": "101", + "type": "mapping(bytes32 => bool)" + }, + { + "bytes": "32", + "label": "signerPCR0", + "offset": 0, + "slot": "102", + "type": "mapping(address => bytes32)" + }, + { + "bytes": "32", + "label": "isValidProposer", + "offset": 0, + "slot": "103", + "type": "mapping(address => bool)" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/TEEVerifier.json b/snapshots/storageLayout/TEEVerifier.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/snapshots/storageLayout/TEEVerifier.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/dispute/DisputeGameFactory.sol b/src/dispute/DisputeGameFactory.sol index 6129c8305..bdc189a58 100644 --- a/src/dispute/DisputeGameFactory.sol +++ b/src/dispute/DisputeGameFactory.sol @@ -139,18 +139,69 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable (gameType_, timestamp_, proxy_) = (gameType, timestamp, IDisputeGame(proxy)); } + function create( + GameType _gameType, + Claim _rootClaim, + bytes calldata _extraData + ) + external + payable + returns (IDisputeGame proxy_) + { + proxy_ = _createGameImpl(_gameType, _rootClaim, _extraData); + proxy_.initialize{ value: msg.value }(); + _finalizeGameCreation(_gameType, _rootClaim, _extraData, proxy_); + } + + function createWithInitData( + GameType _gameType, + Claim _rootClaim, + bytes calldata _extraData, + bytes calldata _initData + ) + external + payable + returns (IDisputeGame proxy_) + { + proxy_ = _createGameImpl(_gameType, _rootClaim, _extraData); + proxy_.initializeWithInitData{ value: msg.value }(_initData); + _finalizeGameCreation(_gameType, _rootClaim, _extraData, proxy_); + } + /// @notice Creates a new DisputeGame proxy contract. /// @param _gameType The type of the DisputeGame - used to decide the proxy implementation. /// @param _rootClaim The root claim of the DisputeGame. /// @param _extraData Any extra data that should be provided to the created dispute game. - /// @return proxy_ The address of the created DisputeGame proxy. - function create( + /// @param proxy_ The address of the created DisputeGame proxy. + function _finalizeGameCreation( + GameType _gameType, + Claim _rootClaim, + bytes calldata _extraData, + IDisputeGame proxy_ + ) + internal + { + // 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); + emit DisputeGameCreated(address(proxy_), _gameType, _rootClaim); + } + + function _createGameImpl( GameType _gameType, Claim _rootClaim, bytes calldata _extraData ) - external - payable + internal returns (IDisputeGame proxy_) { // Grab the implementation contract for the given `GameType`. @@ -165,7 +216,9 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable // Get the hash of the parent block. bytes32 parentHash = blockhash(block.number - 1); - if (gameArgs[_gameType].length == 0) { + // Cache gameArgs to avoid stack-too-deep errors + bytes memory implArgs = gameArgs[_gameType]; + if (implArgs.length == 0) { // Clone the implementation contract and initialize it with the given parameters. // // CWIA Calldata Layout: @@ -194,26 +247,9 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable // └──────────────────────┴─────────────────────────────────────┘ proxy_ = IDisputeGame( address(impl) - .clone( - abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, gameArgs[_gameType]) - ) + .clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) ); } - proxy_.initialize{ value: msg.value }(); - - // 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); - emit DisputeGameCreated(address(proxy_), _gameType, _rootClaim); } /// @notice Returns a unique identifier for the given dispute game parameters. diff --git a/src/dispute/zk/OPSuccinctFaultDisputeGame.sol b/src/dispute/zk/OPSuccinctFaultDisputeGame.sol index f7c1e6e76..61579b105 100644 --- a/src/dispute/zk/OPSuccinctFaultDisputeGame.sol +++ b/src/dispute/zk/OPSuccinctFaultDisputeGame.sol @@ -321,6 +321,8 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); } + function initializeWithInitData(bytes calldata) external payable { } + /// @notice The L2 block number for which this game is proposing an output root. function l2SequenceNumber() public pure returns (uint256 l2SequenceNumber_) { l2SequenceNumber_ = _getArgUint256(0x54); diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol new file mode 100644 index 000000000..21d255d48 --- /dev/null +++ b/src/multiproof/AggregateVerifier.sol @@ -0,0 +1,991 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +// Optimism +import { + AlreadyInitialized, + BondTransferFailed, + ClaimAlreadyResolved, + GameNotFinalized, + GameNotInProgress, + GameNotResolved, + GamePaused, + NoCreditToClaim +} from "src/dispute/lib/Errors.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { Claim, GameStatus, GameType, Hash, Proposal, Timestamp } from "src/dispute/lib/Types.sol"; + +// Solady +import { Clone } from "@solady/utils/Clone.sol"; +import { FixedPointMathLib } from "@solady/utils/FixedPointMathLib.sol"; +import { ReentrancyGuard } from "@solady/utils/ReentrancyGuard.sol"; + +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; + +contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { + //////////////////////////////////////////////////////////////// + // Enums // + //////////////////////////////////////////////////////////////// + + /// @notice The type of proof. Can be expanded for different types of ZK proofs. + enum ProofType { + TEE, + ZK + } + + //////////////////////////////////////////////////////////////// + // Constants // + //////////////////////////////////////////////////////////////// + /// @notice The slow finalization delay. + uint64 public constant SLOW_FINALIZATION_DELAY = 7 days; + + /// @notice The fast finalization delay. + uint64 public constant FAST_FINALIZATION_DELAY = 1 days; + + /// @notice The EIP-2935 blockhash history contract address (deployed post-Pectra). + /// @dev This contract stores blockhashes for the last ~8192 blocks, extending the + /// 256-block window of the native blockhash() opcode. + address public constant EIP2935_CONTRACT = 0x0000F90827F1C53a10cb7A02335B175320002935; + + /// @notice The maximum number of blocks that blockhash() can look back. + uint256 public constant BLOCKHASH_WINDOW = 256; + + /// @notice The maximum number of blocks that EIP-2935 can look back (~8192). + uint256 public constant EIP2935_WINDOW = 8191; + + /// @notice For when the game no longer accepts proofs and prevents resolution. + int8 internal constant NEGATIVE_PROOF_COUNT = type(int8).min; + + //////////////////////////////////////////////////////////////// + // Immutables // + //////////////////////////////////////////////////////////////// + /// @notice The anchor state registry. + IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; + + /// @notice The dispute game factory. + IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY; + + /// @notice The delayed WETH contract. + IDelayedWETH public immutable DELAYED_WETH; + + /// @notice The TEE prover. + IVerifier public immutable TEE_VERIFIER; + + /// @notice The hash of the TEE image. + bytes32 public immutable TEE_IMAGE_HASH; + + /// @notice The ZK prover. + IVerifier public immutable ZK_VERIFIER; + + /// @notice The hash of the ZK image. + bytes32 public immutable ZK_IMAGE_HASH; + + /// @notice The hash of the rollup configuration. + bytes32 public immutable CONFIG_HASH; + + /// @notice The chain ID of the L2 network this contract argues about. + uint256 public immutable L2_CHAIN_ID; + + /// @notice The block interval between each proposal. + /// @dev The parent's block number + BLOCK_INTERVAL = this proposal's block number. + uint256 public immutable BLOCK_INTERVAL; + + /// @notice The block interval for intermediate proposals. + /// @dev BLOCK_INTERVAL must be divisible by INTERMEDIATE_BLOCK_INTERVAL. + uint256 public immutable INTERMEDIATE_BLOCK_INTERVAL; + + /// @notice The size of the initialize call data. + uint256 internal immutable INITIALIZE_CALLDATA_SIZE; + + /// @notice The game type ID. + GameType internal immutable GAME_TYPE; + + /// @notice The minimum number of proofs required to resolve the game. + uint256 public immutable PROOF_THRESHOLD; + + //////////////////////////////////////////////////////////////// + // State Vars // + //////////////////////////////////////////////////////////////// + /// @notice The starting timestamp of the game. + Timestamp public createdAt; + + /// @notice The timestamp of the game's global resolution. + Timestamp public resolvedAt; + + /// @notice The current status of the game. + GameStatus public status; + + /// @notice Flag for the `initialize` function to prevent re-initialization. + bool internal initialized; + + /// @notice A boolean for whether or not the game type was respected when the game was created. + bool public wasRespectedGameTypeWhenCreated; + + /// @notice The starting output root of the game that is proven from in case of a challenge. + /// @dev This should match the claim root of the parent game. + Proposal public startingOutputRoot; + + /// @notice The address that can claim the bond. + address public bondRecipient; + + /// @notice Whether or not the bond has been unlocked. + bool public bondUnlocked; + + /// @notice Whether or not the bond has been claimed. + bool public bondClaimed; + + /// @notice The amount of the bond. + uint256 public bondAmount; + + /// @notice The address of the game that countered this game. + address public counteredByGameAddress; + + /// @notice The address that provided a proof of the given type. + mapping(ProofType => address) internal proofTypeToProver; + + /// @notice The timestamp of the game's expected resolution. + Timestamp public expectedResolution; + + /// @notice The number of proofs provided. + /// @dev Can be negative if a ZK proof is nullified. + int8 public proofCount; + + //////////////////////////////////////////////////////////////// + // Events // + //////////////////////////////////////////////////////////////// + + /// @notice Emitted when the game is resolved. + /// @param status The status of the game. + event Resolved(GameStatus status); + + /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. + /// @param challenger The address of the challenger. + /// @param game The game used to challenge this proposal. + event Challenged(address indexed challenger, IDisputeGame game); + + /// @notice Emitted when the game is proved. + /// @param prover The address of the prover. + /// @param proofType The type of proof. + event Proved(address indexed prover, ProofType indexed proofType); + + /// @notice Emitted when the game is nullified. + /// @param nullifier The address of the nullifier. + /// @param intermediateRootIndex The index of the intermediate root. + /// @param intermediateRoot The intermediate root. + event Nullified(address indexed nullifier, uint256 intermediateRootIndex, bytes32 intermediateRoot); + + /// @notice Emitted when the credit is claimed. + /// @param recipient The address of the recipient. + /// @param amount The amount of credit claimed. + event CreditClaimed(address indexed recipient, uint256 amount); + + //////////////////////////////////////////////////////////////// + // Errors // + //////////////////////////////////////////////////////////////// + /// @notice When the block interval or intermediate block interval is invalid. + error InvalidBlockInterval(uint256 blockInterval, uint256 intermediateBlockInterval); + + /// @notice When the block number is unexpected. + error UnexpectedBlockNumber(uint256 expectedBlockNumber, uint256 actualBlockNumber); + + /// @notice When the game is over. + error GameOver(); + + /// @notice When the game is not over. + error GameNotOver(); + + /// @notice When the game is invalid. + error InvalidGame(); + + /// @notice When the parent game is invalid. + error InvalidParentGame(); + + /// @notice When the parent game has not resolved. + error ParentGameNotResolved(); + + /// @notice When there is no proof of the given type. + error MissingProof(ProofType proofType); + + /// @notice When the proof has already been verified. + error AlreadyProven(ProofType proofType); + + /// @notice When the proof is invalid. + error InvalidProof(); + + /// @notice When an invalid proof type is provided. + error InvalidProofType(); + + /// @notice When no proof was provided. + error NoProofProvided(); + + /// @notice When the countered by game is invalid. + error InvalidCounteredByGame(); + + /// @notice When the countered by game is not resolved. + error CounteredByGameNotResolved(); + + /// @notice When the bond recipient is empty. + error BondRecipientEmpty(); + + /// @notice When the intermediate root index is invalid. + error InvalidIntermediateRootIndex(); + + /// @notice When the intermediate root is the same as the proposed intermediate root. + error IntermediateRootSameAsProposed(); + + /// @notice When the intermediate root does not match the claim. + error IntermediateRootMismatch(bytes32 intermediateRoot, bytes32 claim); + + /// @notice Thrown when the L1 origin block is too old to verify. + error L1OriginTooOld(uint256 l1OriginNumber, uint256 currentBlock); + + /// @notice Thrown when the L1 origin block number is in the future. + error L1OriginInFuture(uint256 l1OriginNumber, uint256 currentBlock); + + /// @notice Thrown when the L1 origin hash doesn't match the actual blockhash. + error L1OriginHashMismatch(bytes32 claimed, bytes32 actual); + + /// @notice Thrown when there are not enough proofs to resolve the game. + error NotEnoughProofs(); + + /// @notice Thrown when the proof threshold is not positive. + error InvalidProofThreshold(); + + /// @param gameType_ The game type. + /// @param anchorStateRegistry_ The anchor state registry. + /// @param delayedWETH The delayed WETH contract. + /// @param teeVerifier The TEE verifier. + /// @param zkVerifier The ZK verifier. + /// @param teeImageHash The hash of the TEE image. + /// @param zkImageHash The hash of the ZK image. + /// @param configHash The hash of the rollup configuration. + /// @param l2ChainId The chain ID of the L2 network. + /// @param blockInterval The block interval. + /// @param intermediateBlockInterval The intermediate block interval. + /// @param proofThreshold The minimum number of proofs required to resolve the game. + constructor( + GameType gameType_, + IAnchorStateRegistry anchorStateRegistry_, + IDelayedWETH delayedWETH, + IVerifier teeVerifier, + IVerifier zkVerifier, + bytes32 teeImageHash, + bytes32 zkImageHash, + bytes32 configHash, + uint256 l2ChainId, + uint256 blockInterval, + uint256 intermediateBlockInterval, + uint256 proofThreshold + ) { + // Block interval and intermediate block interval must be positive and divisible. + if (blockInterval == 0 || intermediateBlockInterval == 0 || blockInterval % intermediateBlockInterval != 0) { + revert InvalidBlockInterval(blockInterval, intermediateBlockInterval); + } + + // Proof threshold must be between 1 and 2. + if (proofThreshold != 1 && proofThreshold != 2) revert InvalidProofThreshold(); + + // Set up initial game state. + GAME_TYPE = gameType_; + ANCHOR_STATE_REGISTRY = anchorStateRegistry_; + DISPUTE_GAME_FACTORY = ANCHOR_STATE_REGISTRY.disputeGameFactory(); + DELAYED_WETH = delayedWETH; + TEE_VERIFIER = teeVerifier; + ZK_VERIFIER = zkVerifier; + TEE_IMAGE_HASH = teeImageHash; + ZK_IMAGE_HASH = zkImageHash; + CONFIG_HASH = configHash; + L2_CHAIN_ID = l2ChainId; + BLOCK_INTERVAL = blockInterval; + INTERMEDIATE_BLOCK_INTERVAL = intermediateBlockInterval; + PROOF_THRESHOLD = proofThreshold; + + INITIALIZE_CALLDATA_SIZE = 0x7E + 0x20 * intermediateOutputRootsCount(); + } + + /// @notice Initializes the contract. + /// @param proof The proof. + /// @dev This function may only be called once. + /// @dev First byte of the proof is the proof type. + function initializeWithInitData(bytes calldata proof) external payable virtual { + // The game must not have already been initialized. + if (initialized) revert AlreadyInitialized(); + + // 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: 0x7E + // - 0x04 selector + // - 0x14 creator address + // - 0x20 root claim + // - 0x20 l1 head + // - 0x20 extraData (l2BlockNumber) + // - 0x04 extraData (parentIndex) + // - 0x20 x (BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL) extraData (intermediate roots) + // - 0x02 CWIA bytes + + // - 0x20 proof length location + // - 0x20 proof length + // - ((proof.length + 32 - 1)/ 32) * 32 (round up to the nearest 32 bytes) + uint256 proofLength = (proof.length + 32 - 1) / 32 * 32; + uint256 expectedCallDataSize = INITIALIZE_CALLDATA_SIZE + 0x40 + proofLength; + assembly { + if iszero(eq(calldatasize(), expectedCallDataSize)) { + // Store the selector for `BadExtraData()` & revert. + mstore(0x00, 0x9824bdab) + revert(0x1C, 0x04) + } + } + + // Last intermediate root has to match the proposal's claim + if (intermediateOutputRoot(intermediateOutputRootsCount() - 1) != rootClaim().raw()) { + revert IntermediateRootMismatch( + intermediateOutputRoot(intermediateOutputRootsCount() - 1), rootClaim().raw() + ); + } + + // The first game is initialized with a parent index of uint32.max. + if (parentIndex() != type(uint32).max) { + // For subsequent games, get the parent game's information. + (,, IDisputeGame parentGame) = DISPUTE_GAME_FACTORY.gameAtIndex(parentIndex()); + + // Parent game must be respected, not blacklisted, not retired, and not challenged. + if (!_isValidGame(parentGame)) revert InvalidParentGame(); + + startingOutputRoot = Proposal({ + l2SequenceNumber: parentGame.l2SequenceNumber(), root: Hash.wrap(parentGame.rootClaim().raw()) + }); + } else { + // When there is no parent game, the starting output root is the anchor state for the game type. + (startingOutputRoot.root, startingOutputRoot.l2SequenceNumber) = ANCHOR_STATE_REGISTRY.getAnchorRoot(); + } + + // The block number must be BLOCK_INTERVAL blocks after the starting block number. + if (l2SequenceNumber() != startingOutputRoot.l2SequenceNumber + BLOCK_INTERVAL) { + revert UnexpectedBlockNumber(startingOutputRoot.l2SequenceNumber + BLOCK_INTERVAL, l2SequenceNumber()); + } + + // Set the game as initialized. + initialized = true; + + // Set the game's starting timestamp. + createdAt = Timestamp.wrap(uint64(block.timestamp)); + + // Set the game as respected if the game type is respected. + wasRespectedGameTypeWhenCreated = + GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); + + // Set expected resolution. + expectedResolution = Timestamp.wrap(type(uint64).max); + + // Verify the proof. + ProofType proofType = ProofType(uint8(proof[0])); + + bytes32 l1OriginHash = bytes32(proof[1:33]); + uint256 l1OriginNumber = uint256(bytes32(proof[33:65])); + // Verify claimed L1 origin hash matches actual blockhash + _verifyL1Origin(l1OriginHash, l1OriginNumber); + + _verifyProof( + proof[65:], + proofType, + gameCreator(), + l1OriginHash, + startingOutputRoot.root.raw(), + startingOutputRoot.l2SequenceNumber, + rootClaim().raw(), + l2SequenceNumber(), + intermediateOutputRoots() + ); + + _updateProvingData(proofType, gameCreator()); + + emit Proved(gameCreator(), proofType); + + // Deposit the bond. + bondAmount = msg.value; + DELAYED_WETH.deposit{ value: msg.value }(); + } + + /// @notice Verifies a proof for the current game. + /// @param proofBytes The proof. + /// @dev The first byte of the proof is the proof type. + function verifyProposalProof(bytes calldata proofBytes) external { + // The game must be in progress. + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + + // The game must not be over. + if (gameOver()) revert GameOver(); + + ProofType proofType = ProofType(uint8(proofBytes[0])); + if (proofTypeToProver[proofType] != address(0)) revert AlreadyProven(proofType); + + _verifyProof( + proofBytes[1:], + proofType, + msg.sender, + l1Head().raw(), + startingOutputRoot.root.raw(), + startingOutputRoot.l2SequenceNumber, + rootClaim().raw(), + l2SequenceNumber(), + intermediateOutputRoots() + ); + _updateProvingData(proofType, msg.sender); + + emit Proved(msg.sender, proofType); + } + + /// @notice Resolves the game after a proof has been provided and enough time has passed. + function resolve() external returns (GameStatus) { + // The game must be in progress. + if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); + + GameStatus parentGameStatus = _getParentGameStatus(); + // The parent game must have resolved. + if (parentGameStatus == GameStatus.IN_PROGRESS) revert ParentGameNotResolved(); + + // If the parent game's claim is invalid, blacklisted, or retired, then the current game's claim is invalid. + if (parentGameStatus == GameStatus.CHALLENGER_WINS) { + status = GameStatus.CHALLENGER_WINS; + } else { + // Game must be completed with a valid proof. + if (!gameOver()) revert GameNotOver(); + status = GameStatus.DEFENDER_WINS; + } + + // casting to 'int256' is safe because 1 <= PROOF_THRESHOLD <= 2 + // forge-lint: disable-next-line(unsafe-typecast) + if (proofCount < int256(PROOF_THRESHOLD)) revert NotEnoughProofs(); + + // Bond is refunded as no challenge was made or parent is invalid. + bondRecipient = gameCreator(); + // Mark the game as resolved. + resolvedAt = Timestamp.wrap(uint64(block.timestamp)); + emit Resolved(status); + + return status; + } + + /// @notice Challenges the TEE proof with a ZK proof. + /// @param gameIndex The index of the game used to challenge. + /// @dev The game used to challenge must have a ZK proof for the same + /// block number but a different root claim as the current game. + function challenge(uint256 gameIndex) external { + // Can only challenge a game that has not been challenged or resolved yet. + if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); + + // This game cannot be blacklisted or retired. + if (!_isValidGame(IDisputeGame(address(this)))) revert InvalidGame(); + + // The parent game cannot have been challenged. + if (_getParentGameStatus() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); + + // The TEE prover must not be empty. + // You should nullify the game if a ZK proof has already been provided. + if (proofTypeToProver[ProofType.TEE] == address(0)) revert MissingProof(ProofType.TEE); + if (proofTypeToProver[ProofType.ZK] != address(0)) revert AlreadyProven(ProofType.ZK); + + // Prevents challenging after TEE nullification. + if (proofCount != 1) revert NotEnoughProofs(); + + (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); + + // The game must be a valid game used to challenge. + if (!_isValidChallengingGame(game)) revert InvalidGame(); + + AggregateVerifier challengingGame = AggregateVerifier(address(game)); + + // The ZK prover must not be empty. + if (challengingGame.zkProver() == address(0)) revert MissingProof(ProofType.ZK); + + // Update the counteredBy address. + counteredByGameAddress = address(challengingGame); + + // Set the game as challenged. + status = GameStatus.CHALLENGER_WINS; + + // Prevent resolution if any proof was somehow able to be provided later. + proofCount = NEGATIVE_PROOF_COUNT; + + // Update the expected resolution. + _updateExpectedResolution(); + + // Set the bond recipient. + // Bond cannot be claimed until the game used to challenge resolves as DEFENDER_WINS. + bondRecipient = challengingGame.zkProver(); + + // Emit the challenged event. + emit Challenged(challengingGame.zkProver(), game); + } + + /// @notice Nullifies the game if a soundness issue is found. + /// @param proofBytes The proof. + /// @param intermediateRootIndex Index of the intermediate root to challenge. + /// @param intermediateRootToProve The intermediate root that the proof claims to be correct. + /// @dev The first byte of the proof is the proof type. + function nullify( + bytes calldata proofBytes, + uint256 intermediateRootIndex, + bytes32 intermediateRootToProve + ) + external + { + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + + if (intermediateRootIndex >= intermediateOutputRootsCount()) revert InvalidIntermediateRootIndex(); + + bytes32 proposedIntermediateRoot = intermediateOutputRoot(intermediateRootIndex); + if (proposedIntermediateRoot == intermediateRootToProve) revert IntermediateRootSameAsProposed(); + + ProofType proofType = ProofType(uint8(proofBytes[0])); + + if (proofTypeToProver[proofType] == address(0)) revert MissingProof(proofType); + + bytes32 startingRoot = intermediateRootIndex == 0 + ? startingOutputRoot.root.raw() + : intermediateOutputRoot(intermediateRootIndex - 1); + uint256 startingL2SequenceNumber = + startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL; + uint256 endingL2SequenceNumber = startingL2SequenceNumber + INTERMEDIATE_BLOCK_INTERVAL; + + _verifyProof( + proofBytes[1:], + proofType, + msg.sender, + l1Head().raw(), + startingRoot, + startingL2SequenceNumber, + intermediateRootToProve, + endingL2SequenceNumber, + abi.encodePacked(intermediateRootToProve) + ); + + if (proofType == ProofType.ZK) { + // Since a ZK proof vetoes a TEE proof, we make the proof count negative for ZK nullifications. + // This ensures that the game cannot resolve if a TEE proof can somehow be provided later. + proofCount = NEGATIVE_PROOF_COUNT; + + // Set the game as challenged so that child games can't resolve. + status = GameStatus.CHALLENGER_WINS; + } else if (proofType == ProofType.TEE) { + // The status is not updated here to still allow a ZK proof to be provided later. + proofCount -= 1; + + // Increase the expected resolution by the SLOW_FINALIZATION_DELAY. + // This gives us enough time to nullify a ZK proof if it was already provided. + // Otherwise the below _updateExpectedResolution() makes the expected resolution + // the maximum timestamp. + expectedResolution = Timestamp.wrap(uint64(block.timestamp + SLOW_FINALIZATION_DELAY)); + } + + // If there are no proofs, the expected resolution will be set to type(uint64).max. + // It's not possible to go from FAST_FINALIZATION_DELAY to SLOW_FINALIZATION_DELAY + // as we can only do a ZK nullification in this case, causing proofCount to be negative. + _updateExpectedResolution(); + + // Refund the bond as either a ZK proof was nullified or a ZK proof has to be provided later. + bondRecipient = gameCreator(); + + emit Nullified(msg.sender, intermediateRootIndex, intermediateRootToProve); + } + + /// @notice Claim the credit belonging to the bond recipient. Reverts if the game isn't + /// finalized or if the bond transfer fails. + function claimCredit() external nonReentrant { + // The bond must not have been claimed yet. + if (bondClaimed) revert NoCreditToClaim(); + + // The bond recipient must not be empty. + if (bondRecipient == address(0)) revert BondRecipientEmpty(); + + // If this game was challenged, the countered by game must be valid or else the bond is refunded. + if (counteredByGameAddress != address(0)) { + GameStatus counteredByGameStatus = IDisputeGame(counteredByGameAddress).status(); + if (counteredByGameStatus == GameStatus.IN_PROGRESS) { + revert CounteredByGameNotResolved(); + } + // If the countered by game is invalid or not resolved, the bond is refunded. + if (!_isValidChallengingGame(IDisputeGame(counteredByGameAddress))) { + bondRecipient = gameCreator(); + } + } + + if (!bondUnlocked) { + DELAYED_WETH.unlock(bondRecipient, bondAmount); + bondUnlocked = true; + return; + } + + bondClaimed = true; + // This can fail if this game was challenged and the countered by game is + // blacklisted/retired after it resolved to DEFENDER_WINS. + // The centralized functions in DELAYED_WETH will handle this as it's a already + // a very centralized action to blacklist/retire a valid challenging game. + DELAYED_WETH.withdraw(bondRecipient, bondAmount); + + // Transfer the credit to the bond recipient. + (bool success,) = bondRecipient.call{ value: bondAmount }(hex""); + if (!success) revert BondTransferFailed(); + + // Emit the credit claimed event. + emit CreditClaimed(bondRecipient, bondAmount); + } + + /// @notice Closes the game by trying to update the anchor state. + function closeGame() external { + // We won't close the game if the system is currently paused. + if (ANCHOR_STATE_REGISTRY.paused()) { + revert GamePaused(); + } + + // Make sure that the game is resolved. + // AnchorStateRegistry should be checking this but we're being defensive here. + if (resolvedAt.raw() == 0) { + revert GameNotResolved(); + } + + // Game must be finalized according to the AnchorStateRegistry. + bool finalized = ANCHOR_STATE_REGISTRY.isGameFinalized(IDisputeGame(address(this))); + if (!finalized) { + revert GameNotFinalized(); + } + + // Try to update the anchor game first. Won't always succeed because delays can lead + // to situations in which this game might not be eligible to be a new anchor game. + // eip150-safe + try ANCHOR_STATE_REGISTRY.setAnchorState(IDisputeGame(address(this))) { } catch { } + } + + /// @notice The starting block number of the game. + function startingBlockNumber() external view returns (uint256) { + return startingOutputRoot.l2SequenceNumber; + } + + /// @notice The starting output root of the game. + function startingRootHash() external view returns (Hash) { + return startingOutputRoot.root; + } + + /// @notice A compliant implementation of this interface should return the components of the + /// game UUID's preimage provided in the cwia payload. The preimage of the UUID is + /// constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes + /// concatenation. + /// @return gameType_ The type of proof system being used. + /// @return rootClaim_ The root claim of the DisputeGame. + /// @return extraData_ Any extra data supplied to the dispute game contract by the creator. + function gameData() external view returns (GameType, Claim, bytes memory) { + return (GAME_TYPE, rootClaim(), extraData()); + } + + /// @notice Address that provided a TEE proof. + function teeProver() external view returns (address) { + return proofTypeToProver[ProofType.TEE]; + } + + /// @notice Address that provided a ZK proof. + function zkProver() external view returns (address) { + return proofTypeToProver[ProofType.ZK]; + } + + /// @notice The game type. + /// @dev For compliance with the IDisputeGame interface. + function gameType() external view returns (GameType) { + return GAME_TYPE; + } + + /// @notice The anchor state registry. + /// @dev Needed for anchorStateRegistry.isGameRegistered() + function anchorStateRegistry() external view returns (IAnchorStateRegistry) { + return ANCHOR_STATE_REGISTRY; + } + + /// @notice Determines if the game is finished. + function gameOver() public view returns (bool) { + return expectedResolution.raw() <= block.timestamp; + } + + /// @notice The number of intermediate output roots. + /// @dev At least one as the proposal's root claim is considered an intermediate root. + function intermediateOutputRootsCount() public view returns (uint256) { + return (BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL); + } + + /// @notice The intermediate output roots of the game. + function intermediateOutputRoots() public view returns (bytes memory) { + return _getArgBytes(0x78, 0x20 * intermediateOutputRootsCount()); + } + + /// @notice The intermediate output root at the given index. + /// @param index The index of the intermediate output root. + function intermediateOutputRoot(uint256 index) public view returns (bytes32) { + if (index >= intermediateOutputRootsCount()) revert InvalidIntermediateRootIndex(); + return _getArgBytes32(0x78 + 0x20 * index); + } + + /// @notice Getter for the extra data. + function extraData() public view returns (bytes memory) { + // The extra data starts at the second word within the cwia calldata and + // is 36 + 32 x intermediateRootsCount() bytes long. + // 32 bytes are for the l2BlockNumber + // 4 bytes are for the parentIndex + // 32 bytes are for each intermediate root + return _getArgBytes(0x54, 0x24 + 0x20 * intermediateOutputRootsCount()); + } + + /// @notice Getter for the creator of the dispute game. + function gameCreator() public pure returns (address) { + return _getArgAddress(0x00); + } + + /// @notice Getter for the root claim. + function rootClaim() public pure returns (Claim) { + return Claim.wrap(_getArgBytes32(0x14)); + } + + /// @notice Getter for the parent hash of the L1 block when the dispute game was created. + function l1Head() public pure returns (Hash) { + return Hash.wrap(_getArgBytes32(0x34)); + } + + /// @notice The L2 sequence number for which this game is proposing an output root (in this case - the block + /// number). + function l2SequenceNumber() public pure returns (uint256) { + return _getArgUint256(0x54); + } + + /// @notice The parent index of the game. + function parentIndex() public pure returns (uint32) { + return _getArgUint32(0x74); + } + + /// @notice Updates the expected resolution timestamp. + function _updateExpectedResolution() internal { + uint64 delay; + + if (proofCount >= 2) { + delay = FAST_FINALIZATION_DELAY; + } else if (proofCount >= 1) { + delay = SLOW_FINALIZATION_DELAY; + } else { + // If there are no proofs, don't allow the game to resolve. + expectedResolution = Timestamp.wrap(type(uint64).max); + return; + } + + uint64 newResolution = uint64(block.timestamp) + delay; + expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, expectedResolution.raw()))); + } + + function _updateProvingData(ProofType proofType, address prover) internal { + proofTypeToProver[proofType] = prover; + + // Bond can be reclaimed after a ZK proof is provided. + if (proofType == ProofType.ZK) { + bondRecipient = gameCreator(); + } + + proofCount += 1; + + _updateExpectedResolution(); + } + + function _verifyProof( + bytes calldata proofBytes, + ProofType proofType, + address prover, + bytes32 l1OriginHash, + bytes32 startingRoot, + uint256 startingL2SequenceNumber, + bytes32 endingRoot, + uint256 endingL2SequenceNumber, + bytes memory intermediateRoots + ) + internal + view + { + if (proofBytes.length < 1) revert InvalidProof(); + + if (proofType == ProofType.TEE) { + _verifyTeeProof( + proofBytes, + prover, + l1OriginHash, + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots + ); + } else if (proofType == ProofType.ZK) { + _verifyZkProof( + proofBytes, + prover, + l1OriginHash, + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots + ); + } else { + revert InvalidProofType(); + } + } + + /// @notice Verifies a TEE proof for the current game. + /// @param proofBytes The proof: signature(65). + function _verifyTeeProof( + bytes calldata proofBytes, + address prover, + bytes32 l1OriginHash, + bytes32 startingRoot, + uint256 startingL2SequenceNumber, + bytes32 endingRoot, + uint256 endingL2SequenceNumber, + bytes memory intermediateRoots + ) + internal + view + { + bytes32 journal = keccak256( + abi.encodePacked( + prover, + l1OriginHash, + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots, + CONFIG_HASH, + TEE_IMAGE_HASH + ) + ); + + // Validate the proof. + bytes memory proof = abi.encodePacked(prover, proofBytes); + if (!TEE_VERIFIER.verify(proof, TEE_IMAGE_HASH, journal)) revert InvalidProof(); + } + + /// @notice Verifies a ZK proof for the current game. + /// @param proofBytes The proof: zkProof (variable). + function _verifyZkProof( + bytes calldata proofBytes, + address prover, + bytes32 l1OriginHash, + bytes32 startingRoot, + uint256 startingL2SequenceNumber, + bytes32 endingRoot, + uint256 endingL2SequenceNumber, + bytes memory intermediateRoots + ) + internal + view + { + bytes32 journal = keccak256( + abi.encodePacked( + prover, + l1OriginHash, + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots, + CONFIG_HASH, + ZK_IMAGE_HASH + ) + ); + + // Validate the proof. + if (!ZK_VERIFIER.verify(proofBytes, ZK_IMAGE_HASH, journal)) revert InvalidProof(); + } + + /// @notice Returns the status of the parent game. + /// @dev If the parent game index is `uint32.max`, then the parent game's status is considered as `DEFENDER_WINS`. + function _getParentGameStatus() internal view returns (GameStatus) { + if (parentIndex() != type(uint32).max) { + (,, IDisputeGame parentGame) = DISPUTE_GAME_FACTORY.gameAtIndex(parentIndex()); + if (ANCHOR_STATE_REGISTRY.isGameBlacklisted(parentGame) || ANCHOR_STATE_REGISTRY.isGameRetired(parentGame)) + { + return GameStatus.CHALLENGER_WINS; + } + return parentGame.status(); + } + // If this is the first dispute game (i.e. parent game index is `uint32.max`), then the + // parent game's status is considered as `DEFENDER_WINS`. + return GameStatus.DEFENDER_WINS; + } + + /// @notice Checks if the game is respected, not blacklisted, and not retired. + /// @param game The game to check. + function _isValidGame(IDisputeGame game) internal view returns (bool) { + return ANCHOR_STATE_REGISTRY.isGameRespected(game) && !ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) + && !ANCHOR_STATE_REGISTRY.isGameRetired(game) && (game.status() != GameStatus.CHALLENGER_WINS); + } + + /// @notice Checks if the game is a valid game used to challenge or nullify. + /// @param game The game to check. + function _isValidChallengingGame(IDisputeGame game) internal view returns (bool) { + return + // The game type must be the same. + game.gameType().raw() == GAME_TYPE.raw() && + // The parent game must be the same. + AggregateVerifier(address(game)).parentIndex() == parentIndex() && + // The block number must be the same. + game.l2SequenceNumber() == l2SequenceNumber() && + // The root claim must be different. + game.rootClaim().raw() != rootClaim().raw() && + // The game must be valid. + _isValidGame(game); + } + + /// @notice Verifies that the claimed L1 origin hash matches the actual blockhash. + /// @param l1OriginHash The L1 block hash claimed in the proof. + /// @param l1OriginNumber The L1 block number claimed in the proof. + function _verifyL1Origin(bytes32 l1OriginHash, uint256 l1OriginNumber) internal view { + // Check for future block + if (l1OriginNumber >= block.number) { + revert L1OriginInFuture(l1OriginNumber, block.number); + } + + bytes32 actualHash; + uint256 blockAge = block.number - l1OriginNumber; + + // Prefer blockhash() over EIP-2935 when possible since it's cheaper (no external call). + if (blockAge <= BLOCKHASH_WINDOW) { + actualHash = blockhash(l1OriginNumber); + } else if (blockAge <= EIP2935_WINDOW) { + // EIP-2935 expects raw calldata: exactly 32 bytes containing the block number. + // Using a Solidity interface would add a 4-byte function selector, causing a revert. + // We use a low-level staticcall with raw 32-byte calldata instead. + (bool success, bytes memory result) = EIP2935_CONTRACT.staticcall(abi.encode(l1OriginNumber)); + if (!success || result.length != 32) { + revert L1OriginTooOld(l1OriginNumber, block.number); + } + actualHash = abi.decode(result, (bytes32)); + } else { + revert L1OriginTooOld(l1OriginNumber, block.number); + } + + if (actualHash == bytes32(0)) { + revert L1OriginTooOld(l1OriginNumber, block.number); + } + + if (actualHash != l1OriginHash) { + revert L1OriginHashMismatch(l1OriginHash, actualHash); + } + } + + /// @notice Semantic version. + /// @custom:semver 0.1.0 + function version() public pure virtual returns (string memory) { + return "0.1.0"; + } +} diff --git a/src/multiproof/mocks/MockDevSystemConfigGlobal.sol b/src/multiproof/mocks/MockDevSystemConfigGlobal.sol new file mode 100644 index 000000000..dfd61fa55 --- /dev/null +++ b/src/multiproof/mocks/MockDevSystemConfigGlobal.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; + +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; + +/// @title DevSystemConfigGlobal +/// @notice Development version of SystemConfigGlobal with bypassed attestation for testing. +/// @dev This contract adds addDevSigner() which bypasses AWS Nitro attestation verification. +/// DO NOT deploy this contract to production networks. +contract DevSystemConfigGlobal is SystemConfigGlobal { + constructor(INitroEnclaveVerifier nitroVerifier) SystemConfigGlobal(nitroVerifier) { } + + /// @notice Registers a signer for testing (bypasses attestation verification). + /// @dev Only callable by owner. For development/testing use only. + /// @param signer The address of the signer to register. + /// @param pcr0Hash The PCR0 hash to associate with this signer. + function addDevSigner(address signer, bytes32 pcr0Hash) external onlyOwner { + signerPCR0[signer] = pcr0Hash; + emit SignerRegistered(signer, pcr0Hash); + } +} diff --git a/src/multiproof/mocks/MockSystemConfig.sol b/src/multiproof/mocks/MockSystemConfig.sol new file mode 100644 index 000000000..5182c4c0f --- /dev/null +++ b/src/multiproof/mocks/MockSystemConfig.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +contract MockSystemConfig { + address public guardian; + + constructor() { + guardian = msg.sender; + } + + function paused() public pure returns (bool) { + return false; + } +} diff --git a/src/multiproof/mocks/MockVerifier.sol b/src/multiproof/mocks/MockVerifier.sol new file mode 100644 index 000000000..fff83d15d --- /dev/null +++ b/src/multiproof/mocks/MockVerifier.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; + +contract MockVerifier is IVerifier { + function verify(bytes calldata, bytes32, bytes32) external pure returns (bool) { + return true; + } +} diff --git a/src/multiproof/tee/SystemConfigGlobal.sol b/src/multiproof/tee/SystemConfigGlobal.sol new file mode 100644 index 000000000..3cce9895f --- /dev/null +++ b/src/multiproof/tee/SystemConfigGlobal.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { + INitroEnclaveVerifier, + ZkCoProcessorType, + VerifierJournal, + VerificationResult, + Pcr, + Bytes48 +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; +import { OwnableManagedUpgradeable } from "lib/op-enclave/contracts/src/OwnableManagedUpgradeable.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; + +/// @title SystemConfigGlobal +/// @notice Manages TEE signer registration via ZK-verified AWS Nitro attestation. +/// @dev Signers are registered by providing a ZK proof of a valid AWS Nitro attestation document, +/// verified through an external NitroEnclaveVerifier contract (Risc0). +/// Each signer is associated with the PCR0 (enclave image hash) from their attestation, +/// which allows TEEVerifier to validate that a signer was registered with a specific image. +contract SystemConfigGlobal is OwnableManagedUpgradeable, ISemver { + /// @notice Maximum age of an attestation document (60 minutes), in seconds. + uint256 public constant MAX_AGE = 60 minutes; + + /// @notice Conversion factor from milliseconds to seconds. + /// @dev AWS Nitro attestation timestamps are in milliseconds since epoch, + /// but block.timestamp is in seconds. + uint256 private constant MS_PER_SECOND = 1000; + + /// @notice The external NitroEnclaveVerifier contract used for ZK attestation verification. + INitroEnclaveVerifier public immutable NITRO_VERIFIER; + + /// @notice Mapping of valid PCR0s (enclave image hashes) attested from AWS Nitro. + /// @dev Only attestations with a PCR0 in this mapping can register signers. + mapping(bytes32 => bool) public validPCR0s; + + /// @notice Mapping of signer address to the PCR0 they were registered with. + /// @dev A non-zero value indicates the signer is valid and was registered with that PCR0. + mapping(address => bytes32) public signerPCR0; + + /// @notice Mapping of whether an address is a valid proposer. + mapping(address => bool) public isValidProposer; + + /// @notice Emitted when a signer is registered. + event SignerRegistered(address indexed signer, bytes32 indexed pcr0); + + /// @notice Emitted when a signer is deregistered. + event SignerDeregistered(address indexed signer); + + /// @notice Emitted when a PCR0 is registered. + event PCR0Registered(bytes32 indexed pcr0Hash); + + /// @notice Emitted when a PCR0 is deregistered. + event PCR0Deregistered(bytes32 indexed pcr0Hash); + + /// @notice Emitted when the proposer is set. + event ProposerSet(address indexed proposer, bool isValid); + + /// @notice Thrown when the PCR0 in the attestation is not registered as valid. + error InvalidPCR0(); + + /// @notice Thrown when the attestation document is too old. + error AttestationTooOld(); + + /// @notice Thrown when the ZK attestation verification fails. + error AttestationVerificationFailed(); + + /// @notice Thrown when PCR0 (index 0) is not found in the attestation's PCR list. + error PCR0NotFound(); + + /// @notice Thrown when the attestation's public key is too short to derive a signer address. + error InvalidPublicKey(); + + constructor(INitroEnclaveVerifier nitroVerifier) { + NITRO_VERIFIER = nitroVerifier; + initialize({ initialOwner: address(0xdEaD), initialManager: address(0xdEaD) }); + } + + /// @notice Sets the proposer address. + /// @param proposer The proposer address. + /// @param isValid Whether the proposer is valid. + function setProposer(address proposer, bool isValid) external onlyOwner { + isValidProposer[proposer] = isValid; + emit ProposerSet(proposer, isValid); + } + + /// @notice Registers a PCR0 (enclave image hash) as valid. + /// @param pcr0 The raw PCR0 bytes from the enclave. + function registerPCR0(bytes calldata pcr0) external onlyOwner { + bytes32 pcr0Hash = keccak256(pcr0); + validPCR0s[pcr0Hash] = true; + emit PCR0Registered(pcr0Hash); + } + + /// @notice Deregisters a PCR0 (enclave image hash). + /// @param pcr0 The raw PCR0 bytes from the enclave. + function deregisterPCR0(bytes calldata pcr0) external onlyOwner { + bytes32 pcr0Hash = keccak256(pcr0); + delete validPCR0s[pcr0Hash]; + emit PCR0Deregistered(pcr0Hash); + } + + /// @notice Registers a signer using a ZK proof of an AWS Nitro attestation document. + /// @dev The ZK proof must verify a valid attestation that: + /// 1. Has a valid AWS Nitro certificate chain (verified offchain via ZK) + /// 2. Contains a PCR0 that has been pre-registered via registerPCR0 + /// 3. Is less than MAX_AGE old + /// @param output The ABI-encoded VerifierJournal from the ZK proof. + /// @param proofBytes The Risc0 ZK proof bytes. + function registerSigner(bytes calldata output, bytes calldata proofBytes) external onlyOwnerOrManager { + VerifierJournal memory journal = NITRO_VERIFIER.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + if (journal.result != VerificationResult.Success) revert AttestationVerificationFailed(); + + if (journal.timestamp / MS_PER_SECOND + MAX_AGE <= block.timestamp) revert AttestationTooOld(); + + bytes32 pcr0Hash = _extractPCR0Hash(journal.pcrs); + if (!validPCR0s[pcr0Hash]) revert InvalidPCR0(); + + // The publicKey is encoded in ANSI X9.62 format: 0x04 || x || y (65 bytes). + // We skip the first byte (0x04 prefix) when hashing to derive the address. + bytes memory pubKey = journal.publicKey; + if (pubKey.length != 65) revert InvalidPublicKey(); + bytes32 publicKeyHash; + assembly { + publicKeyHash := keccak256(add(pubKey, 0x21), sub(mload(pubKey), 1)) + } + address enclaveAddress = address(uint160(uint256(publicKeyHash))); + + signerPCR0[enclaveAddress] = pcr0Hash; + emit SignerRegistered(enclaveAddress, pcr0Hash); + } + + /// @notice Deregisters a signer. + /// @param signer The address of the signer to deregister. + function deregisterSigner(address signer) external onlyOwnerOrManager { + delete signerPCR0[signer]; + emit SignerDeregistered(signer); + } + + /// @notice Checks if an address is a valid signer. + /// @param signer The address to check. + /// @return True if the signer is registered, false otherwise. + function isValidSigner(address signer) external view returns (bool) { + return signerPCR0[signer] != bytes32(0); + } + + /// @notice Initializes the contract with owner and manager. + /// @param initialOwner The initial owner address. + /// @param initialManager The initial manager address. + function initialize(address initialOwner, address initialManager) public initializer { + __OwnableManaged_init(); + transferOwnership(initialOwner); + transferManagement(initialManager); + } + + /// @notice Semantic version. + /// @custom:semver 0.1.0 + function version() public pure virtual returns (string memory) { + return "0.1.0"; + } + + /// @dev Finds PCR0 (index 0) in the PCR array and returns its keccak256 hash. + function _extractPCR0Hash(Pcr[] memory pcrs) internal pure returns (bytes32) { + for (uint256 i = 0; i < pcrs.length; i++) { + if (pcrs[i].index == 0) { + Bytes48 memory value = pcrs[i].value; + return keccak256(abi.encodePacked(value.first, value.second)); + } + } + revert PCR0NotFound(); + } +} diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol new file mode 100644 index 000000000..5bbf4e90c --- /dev/null +++ b/src/multiproof/tee/TEEVerifier.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; + +import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; + +/// @title TEEVerifier +/// @notice Stateless TEE proof verifier that validates signatures against registered signers. +/// @dev This contract is designed to be used as the TEE_VERIFIER in the AggregateVerifier. +/// It verifies that proofs are signed by enclave addresses registered in SystemConfigGlobal +/// via AWS Nitro attestation, and that the signer's PCR0 matches the claimed imageId. +/// The contract is intentionally stateless - all state related to output proposals and +/// L1 origin verification is managed by the calling contract (e.g., AggregateVerifier). +contract TEEVerifier is IVerifier, ISemver { + /// @notice The SystemConfigGlobal contract that manages valid TEE signers. + /// @dev Signers are registered via AWS Nitro attestation in SystemConfigGlobal. + SystemConfigGlobal public immutable SYSTEM_CONFIG_GLOBAL; + + /// @notice Thrown when the recovered signer is not a valid registered signer. + error InvalidSigner(address signer); + + /// @notice Thrown when the signature format is invalid. + error InvalidSignature(); + + /// @notice Thrown when the signer's registered PCR0 does not match the claimed imageId. + error ImageIdMismatch(bytes32 signerPCR0, bytes32 claimedImageId); + + /// @notice Thrown when the proof format is invalid. + error InvalidProofFormat(); + + /// @notice Thrown when the proposer is not a valid registered proposer. + error InvalidProposer(address proposer); + + /// @notice Constructs the TEEVerifier contract. + /// @param systemConfigGlobal The SystemConfigGlobal contract address. + constructor(SystemConfigGlobal systemConfigGlobal) { + SYSTEM_CONFIG_GLOBAL = systemConfigGlobal; + } + + /// @notice Verifies a TEE proof for a state transition. + /// @param proofBytes The proof: proposer(20) + signature(65) = 85 bytes. + /// @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. + /// @param journal The keccak256 hash of the proof's public inputs. + /// @return valid Whether the proof is valid. + function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view override returns (bool) { + if (proofBytes.length < 85) revert InvalidProofFormat(); + + address proposer = address(bytes20(proofBytes[0:20])); + bytes calldata signature = proofBytes[20:85]; + + // Recover the signer from the signature + // The signature should be over the journal hash directly (not eth-signed-message prefixed) + (address signer, ECDSA.RecoverError err) = ECDSA.tryRecover(journal, signature); + + if (err != ECDSA.RecoverError.NoError) { + revert InvalidSignature(); + } + + if (!SYSTEM_CONFIG_GLOBAL.isValidProposer(proposer)) { + revert InvalidProposer(proposer); + } + + // Get the PCR0 the signer was registered with + bytes32 registeredPCR0 = SYSTEM_CONFIG_GLOBAL.signerPCR0(signer); + + // Check that the signer is registered (PCR0 != 0) + if (registeredPCR0 == bytes32(0)) { + revert InvalidSigner(signer); + } + + // Check that the signer's registered PCR0 matches the claimed imageId + // This ensures the signer was running the exact enclave image specified + if (registeredPCR0 != imageId) { + revert ImageIdMismatch(registeredPCR0, imageId); + } + + return true; + } + + /// @notice Semantic version. + /// @custom:semver 0.1.0 + function version() public pure virtual returns (string memory) { + return "0.1.0"; + } +} diff --git a/test/libraries/SemverComp.t.sol b/test/libraries/SemverComp.t.sol index b182af378..eb07ea149 100644 --- a/test/libraries/SemverComp.t.sol +++ b/test/libraries/SemverComp.t.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; // Libraries -import { JSONParserLib } from "solady/src/utils/JSONParserLib.sol"; +import { JSONParserLib } from "@solady/utils/JSONParserLib.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; /// @title SemverComp_Harness diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol new file mode 100644 index 000000000..d7d00dff1 --- /dev/null +++ b/test/multiproof/AggregateVerifier.t.sol @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import { BadExtraData } from "src/dispute/lib/Errors.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { Claim, GameStatus, Hash, Timestamp } from "src/dispute/lib/Types.sol"; + +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; + +import { BaseTest } from "./BaseTest.t.sol"; + +contract AggregateVerifierTest is BaseTest { + function testInitializeWithTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); + + assertEq(game.wasRespectedGameTypeWhenCreated(), true); + assertEq(address(game.teeProver()), TEE_PROVER); + assertEq(address(game.zkProver()), address(0)); + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + assertEq(game.l2SequenceNumber(), currentL2BlockNumber); + assertEq(game.rootClaim().raw(), rootClaim.raw()); + assertEq(game.parentIndex(), type(uint32).max); + assertEq(game.gameType().raw(), AGGREGATE_VERIFIER_GAME_TYPE.raw()); + assertEq(game.gameCreator(), TEE_PROVER); + assertEq( + game.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max, game.intermediateOutputRoots()) + ); + assertEq(game.bondRecipient(), address(0)); + assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); + assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); + assertEq(game.proofCount(), 1); + } + + function testInitializeWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); + + assertEq(game.wasRespectedGameTypeWhenCreated(), true); + assertEq(address(game.teeProver()), address(0)); + assertEq(address(game.zkProver()), ZK_PROVER); + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + assertEq(game.l2SequenceNumber(), currentL2BlockNumber); + assertEq(game.rootClaim().raw(), rootClaim.raw()); + assertEq(game.parentIndex(), type(uint32).max); + assertEq(game.gameType().raw(), AGGREGATE_VERIFIER_GAME_TYPE.raw()); + assertEq(game.gameCreator(), ZK_PROVER); + assertEq( + game.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max, game.intermediateOutputRoots()) + ); + assertEq(game.bondRecipient(), ZK_PROVER); + assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); + assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); + assertEq(game.proofCount(), 1); + } + + function testInitializeFailsIfInvalidCallDataSize() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + vm.deal(TEE_PROVER, INIT_BOND); + bytes memory extraData = ""; + bytes memory initData = ""; + + vm.prank(TEE_PROVER); + vm.expectRevert(BadExtraData.selector); + factory.createWithInitData{ value: INIT_BOND }(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData); + } + + function testUpdatingAnchorStateRegistryWithTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); + + // Cannot claim bond before resolving + vm.expectRevert(AggregateVerifier.BondRecipientEmpty.selector); + game.claimCredit(); + + // Resolve after 7 days + vm.warp(block.timestamp + 7 days); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + + // Unlock and reclaim bond after resolving + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); + + // Update AnchorStateRegistry + vm.warp(block.timestamp + 1); + game.closeGame(); + (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), rootClaim.raw()); + assertEq(l2SequenceNumber, currentL2BlockNumber); + } + + function testUpdatingAnchorStateRegistryWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); + + // Unlock and reclaim bond after delay + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); + + // Resolve after another 7 days + vm.warp(block.timestamp + 7 days); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + + // Update AnchorStateRegistry + vm.warp(block.timestamp + 1); + game.closeGame(); + (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), rootClaim.raw()); + assertEq(l2SequenceNumber, currentL2BlockNumber); + } + + function testUpdatingAnchorStateRegistryWithBothProofs() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof); + + _provideProof(game, ZK_PROVER, zkProof); + assertEq(game.proofCount(), 2); + + // Unlock bond + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + + // Resolve after 1 day + vm.warp(block.timestamp + 1 days); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + + // Update AnchorStateRegistry + vm.warp(block.timestamp + 1); + game.closeGame(); + (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), rootClaim.raw()); + assertEq(l2SequenceNumber, currentL2BlockNumber); + + // Unlock and reclaim bond after delay + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); + } + + function testProofCannotIncreaseExpectedResolution() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof); + + Timestamp originalExpectedResolution = game.expectedResolution(); + assertEq(originalExpectedResolution.raw(), block.timestamp + 7 days); + + vm.warp(block.timestamp + 7 days - 1); + // Cannot resolve yet + vm.expectRevert(AggregateVerifier.GameNotOver.selector); + game.resolve(); + + // Provide ZK proof + _provideProof(game, ZK_PROVER, zkProof); + + // Proof should not have increased expected resolution + Timestamp expectedResolution = game.expectedResolution(); + assertEq(expectedResolution.raw(), originalExpectedResolution.raw()); + + // Resolve after 1 second + vm.warp(block.timestamp + 1); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + } + + function testCannotCreateSameProposal() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof); + + Hash uuid = factory.getGameUUID( + AGGREGATE_VERIFIER_GAME_TYPE, + rootClaim, + abi.encodePacked(currentL2BlockNumber, type(uint32).max, game.intermediateOutputRoots()) + ); + vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, uuid)); + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, zkProof); + } + + function testVerifyFailsWithL1OriginInFuture() public { + currentL2BlockNumber += BLOCK_INTERVAL; + // Use a future block number + uint256 l1OriginNumber = block.number + 1; + bytes32 l1OriginHash = bytes32(uint256(1)); // Fake hash + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + bytes memory proofBytes = + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); + + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number) + ); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); + } + + function testVerifyFailsWithL1OriginTooOld() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // Roll forward many blocks to make old blocks unavailable + vm.roll(block.number + 300); + + // Use a block number that's too old (outside both blockhash window and EIP-2935 window) + uint256 l1OriginNumber = 1; + bytes32 l1OriginHash = bytes32(uint256(1)); // Fake hash + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + bytes memory proofBytes = + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); + + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); + } + + function testVerifyFailsWithL1OriginHashMismatch() public { + currentL2BlockNumber += BLOCK_INTERVAL; + uint256 l1OriginNumber = block.number - 1; + bytes32 wrongHash = bytes32(uint256(0xdeadbeef)); // Wrong hash + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + bytes memory proofBytes = + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), wrongHash, l1OriginNumber, rootClaim.raw()); + + bytes32 actualHash = blockhash(l1OriginNumber); + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash)); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); + } + + function testVerifyWithBlockhashWindow() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // Test verification within the 256 block window + vm.roll(block.number + 100); + + // Use a block that's within the 256 block window + uint256 l1OriginNumber = block.number - 50; + bytes32 l1OriginHash = blockhash(l1OriginNumber); + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + bytes memory proofBytes = + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); + + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); + } + + function testVerifyWithEIP2935Window() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // Roll forward past the 256 blockhash window + vm.roll(block.number + 300); + + // Use a block that's outside blockhash window but within EIP-2935 window + uint256 l1OriginNumber = block.number - 260; // 260 > 256, so blockhash() returns 0 + bytes32 expectedHash = keccak256(abi.encodePacked("mock-blockhash", l1OriginNumber)); + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + // Mock the EIP-2935 contract response + vm.mockCall( + 0x0000F90827F1C53a10cb7A02335B175320002935, // EIP-2935 contract address + abi.encode(l1OriginNumber), // raw 32-byte calldata + abi.encode(expectedHash) // returns the blockhash + ); + + bytes memory proofBytes = + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber, rootClaim.raw()); + + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); + } + + function testDeployWithInvalidBlockIntervals() public { + // Case 1: BLOCK_INTERVAL is 0 + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 0, INTERMEDIATE_BLOCK_INTERVAL) + ); + new AggregateVerifier( + AGGREGATE_VERIFIER_GAME_TYPE, + IAnchorStateRegistry(address(anchorStateRegistry)), + IDelayedWETH(payable(address(delayedWETH))), + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), + TEE_IMAGE_HASH, + ZK_IMAGE_HASH, + CONFIG_HASH, + L2_CHAIN_ID, + 0, + INTERMEDIATE_BLOCK_INTERVAL, + PROOF_THRESHOLD + ); + + // Case 2: INTERMEDIATE_BLOCK_INTERVAL is 0 + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, BLOCK_INTERVAL, 0)); + new AggregateVerifier( + AGGREGATE_VERIFIER_GAME_TYPE, + IAnchorStateRegistry(address(anchorStateRegistry)), + IDelayedWETH(payable(address(delayedWETH))), + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), + TEE_IMAGE_HASH, + ZK_IMAGE_HASH, + CONFIG_HASH, + L2_CHAIN_ID, + BLOCK_INTERVAL, + 0, + PROOF_THRESHOLD + ); + + // Case 3: BLOCK_INTERVAL is not divisible by INTERMEDIATE_BLOCK_INTERVAL + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 3, 2)); + new AggregateVerifier( + AGGREGATE_VERIFIER_GAME_TYPE, + IAnchorStateRegistry(address(anchorStateRegistry)), + IDelayedWETH(payable(address(delayedWETH))), + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), + TEE_IMAGE_HASH, + ZK_IMAGE_HASH, + CONFIG_HASH, + L2_CHAIN_ID, + 3, + 2, + PROOF_THRESHOLD + ); + } + + function testDeployWithInvalidProofThreshold() public { + // Case 1: PROOF_THRESHOLD is 0 + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidProofThreshold.selector)); + new AggregateVerifier( + AGGREGATE_VERIFIER_GAME_TYPE, + IAnchorStateRegistry(address(anchorStateRegistry)), + IDelayedWETH(payable(address(delayedWETH))), + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), + TEE_IMAGE_HASH, + ZK_IMAGE_HASH, + CONFIG_HASH, + L2_CHAIN_ID, + BLOCK_INTERVAL, + INTERMEDIATE_BLOCK_INTERVAL, + 0 + ); + + // Case 2: PROOF_THRESHOLD is > 2 + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidProofThreshold.selector)); + new AggregateVerifier( + AGGREGATE_VERIFIER_GAME_TYPE, + IAnchorStateRegistry(address(anchorStateRegistry)), + IDelayedWETH(payable(address(delayedWETH))), + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), + TEE_IMAGE_HASH, + ZK_IMAGE_HASH, + CONFIG_HASH, + L2_CHAIN_ID, + BLOCK_INTERVAL, + INTERMEDIATE_BLOCK_INTERVAL, + 3 + ); + } +} diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol new file mode 100644 index 000000000..69cd7dd72 --- /dev/null +++ b/test/multiproof/BaseTest.t.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; + +// Optimism +import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; +import { DelayedWETH } from "src/dispute/DelayedWETH.sol"; +import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { Claim, GameStatus, GameType, Hash, Proposal, Timestamp } from "src/dispute/lib/Types.sol"; + +// OpenZeppelin +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; + +import { MockSystemConfig } from "src/multiproof/mocks/MockSystemConfig.sol"; +import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; + +contract BaseTest is Test { + // Constants + GameType public constant AGGREGATE_VERIFIER_GAME_TYPE = GameType.wrap(621); + uint256 public constant L2_CHAIN_ID = 8453; + + // MUST HAVE: BLOCK_INTERVAL % INTERMEDIATE_BLOCK_INTERVAL == 0 + uint256 public constant BLOCK_INTERVAL = 100; + uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; + + uint256 public constant INIT_BOND = 1 ether; + uint256 public constant DELAYED_WETH_DELAY = 1 days; + // Finality delay handled by the AggregateVerifier + uint256 public constant FINALITY_DELAY = 0 days; + uint256 public constant PROOF_THRESHOLD = 1; + + uint256 public currentL2BlockNumber = 0; + + address public immutable TEE_PROVER = makeAddr("tee-prover"); + address public immutable ZK_PROVER = makeAddr("zk-prover"); + address public immutable ATTACKER = makeAddr("attacker"); + + bytes32 public immutable TEE_IMAGE_HASH = keccak256("tee-image"); + bytes32 public immutable ZK_IMAGE_HASH = keccak256("zk-image"); + bytes32 public immutable CONFIG_HASH = keccak256("config"); + + ProxyAdmin public proxyAdmin; + MockSystemConfig public systemConfig; + + DisputeGameFactory public factory; + AnchorStateRegistry public anchorStateRegistry; + DelayedWETH public delayedWETH; + + MockVerifier public teeVerifier; + MockVerifier public zkVerifier; + + function setUp() public virtual { + _deployContractsAndProxies(); + _initializeProxies(); + + // Deploy the implementations + _deployAndSetAggregateVerifier(); + + anchorStateRegistry.setRespectedGameType(AGGREGATE_VERIFIER_GAME_TYPE); + + // Set the timestamp to after the retirement timestamp + vm.warp(block.timestamp + 1); + } + + function _deployContractsAndProxies() internal { + // Deploy the system config + systemConfig = new MockSystemConfig(); + // Deploy the relay anchor state registry + AnchorStateRegistry _anchorStateRegistry = new AnchorStateRegistry(FINALITY_DELAY); + // Deploy the delayed WETH + DelayedWETH _delayedWETH = new DelayedWETH(DELAYED_WETH_DELAY); + // Deploy the dispute game factory + DisputeGameFactory _factory = new DisputeGameFactory(); + + // Deploy proxy admin + proxyAdmin = new ProxyAdmin(address(this)); + + // Deploy proxy for anchor state registry + TransparentUpgradeableProxy anchorStateRegistryProxy = + new TransparentUpgradeableProxy(address(_anchorStateRegistry), address(proxyAdmin), ""); + anchorStateRegistry = AnchorStateRegistry(address(anchorStateRegistryProxy)); + + // Deploy proxy for factory + TransparentUpgradeableProxy factoryProxy = + new TransparentUpgradeableProxy(address(_factory), address(proxyAdmin), ""); + factory = DisputeGameFactory(address(factoryProxy)); + + // Deploy proxy for delayed WETH + TransparentUpgradeableProxy delayedWETHProxy = + new TransparentUpgradeableProxy(address(_delayedWETH), address(proxyAdmin), ""); + delayedWETH = DelayedWETH(payable(address(delayedWETHProxy))); + + // Deploy the verifiers + teeVerifier = new MockVerifier(); + zkVerifier = new MockVerifier(); + } + + function _initializeProxies() internal { + // Initialize the proxies + anchorStateRegistry.initialize( + ISystemConfig(address(systemConfig)), + IDisputeGameFactory(address(factory)), + Proposal({ + root: Hash.wrap(keccak256(abi.encode(currentL2BlockNumber))), l2SequenceNumber: currentL2BlockNumber + }), + GameType.wrap(0) + ); + factory.initialize(address(this)); + delayedWETH.initialize(ISystemConfig(address(systemConfig))); + } + + function _deployAndSetAggregateVerifier() internal { + // Deploy the dispute game relay implementation + AggregateVerifier aggregateVerifierImpl = new AggregateVerifier( + AGGREGATE_VERIFIER_GAME_TYPE, + IAnchorStateRegistry(address(anchorStateRegistry)), + IDelayedWETH(payable(address(delayedWETH))), + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), + TEE_IMAGE_HASH, + ZK_IMAGE_HASH, + CONFIG_HASH, + L2_CHAIN_ID, + BLOCK_INTERVAL, + INTERMEDIATE_BLOCK_INTERVAL, + PROOF_THRESHOLD + ); + + // Set the implementation for the aggregate verifier + factory.setImplementation(AGGREGATE_VERIFIER_GAME_TYPE, IDisputeGame(address(aggregateVerifierImpl))); + + // Set the bond amount for the aggregate verifier + factory.setInitBond(AGGREGATE_VERIFIER_GAME_TYPE, INIT_BOND); + } + + // Helper function to create a game via factory + function _createAggregateVerifierGame( + address creator, + Claim rootClaim, + uint256 l2BlockNumber, + uint32 parentIndex, + bytes memory proof + ) + internal + returns (AggregateVerifier game) + { + bytes memory intermediateRoots = + abi.encodePacked(_generateIntermediateRootsExceptLast(l2BlockNumber), rootClaim.raw()); + bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), uint32(parentIndex), intermediateRoots); + + vm.deal(creator, INIT_BOND); + vm.prank(creator); + return AggregateVerifier( + address( + factory.createWithInitData{ value: INIT_BOND }( + AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, proof + ) + ) + ); + } + + function _provideProof(AggregateVerifier game, address prover, bytes memory proofBytes) internal { + vm.prank(prover); + game.verifyProposalProof(proofBytes); + } + + /// @notice Generates a properly formatted proof for testing. + /// @dev The proof format is: l1OriginHash (32) + l1OriginNumber (32) + additional data. + /// Since MockVerifier always returns true, we just need the correct structure. + /// @param salt A salt to make proofs unique. + /// @param proofType The type of proof to generate. + /// @return proof The formatted proof bytes. + function _generateProof( + bytes memory salt, + AggregateVerifier.ProofType proofType + ) + internal + view + returns (bytes memory) + { + // Use the previous block hash as l1OriginHash + bytes32 l1OriginHash = blockhash(block.number - 1); + // Use the previous block number as l1OriginNumber + uint256 l1OriginNumber = block.number - 1; + // Add some padding/signature data (65 bytes minimum for a signature) + bytes memory signature = abi.encodePacked(salt, bytes32(0), bytes32(0), uint8(27)); + + return abi.encodePacked(uint8(proofType), l1OriginHash, l1OriginNumber, signature); + } + + function _generateIntermediateRootsExceptLast(uint256 l2BlockNumber) internal pure returns (bytes memory) { + bytes memory intermediateRoots; + uint256 startingL2BlockNumber = l2BlockNumber - BLOCK_INTERVAL; + for (uint256 i = 1; i < BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; i++) { + intermediateRoots = abi.encodePacked( + intermediateRoots, keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)) + ); + } + return intermediateRoots; + } +} diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol new file mode 100644 index 000000000..3d0ef2f65 --- /dev/null +++ b/test/multiproof/Challenge.t.sol @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import { ClaimAlreadyResolved } from "src/dispute/lib/Errors.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { Claim, GameStatus, Hash } from "src/dispute/lib/Types.sol"; + +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; + +import { BaseTest } from "./BaseTest.t.sol"; + +contract ChallengeTest is BaseTest { + function testChallengeTEEProofWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // Create first game with TEE proof + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); + + // Create second game with different root claim and ZK proof + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); + + // Get game index from factory + uint256 gameIndex = factory.gameCount() - 1; + + // Challenge game1 with game2 + game1.challenge(gameIndex); + + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game1.bondRecipient(), ZK_PROVER); + address counteredBy = game1.counteredByGameAddress(); + assertEq(counteredBy, address(game2)); + assertEq(game1.proofCount(), -128); + assertEq(game1.expectedResolution().raw(), type(uint64).max); + + // Retrieve bond after challenge + vm.warp(block.timestamp + 7 days); + game2.resolve(); + assertEq(uint8(game2.status()), uint8(GameStatus.DEFENDER_WINS)); + assertEq(ZK_PROVER.balance, 0); + assertEq(delayedWETH.balanceOf(address(game1)), INIT_BOND); + game1.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game1.claimCredit(); + assertEq(ZK_PROVER.balance, INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game1)), 0); + } + + function testChallengeFailsIfNoTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // Create first game with ZK proof (no TEE proof) + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game1 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1); + + // Create second game with different root claim and ZK proof + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); + + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2); + + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) + ); + game1.challenge(gameIndex); + } + + function testChallengeFailsIfDifferentParentIndex() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); + + // Create game2 with game1 as parent + uint256 game1Index = factory.gameCount() - 1; + uint256 nextBlockNumber = currentL2BlockNumber + BLOCK_INTERVAL; + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + // forge-lint: disable-next-line(unsafe-typecast) + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof); + + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert(AggregateVerifier.InvalidGame.selector); + game1.challenge(gameIndex); + } + + function testChallengeFailsIfChallengingGameHasNoZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); + + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2); + + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.ZK)); + game1.challenge(gameIndex); + } + + function testChallengeFailsIfGameAlreadyResolved() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); + + // Resolve game1 + vm.warp(block.timestamp + 7 days + 1); + game1.resolve(); + + // Try to challenge game1 + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); + + uint256 challengeIndex1 = factory.gameCount() - 1; + vm.expectRevert(ClaimAlreadyResolved.selector); + game1.challenge(challengeIndex1); + } + + function testChallengeFailsIfParentGameStatusIsChallenged() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // create parent game + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory parentProof = _generateProof("parent-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier parentGame = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, parentProof); + + uint256 parentGameIndex = factory.gameCount() - 1; + currentL2BlockNumber += BLOCK_INTERVAL; + + // create child game + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory childProof = _generateProof("child-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier childGame = + // forge-lint: disable-next-line(unsafe-typecast) + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex), childProof); + + // blacklist parent game + anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); + + // challenge child game + uint256 childGameIndex = factory.gameCount() - 1; + vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); + childGame.challenge(childGameIndex); + } + + function testChallengeFailsIfGameItselfIsBlacklisted() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); + + // blacklist game + anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); + + // challenge game + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(AggregateVerifier.InvalidGame.selector); + game.challenge(gameIndex); + } + + function testChallengeFailsAfterTEENullification() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); + + game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + // challenge game + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(AggregateVerifier.NotEnoughProofs.selector); + game.challenge(gameIndex); + } + + function testChallengeFailsAfterZKNullification() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); + + game.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + // challenge game + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(ClaimAlreadyResolved.selector); + game.challenge(gameIndex); + } +} diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol new file mode 100644 index 000000000..9ee51c066 --- /dev/null +++ b/test/multiproof/Nullify.t.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import { GameNotInProgress } from "src/dispute/lib/Errors.sol"; +import { Claim, GameStatus } from "src/dispute/lib/Types.sol"; + +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; + +import { BaseTest } from "./BaseTest.t.sol"; + +contract NullifyTest is BaseTest { + function testNullifyWithTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); + + game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + assertEq(game.bondRecipient(), TEE_PROVER); + assertEq(game.proofCount(), 0); + assertEq(game.expectedResolution().raw(), type(uint64).max); + + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); + } + + function testNullifyWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game1 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); + + game1.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game1.bondRecipient(), ZK_PROVER); + assertEq(game1.proofCount(), -128); + assertEq(game1.expectedResolution().raw(), type(uint64).max); + + uint256 balanceBefore = game1.gameCreator().balance; + game1.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game1.claimCredit(); + assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game1)), 0); + } + + function testNullifyWithTEEProofWhenTEEAndZKProofsAreProvided() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); + + bytes memory zkProof = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); + game.verifyProposalProof(zkProof); + + assertEq(game.expectedResolution().raw(), block.timestamp + 1 days); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); + game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + assertEq(game.bondRecipient(), TEE_PROVER); + assertEq(game.proofCount(), 1); + assertEq(game.expectedResolution().raw(), block.timestamp + 7 days); + } + + function testZKNullifyFailsIfNoZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.ZK)); + game1.nullify(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + } + + function testNullifyFailsIfGameAlreadyResolved() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); + + // Resolve game1 + vm.warp(block.timestamp + 7 days); + game1.resolve(); + + // Try to nullify game1 + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); + + vm.expectRevert(GameNotInProgress.selector); + game1.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + } + + function testNullifyCanOverrideChallenge() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); + + // Challenge game1 + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); + + uint256 challengeIndex = factory.gameCount() - 1; + game1.challenge(challengeIndex); + assertEq(game1.bondRecipient(), ZK_PROVER); + + // Nullify can override challenge + game2.nullify(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); + + uint256 balanceBefore = game1.gameCreator().balance; + game1.claimCredit(); + assertEq(game1.bondRecipient(), TEE_PROVER); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game1.claimCredit(); + assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game1)), 0); + } +} diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol new file mode 100644 index 000000000..10636cbfa --- /dev/null +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import { Test } from "forge-std/Test.sol"; + +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; + +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; + +import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; + +/// @notice Tests for SystemConfigGlobal and DevSystemConfigGlobal contracts. +/// @dev IMPORTANT: This test file uses DevSystemConfigGlobal as the implementation because +/// registering signers on the production SystemConfigGlobal requires a ZK proof of a valid +/// AWS Nitro attestation, which cannot be generated in a test environment. DevSystemConfigGlobal extends +/// SystemConfigGlobal with an `addDevSigner` function that bypasses attestation verification, +/// allowing us to test all signer-related functionality. All tests for base SystemConfigGlobal +/// functionality (PCR0 management, ownership, proposer, etc.) are equally valid since +/// DevSystemConfigGlobal inherits from SystemConfigGlobal without modifying those functions. +contract SystemConfigGlobalTest is Test { + DevSystemConfigGlobal public systemConfigGlobal; + ProxyAdmin public proxyAdmin; + + address public owner; + address public manager; + address public unauthorized; + + bytes public constant TEST_PCR0 = hex"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; + bytes32 public pcr0Hash; + + // Events must be redeclared here because Solidity 0.8.15 doesn't support + // referencing events from other contracts via qualified names (requires 0.8.21+) + event SignerRegistered(address indexed signer, bytes32 indexed pcr0); + event SignerDeregistered(address indexed signer); + event PCR0Registered(bytes32 indexed pcr0Hash); + event PCR0Deregistered(bytes32 indexed pcr0Hash); + event ProposerSet(address indexed proposer, bool isValid); + + function setUp() public { + owner = makeAddr("owner"); + manager = makeAddr("manager"); + unauthorized = makeAddr("unauthorized"); + + pcr0Hash = keccak256(TEST_PCR0); + + // Deploy implementation (using DevSystemConfigGlobal for test flexibility) + // NitroEnclaveVerifier is not needed since tests use addDevSigner(), so pass address(0). + DevSystemConfigGlobal impl = new DevSystemConfigGlobal(INitroEnclaveVerifier(address(0))); + + // Deploy proxy admin + proxyAdmin = new ProxyAdmin(address(this)); + + // Deploy proxy + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), address(proxyAdmin), abi.encodeCall(SystemConfigGlobal.initialize, (owner, manager)) + ); + + systemConfigGlobal = DevSystemConfigGlobal(address(proxy)); + } + + // ============ Initialization Tests ============ + + function testInitialization() public view { + assertEq(systemConfigGlobal.owner(), owner); + assertEq(systemConfigGlobal.manager(), manager); + assertEq(systemConfigGlobal.version(), "0.1.0"); + } + + // ============ PCR0 Registration Tests ============ + + function testRegisterPCR0() public { + vm.expectEmit(true, false, false, false); + emit PCR0Registered(pcr0Hash); + + vm.prank(owner); + systemConfigGlobal.registerPCR0(TEST_PCR0); + + assertTrue(systemConfigGlobal.validPCR0s(pcr0Hash)); + } + + function testRegisterPCR0FailsIfNotOwner() public { + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.registerPCR0(TEST_PCR0); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.registerPCR0(TEST_PCR0); + } + + function testDeregisterPCR0() public { + // First register + vm.prank(owner); + systemConfigGlobal.registerPCR0(TEST_PCR0); + assertTrue(systemConfigGlobal.validPCR0s(pcr0Hash)); + + // Then deregister + vm.expectEmit(true, false, false, false); + emit PCR0Deregistered(pcr0Hash); + + vm.prank(owner); + systemConfigGlobal.deregisterPCR0(TEST_PCR0); + + assertFalse(systemConfigGlobal.validPCR0s(pcr0Hash)); + } + + function testDeregisterPCR0FailsIfNotOwner() public { + vm.prank(owner); + systemConfigGlobal.registerPCR0(TEST_PCR0); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.deregisterPCR0(TEST_PCR0); + } + + // ============ Signer Deregistration Tests ============ + + function testDeregisterSignerAsOwner() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + // Add signer via DevSystemConfigGlobal + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, signerPcr0); + + // Verify signer is registered + assertTrue(systemConfigGlobal.isValidSigner(signer)); + + // Deregister as owner + vm.expectEmit(true, false, false, false); + emit SignerDeregistered(signer); + + vm.prank(owner); + systemConfigGlobal.deregisterSigner(signer); + + assertFalse(systemConfigGlobal.isValidSigner(signer)); + assertEq(systemConfigGlobal.signerPCR0(signer), bytes32(0)); + } + + function testDeregisterSignerAsManager() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + // Add signer via DevSystemConfigGlobal + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, signerPcr0); + + assertTrue(systemConfigGlobal.isValidSigner(signer)); + + vm.prank(manager); + systemConfigGlobal.deregisterSigner(signer); + + assertFalse(systemConfigGlobal.isValidSigner(signer)); + } + + function testDeregisterSignerFailsIfUnauthorized() public { + address signer = makeAddr("signer"); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner or the manager"); + systemConfigGlobal.deregisterSigner(signer); + } + + // ============ Proposer Tests ============ + + function testSetProposer() public { + address newProposer = makeAddr("proposer"); + + vm.expectEmit(true, false, false, false); + emit ProposerSet(newProposer, true); + + vm.prank(owner); + systemConfigGlobal.setProposer(newProposer, true); + + assertTrue(systemConfigGlobal.isValidProposer(newProposer)); + } + + function testSetProposerFailsIfNotOwner() public { + address newProposer = makeAddr("proposer"); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.setProposer(newProposer, true); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.setProposer(newProposer, true); + } + + // ============ isValidSigner Tests ============ + + function testIsValidSignerReturnsFalseForUnregistered() public { + address unregistered = makeAddr("unregistered"); + assertFalse(systemConfigGlobal.isValidSigner(unregistered)); + } + + function testIsValidSignerReturnsTrueForRegistered() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, signerPcr0); + + assertTrue(systemConfigGlobal.isValidSigner(signer)); + } + + // ============ signerPCR0 Tests ============ + + function testSignerPCR0ReturnsZeroForUnregistered() public { + address unregistered = makeAddr("unregistered"); + assertEq(systemConfigGlobal.signerPCR0(unregistered), bytes32(0)); + } + + function testSignerPCR0ReturnsCorrectValue() public { + address signer = makeAddr("signer"); + bytes32 expectedPcr0 = keccak256("signer-pcr0"); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, expectedPcr0); + + assertEq(systemConfigGlobal.signerPCR0(signer), expectedPcr0); + } + + // ============ MAX_AGE Tests ============ + + function testMaxAgeConstant() public view { + assertEq(systemConfigGlobal.MAX_AGE(), 60 minutes); + } + + // ============ Ownership Transfer Tests ============ + + function testTransferOwnership() public { + address newOwner = makeAddr("newOwner"); + + vm.prank(owner); + systemConfigGlobal.transferOwnership(newOwner); + + assertEq(systemConfigGlobal.owner(), newOwner); + } + + function testTransferOwnershipFailsIfNotOwner() public { + address newOwner = makeAddr("newOwner"); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.transferOwnership(newOwner); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.transferOwnership(newOwner); + } + + function testTransferOwnershipFailsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert("OwnableManaged: new owner is the zero address"); + systemConfigGlobal.transferOwnership(address(0)); + } + + // ============ Management Transfer Tests ============ + + function testTransferManagementAsOwner() public { + address newManager = makeAddr("newManager"); + + vm.prank(owner); + systemConfigGlobal.transferManagement(newManager); + + assertEq(systemConfigGlobal.manager(), newManager); + } + + function testTransferManagementAsManager() public { + address newManager = makeAddr("newManager"); + + vm.prank(manager); + systemConfigGlobal.transferManagement(newManager); + + assertEq(systemConfigGlobal.manager(), newManager); + } + + function testTransferManagementFailsIfUnauthorized() public { + address newManager = makeAddr("newManager"); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner or the manager"); + systemConfigGlobal.transferManagement(newManager); + } + + function testTransferManagementFailsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert("OwnableManaged: new manager is the zero address"); + systemConfigGlobal.transferManagement(address(0)); + } + + // ============ Renounce Tests ============ + + function testRenounceOwnership() public { + vm.prank(owner); + systemConfigGlobal.renounceOwnership(); + + assertEq(systemConfigGlobal.owner(), address(0)); + } + + function testRenounceManagementAsOwner() public { + vm.prank(owner); + systemConfigGlobal.renounceManagement(); + + assertEq(systemConfigGlobal.manager(), address(0)); + } + + function testRenounceManagementAsManager() public { + vm.prank(manager); + systemConfigGlobal.renounceManagement(); + + assertEq(systemConfigGlobal.manager(), address(0)); + } + + // ============ DevSystemConfigGlobal: addDevSigner Tests ============ + + function testAddDevSigner() public { + address signer = makeAddr("dev-signer"); + bytes32 devPcr0Hash = keccak256("dev-pcr0"); + + vm.expectEmit(true, true, false, false); + emit SignerRegistered(signer, devPcr0Hash); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, devPcr0Hash); + + assertTrue(systemConfigGlobal.isValidSigner(signer)); + assertEq(systemConfigGlobal.signerPCR0(signer), devPcr0Hash); + } + + function testAddDevSignerFailsIfNotOwner() public { + address signer = makeAddr("dev-signer"); + bytes32 devPcr0Hash = keccak256("dev-pcr0"); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.addDevSigner(signer, devPcr0Hash); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.addDevSigner(signer, devPcr0Hash); + } + + function testAddDevSignerCanOverwriteExisting() public { + address signer = makeAddr("dev-signer"); + bytes32 firstPcr0 = keccak256("first-pcr0"); + bytes32 secondPcr0 = keccak256("second-pcr0"); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, firstPcr0); + assertEq(systemConfigGlobal.signerPCR0(signer), firstPcr0); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, secondPcr0); + assertEq(systemConfigGlobal.signerPCR0(signer), secondPcr0); + } + + function testAddDevSignerWithZeroPcr0() public { + address signer = makeAddr("dev-signer"); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, bytes32(0)); + + // Signer should not be valid because PCR0 is zero + assertFalse(systemConfigGlobal.isValidSigner(signer)); + assertEq(systemConfigGlobal.signerPCR0(signer), bytes32(0)); + } + + function testAddMultipleDevSigners() public { + address signer1 = makeAddr("dev-signer-1"); + address signer2 = makeAddr("dev-signer-2"); + address signer3 = makeAddr("dev-signer-3"); + + bytes32 pcr0Hash1 = keccak256("pcr0-1"); + bytes32 pcr0Hash2 = keccak256("pcr0-2"); + bytes32 pcr0Hash3 = keccak256("pcr0-3"); + + vm.startPrank(owner); + systemConfigGlobal.addDevSigner(signer1, pcr0Hash1); + systemConfigGlobal.addDevSigner(signer2, pcr0Hash2); + systemConfigGlobal.addDevSigner(signer3, pcr0Hash3); + vm.stopPrank(); + + assertTrue(systemConfigGlobal.isValidSigner(signer1)); + assertTrue(systemConfigGlobal.isValidSigner(signer2)); + assertTrue(systemConfigGlobal.isValidSigner(signer3)); + + assertEq(systemConfigGlobal.signerPCR0(signer1), pcr0Hash1); + assertEq(systemConfigGlobal.signerPCR0(signer2), pcr0Hash2); + assertEq(systemConfigGlobal.signerPCR0(signer3), pcr0Hash3); + } +} diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol new file mode 100644 index 000000000..3a1095d95 --- /dev/null +++ b/test/multiproof/TEEVerifier.t.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import { Test } from "forge-std/Test.sol"; + +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; + +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; + +import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; + +contract TEEVerifierTest is Test { + TEEVerifier public verifier; + DevSystemConfigGlobal public systemConfigGlobal; + ProxyAdmin public proxyAdmin; + + // Test signer - we'll derive address from private key + uint256 internal constant SIGNER_PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + address internal signerAddress; + + bytes32 internal constant PCR0_HASH = keccak256("test-pcr0"); + bytes32 internal constant IMAGE_ID = PCR0_HASH; // imageId must match PCR0 hash + address internal immutable PROPOSER = makeAddr("proposer"); + + address internal owner; + + function setUp() public { + owner = address(this); + + // Derive signer address from private key + signerAddress = vm.addr(SIGNER_PRIVATE_KEY); + + // Deploy implementation (NitroEnclaveVerifier not needed for dev signer tests) + DevSystemConfigGlobal impl = new DevSystemConfigGlobal(INitroEnclaveVerifier(address(0))); + + // Deploy proxy admin + proxyAdmin = new ProxyAdmin(address(this)); + + // Deploy proxy + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), address(proxyAdmin), abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) + ); + + systemConfigGlobal = DevSystemConfigGlobal(address(proxy)); + + // Register the signer with PCR0 hash + systemConfigGlobal.addDevSigner(signerAddress, PCR0_HASH); + + // Set the proposer as valid + systemConfigGlobal.setProposer(PROPOSER, true); + + // Deploy TEEVerifier + verifier = new TEEVerifier(SystemConfigGlobal(address(systemConfigGlobal))); + } + + function testVerifyValidSignature() public view { + // Create a journal hash + bytes32 journal = keccak256("test-journal"); + + // Sign the journal with the signer's private key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal); + bytes memory signature = abi.encodePacked(r, s, v); + + // Construct proof: proposer(20) + signature(65) = 85 bytes + bytes memory proofBytes = abi.encodePacked(PROPOSER, signature); + + // Verify should return true + bool result = verifier.verify(proofBytes, IMAGE_ID, journal); + assertTrue(result); + } + + function testVerifyFailsWithInvalidSignature() public { + bytes32 journal = keccak256("test-journal"); + + // Create an invalid signature (all zeros except v) + bytes memory invalidSignature = new bytes(65); + invalidSignature[64] = bytes1(uint8(27)); // Set v to 27 + + bytes memory proofBytes = abi.encodePacked(PROPOSER, invalidSignature); + + vm.expectRevert(TEEVerifier.InvalidSignature.selector); + verifier.verify(proofBytes, IMAGE_ID, journal); + } + + function testVerifyFailsWithInvalidProposer() public { + // Create a journal hash + bytes32 journal = keccak256("test-journal"); + + // Sign the journal with the signer's private key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal); + bytes memory signature = abi.encodePacked(r, s, v); + + // Construct proof: proposer(20) + signature(65) = 85 bytes + bytes memory proofBytes = abi.encodePacked(address(0), signature); + + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidProposer.selector, address(0))); + verifier.verify(proofBytes, IMAGE_ID, journal); + } + + function testVerifyFailsWithUnregisteredSigner() public { + // Use a different private key that's not registered + uint256 unregisteredKey = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef; + address unregisteredSigner = vm.addr(unregisteredKey); + + bytes32 journal = keccak256("test-journal"); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(unregisteredKey, journal); + bytes memory signature = abi.encodePacked(r, s, v); + + bytes memory proofBytes = abi.encodePacked(PROPOSER, signature); + + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidSigner.selector, unregisteredSigner)); + verifier.verify(proofBytes, IMAGE_ID, journal); + } + + function testVerifyFailsWithImageIdMismatch() public { + bytes32 journal = keccak256("test-journal"); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal); + bytes memory signature = abi.encodePacked(r, s, v); + + bytes memory proofBytes = abi.encodePacked(PROPOSER, signature); + + // Use a different imageId that doesn't match the registered PCR0 + bytes32 wrongImageId = keccak256("wrong-image-id"); + + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.ImageIdMismatch.selector, PCR0_HASH, wrongImageId)); + verifier.verify(proofBytes, wrongImageId, journal); + } + + function testVerifyFailsWithInvalidProofFormat() public { + bytes32 journal = keccak256("test-journal"); + + // Proof too short (less than 85 bytes) + bytes memory shortProof = new bytes(50); + + vm.expectRevert(TEEVerifier.InvalidProofFormat.selector); + verifier.verify(shortProof, IMAGE_ID, journal); + } + + function testConstants() public view { + assertEq(address(verifier.SYSTEM_CONFIG_GLOBAL()), address(systemConfigGlobal)); + } +} diff --git a/test/opcm/DeployImplementations.t.sol b/test/opcm/DeployImplementations.t.sol index 1a4f4162f..73315b82f 100644 --- a/test/opcm/DeployImplementations.t.sol +++ b/test/opcm/DeployImplementations.t.sol @@ -236,6 +236,14 @@ contract DeployImplementations_Test is Test, FeatureFlags { _faultGameV2SplitDepth, // faultGameV2SplitDepth (bounded) _faultGameV2ClockExtension, // faultGameV2ClockExtension (bounded) _faultGameV2MaxClockDuration, // faultGameV2MaxClockDuration (bounded) + bytes32(uint256(1)), // teeImageHash + bytes32(0), // multiproofConfigHash + 621, // multiproofGameType + address(0), // nitroEnclaveVerifier + 8453, // l2ChainID + 100, // multiproofBlockInterval + 10, // multiproofIntermediateBlockInterval + 1, // multiproofProofThreshold superchainConfigProxy, protocolVersionsProxy, superchainProxyAdmin, @@ -524,6 +532,14 @@ contract DeployImplementations_Test is Test, FeatureFlags { 30, // faultGameV2SplitDepth 10800, // faultGameV2ClockExtension 302400, // faultGameV2MaxClockDuration + bytes32(uint256(1)), // teeImageHash + bytes32(0), // multiproofConfigHash + 621, // multiproofGameType + address(0), // nitroEnclaveVerifier + 8453, // l2ChainID + 100, // multiproofBlockInterval + 10, // multiproofIntermediateBlockInterval + 1, // multiproofProofThreshold superchainConfigProxy, protocolVersionsProxy, superchainProxyAdmin, diff --git a/test/opcm/DeployOPChain.t.sol b/test/opcm/DeployOPChain.t.sol index c5f945822..b8520079c 100644 --- a/test/opcm/DeployOPChain.t.sol +++ b/test/opcm/DeployOPChain.t.sol @@ -94,6 +94,14 @@ contract DeployOPChain_TestBase is Test, FeatureFlags { faultGameV2SplitDepth: 30, faultGameV2ClockExtension: 10800, faultGameV2MaxClockDuration: 302400, + teeImageHash: bytes32(uint256(1)), + multiproofConfigHash: bytes32(0), + multiproofGameType: 621, + nitroEnclaveVerifier: address(0), + l2ChainID: 8453, + multiproofBlockInterval: 100, + multiproofIntermediateBlockInterval: 10, + multiproofProofThreshold: 1, superchainConfigProxy: dso.superchainConfigProxy, protocolVersionsProxy: dso.protocolVersionsProxy, superchainProxyAdmin: dso.superchainProxyAdmin, diff --git a/test/setup/Setup.sol b/test/setup/Setup.sol index 3ffc56b06..0e9f1fe1d 100644 --- a/test/setup/Setup.sol +++ b/test/setup/Setup.sol @@ -67,6 +67,8 @@ import { INativeAssetLiquidity } from "interfaces/L2/INativeAssetLiquidity.sol"; import { IFeeSplitter } from "interfaces/L2/IFeeSplitter.sol"; import { IL1Withdrawer } from "interfaces/L2/IL1Withdrawer.sol"; import { ISuperchainRevSharesCalculator } from "interfaces/L2/ISuperchainRevSharesCalculator.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -156,6 +158,8 @@ abstract contract Setup is FeatureFlags { IFeeSplitter feeSplitter = IFeeSplitter(payable(Predeploys.FEE_SPLITTER)); IL1Withdrawer l1Withdrawer; ISuperchainRevSharesCalculator superchainRevSharesCalculator; + IVerifier aggregateVerifier; + SystemConfigGlobal systemConfigGlobal; /// @notice Indicates whether a test is running against a forked production network. function isForkTest() public view returns (bool) { @@ -294,6 +298,8 @@ abstract contract Setup is FeatureFlags { superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); superchainProxyAdminOwner = superchainProxyAdmin.owner(); mips = IBigStepper(artifacts.mustGetAddress("MipsSingleton")); + aggregateVerifier = IVerifier(artifacts.mustGetAddress("AggregateVerifier")); + systemConfigGlobal = SystemConfigGlobal(artifacts.mustGetAddress("SystemConfigGlobal")); if (deploy.cfg().useAltDA()) { dataAvailabilityChallenge = diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 1e2425daa..306eb2628 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -22,6 +22,7 @@ import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol" import { ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; /// @title Initializer_Test /// @dev Ensures that the `initialize()` function on contracts cannot be called more than @@ -338,25 +339,40 @@ contract Initializer_Test is CommonTest { }) ); - // ETHLockboxImpl - contracts.push( - InitializeableContract({ - name: "ETHLockboxImpl", - target: EIP1967Helper.getImplementation(address(ethLockbox)), - initCalldata: abi.encodeCall( - ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) - ) - }) - ); + // ETHLockbox is only deployed when interop is enabled + if (address(ethLockbox) != address(0)) { + // ETHLockboxImpl + contracts.push( + InitializeableContract({ + name: "ETHLockboxImpl", + target: EIP1967Helper.getImplementation(address(ethLockbox)), + initCalldata: abi.encodeCall( + ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) + ) + }) + ); - // ETHLockboxProxy + // ETHLockboxProxy + contracts.push( + InitializeableContract({ + name: "ETHLockboxProxy", + target: address(ethLockbox), + initCalldata: abi.encodeCall( + ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) + ) + }) + ); + } + + // AggregateVerifier uses a custom `bool initialized` instead of OpenZeppelin's `_initialized` + // uint8, so it cannot be tested by this framework. It is excluded below. + + // SystemConfigGlobalImpl contracts.push( InitializeableContract({ - name: "ETHLockboxProxy", - target: address(ethLockbox), - initCalldata: abi.encodeCall( - ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) - ) + name: "SystemConfigGlobalImpl", + target: address(systemConfigGlobal), + initCalldata: abi.encodeCall(SystemConfigGlobal.initialize, (address(0), address(0))) }) ); } @@ -368,7 +384,7 @@ contract Initializer_Test is CommonTest { function test_cannotReinitialize_succeeds() public { // Collect exclusions. uint256 j; - string[] memory excludes = new string[](14); + string[] memory excludes = new string[](20); // Contract is currently not being deployed as part of the standard deployment script. excludes[j++] = "src/L2/OptimismSuperchainERC20.sol"; // Periphery contracts don't get deployed as part of the standard deployment script. @@ -394,6 +410,14 @@ contract Initializer_Test is CommonTest { excludes[j++] = "src/L1/FeesDepositor.sol"; // Contract is not deployed as part of the standard deployment script. excludes[j++] = "src/revenue-share/BalanceTracker.sol"; + // Multiproof mocks are not deployed as part of the standard deployment script. + excludes[j++] = "src/multiproof/mocks/*"; + // AggregateVerifier uses a custom `bool initialized` instead of OpenZeppelin's `_initialized` uint8. + excludes[j++] = "src/multiproof/AggregateVerifier.sol"; + // ETHLockbox is only deployed when interop is enabled. + if (address(ethLockbox) == address(0)) { + excludes[j++] = "src/L1/ETHLockbox.sol"; + } // Get all contract names in the src directory, minus the excluded contracts. string[] memory contractNames = ForgeArtifacts.getContractNames("src/*", excludes);