diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b627c1474..f5d33af51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,12 @@ jobs: just semver-lock-no-build git diff --exit-code snapshots/semver-lock.json + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Check Rust bindings + run: just bindings-check + - name: Run Forge tests run: just test id: test diff --git a/.gitignore b/.gitignore index 73a2e81b4..89badc510 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Artifacts and Cache artifacts +!bindings/rust/artifacts/ forge-artifacts +target +Cargo.lock cache broadcast kout-proofs @@ -44,5 +47,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/bindings/balance_tracker.go b/bindings/go/balance_tracker.go similarity index 100% rename from bindings/balance_tracker.go rename to bindings/go/balance_tracker.go diff --git a/bindings/fee_disburser.go b/bindings/go/fee_disburser.go similarity index 100% rename from bindings/fee_disburser.go rename to bindings/go/fee_disburser.go diff --git a/bindings/go.mod b/bindings/go/go.mod similarity index 96% rename from bindings/go.mod rename to bindings/go/go.mod index 96c1023e9..96acf234e 100644 --- a/bindings/go.mod +++ b/bindings/go/go.mod @@ -1,4 +1,4 @@ -module github.com/base/contracts/bindings +module github.com/base/contracts/bindings/go go 1.23.0 diff --git a/bindings/go.sum b/bindings/go/go.sum similarity index 100% rename from bindings/go.sum rename to bindings/go/go.sum diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 000000000..5de725986 --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "base-contracts-bindings" +version = "0.1.0" +edition = "2021" + +[dependencies] +alloy-contract = "1.5.0" +alloy-sol-types = { version = "1.5.0", features = ["json"] } diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/bindings/rust/src/lib.rs @@ -0,0 +1 @@ + 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..5d7f92521 --- /dev/null +++ b/deployments/11155111-dev-no-nitro.json @@ -0,0 +1 @@ +{"TEEProverRegistry":"0x8897f8E1379C7548caE146e0ee26f6b1108A8570","TEEVerifier":"0xB319EA8fAac05BB01ABcce3671BC66c5A1f9A077","DisputeGameFactory":"0xF2e2f5F97e1aE0899924bE4Dd1D1795052Ea7551","AnchorStateRegistry":"0x59C17E560057A1cF2327E6Be35b070E46e4EC8b4","DelayedWETH":"0xD455276eF429eE495d91567BF3e78066bdfAc2b8","AggregateVerifier":"0x3EE78cfac34D7Cbe49D35B901B354272d2084951"} \ 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..4f5fbd38b --- /dev/null +++ b/interfaces/multiproof/IVerifier.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IVerifier { + /// @notice Verifies a proof. + /// @param proofBytes The proof. + /// @param imageId The image ID. + /// @param journal The journal. + /// @return valid Whether the proof is valid. + function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view returns (bool); + + /// @notice Nullifies the prover to prevent further proof verification. + /// @dev Should only occur if a soundness issue is found. + /// @dev Should only be callable by a proper dispute game. + function nullify() external; +} diff --git a/interfaces/multiproof/tee/INitroEnclaveVerifier.sol b/interfaces/multiproof/tee/INitroEnclaveVerifier.sol new file mode 100644 index 000000000..a3edb91cf --- /dev/null +++ b/interfaces/multiproof/tee/INitroEnclaveVerifier.sol @@ -0,0 +1,416 @@ +//SPDX-License-Identifier: Apache2.0 +pragma solidity ^0.8.0; + +/// @dev Custom version of Automata's NitroEnclaveVerifier contract at +/// https://github.com/automata-network/aws-nitro-enclave-attestation/tree/26c90565cb009e6539643a0956f9502a12ade672 +/// +/// Differences from the upstream Automata contract: +/// - Removes verification-with-explicit-program-ID and Pico logic +/// - Errors and events moved to the implementation contract +/// - Adds new admin functions + +/** + * @dev Enumeration of supported zero-knowledge proof coprocessor types + * Used to specify which proving system to use for attestation verification + */ +enum ZkCoProcessorType { + Unknown, + // RISC Zero zkVM proving system + RiscZero, + // Succinct SP1 proving system + Succinct +} + +/** + * @dev Configuration parameters for a specific zero-knowledge coprocessor + * Contains all necessary identifiers and addresses for ZK proof verification + * + * Note: This struct stores the "latest" (active) program identifiers. + * Multiple versions can be supported simultaneously via the version management functions. + */ +struct ZkCoProcessorConfig { + // Latest program ID for single attestation verification + bytes32 verifierId; + // Latest program ID for batch/aggregated verification + bytes32 aggregatorId; + // Default ZK verifier contract address (can be overridden per route) + address zkVerifier; +} + +/** + * @dev Input structure for attestation report verification + * Contains the raw attestation data and trusted certificate chain length + */ +struct VerifierInput { + // Number of trusted certificates in the chain + uint8 trustedCertsPrefixLen; + // Raw AWS Nitro Enclave attestation report (COSE_Sign1 format) + bytes attestationReport; +} + +/** + * @dev Output structure containing verified attestation data and metadata + * This represents the journal/output from zero-knowledge proof verification + */ +struct VerifierJournal { + // Overall verification result status + VerificationResult result; + // Number of certificates that were trusted during verification + uint8 trustedCertsPrefixLen; + // Attestation timestamp (Unix timestamp in milliseconds) + uint64 timestamp; + // Array of certificate hashes in the chain (root to leaf) + bytes32[] certs; + // User-defined data embedded in the attestation + bytes userData; + // Cryptographic nonce used for replay protection + bytes nonce; + // Public key extracted from the attestation + bytes publicKey; + // Platform Configuration Registers (integrity measurements) + Pcr[] pcrs; + // AWS Nitro Enclave module identifier + string moduleId; +} + +/** + * @dev Public value (journal) structure for batch verification operations + * Contains the aggregated results of multiple attestation verifications + */ +struct BatchVerifierJournal { + // Verification key that was used for batch verification + bytes32 verifierVk; + // Array of verified attestation results + VerifierJournal[] outputs; +} + +/** + * @dev 48-byte data structure for storing PCR values + * Split into two parts due to Solidity's 32-byte word limitation + */ +struct Bytes48 { + bytes32 first; + bytes16 second; +} + +/** + * @dev Platform Configuration Register (PCR) entry + * PCRs contain cryptographic measurements of the enclave's runtime state + */ +struct Pcr { + // PCR index number (0-23 for AWS Nitro Enclaves) + uint64 index; + // 48-byte PCR measurement value (SHA-384 hash) + Bytes48 value; +} + +/** + * @dev Enumeration of possible attestation verification results + * Indicates the outcome of the verification process + */ +enum VerificationResult { + // Attestation successfully verified + Success, + // Root certificate is not in the trusted set + RootCertNotTrusted, + // One or more intermediate certificates are not trusted + IntermediateCertsNotTrusted, + // Attestation timestamp is outside acceptable range + InvalidTimestamp +} + +/** + * @title INitroEnclaveVerifier + * @dev Interface for AWS Nitro Enclave attestation verification using zero-knowledge proofs + * + * This interface defines the contract for verifying AWS Nitro Enclave attestation reports + * on-chain using zero-knowledge proof systems (RISC Zero or Succinct SP1). The verifier + * validates the cryptographic integrity of attestation reports while maintaining privacy + * and reducing gas costs through ZK proofs. + * + * Key features: + * - Single and batch attestation verification + * - Support for multiple ZK proving systems + * - Multi-version program support for seamless upgrades + * - Route-based verifier configuration + * - Certificate chain management and revocation + * - Timestamp validation with configurable tolerance + * - Platform Configuration Register (PCR) verification + */ +interface INitroEnclaveVerifier { + // ============ Query Functions ============ + + /** + * @dev Returns the maximum allowed time difference for attestation timestamp validation + * @return Maximum time difference in seconds between attestation time and current block time + */ + function maxTimeDiff() external view returns (uint64); + + /** + * @dev Returns the hash of the trusted root certificate + * @return Hash of the AWS Nitro Enclave root certificate + */ + function rootCert() external view returns (bytes32); + + /** + * @dev Returns the address of the proof submitter + * @return Address of the proof submitter + */ + function proofSubmitter() external view returns (address); + + /** + * @dev Retrieves the configuration for a specific coprocessor + * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) + * @return ZkCoProcessorConfig Configuration parameters including program IDs and verifier address + */ + function getZkConfig(ZkCoProcessorType _zkCoProcessor) external view returns (ZkCoProcessorConfig memory); + + /** + * @dev Returns all supported verifier program IDs for a coprocessor + * @param _zkCoProcessor Type of ZK coprocessor + * @return Array of all supported verifier program IDs + */ + function getVerifierIds(ZkCoProcessorType _zkCoProcessor) external view returns (bytes32[] memory); + + /** + * @dev Returns all supported aggregator program IDs for a coprocessor + * @param _zkCoProcessor Type of ZK coprocessor + * @return Array of all supported aggregator program IDs + */ + function getAggregatorIds(ZkCoProcessorType _zkCoProcessor) external view returns (bytes32[] memory); + + /** + * @dev Checks if a verifier program ID is in the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId Verifier program ID to check + * @return True if the ID is supported + */ + function isVerifierIdSupported(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external view returns (bool); + + /** + * @dev Checks if an aggregator program ID is in the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _aggregatorId Aggregator program ID to check + * @return True if the ID is supported + */ + function isAggregatorIdSupported( + ZkCoProcessorType _zkCoProcessor, + bytes32 _aggregatorId + ) + external + view + returns (bool); + + /** + * @dev Gets the verifier address for a specific route + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector + * @return Verifier address (route-specific or default fallback) + * + * Note: Reverts if the route is frozen + */ + function getZkVerifier(ZkCoProcessorType _zkCoProcessor, bytes4 _selector) external view returns (address); + + /** + * @dev Returns the verifierProofId for a given verifierId + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId The verifier program ID + * @return The corresponding verifierProofId + */ + function getVerifierProofId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external view returns (bytes32); + + /** + * @dev Checks how many certificates in each report are trusted + * @param _report_certs Array of certificate chains, each containing certificate hashes + * @return Array indicating the number of trusted certificates in each chain + * + * For each certificate chain: + * - Validates that the first certificate matches the root certificate + * - Counts consecutive trusted certificates starting from the root + * - Returns the count of trusted certificates for each chain + */ + function checkTrustedIntermediateCerts(bytes32[][] calldata _report_certs) external view returns (uint8[] memory); + + // ============ Admin Functions ============ + + /** + * @dev Sets the trusted root certificate hash + * @param _rootCert Hash of the new root certificate + * + * Requirements: + * - Only callable by contract owner + */ + function setRootCert(bytes32 _rootCert) external; + + /** + * @dev Updates the maximum allowed time difference for attestation timestamp validation + * @param _maxTimeDiff New maximum time difference in seconds + * + * Requirements: + * - Only callable by contract owner + * - Must be greater than zero + */ + function setMaxTimeDiff(uint64 _maxTimeDiff) external; + + /** + * @dev Sets the proof submitter address + * @param _proofSubmitter The address of the proof submitter + * + * Requirements: + * - Only callable by contract owner + * - Address must not be zero + */ + function setProofSubmitter(address _proofSubmitter) external; + + /** + * @dev Configures the zero-knowledge verification parameters for a specific coprocessor + * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) + * @param _config Configuration parameters including program IDs and verifier address + * @param _verifierProofId The verifierProofId corresponding to the verifierId in config + * + * Requirements: + * - Only callable by contract owner + * - Must specify valid coprocessor type and configuration + */ + function setZkConfiguration( + ZkCoProcessorType _zkCoProcessor, + ZkCoProcessorConfig memory _config, + bytes32 _verifierProofId + ) + external; + + /** + * @dev Revokes a trusted intermediate certificate + * @param _certHash Hash of the certificate to revoke + * + * Requirements: + * - Only callable by contract owner + * - Certificate must exist in the trusted set + */ + function revokeCert(bytes32 _certHash) external; + + /** + * @dev Updates the verifier program ID, adding the new version to the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _newVerifierId New verifier program ID to set as latest + * @param _newVerifierProofId New verifier proof ID (used in batch verification) + * + * Requirements: + * - Only callable by contract owner + * - New ID must be different from current latest + */ + function updateVerifierId( + ZkCoProcessorType _zkCoProcessor, + bytes32 _newVerifierId, + bytes32 _newVerifierProofId + ) + external; + + /** + * @dev Updates the aggregator program ID, adding the new version to the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _newAggregatorId New aggregator program ID to set as latest + * + * Requirements: + * - Only callable by contract owner + * - New ID must be different from current latest + */ + function updateAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _newAggregatorId) external; + + /** + * @dev Removes a verifier program ID from the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId Verifier program ID to remove + * + * Requirements: + * - Only callable by contract owner + * - Cannot remove the currently active (latest) verifier ID + * - ID must exist in the supported set + */ + function removeVerifierId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external; + + /** + * @dev Removes an aggregator program ID from the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _aggregatorId Aggregator program ID to remove + * + * Requirements: + * - Only callable by contract owner + * - Cannot remove the currently active (latest) aggregator ID + * - ID must exist in the supported set + */ + function removeAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _aggregatorId) external; + + /** + * @dev Adds a route-specific verifier override + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector (first 4 bytes of proof data) + * @param _verifier Address of the verifier contract for this route + * + * Requirements: + * - Only callable by contract owner + * - Route must not be frozen + * - Verifier address must not be zero + */ + function addVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector, address _verifier) external; + + /** + * @dev Permanently freezes a verification route + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector to freeze + * + * Requirements: + * - Only callable by contract owner + * - Route must not already be frozen + * + * WARNING: This action is IRREVERSIBLE + */ + function freezeVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector) external; + + // ============ Verification Functions ============ + + /** + * @dev Verifies a single attestation report using zero-knowledge proof + * @param output Encoded VerifierJournal containing the verification result + * @param zkCoprocessor Type of ZK coprocessor used to generate the proof + * @param proofBytes Zero-knowledge proof data for the attestation + * @return VerifierJournal containing the verification result and extracted data + * + * This function: + * 1. Verifies the ZK proof using the specified coprocessor + * 2. Decodes the verification result + * 3. Validates the certificate chain against trusted certificates + * 4. Checks timestamp validity within the allowed time difference + * 5. Caches newly discovered trusted certificates + * 6. Returns the complete verification result + */ + function verify( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + returns (VerifierJournal memory); + + /** + * @dev Verifies multiple attestation reports in a single batch operation + * @param output Encoded BatchVerifierJournal containing aggregated verification results + * @param zkCoprocessor Type of ZK coprocessor used to generate the proof + * @param proofBytes Zero-knowledge proof data for batch verification + * @return Array of VerifierJournal results, one for each attestation in the batch + * + * This function: + * 1. Verifies the ZK proof using the specified coprocessor + * 2. Decodes the batch verification results + * 3. Validates each attestation's certificate chain and timestamp + * 4. Caches newly discovered trusted certificates + * 5. Returns the verification results for all attestations + */ + function batchVerify( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + returns (VerifierJournal[] memory); +} diff --git a/justfile b/justfile index 45fd3ac79..972472e9b 100644 --- a/justfile +++ b/justfile @@ -222,6 +222,72 @@ snapshots-no-build: snapshots-abi-storage-no-build semver-lock-no-build snapshots: build-source snapshots-no-build + +######################################################## +# BINDINGS # +######################################################## + +# Adds a new Rust binding for a Solidity contract. +# Usage: just bindings-add src/L2/FlashblockIndex.sol +bindings-add SOL_PATH: build-source + #!/bin/bash + set -euo pipefail + + sol_path="{{SOL_PATH}}" + + # Extract contract name (e.g. FlashblockIndex from src/L2/FlashblockIndex.sol) + contract=$(basename "$sol_path" .sol) + + # Extract module (e.g. l2 from src/L2/FlashblockIndex.sol) + module=$(echo "$sol_path" | sed 's|^src/||' | xargs dirname | tr '[:upper:]' '[:lower:]') + + # Convert PascalCase to snake_case by inserting _ at lower|upper and acronym|word boundaries + snake=$(echo "$contract" | sed -E 's/([a-z0-9])([A-Z])/\1_\2/g' | sed -E 's/([A-Z]+)([A-Z][a-z])/\1_\2/g' | tr '[:upper:]' '[:lower:]') + + rust_dir="bindings/rust/src/${module}" + rust_file="${rust_dir}/${snake}.rs" + mod_file="${rust_dir}/mod.rs" + artifact="bindings/rust/artifacts/${contract}.json" + + # Ensure output directories exist before writing generated files. + mkdir -p "bindings/rust/artifacts" "$rust_dir" + + # 1. Strip artifact + jq '{abi, bytecode: {object: .bytecode.object}, deployedBytecode: {object: .deployedBytecode.object}}' \ + "forge-artifacts/${contract}.sol/${contract}.json" > "$artifact" + + # 2. Create .rs file + cat > "$rust_file" << EOF + use alloy_sol_types::sol; + + sol!( + #[sol(rpc, abi)] + ${contract}, + concat!( + env!("CARGO_MANIFEST_DIR"), + "/artifacts/${contract}.json" + ) + ); + EOF + + # 3. Add to mod.rs (skip if already present) + if ! grep -q "mod ${snake};" "$mod_file" 2>/dev/null; then + echo "" >> "$mod_file" + echo "mod ${snake};" >> "$mod_file" + echo "pub use ${snake}::${contract};" >> "$mod_file" + fi + + # 4. Add module to lib.rs (skip if already present) + if ! grep -q "pub mod ${module};" "bindings/rust/src/lib.rs" 2>/dev/null; then + echo "pub mod ${module};" >> "bindings/rust/src/lib.rs" + fi + + # 5. Format generated code + cd bindings/rust && cargo fmt + + echo "Added binding: ${contract} -> ${rust_file}" + + ######################################################## # CHECKS # ######################################################## @@ -232,6 +298,21 @@ snapshots-check-no-build: snapshots-no-build # Checks if the snapshots are up to date. snapshots-check: build snapshots-check-no-build +# Checks that the Rust bindings crate compiles. +bindings-check: + cd bindings/rust && cargo check + +# Checks that committed Rust binding artifacts match forge-artifacts. +bindings-artifacts-check-no-build: + #!/bin/bash + set -euo pipefail + for src in bindings/rust/artifacts/*.json; do + name=$(basename "$src" .json) + jq '{abi, bytecode: {object: .bytecode.object}, deployedBytecode: {object: .deployedBytecode.object}}' \ + "forge-artifacts/${name}.sol/${name}.json" > "$src" + done + git diff --exit-code bindings/rust/artifacts/ + # Checks interface correctness without building. interfaces-check-no-build: go run ./scripts/checks/interfaces @@ -338,7 +419,8 @@ check: validate-spacers-no-build \ reinitializer-check-no-build \ interfaces-check-no-build \ - lint-forge-tests-check-no-build + lint-forge-tests-check-no-build \ + bindings-artifacts-check-no-build ######################################################## # DEV TOOLS # 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..cc91cad7a 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("TEEProverRegistry", address(dio.teeProverRegistryImpl)); // 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..38bb6691c 100644 --- a/scripts/deploy/DeployImplementations.s.sol +++ b/scripts/deploy/DeployImplementations.s.sol @@ -39,10 +39,17 @@ 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 "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; +import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.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 +65,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 +111,8 @@ contract DeployImplementations is Script { IPermissionedDisputeGameV2 permissionedDisputeGameV2Impl; ISuperFaultDisputeGame superFaultDisputeGameImpl; ISuperPermissionedDisputeGame superPermissionedDisputeGameImpl; + IVerifier aggregateVerifierImpl; + TEEProverRegistry teeProverRegistryImpl; } bytes32 internal _salt = DeployUtils.DEFAULT_SALT; @@ -128,6 +146,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 +714,38 @@ contract DeployImplementations is Script { _output.opcmStandardValidator = impl; } + function deployAggregateVerifierImpl(Input memory _input, Output memory _output) private { + address zkVerifier = address(new MockVerifier(_output.anchorStateRegistryImpl)); + + address teeVerifierImpl; + { + TEEProverRegistry scgImpl = new TEEProverRegistry(INitroEnclaveVerifier(_input.nitroEnclaveVerifier)); + vm.label(address(scgImpl), "TEEProverRegistryImpl"); + _output.teeProverRegistryImpl = scgImpl; + teeVerifierImpl = address(new TEEVerifier(scgImpl, _output.anchorStateRegistryImpl)); + } + + _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..4e7923058 --- /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 DevTEEProverRegistry, 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 $TEE_PROVER_REGISTRY \ + * "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 | + * |----------------------------|------------------------|------------------------| + * | TEEProverRegistry | DevTEEProverRegistry | TEEProverRegistry | + * | 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 "interfaces/multiproof/tee/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 { DevTEEProverRegistry } from "src/multiproof/mocks/MockDevTEEProverRegistry.sol"; +import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.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 DevTEEProverRegistry 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 teeProverRegistryProxy; + 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 DevTEEProverRegistry - NO attestation required."); + + vm.startBroadcast(); + + _registerProposer(cfg.teeProposer()); + _deployInfrastructure(gameType); + _deployTEEContracts(cfg.finalSystemOwner()); + _deployAggregateVerifier(gameType); + + vm.stopBroadcast(); + + _printSummary(); + _writeOutput(); + } + + function _deployTEEContracts(address owner) internal { + address scgImpl = address(new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)))); + teeProverRegistryProxy = address( + new TransparentUpgradeableProxy( + scgImpl, address(0xdead), abi.encodeCall(TEEProverRegistry.initialize, (owner, owner)) + ) + ); + console.log("DevTEEProverRegistry:", teeProverRegistryProxy); + + teeVerifier = address( + new TEEVerifier(TEEProverRegistry(teeProverRegistryProxy), IAnchorStateRegistry(mockAnchorRegistry)) + ); + console.log("TEEVerifier:", teeVerifier); + } + + function _registerProposer(address teeProposer) internal { + TEEProverRegistry(teeProverRegistryProxy).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(IAnchorStateRegistry(mockAnchorRegistry))); + 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(" DevTEEProverRegistry:", teeProverRegistryProxy); + 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", teeProverRegistryProxy); + 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( + '{"TEEProverRegistry":"', + vm.toString(teeProverRegistryProxy), + '","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..d812be401 --- /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 TEEProverRegistry, 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 $TEE_PROVER_REGISTRY "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 $TEE_PROVER_REGISTRY \ + * "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 $TEE_PROVER_REGISTRY "isValidSigner(address)(bool)" $SIGNER_ADDRESS + * + * # Get the PCR0 hash associated with the signer + * cast call $TEE_PROVER_REGISTRY "signerPCR0(address)(bytes32)" $SIGNER_ADDRESS + * + * ───────────────────────────────────────────────────────────────────────────────── + * COMPARISON WITH DeployDevNoNitro + * ───────────────────────────────────────────────────────────────────────────────── + * + * | Feature | DeployDevNoNitro | DeployDevWithNitro | + * |----------------------------|------------------------|------------------------| + * | TEEProverRegistry | DevTEEProverRegistry | TEEProverRegistry | + * | 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 "interfaces/multiproof/tee/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 { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.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 TEEProverRegistry 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 teeProverRegistryProxy; + 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 TEEProverRegistry - ZK attestation proof REQUIRED."); + console.log("NitroEnclaveVerifier:", cfg.nitroEnclaveVerifier()); + + vm.startBroadcast(); + + _registerProposer(cfg.teeProposer()); + _deployInfrastructure(gameType); + _deployTEEContracts(cfg.finalSystemOwner(), cfg.nitroEnclaveVerifier()); + _deployAggregateVerifier(gameType); + + vm.stopBroadcast(); + + _printSummary(); + _writeOutput(); + } + + function _deployTEEContracts(address owner, address _nitroEnclaveVerifier) internal { + address scgImpl = address(new TEEProverRegistry(INitroEnclaveVerifier(_nitroEnclaveVerifier))); + console.log("NitroEnclaveVerifier (external):", _nitroEnclaveVerifier); + teeProverRegistryProxy = address( + new TransparentUpgradeableProxy( + scgImpl, address(0xdead), abi.encodeCall(TEEProverRegistry.initialize, (owner, owner)) + ) + ); + console.log("TEEProverRegistry:", teeProverRegistryProxy); + + teeVerifier = address( + new TEEVerifier(TEEProverRegistry(teeProverRegistryProxy), IAnchorStateRegistry(mockAnchorRegistry)) + ); + console.log("TEEVerifier:", teeVerifier); + } + + function _registerProposer(address teeProposer) internal { + TEEProverRegistry(teeProverRegistryProxy).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(IAnchorStateRegistry(mockAnchorRegistry))); + 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(" TEEProverRegistry:", teeProverRegistryProxy); + 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", teeProverRegistryProxy); + 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", teeProverRegistryProxy); + 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( + '{"TEEProverRegistry":"', + vm.toString(teeProverRegistryProxy), + '","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..38f06763e --- /dev/null +++ b/snapshots/abi/AggregateVerifier.json @@ -0,0 +1,1099 @@ +[ + { + "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" + }, + { + "internalType": "uint256", + "name": "proofThreshold", + "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": "PROOF_THRESHOLD", + "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": "bytes", + "name": "proofBytes", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "intermediateRootIndex", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "intermediateRootToProve", + "type": "bytes32" + } + ], + "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": "counteredByIntermediateRootIndexPlusOne", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "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": "proofCount", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "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": "uint256", + "name": "intermediateRootIndex", + "type": "uint256" + } + ], + "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": "BondTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "ClaimAlreadyResolved", + "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": "InvalidGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIntermediateRootIndex", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidParentGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProof", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProofThreshold", + "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": "NotEnoughProofs", + "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/DevTEEProverRegistry.json b/snapshots/abi/DevTEEProverRegistry.json new file mode 100644 index 000000000..6a61389fd --- /dev/null +++ b/snapshots/abi/DevTEEProverRegistry.json @@ -0,0 +1,471 @@ +[ + { + "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": "address", + "name": "signer", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "pcr0Hash", + "type": "bytes32" + } + ], + "name": "addDevSigner", + "outputs": [], + "stateMutability": "nonpayable", + "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": [], + "name": "getRegisteredSigners", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "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/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/MockSystemConfig.json b/snapshots/abi/MockSystemConfig.json new file mode 100644 index 000000000..b36dfe198 --- /dev/null +++ b/snapshots/abi/MockSystemConfig.json @@ -0,0 +1,33 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "guardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + } +] \ No newline at end of file diff --git a/snapshots/abi/MockVerifier.json b/snapshots/abi/MockVerifier.json new file mode 100644 index 000000000..7129f5617 --- /dev/null +++ b/snapshots/abi/MockVerifier.json @@ -0,0 +1,85 @@ +[ + { + "inputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ANCHOR_STATE_REGISTRY", + "outputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nullified", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nullify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "verify", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NotProperGame", + "type": "error" + }, + { + "inputs": [], + "name": "Nullified", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/NitroEnclaveVerifier.json b/snapshots/abi/NitroEnclaveVerifier.json new file mode 100644 index 000000000..9f2b98569 --- /dev/null +++ b/snapshots/abi/NitroEnclaveVerifier.json @@ -0,0 +1,1263 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "uint64", + "name": "_maxTimeDiff", + "type": "uint64" + }, + { + "internalType": "bytes32[]", + "name": "_initializeTrustedCerts", + "type": "bytes32[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes4", + "name": "_selector", + "type": "bytes4" + }, + { + "internalType": "address", + "name": "_verifier", + "type": "address" + } + ], + "name": "addVerifyRoute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "output", + "type": "bytes" + }, + { + "internalType": "enum ZkCoProcessorType", + "name": "zkCoprocessor", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "batchVerify", + "outputs": [ + { + "components": [ + { + "internalType": "enum VerificationResult", + "name": "result", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "trustedCertsPrefixLen", + "type": "uint8" + }, + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + }, + { + "internalType": "bytes32[]", + "name": "certs", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "nonce", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "publicKey", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "index", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "first", + "type": "bytes32" + }, + { + "internalType": "bytes16", + "name": "second", + "type": "bytes16" + } + ], + "internalType": "struct Bytes48", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct Pcr[]", + "name": "pcrs", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "moduleId", + "type": "string" + } + ], + "internalType": "struct VerifierJournal[]", + "name": "results", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cancelOwnershipHandover", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[][]", + "name": "_report_certs", + "type": "bytes32[][]" + } + ], + "name": "checkTrustedIntermediateCerts", + "outputs": [ + { + "internalType": "uint8[]", + "name": "", + "type": "uint8[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pendingOwner", + "type": "address" + } + ], + "name": "completeOwnershipHandover", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes4", + "name": "_selector", + "type": "bytes4" + } + ], + "name": "freezeVerifyRoute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + } + ], + "name": "getAggregatorIds", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + } + ], + "name": "getVerifierIds", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_verifierId", + "type": "bytes32" + } + ], + "name": "getVerifierProofId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + } + ], + "name": "getZkConfig", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "verifierId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "aggregatorId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "zkVerifier", + "type": "address" + } + ], + "internalType": "struct ZkCoProcessorConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes4", + "name": "_selector", + "type": "bytes4" + } + ], + "name": "getZkVerifier", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_aggregatorId", + "type": "bytes32" + } + ], + "name": "isAggregatorIdSupported", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_verifierId", + "type": "bytes32" + } + ], + "name": "isVerifierIdSupported", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxTimeDiff", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "result", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pendingOwner", + "type": "address" + } + ], + "name": "ownershipHandoverExpiresAt", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proofSubmitter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_aggregatorId", + "type": "bytes32" + } + ], + "name": "removeAggregatorId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_verifierId", + "type": "bytes32" + } + ], + "name": "removeVerifierId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "requestOwnershipHandover", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_certHash", + "type": "bytes32" + } + ], + "name": "revokeCert", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rootCert", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "_maxTimeDiff", + "type": "uint64" + } + ], + "name": "setMaxTimeDiff", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_proofSubmitter", + "type": "address" + } + ], + "name": "setProofSubmitter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_rootCert", + "type": "bytes32" + } + ], + "name": "setRootCert", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "verifierId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "aggregatorId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "zkVerifier", + "type": "address" + } + ], + "internalType": "struct ZkCoProcessorConfig", + "name": "_config", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_verifierProofId", + "type": "bytes32" + } + ], + "name": "setZkConfiguration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "trustedCertHash", + "type": "bytes32" + } + ], + "name": "trustedIntermediateCerts", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_newAggregatorId", + "type": "bytes32" + } + ], + "name": "updateAggregatorId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "_zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_newVerifierId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_newVerifierProofId", + "type": "bytes32" + } + ], + "name": "updateVerifierId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "output", + "type": "bytes" + }, + { + "internalType": "enum ZkCoProcessorType", + "name": "zkCoprocessor", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "verify", + "outputs": [ + { + "components": [ + { + "internalType": "enum VerificationResult", + "name": "result", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "trustedCertsPrefixLen", + "type": "uint8" + }, + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + }, + { + "internalType": "bytes32[]", + "name": "certs", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "nonce", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "publicKey", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "index", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "first", + "type": "bytes32" + }, + { + "internalType": "bytes16", + "name": "second", + "type": "bytes16" + } + ], + "internalType": "struct Bytes48", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct Pcr[]", + "name": "pcrs", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "moduleId", + "type": "string" + } + ], + "internalType": "struct VerifierJournal", + "name": "journal", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "", + "type": "uint8" + } + ], + "name": "zkConfig", + "outputs": [ + { + "internalType": "bytes32", + "name": "verifierId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "aggregatorId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "zkVerifier", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newId", + "type": "bytes32" + } + ], + "name": "AggregatorIdUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "enum VerificationResult", + "name": "result", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "output", + "type": "bytes" + } + ], + "name": "AttestationSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "verifierId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "encodedBatch", + "type": "bytes" + } + ], + "name": "BatchAttestationSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "certHash", + "type": "bytes32" + } + ], + "name": "CertRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "newMaxTimeDiff", + "type": "uint64" + } + ], + "name": "MaxTimeDiffUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pendingOwner", + "type": "address" + } + ], + "name": "OwnershipHandoverCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pendingOwner", + "type": "address" + } + ], + "name": "OwnershipHandoverRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "programId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isAggregator", + "type": "bool" + } + ], + "name": "ProgramIdRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newProofSubmitter", + "type": "address" + } + ], + "name": "ProofSubmitterChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "newRootCert", + "type": "bytes32" + } + ], + "name": "RootCertChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "newProofId", + "type": "bytes32" + } + ], + "name": "VerifierIdUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "verifierId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "aggregatorId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "zkVerifier", + "type": "address" + } + ], + "indexed": false, + "internalType": "struct ZkCoProcessorConfig", + "name": "config", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "verifierProofId", + "type": "bytes32" + } + ], + "name": "ZKConfigurationUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "address", + "name": "verifier", + "type": "address" + } + ], + "name": "ZkRouteAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "ZkRouteWasFrozen", + "type": "event" + }, + { + "inputs": [], + "name": "AlreadyInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "CallerNotProofSubmitter", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "identifier", + "type": "bytes32" + } + ], + "name": "CannotRemoveLatestProgramId", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "certHash", + "type": "bytes32" + } + ], + "name": "CertificateNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "NewOwnerIsZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "NoHandoverRequest", + "type": "error" + }, + { + "inputs": [], + "name": "NotImplemented", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "identifier", + "type": "bytes32" + } + ], + "name": "ProgramIdAlreadyLatest", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "identifier", + "type": "bytes32" + } + ], + "name": "ProgramIdNotFound", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "expected", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "actual", + "type": "bytes32" + } + ], + "name": "RootCertMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "Unknown_Zk_Coprocessor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "expected", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "actual", + "type": "bytes32" + } + ], + "name": "VerifierVkMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroMaxTimeDiff", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroProgramId", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroProofSubmitter", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroVerifierAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + }, + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "ZkRouteFrozen", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum ZkCoProcessorType", + "name": "zkCoProcessor", + "type": "uint8" + } + ], + "name": "ZkVerifierNotConfigured", + "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/TEEProverRegistry.json b/snapshots/abi/TEEProverRegistry.json new file mode 100644 index 000000000..7250c1f7d --- /dev/null +++ b/snapshots/abi/TEEProverRegistry.json @@ -0,0 +1,453 @@ +[ + { + "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": [], + "name": "getRegisteredSigners", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "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..4e408dd33 --- /dev/null +++ b/snapshots/abi/TEEVerifier.json @@ -0,0 +1,164 @@ +[ + { + "inputs": [ + { + "internalType": "contract TEEProverRegistry", + "name": "teeProverRegistry", + "type": "address" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ANCHOR_STATE_REGISTRY", + "outputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TEE_PROVER_REGISTRY", + "outputs": [ + { + "internalType": "contract TEEProverRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nullified", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nullify", + "outputs": [], + "stateMutability": "nonpayable", + "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" + }, + { + "inputs": [], + "name": "NotProperGame", + "type": "error" + }, + { + "inputs": [], + "name": "Nullified", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 5f3043e6d..cab2951cd 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -48,7 +48,7 @@ "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" }, "src/L1/SystemConfig.sol:SystemConfig": { - "initCodeHash": "0x184e74fa4993271e35d477155689ceba54c083c71f0f93f2dea0ee6330c61693", + "initCodeHash": "0x3e8e52d96398a6de91d8922769cc5d0bc7acb2a692689ceb70f1de816e8d6b14", "sourceCodeHash": "0x1a24ffe154eddbe0088a4b0848e662bf5eb482fe031ba528f74c51804f6e0395" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { @@ -128,11 +128,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 +180,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 +216,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 +231,18 @@ "initCodeHash": "0x3a82e248129d19764bb975bb79b48a982f077f33bb508480bf8d2ec1c0c9810d", "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, + "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { + "initCodeHash": "0xc3866b1d4515c9d7b0ac6679b182d836f79371402d9e649e301b24cf8ae8fade", + "sourceCodeHash": "0x98267b52a71222506c4893d6632dc5a36dd197a8d60de9a1f6578a80f7ebdf2d" + }, + "src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": { + "initCodeHash": "0x09760f3a16c82db6a0e292a394d8a97cc34cc4aa799aeee9c1e064c9e8181050", + "sourceCodeHash": "0xae23e7c3e21c373d491a0e496ece54c63b5ad09171db1a011011eae3e2831c72" + }, + "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { + "initCodeHash": "0xd65f1d604f979045a86d662e168cb7a867478da8a0ca294181872a90d209a66f", + "sourceCodeHash": "0xe8aa3c0710ee72546da52957730c11dc932065c444a013658bd2f5fd0c79085d" + }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", "sourceCodeHash": "0xac49a0ecf22b8a7bb3ebef830a2d27b19050f9b08941186e8563d5113cf0ce9c" @@ -255,8 +267,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..08fd716cb --- /dev/null +++ b/snapshots/storageLayout/AggregateVerifier.json @@ -0,0 +1,100 @@ +[ + { + "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": "32", + "label": "counteredByIntermediateRootIndexPlusOne", + "offset": 0, + "slot": "5", + "type": "uint256" + }, + { + "bytes": "32", + "label": "proofTypeToProver", + "offset": 0, + "slot": "6", + "type": "mapping(enum AggregateVerifier.ProofType => address)" + }, + { + "bytes": "8", + "label": "expectedResolution", + "offset": 0, + "slot": "7", + "type": "Timestamp" + }, + { + "bytes": "1", + "label": "proofCount", + "offset": 8, + "slot": "7", + "type": "uint8" + } +] \ 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/DevTEEProverRegistry.json b/snapshots/storageLayout/DevTEEProverRegistry.json new file mode 100644 index 000000000..590893632 --- /dev/null +++ b/snapshots/storageLayout/DevTEEProverRegistry.json @@ -0,0 +1,72 @@ +[ + { + "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)" + }, + { + "bytes": "32", + "label": "_registeredSigners", + "offset": 0, + "slot": "104", + "type": "struct EnumerableSetLib.AddressSet" + } +] \ 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/MockSystemConfig.json b/snapshots/storageLayout/MockSystemConfig.json new file mode 100644 index 000000000..8ef57702b --- /dev/null +++ b/snapshots/storageLayout/MockSystemConfig.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "20", + "label": "guardian", + "offset": 0, + "slot": "0", + "type": "address" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/MockVerifier.json b/snapshots/storageLayout/MockVerifier.json new file mode 100644 index 000000000..7b7ad77a9 --- /dev/null +++ b/snapshots/storageLayout/MockVerifier.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "1", + "label": "nullified", + "offset": 0, + "slot": "0", + "type": "bool" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/NitroEnclaveVerifier.json b/snapshots/storageLayout/NitroEnclaveVerifier.json new file mode 100644 index 000000000..0feab09c3 --- /dev/null +++ b/snapshots/storageLayout/NitroEnclaveVerifier.json @@ -0,0 +1,65 @@ +[ + { + "bytes": "20", + "label": "proofSubmitter", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "32", + "label": "zkConfig", + "offset": 0, + "slot": "1", + "type": "mapping(enum ZkCoProcessorType => struct ZkCoProcessorConfig)" + }, + { + "bytes": "32", + "label": "trustedIntermediateCerts", + "offset": 0, + "slot": "2", + "type": "mapping(bytes32 => bool)" + }, + { + "bytes": "8", + "label": "maxTimeDiff", + "offset": 0, + "slot": "3", + "type": "uint64" + }, + { + "bytes": "32", + "label": "rootCert", + "offset": 0, + "slot": "4", + "type": "bytes32" + }, + { + "bytes": "32", + "label": "_verifierIdSet", + "offset": 0, + "slot": "5", + "type": "mapping(enum ZkCoProcessorType => struct EnumerableSet.Bytes32Set)" + }, + { + "bytes": "32", + "label": "_aggregatorIdSet", + "offset": 0, + "slot": "6", + "type": "mapping(enum ZkCoProcessorType => struct EnumerableSet.Bytes32Set)" + }, + { + "bytes": "32", + "label": "_zkVerifierRoutes", + "offset": 0, + "slot": "7", + "type": "mapping(enum ZkCoProcessorType => mapping(bytes4 => address))" + }, + { + "bytes": "32", + "label": "_verifierProofIds", + "offset": 0, + "slot": "8", + "type": "mapping(enum ZkCoProcessorType => mapping(bytes32 => bytes32))" + } +] \ 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/TEEProverRegistry.json b/snapshots/storageLayout/TEEProverRegistry.json new file mode 100644 index 000000000..590893632 --- /dev/null +++ b/snapshots/storageLayout/TEEProverRegistry.json @@ -0,0 +1,72 @@ +[ + { + "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)" + }, + { + "bytes": "32", + "label": "_registeredSigners", + "offset": 0, + "slot": "104", + "type": "struct EnumerableSetLib.AddressSet" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/TEEVerifier.json b/snapshots/storageLayout/TEEVerifier.json new file mode 100644 index 000000000..7b7ad77a9 --- /dev/null +++ b/snapshots/storageLayout/TEEVerifier.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "1", + "label": "nullified", + "offset": 0, + "slot": "0", + "type": "bool" + } +] \ 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..43bac056e --- /dev/null +++ b/src/multiproof/AggregateVerifier.sol @@ -0,0 +1,1016 @@ +// SPDX-License-Identifier: MIT +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; + //////////////////////////////////////////////////////////////// + // 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 index of the intermediate root that countered this game. + /// @dev The index is 1-based, so the countered intermediate root index is counteredByIntermediateRootIndexPlusOne - + /// 1. 0 is used to indicate that the game was not countered. + uint256 public counteredByIntermediateRootIndexPlusOne; + + /// @notice The address that provided a proof of the given type. + /// @dev The address is the zero address if no proof has been provided or the proof has been nullified. + mapping(ProofType => address) internal proofTypeToProver; + + /// @notice The timestamp of the game's expected resolution. + Timestamp public expectedResolution; + + /// @notice The number of proofs provided. + uint8 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 intermediateRootIndex The index of the intermediate root that was countered. + event Challenged(address indexed challenger, uint256 intermediateRootIndex); + + /// @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 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 starting root in the AnchorStateRegistry. + startingOutputRoot = ANCHOR_STATE_REGISTRY.getStartingAnchorRoot(); + } + + // 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() + ); + + _proofVerifiedUpdate(proofType, gameCreator()); + + // Set the bond recipient to the creator. It can change if challenged successfully. + bondRecipient = gameCreator(); + + // 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() + ); + _proofVerifiedUpdate(proofType, msg.sender); + } + + /// @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(); + + bool isChallenged = counteredByIntermediateRootIndexPlusOne > 0; + + // 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(); + // If the game is challenged, status is CHALLENGER_WINS. + // If the game is not challenged, status is DEFENDER_WINS. + status = isChallenged ? GameStatus.CHALLENGER_WINS : GameStatus.DEFENDER_WINS; + } + + if (proofCount < PROOF_THRESHOLD) revert NotEnoughProofs(); + + // Default bond recipient is the creator. We only change if successfully challenged. + if (isChallenged) { + bondRecipient = proofTypeToProver[ProofType.ZK]; + } + // 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 proofBytes The proof bytes. + /// @param intermediateRootIndex The index of the intermediate root to challenge. + /// @param intermediateRootToProve The intermediate root that the proof claims to be correct. + function challenge( + bytes calldata proofBytes, + uint256 intermediateRootIndex, + bytes32 intermediateRootToProve + ) + 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. + if (proofTypeToProver[ProofType.TEE] == address(0)) revert MissingProof(ProofType.TEE); + // You should nullify the game if a ZK proof has already been provided. + // This also prevents another challenge while the current challenge is in progress. + if (proofTypeToProver[ProofType.ZK] != address(0)) revert AlreadyProven(ProofType.ZK); + + // Can only challenge with a ZK proof. + ProofType proofType = ProofType(uint8(proofBytes[0])); + if (proofType != ProofType.ZK) revert InvalidProofType(); + + _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); + + (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = + _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); + + _verifyProof( + proofBytes[1:], + proofType, + msg.sender, + l1Head().raw(), + startingRoot, + startingL2SequenceNumber, + intermediateRootToProve, + endingL2SequenceNumber, + abi.encodePacked(intermediateRootToProve) + ); + + // This allows a ZK nullification to be performed. + proofTypeToProver[proofType] = msg.sender; + + // This is only in case the ZK proof is nullified, which would lower the proof count. + // If the ZK is nullified, we allow the remaining TEE proof to resolve. + // The expected resolution time can no longer be increased as both proof types have been submitted. + proofCount += 1; + + // We purposely increase the resolution to allow for a ZK nullification. + expectedResolution = Timestamp.wrap(uint64(block.timestamp + SLOW_FINALIZATION_DELAY)); + + // Store which intermediate root was countered. + counteredByIntermediateRootIndexPlusOne = intermediateRootIndex + 1; + + // Emit the challenged event. + emit Challenged(msg.sender, intermediateRootIndex); + } + + /// @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 + { + // Can only nullify if the game is still in progress. + if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); + + ProofType proofType = ProofType(uint8(proofBytes[0])); + if (proofTypeToProver[proofType] == address(0)) revert MissingProof(proofType); + + // If this game has been challenged, can only nullify the challenged intermediate root and only with ZK. + if (counteredByIntermediateRootIndexPlusOne > 0) { + if (intermediateRootIndex != counteredByIntermediateRootIndexPlusOne - 1) { + revert InvalidIntermediateRootIndex(); + } + if (intermediateRootToProve != intermediateOutputRoot(intermediateRootIndex)) { + revert IntermediateRootMismatch(intermediateRootToProve, intermediateOutputRoot(intermediateRootIndex)); + } + if (proofType != ProofType.ZK) revert InvalidProofType(); + } else { + _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); + } + + (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = + _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); + + _verifyProof( + proofBytes[1:], + proofType, + msg.sender, + l1Head().raw(), + startingRoot, + startingL2SequenceNumber, + intermediateRootToProve, + endingL2SequenceNumber, + abi.encodePacked(intermediateRootToProve) + ); + + _proofRefutedUpdate(proofType); + + emit Nullified(msg.sender, intermediateRootIndex, intermediateRootToProve); + + // Nullify the verifier to prevent further proof verification. + if (proofType == ProofType.ZK) { + // Delete the challenged intermediate root if one existed. + delete counteredByIntermediateRootIndexPlusOne; + + IVerifier(ZK_VERIFIER).nullify(); + } else if (proofType == ProofType.TEE) { + IVerifier(TEE_VERIFIER).nullify(); + } + } + + /// @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 game must have resolved or 14 days have passed since creation. + // 14 days chosen as the proof system should have progressed enough so this can't update the + // anchor state registry anymore. + if (expectedResolution.raw() != type(uint64).max) { + if (resolvedAt.raw() == 0) revert GameNotResolved(); + } else { + if (block.timestamp < createdAt.raw() + 14 days) revert GameNotOver(); + } + + if (!bondUnlocked) { + DELAYED_WETH.unlock(bondRecipient, bondAmount); + bondUnlocked = true; + return; + } + + bondClaimed = true; + 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); + } + + function _proofVerifiedUpdate(ProofType proofType, address prover) internal { + proofTypeToProver[proofType] = prover; + proofCount += 1; + + _decreaseExpectedResolution(); + + emit Proved(prover, proofType); + } + + /// @notice Decreases the expected resolution timestamp. + function _decreaseExpectedResolution() 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; + } + + // Only allow decreases to the expected resolution. + uint64 newResolution = uint64(block.timestamp) + delay; + expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, expectedResolution.raw()))); + } + + /// @dev Should only occur if challenged or nullified. + function _proofRefutedUpdate(ProofType proofType) internal { + delete proofTypeToProver[proofType]; + + // Should not be possible, but just in case. + if (proofCount == 0) revert NotEnoughProofs(); + unchecked { + proofCount -= 1; + } + + _increaseExpectedResolution(); + } + + function _increaseExpectedResolution() 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; + } + + // We purposely increase the resolution even if it's longer than it should be + // as this can only occur if there is an issue with the proof system so + // we give enough time to resolve the issue and possibly blacklist this game. + expectedResolution = Timestamp.wrap(uint64(block.timestamp) + delay); + } + + 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 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 Checks if the intermediate root index is valid and that the intermediate root differs from the proposed + /// intermediate root. + ///@param intermediateRootIndex The index of the intermediate root to check. + /// @param intermediateRootToProve The intermediate root that the proof claims to be correct. + function _checkIntermediateRoot(uint256 intermediateRootIndex, bytes32 intermediateRootToProve) internal view { + if (intermediateRootIndex >= intermediateOutputRootsCount()) revert InvalidIntermediateRootIndex(); + if (intermediateOutputRoot(intermediateRootIndex) == intermediateRootToProve) { + revert IntermediateRootSameAsProposed(); + } + } + + /// @notice Gets the starting intermediate root and the starting and ending L2 sequence numbers. + /// @param intermediateRootIndex The index of the intermediate root to get the starting intermediate root and L2 + /// sequence numbers for. @return startingRoot The starting intermediate root. + /// @return startingL2SequenceNumber The starting L2 sequence number. + /// @return endingL2SequenceNumber The ending L2 sequence number. + function _getStartingIntermediateRootAndL2SequenceNumbers(uint256 intermediateRootIndex) + internal + view + returns (bytes32, uint256, uint256) + { + bytes32 startingRoot = intermediateRootIndex == 0 + ? startingOutputRoot.root.raw() + : intermediateOutputRoot(intermediateRootIndex - 1); + uint256 startingL2SequenceNumber = + startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL; + uint256 endingL2SequenceNumber = startingL2SequenceNumber + INTERMEDIATE_BLOCK_INTERVAL; + return (startingRoot, startingL2SequenceNumber, endingL2SequenceNumber); + } + + /// @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/Verifier.sol b/src/multiproof/Verifier.sol new file mode 100644 index 000000000..a792f11dd --- /dev/null +++ b/src/multiproof/Verifier.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; + +abstract contract Verifier is IVerifier { + /// @notice The anchor state registry. + IAnchorStateRegistry public immutable ANCHOR_STATE_REGISTRY; + + /// @notice Whether this verifier has been nullified. + /// @dev This is used to prevent further proof verification after a soundness issue is found. + bool public nullified; + + /// @notice Thrown when the verifier has been nullified. + error Nullified(); + + /// @notice Thrown when the caller trying to nullify is not a proper dispute game. + error NotProperGame(); + + /// @notice Modifier to prevent execution if the verifier has been nullified. + modifier notNullified() { + if (nullified) revert Nullified(); + _; + } + + constructor(IAnchorStateRegistry anchorStateRegistry) { + ANCHOR_STATE_REGISTRY = anchorStateRegistry; + } + + /// @notice Nullifies the verifier to prevent further proof verification. + /// @dev Should only occur if a soundness issue is found. + /// @dev Should only be callable by a proper dispute game. + function nullify() external override { + if ( + !ANCHOR_STATE_REGISTRY.isGameProper(IDisputeGame(msg.sender)) + || !ANCHOR_STATE_REGISTRY.isGameRespected(IDisputeGame(msg.sender)) + ) revert NotProperGame(); + nullified = true; + } +} diff --git a/src/multiproof/mocks/MockDevTEEProverRegistry.sol b/src/multiproof/mocks/MockDevTEEProverRegistry.sol new file mode 100644 index 000000000..759a102d0 --- /dev/null +++ b/src/multiproof/mocks/MockDevTEEProverRegistry.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; +import { EnumerableSetLib } from "@solady-v0.0.245/utils/EnumerableSetLib.sol"; + +import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol"; + +/// @title DevTEEProverRegistry +/// @notice Development version of TEEProverRegistry 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 DevTEEProverRegistry is TEEProverRegistry { + using EnumerableSetLib for EnumerableSetLib.AddressSet; + + constructor(INitroEnclaveVerifier nitroVerifier) TEEProverRegistry(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; + _registeredSigners.add(signer); + 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..c9093337b --- /dev/null +++ b/src/multiproof/mocks/MockVerifier.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Verifier } from "../Verifier.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; + +contract MockVerifier is Verifier { + constructor(IAnchorStateRegistry anchorStateRegistry) Verifier(anchorStateRegistry) { } + + function verify(bytes calldata, bytes32, bytes32) external view override notNullified returns (bool) { + return true; + } +} diff --git a/src/multiproof/tee/NitroEnclaveVerifier.sol b/src/multiproof/tee/NitroEnclaveVerifier.sol new file mode 100644 index 000000000..e6c758b64 --- /dev/null +++ b/src/multiproof/tee/NitroEnclaveVerifier.sol @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: Apache2.0 +pragma solidity ^0.8.0; + +import { Ownable } from "@solady/auth/Ownable.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { + INitroEnclaveVerifier, + ZkCoProcessorType, + ZkCoProcessorConfig, + VerifierJournal, + BatchVerifierJournal, + VerificationResult +} from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; +import { + IRiscZeroVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; +import { + ISP1Verifier +} from "lib/aws-nitro-enclave-attestation/contracts/lib/sp1-contracts/contracts/src/ISP1Verifier.sol"; + +/** + * @title NitroEnclaveVerifier + * @dev Implementation contract for AWS Nitro Enclave attestation verification using zero-knowledge proofs + * @dev Custom version of Automata's NitroEnclaveVerifier contract at + * https://github.com/automata-network/aws-nitro-enclave-attestation/tree/26c90565cb009e6539643a0956f9502a12ade672 + * + * Differences from the upstream Automata contract: + * - Verification of ZK proofs is restricted to an authorized proof submitter address + * - All privileged actions emit events for monitoring + * - Removes verification-with-explicit-program-ID and Pico logic + * + * This contract provides on-chain verification of AWS Nitro Enclave attestation reports by validating + * zero-knowledge proofs generated off-chain. It supports both single and batch verification modes + * and can work with multiple ZK proof systems (RISC Zero and Succinct SP1). + * + * Key features: + * - Certificate chain management with automatic caching of newly discovered certificates + * - Timestamp validation with configurable time tolerance + * - Certificate revocation capabilities for compromised intermediate certificates + * - Gas-efficient batch verification for multiple attestations + * - Support for both RISC Zero and SP1 proving systems + * + * Security considerations: + * - Only the contract owner can manage certificates and configurations + * - Root certificate is immutable once set (requires owner to change) + * - Intermediate certificates are automatically cached but can be revoked + * - Timestamp validation prevents replay attacks within the configured time window + */ +contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { + using EnumerableSet for EnumerableSet.Bytes32Set; + + /// @dev Sentinel address to indicate a route has been permanently frozen + address private constant FROZEN = address(0xdead); + + /// @dev Address that can submit proofs + address public proofSubmitter; + + /// @dev Configuration mapping for each supported ZK coprocessor type + mapping(ZkCoProcessorType => ZkCoProcessorConfig) public zkConfig; + + /// @dev Mapping of trusted intermediate certificate hashes (excludes root certificate) + mapping(bytes32 trustedCertHash => bool) public trustedIntermediateCerts; + + /// @dev Maximum allowed time difference in seconds for attestation timestamp validation + uint64 public maxTimeDiff; + + /// @dev Hash of the trusted AWS Nitro Enclave root certificate + bytes32 public rootCert; + + /// @dev Set of all supported verifier program IDs per coprocessor + mapping(ZkCoProcessorType => EnumerableSet.Bytes32Set) private _verifierIdSet; + + /// @dev Set of all supported aggregator program IDs per coprocessor + mapping(ZkCoProcessorType => EnumerableSet.Bytes32Set) private _aggregatorIdSet; + + /// @dev Route-specific verifier overrides (selector -> verifier address) + mapping(ZkCoProcessorType => mapping(bytes4 selector => address zkVerifier)) private _zkVerifierRoutes; + + /// @dev Mapping from verifierId to its corresponding verifierProofId representation + mapping(ZkCoProcessorType => mapping(bytes32 verifierId => bytes32 verifierProofId)) private _verifierProofIds; + + // ============ Custom Errors ============ + + /// @dev Error thrown when an unsupported or unknown ZK coprocessor type is used + error Unknown_Zk_Coprocessor(); + + /// @dev Error thrown when attempting to remove the currently active (latest) program ID + error CannotRemoveLatestProgramId(ZkCoProcessorType zkCoProcessor, bytes32 identifier); + + /// @dev Error thrown when a ZK route has been permanently frozen + error ZkRouteFrozen(ZkCoProcessorType zkCoProcessor, bytes4 selector); + + /// @dev Error thrown when no ZK verifier is configured for the coprocessor + error ZkVerifierNotConfigured(ZkCoProcessorType zkCoProcessor); + + /// @dev Thrown when a caller other than the authorized proof submitter calls verify or batchVerify + error CallerNotProofSubmitter(); + + /// @dev Thrown when a certificate hash is not found in the trusted intermediate certificates set + error CertificateNotFound(bytes32 certHash); + + /// @dev Thrown when a program ID argument is bytes32(0) + error ZeroProgramId(); + + /// @dev Thrown when attempting to set a program ID that is already the latest + error ProgramIdAlreadyLatest(ZkCoProcessorType zkCoProcessor, bytes32 identifier); + + /// @dev Thrown when attempting to remove or operate on a program ID that does not exist in the set + error ProgramIdNotFound(ZkCoProcessorType zkCoProcessor, bytes32 identifier); + + /// @dev Thrown when a zero address is provided where a verifier address is required + error ZeroVerifierAddress(); + + /// @dev Thrown when a zero address is provided for the proof submitter + error ZeroProofSubmitter(); + + /// @dev Thrown when the batch journal's verifier VK does not match the expected verifier proof ID + error VerifierVkMismatch(bytes32 expected, bytes32 actual); + + /// @dev Thrown when the first certificate in a chain does not match the stored root certificate + error RootCertMismatch(bytes32 expected, bytes32 actual); + + /// @dev Thrown when calling verifyWithProgramId or batchVerifyWithProgramId, which are intentionally disabled + error NotImplemented(); + + /// @dev Error thrown when a zero maxTimeDiff is provided + error ZeroMaxTimeDiff(); + + // ============ Events ============ + + /// @dev Emitted when a new verifier program ID is added/updated + event VerifierIdUpdated(ZkCoProcessorType indexed zkCoProcessor, bytes32 indexed newId, bytes32 newProofId); + + /// @dev Emitted when a new aggregator program ID is added/updated + event AggregatorIdUpdated(ZkCoProcessorType indexed zkCoProcessor, bytes32 indexed newId); + + /// @dev Emitted when a program ID is removed from the supported set + event ProgramIdRemoved(ZkCoProcessorType indexed zkCoProcessor, bytes32 indexed programId, bool isAggregator); + + /// @dev Emitted when a route-specific verifier is added + event ZkRouteAdded(ZkCoProcessorType indexed zkCoProcessor, bytes4 indexed selector, address verifier); + + /// @dev Emitted when a route is permanently frozen + event ZkRouteWasFrozen(ZkCoProcessorType indexed zkCoProcessor, bytes4 indexed selector); + + /// @dev Emitted when the proof of attestation has been successfully verified + event AttestationSubmitted(VerificationResult result, ZkCoProcessorType zkCoProcessor, bytes output); + + /// @dev Emitted when a batched proof has been successfully verified; encodedBatched = abi.encode(VerifierJournal[]) + event BatchAttestationSubmitted(bytes32 verifierId, ZkCoProcessorType zkCoProcessor, bytes encodedBatch); + + /// @dev Event emitted when the proof submitter address is changed + event ProofSubmitterChanged(address newProofSubmitter); + + /// @dev Event emitted when the root certificate is changed + event RootCertChanged(bytes32 newRootCert); + + /// @dev Event emitted when the ZK configuration is updated + event ZKConfigurationUpdated(ZkCoProcessorType zkCoProcessor, ZkCoProcessorConfig config, bytes32 verifierProofId); + + /// @dev Event emitted when a certificate is revoked + event CertRevoked(bytes32 certHash); + + /// @dev Event emitted when the maximum time difference is updated + event MaxTimeDiffUpdated(uint64 newMaxTimeDiff); + + /** + * @dev Initializes the contract with owner, time tolerance and initial trusted certificates + * @param _owner Address to be set as the contract owner + * @param _maxTimeDiff Maximum time difference in seconds for timestamp validation + * @param _initializeTrustedCerts Array of initial trusted intermediate certificate hashes + * + * Sets the provided address as the contract owner and initializes the trusted certificate set. + * The root certificate must be set separately after deployment. + */ + constructor(address _owner, uint64 _maxTimeDiff, bytes32[] memory _initializeTrustedCerts) { + if (_maxTimeDiff == 0) revert ZeroMaxTimeDiff(); + maxTimeDiff = _maxTimeDiff; + for (uint256 i = 0; i < _initializeTrustedCerts.length; i++) { + trustedIntermediateCerts[_initializeTrustedCerts[i]] = true; + } + _initializeOwner(_owner); + } + + // ============ Query Functions ============ + + /** + * @dev Retrieves the configuration for a specific coprocessor + * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) + * @return ZkCoProcessorConfig Configuration parameters including program IDs and verifier address + */ + function getZkConfig(ZkCoProcessorType _zkCoProcessor) external view returns (ZkCoProcessorConfig memory) { + return zkConfig[_zkCoProcessor]; + } + + /** + * @dev Returns all supported verifier program IDs for a coprocessor + * @param _zkCoProcessor Type of ZK coprocessor + * @return Array of all supported verifier program IDs + */ + function getVerifierIds(ZkCoProcessorType _zkCoProcessor) external view returns (bytes32[] memory) { + return _verifierIdSet[_zkCoProcessor].values(); + } + + /** + * @dev Returns all supported aggregator program IDs for a coprocessor + * @param _zkCoProcessor Type of ZK coprocessor + * @return Array of all supported aggregator program IDs + */ + function getAggregatorIds(ZkCoProcessorType _zkCoProcessor) external view returns (bytes32[] memory) { + return _aggregatorIdSet[_zkCoProcessor].values(); + } + + /** + * @dev Checks if a verifier program ID is in the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId Verifier program ID to check + * @return True if the ID is supported + */ + function isVerifierIdSupported(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external view returns (bool) { + return _verifierIdSet[_zkCoProcessor].contains(_verifierId); + } + + /** + * @dev Checks if an aggregator program ID is in the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _aggregatorId Aggregator program ID to check + * @return True if the ID is supported + */ + function isAggregatorIdSupported( + ZkCoProcessorType _zkCoProcessor, + bytes32 _aggregatorId + ) + external + view + returns (bool) + { + return _aggregatorIdSet[_zkCoProcessor].contains(_aggregatorId); + } + + /** + * @dev Gets the verifier address for a specific route + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector + * @return Verifier address (route-specific or default fallback) + */ + function getZkVerifier(ZkCoProcessorType _zkCoProcessor, bytes4 _selector) external view returns (address) { + address verifier = _zkVerifierRoutes[_zkCoProcessor][_selector]; + + if (verifier == FROZEN) { + revert ZkRouteFrozen(_zkCoProcessor, _selector); + } + + if (verifier == address(0)) { + return zkConfig[_zkCoProcessor].zkVerifier; + } + + return verifier; + } + + /** + * @dev Returns the verifierProofId for a given verifierId + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId The verifier program ID + * @return The corresponding verifierProofId + */ + function getVerifierProofId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external view returns (bytes32) { + return _verifierProofIds[_zkCoProcessor][_verifierId]; + } + + /** + * @dev Checks the prefix length of trusted certificates in each provided certificate chain for reports + * @param _report_certs Array of certificate chains, each containing certificate hashes + * @return Array indicating the prefix length of trusted certificates in each chain + * + * For each certificate chain: + * 1. Validates that the first certificate matches the stored root certificate + * 2. Counts consecutive trusted certificates starting from the root + * 3. Stops counting when an untrusted certificate is encountered + * + * This function is used to pre-validate certificate chains before generating proofs, + * helping to optimize the proving process by determining trusted certificate lengths. + * Usually called from off-chain + */ + function checkTrustedIntermediateCerts(bytes32[][] calldata _report_certs) public view returns (uint8[] memory) { + uint8[] memory results = new uint8[](_report_certs.length); + bytes32 rootCertHash = rootCert; + for (uint256 i = 0; i < _report_certs.length; i++) { + bytes32[] calldata certs = _report_certs[i]; + uint8 trustedCertPrefixLen = 1; + if (certs[0] != rootCertHash) { + revert RootCertMismatch(rootCertHash, certs[0]); + } + for (uint256 j = 1; j < certs.length; j++) { + if (!trustedIntermediateCerts[certs[j]]) { + break; + } + trustedCertPrefixLen += 1; + } + results[i] = trustedCertPrefixLen; + } + return results; + } + + // ============ Admin Functions ============ + + /** + * @dev Sets the trusted root certificate hash + * @param _rootCert Hash of the AWS Nitro Enclave root certificate + * + * Requirements: + * - Only callable by contract owner + * + * The root certificate serves as the trust anchor for all certificate chain validations. + * This should be set to the hash of AWS's root certificate for Nitro Enclaves. + */ + function setRootCert(bytes32 _rootCert) external onlyOwner { + rootCert = _rootCert; + emit RootCertChanged(_rootCert); + } + + /** + * @dev Updates the maximum allowed time difference for attestation timestamp validation + * @param _maxTimeDiff New maximum time difference in seconds + * + * Requirements: + * - Only callable by contract owner + * - Must be greater than zero + */ + function setMaxTimeDiff(uint64 _maxTimeDiff) external onlyOwner { + if (_maxTimeDiff == 0) revert ZeroMaxTimeDiff(); + maxTimeDiff = _maxTimeDiff; + emit MaxTimeDiffUpdated(_maxTimeDiff); + } + + /** + * @dev Configures zero-knowledge verification parameters for a specific coprocessor + * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) + * @param _config Configuration parameters including program IDs and verifier address + * @param _verifierProofId The verifierProofId corresponding to the verifierId in config + * + * Requirements: + * - Only callable by contract owner + * + * This function sets up the necessary parameters for ZK proof verification: + * - verifierId: Program ID for single attestation verification + * - aggregatorId: Program ID for batch/aggregated verification + * - zkVerifier: Address of the deployed ZK verifier contract + * + * Note: Program IDs are automatically added to the supported version sets + * The verifierProofId is stored in a separate mapping (verifierId => verifierProofId) + */ + function setZkConfiguration( + ZkCoProcessorType _zkCoProcessor, + ZkCoProcessorConfig memory _config, + bytes32 _verifierProofId + ) + external + onlyOwner + { + zkConfig[_zkCoProcessor] = _config; + + // Auto-add program IDs to the version sets and store verifierProofId mapping + if (_config.verifierId != bytes32(0)) { + _verifierIdSet[_zkCoProcessor].add(_config.verifierId); + _verifierProofIds[_zkCoProcessor][_config.verifierId] = _verifierProofId; + } + if (_config.aggregatorId != bytes32(0)) { + _aggregatorIdSet[_zkCoProcessor].add(_config.aggregatorId); + } + emit ZKConfigurationUpdated(_zkCoProcessor, _config, _verifierProofId); + } + + /** + * @dev Revokes a trusted intermediate certificate + * @param _certHash Hash of the certificate to revoke + * + * Requirements: + * - Only callable by contract owner + * - Certificate must exist in the trusted intermediate certificates set + * + * This function allows the owner to revoke compromised intermediate certificates + * without affecting the root certificate or other trusted certificates. + */ + function revokeCert(bytes32 _certHash) external onlyOwner { + if (!trustedIntermediateCerts[_certHash]) { + revert CertificateNotFound(_certHash); + } + delete trustedIntermediateCerts[_certHash]; + emit CertRevoked(_certHash); + } + + /** + * @dev Updates the verifier program ID, adding the new version to the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _newVerifierId New verifier program ID to set as latest + * @param _newVerifierProofId New verifier proof ID (stored in mapping, used in batch verification) + */ + function updateVerifierId( + ZkCoProcessorType _zkCoProcessor, + bytes32 _newVerifierId, + bytes32 _newVerifierProofId + ) + external + onlyOwner + { + if (_newVerifierId == bytes32(0)) revert ZeroProgramId(); + if (zkConfig[_zkCoProcessor].verifierId == _newVerifierId) { + revert ProgramIdAlreadyLatest(_zkCoProcessor, _newVerifierId); + } + + zkConfig[_zkCoProcessor].verifierId = _newVerifierId; + _verifierIdSet[_zkCoProcessor].add(_newVerifierId); + _verifierProofIds[_zkCoProcessor][_newVerifierId] = _newVerifierProofId; + + emit VerifierIdUpdated(_zkCoProcessor, _newVerifierId, _newVerifierProofId); + } + + /** + * @dev Updates the aggregator program ID, adding the new version to the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _newAggregatorId New aggregator program ID to set as latest + */ + function updateAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _newAggregatorId) external onlyOwner { + if (_newAggregatorId == bytes32(0)) revert ZeroProgramId(); + if (zkConfig[_zkCoProcessor].aggregatorId == _newAggregatorId) { + revert ProgramIdAlreadyLatest(_zkCoProcessor, _newAggregatorId); + } + + zkConfig[_zkCoProcessor].aggregatorId = _newAggregatorId; + _aggregatorIdSet[_zkCoProcessor].add(_newAggregatorId); + + emit AggregatorIdUpdated(_zkCoProcessor, _newAggregatorId); + } + + /** + * @dev Removes a verifier program ID from the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId Verifier program ID to remove + */ + function removeVerifierId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external onlyOwner { + if (!_verifierIdSet[_zkCoProcessor].contains(_verifierId)) { + revert ProgramIdNotFound(_zkCoProcessor, _verifierId); + } + + // Cannot remove the latest verifier ID - must update to a new one first + if (zkConfig[_zkCoProcessor].verifierId == _verifierId) { + revert CannotRemoveLatestProgramId(_zkCoProcessor, _verifierId); + } + + _verifierIdSet[_zkCoProcessor].remove(_verifierId); + delete _verifierProofIds[_zkCoProcessor][_verifierId]; + emit ProgramIdRemoved(_zkCoProcessor, _verifierId, false); + } + + /** + * @dev Removes an aggregator program ID from the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _aggregatorId Aggregator program ID to remove + */ + function removeAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _aggregatorId) external onlyOwner { + if (!_aggregatorIdSet[_zkCoProcessor].contains(_aggregatorId)) { + revert ProgramIdNotFound(_zkCoProcessor, _aggregatorId); + } + + // Cannot remove the latest aggregator ID - must update to a new one first + if (zkConfig[_zkCoProcessor].aggregatorId == _aggregatorId) { + revert CannotRemoveLatestProgramId(_zkCoProcessor, _aggregatorId); + } + + _aggregatorIdSet[_zkCoProcessor].remove(_aggregatorId); + emit ProgramIdRemoved(_zkCoProcessor, _aggregatorId, true); + } + + /** + * @dev Adds a route-specific verifier override + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector (first 4 bytes of proof data) + * @param _verifier Address of the verifier contract for this route + */ + function addVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector, address _verifier) external onlyOwner { + if (_verifier == address(0)) revert ZeroVerifierAddress(); + + if (_zkVerifierRoutes[_zkCoProcessor][_selector] == FROZEN) { + revert ZkRouteFrozen(_zkCoProcessor, _selector); + } + + _zkVerifierRoutes[_zkCoProcessor][_selector] = _verifier; + emit ZkRouteAdded(_zkCoProcessor, _selector, _verifier); + } + + /** + * @dev Permanently freezes a verification route + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector to freeze + * + * WARNING: This action is IRREVERSIBLE + */ + function freezeVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector) external onlyOwner { + address currentVerifier = _zkVerifierRoutes[_zkCoProcessor][_selector]; + + if (currentVerifier == FROZEN) { + revert ZkRouteFrozen(_zkCoProcessor, _selector); + } + + _zkVerifierRoutes[_zkCoProcessor][_selector] = FROZEN; + emit ZkRouteWasFrozen(_zkCoProcessor, _selector); + } + + /** + * @dev Sets the proof submitter address + * @param _proofSubmitter The address of the proof submitter + * + * Requirements: + * - Only callable by contract owner + * - Address must not be zero + */ + function setProofSubmitter(address _proofSubmitter) external onlyOwner { + if (_proofSubmitter == address(0)) revert ZeroProofSubmitter(); + proofSubmitter = _proofSubmitter; + emit ProofSubmitterChanged(_proofSubmitter); + } + + // ============ Verification Functions ============ + + /** + * @dev Verifies a single attestation report using zero-knowledge proof + * @param output Encoded VerifierJournal containing the verification result + * @param zkCoprocessor Type of ZK coprocessor used to generate the proof + * @param proofBytes Zero-knowledge proof data for the attestation + * @return journal VerifierJournal containing the verification result and extracted data + * + * This function performs end-to-end verification of a single attestation: + * 1. Retrieves the single verification program ID from configuration + * 2. Verifies the zero-knowledge proof using the specified coprocessor + * 3. Decodes the verification journal from the output + * 4. Validates the journal through comprehensive checks + * 5. Returns the final verification result + * + * The returned journal contains all extracted attestation data including: + * - Verification status and any error conditions + * - Certificate chain information and trust levels + * - User data, nonce, and public key from the attestation + * - Platform Configuration Registers (PCRs) for integrity measurement + * - Module ID and timestamp information + */ + function verify( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + returns (VerifierJournal memory journal) + { + if (msg.sender != proofSubmitter) revert CallerNotProofSubmitter(); + bytes32 programId = zkConfig[zkCoprocessor].verifierId; + _verifyZk(zkCoprocessor, programId, output, proofBytes); + journal = abi.decode(output, (VerifierJournal)); + journal = _verifyJournal(journal); + emit AttestationSubmitted(journal.result, zkCoprocessor, output); + } + + /** + * @dev Verifies multiple attestation reports in a single batch operation + * @param output Encoded BatchVerifierJournal containing aggregated verification results + * @param zkCoprocessor Type of ZK coprocessor used to generate the proof + * @param proofBytes Zero-knowledge proof data for batch verification + * @return results Array of VerifierJournal results, one for each attestation in the batch + * + * This function provides gas-efficient batch verification by: + * 1. Using the aggregator program ID for ZK proof verification + * 2. Validating the batch verifier key matches the expected value + * 3. Processing each individual attestation through standard validation + * 4. Returning comprehensive results for all attestations + * + * Batch verification is recommended when processing multiple attestations + * as it significantly reduces gas costs compared to individual verifications. + */ + function batchVerify( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + returns (VerifierJournal[] memory results) + { + if (msg.sender != proofSubmitter) revert CallerNotProofSubmitter(); + bytes32 aggregatorId = zkConfig[zkCoprocessor].aggregatorId; + bytes32 verifierId = zkConfig[zkCoprocessor].verifierId; + bytes32 verifierProofId = _verifierProofIds[zkCoprocessor][verifierId]; + + _verifyZk(zkCoprocessor, aggregatorId, output, proofBytes); + BatchVerifierJournal memory batchJournal = abi.decode(output, (BatchVerifierJournal)); + if (batchJournal.verifierVk != verifierProofId) { + revert VerifierVkMismatch(verifierProofId, batchJournal.verifierVk); + } + uint256 n = batchJournal.outputs.length; + results = new VerifierJournal[](n); + for (uint256 i = 0; i < n; i++) { + results[i] = _verifyJournal(batchJournal.outputs[i]); + } + emit BatchAttestationSubmitted(verifierId, zkCoprocessor, abi.encode(results)); + } + + // ============ Internal Functions ============ + + /** + * @dev Internal function to cache newly discovered trusted certificates + * @param journal Verification journal containing certificate chain information + * + * This function automatically adds any certificates beyond the trusted length + * to the trusted intermediate certificates set. This optimizes future verifications + * by expanding the known trusted certificate set based on successful verifications. + */ + function _cacheNewCert(VerifierJournal memory journal) internal { + for (uint256 i = journal.trustedCertsPrefixLen; i < journal.certs.length; i++) { + bytes32 certHash = journal.certs[i]; + trustedIntermediateCerts[certHash] = true; + } + } + + /** + * @dev Internal function to verify and validate a journal entry + * @param journal Verification journal to validate + * @return Updated journal with final verification result + * + * This function performs comprehensive validation: + * 1. Checks if the initial ZK verification was successful + * 2. Validates the root certificate matches the trusted root + * 3. Ensures all trusted certificates are still valid (not revoked) + * 4. Validates the attestation timestamp is within acceptable range + * 5. Caches newly discovered certificates for future use + * + * The timestamp validation converts milliseconds to seconds and checks: + * - Attestation is not too old (timestamp + maxTimeDiff >= block.timestamp) + * - Attestation is not from the future (timestamp <= block.timestamp) + */ + function _verifyJournal(VerifierJournal memory journal) internal returns (VerifierJournal memory) { + if (journal.result != VerificationResult.Success) { + return journal; + } + if (journal.trustedCertsPrefixLen == 0) { + journal.result = VerificationResult.RootCertNotTrusted; + return journal; + } + // Check every trusted certificate to ensure none have been revoked + for (uint256 i = 0; i < journal.trustedCertsPrefixLen; i++) { + bytes32 certHash = journal.certs[i]; + if (i == 0) { + if (certHash != rootCert) { + journal.result = VerificationResult.RootCertNotTrusted; + return journal; + } + continue; + } + if (!trustedIntermediateCerts[certHash]) { + journal.result = VerificationResult.IntermediateCertsNotTrusted; + return journal; + } + } + uint64 timestamp = journal.timestamp / 1000; + if (timestamp + maxTimeDiff < block.timestamp || timestamp > block.timestamp) { + journal.result = VerificationResult.InvalidTimestamp; + return journal; + } + _cacheNewCert(journal); + return journal; + } + + /** + * @dev Internal function to verify zero-knowledge proofs using the appropriate coprocessor + * @param zkCoprocessor Type of ZK coprocessor (RiscZero or Succinct) + * @param programId Program identifier for the verification program + * @param output Encoded output data to verify + * @param proofBytes Zero-knowledge proof data + */ + function _verifyZk( + ZkCoProcessorType zkCoprocessor, + bytes32 programId, + bytes calldata output, + bytes calldata proofBytes + ) + internal + view + { + // Resolve the verifier address (route-specific or default) + address verifier = _resolveZkVerifier(zkCoprocessor, proofBytes); + + if (zkCoprocessor == ZkCoProcessorType.RiscZero) { + IRiscZeroVerifier(verifier).verify(proofBytes, programId, sha256(output)); + } else if (zkCoprocessor == ZkCoProcessorType.Succinct) { + ISP1Verifier(verifier).verifyProof(programId, output, proofBytes); + } else { + revert Unknown_Zk_Coprocessor(); + } + } + + /** + * @dev Internal function to resolve the ZK verifier address based on route configuration + * @param zkCoprocessor Type of ZK coprocessor + * @param proofBytes Proof data (selector extracted from first 4 bytes) + * @return Resolved verifier address + */ + function _resolveZkVerifier( + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + internal + view + returns (address) + { + bytes4 selector = bytes4(proofBytes[0:4]); + address verifier = _zkVerifierRoutes[zkCoprocessor][selector]; + + // Check if route is frozen + if (verifier == FROZEN) { + revert ZkRouteFrozen(zkCoprocessor, selector); + } + + // Fall back to default verifier if no route-specific one configured + if (verifier == address(0)) { + verifier = zkConfig[zkCoprocessor].zkVerifier; + } + + // Ensure verifier is configured + if (verifier == address(0)) { + revert ZkVerifierNotConfigured(zkCoprocessor); + } + + return verifier; + } +} diff --git a/src/multiproof/tee/TEEProverRegistry.sol b/src/multiproof/tee/TEEProverRegistry.sol new file mode 100644 index 000000000..616bb5140 --- /dev/null +++ b/src/multiproof/tee/TEEProverRegistry.sol @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { + INitroEnclaveVerifier, + ZkCoProcessorType, + VerifierJournal, + VerificationResult, + Pcr, + Bytes48 +} from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; +import { OwnableManagedUpgradeable } from "lib/op-enclave/contracts/src/OwnableManagedUpgradeable.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { EnumerableSetLib } from "@solady-v0.0.245/utils/EnumerableSetLib.sol"; + +/// @title TEEProverRegistry +/// @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 TEEProverRegistry is OwnableManagedUpgradeable, ISemver { + using EnumerableSetLib for EnumerableSetLib.AddressSet; + /// @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 Enumerable set of all currently registered signer addresses. + /// @dev Kept in sync with `signerPCR0`: add on register, remove on deregister. + /// Enables O(1) on-chain enumeration via `getRegisteredSigners()`. + EnumerableSetLib.AddressSet internal _registeredSigners; + + /// @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; + _registeredSigners.add(enclaveAddress); + 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]; + _registeredSigners.remove(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 Returns all currently registered signer addresses. + /// @dev Reads directly from the on-chain enumerable set — no event scanning required. + /// The order of addresses in the returned array is not guaranteed. + /// @return An array of all registered signer addresses. + function getRegisteredSigners() external view returns (address[] memory) { + return _registeredSigners.values(); + } + + /// @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.2.0 + function version() public pure virtual returns (string memory) { + return "0.2.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..ba61a30c5 --- /dev/null +++ b/src/multiproof/tee/TEEVerifier.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; + +import { TEEProverRegistry } from "./TEEProverRegistry.sol"; +import { Verifier } from "../Verifier.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 TEEProverRegistry +/// 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 Verifier, ISemver { + /// @notice The TEEProverRegistry contract that manages valid TEE signers. + /// @dev Signers are registered via AWS Nitro attestation in TEEProverRegistry. + TEEProverRegistry public immutable TEE_PROVER_REGISTRY; + + /// @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 teeProverRegistry The TEEProverRegistry contract address. + constructor( + TEEProverRegistry teeProverRegistry, + IAnchorStateRegistry anchorStateRegistry + ) + Verifier(anchorStateRegistry) + { + TEE_PROVER_REGISTRY = teeProverRegistry; + } + + /// @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 + notNullified + 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 (!TEE_PROVER_REGISTRY.isValidProposer(proposer)) { + revert InvalidProposer(proposer); + } + + // Get the PCR0 the signer was registered with + bytes32 registeredPCR0 = TEE_PROVER_REGISTRY.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..0679267e2 --- /dev/null +++ b/test/multiproof/AggregateVerifier.t.sol @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { BadExtraData, GameNotResolved } 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(), TEE_PROVER); + 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 game is over + vm.expectRevert(GameNotResolved.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); + + // 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 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); + + // 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); + + // Resolve after 1 day (FAST_FINALIZATION_DELAY with 2 proofs) + 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 + 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 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..c47811464 --- /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(IAnchorStateRegistry(address(anchorStateRegistry))); + zkVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); + } + + 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..661b68f15 --- /dev/null +++ b/test/multiproof/Challenge.t.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MIT +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 { Verifier } from "src/multiproof/Verifier.sol"; + +import { BaseTest } from "./BaseTest.t.sol"; + +contract ChallengeTest is BaseTest { + function testChallengeTEEProofWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // Create game with TEE proof + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); + + // Challenge game with ZK proof + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + vm.prank(ZK_PROVER); + game.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + // 2 proofs so that it can decrease to 1 if ZK is nullified and then the TEE proof can resolve + assertEq(game.proofCount(), 2); + + // Resolve after SLOW_FINALIZATION_DELAY + vm.warp(block.timestamp + 7 days); + game.resolve(); + + assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game.bondRecipient(), ZK_PROVER); + + uint256 balanceBefore = ZK_PROVER.balance; + game.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(ZK_PROVER.balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 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); + + // Challenge game with ZK proof + bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); + + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) + ); + game1.challenge(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); + } + + function testChallengeFailsIfNotZKProof() 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); + + vm.expectRevert(AggregateVerifier.InvalidProofType.selector); + game1.challenge(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + } + + 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); + + vm.expectRevert(ClaimAlreadyResolved.selector); + game1.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + } + + 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, "tee2"))); + 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 with ZK proof + Claim rootClaim3 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); + childGame.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim3.raw()); + } + + 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 + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + vm.expectRevert(AggregateVerifier.InvalidGame.selector); + game.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + } + + 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 — TEE proof was nullified, so MissingProof(TEE) is expected + Claim rootClaim3 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) + ); + game.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim3.raw()); + } + + function testChallengeFailsAfterZKNullification() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); + + // create game with both proofs + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); + game.verifyProposalProof(zkProof1); + + // nullify ZK proof + 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 — ZK is nullified so Nullified() is expected + Claim rootClaim3 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk3"))); + bytes memory zkProof3 = _generateProof("zk-proof-3", AggregateVerifier.ProofType.ZK); + + vm.expectRevert(Verifier.Nullified.selector); + game.challenge(zkProof3, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim3.raw()); + } +} diff --git a/test/multiproof/NitroEnclaveVerifier.t.sol b/test/multiproof/NitroEnclaveVerifier.t.sol new file mode 100644 index 000000000..604e555b2 --- /dev/null +++ b/test/multiproof/NitroEnclaveVerifier.t.sol @@ -0,0 +1,836 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { Test } from "forge-std/Test.sol"; + +import { + ZkCoProcessorType, + ZkCoProcessorConfig, + VerifierJournal, + BatchVerifierJournal, + VerificationResult, + Pcr +} from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; + +import { NitroEnclaveVerifier } from "src/multiproof/tee/NitroEnclaveVerifier.sol"; + +contract NitroEnclaveVerifierTest is Test { + NitroEnclaveVerifier public verifier; + + address public owner; + address public submitter; + address public mockRiscZeroVerifier; + address public mockSP1Verifier; + + bytes32 public constant ROOT_CERT = keccak256("root-cert"); + bytes32 public constant INTERMEDIATE_CERT_1 = keccak256("intermediate-cert-1"); + bytes32 public constant INTERMEDIATE_CERT_2 = keccak256("intermediate-cert-2"); + bytes32 public constant VERIFIER_ID = keccak256("verifier-id"); + bytes32 public constant AGGREGATOR_ID = keccak256("aggregator-id"); + bytes32 public constant VERIFIER_PROOF_ID = keccak256("verifier-proof-id"); + + uint64 public constant MAX_TIME_DIFF = 3600; // 1 hour + + // Realistic timestamp so timestamp validation tests work correctly + uint256 internal constant REALISTIC_TIMESTAMP = 1_700_000_000; + + function setUp() public { + vm.warp(REALISTIC_TIMESTAMP); + + owner = address(this); + submitter = makeAddr("submitter"); + mockRiscZeroVerifier = makeAddr("mock-riscZero-verifier"); + mockSP1Verifier = makeAddr("mock-sp1-verifier"); + + bytes32[] memory trustedCerts = new bytes32[](1); + trustedCerts[0] = INTERMEDIATE_CERT_1; + + verifier = new NitroEnclaveVerifier(owner, MAX_TIME_DIFF, trustedCerts); + verifier.setRootCert(ROOT_CERT); + verifier.setProofSubmitter(submitter); + } + + // ============ Constructor Tests ============ + + function testConstructorSetsOwner() public view { + assertEq(verifier.owner(), owner); + } + + function testConstructorSetsMaxTimeDiff() public view { + assertEq(verifier.maxTimeDiff(), MAX_TIME_DIFF); + } + + function testConstructorSetsTrustedCerts() public view { + assertTrue(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1)); + assertFalse(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_2)); + } + + function testConstructorRevertsIfZeroMaxTimeDiff() public { + bytes32[] memory certs = new bytes32[](0); + vm.expectRevert(NitroEnclaveVerifier.ZeroMaxTimeDiff.selector); + new NitroEnclaveVerifier(owner, 0, certs); + } + + // ============ setRootCert Tests ============ + + function testSetRootCert() public { + bytes32 newRoot = keccak256("new-root"); + verifier.setRootCert(newRoot); + assertEq(verifier.rootCert(), newRoot); + } + + function testSetRootCertRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.setRootCert(keccak256("bad")); + } + + // ============ setMaxTimeDiff Tests ============ + + function testSetMaxTimeDiff() public { + uint64 newTimeDiff = 7200; + verifier.setMaxTimeDiff(newTimeDiff); + assertEq(verifier.maxTimeDiff(), newTimeDiff); + } + + function testSetMaxTimeDiffRevertsIfZero() public { + vm.expectRevert(NitroEnclaveVerifier.ZeroMaxTimeDiff.selector); + verifier.setMaxTimeDiff(0); + } + + function testSetMaxTimeDiffRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.setMaxTimeDiff(7200); + } + + // ============ setProofSubmitter Tests ============ + + function testSetProofSubmitter() public { + address newSubmitter = makeAddr("new-submitter"); + verifier.setProofSubmitter(newSubmitter); + assertEq(verifier.proofSubmitter(), newSubmitter); + } + + function testSetProofSubmitterRevertsIfZeroAddress() public { + vm.expectRevert(NitroEnclaveVerifier.ZeroProofSubmitter.selector); + verifier.setProofSubmitter(address(0)); + } + + function testSetProofSubmitterRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.setProofSubmitter(makeAddr("anyone")); + } + + // ============ setZkConfiguration Tests ============ + + function testSetZkConfiguration() public { + ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ + verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier + }); + + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + + ZkCoProcessorConfig memory stored = verifier.getZkConfig(ZkCoProcessorType.RiscZero); + assertEq(stored.verifierId, VERIFIER_ID); + assertEq(stored.aggregatorId, AGGREGATOR_ID); + assertEq(stored.zkVerifier, mockRiscZeroVerifier); + + assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, VERIFIER_ID)); + assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, AGGREGATOR_ID)); + assertEq(verifier.getVerifierProofId(ZkCoProcessorType.RiscZero, VERIFIER_ID), VERIFIER_PROOF_ID); + } + + function testSetZkConfigurationRevertsIfNotOwner() public { + ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ + verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier + }); + + vm.prank(submitter); + vm.expectRevert(); + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + } + + // ============ revokeCert Tests ============ + + function testRevokeCert() public { + assertTrue(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1)); + verifier.revokeCert(INTERMEDIATE_CERT_1); + assertFalse(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1)); + } + + function testRevokeCertRevertsIfNotTrusted() public { + bytes32 unknown = keccak256("unknown-cert"); + vm.expectRevert(abi.encodeWithSelector(NitroEnclaveVerifier.CertificateNotFound.selector, unknown)); + verifier.revokeCert(unknown); + } + + function testRevokeCertRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.revokeCert(INTERMEDIATE_CERT_1); + } + + // ============ updateVerifierId Tests ============ + + function testUpdateVerifierId() public { + _setUpRiscZeroConfig(); + + bytes32 newVerifierId = keccak256("new-verifier-id"); + bytes32 newVerifierProofId = keccak256("new-verifier-proof-id"); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, newVerifierId, newVerifierProofId); + + ZkCoProcessorConfig memory config = verifier.getZkConfig(ZkCoProcessorType.RiscZero); + assertEq(config.verifierId, newVerifierId); + assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, newVerifierId)); + assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, VERIFIER_ID)); + assertEq(verifier.getVerifierProofId(ZkCoProcessorType.RiscZero, newVerifierId), newVerifierProofId); + } + + function testUpdateVerifierIdRevertsIfZero() public { + _setUpRiscZeroConfig(); + vm.expectRevert(NitroEnclaveVerifier.ZeroProgramId.selector); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, bytes32(0), bytes32(0)); + } + + function testUpdateVerifierIdRevertsIfSame() public { + _setUpRiscZeroConfig(); + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.ProgramIdAlreadyLatest.selector, ZkCoProcessorType.RiscZero, VERIFIER_ID + ) + ); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID, VERIFIER_PROOF_ID); + } + + function testUpdateVerifierIdRevertsIfNotOwner() public { + _setUpRiscZeroConfig(); + vm.prank(submitter); + vm.expectRevert(); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, keccak256("new"), keccak256("proof")); + } + + // ============ updateAggregatorId Tests ============ + + function testUpdateAggregatorId() public { + _setUpRiscZeroConfig(); + + bytes32 newAggregatorId = keccak256("new-aggregator-id"); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, newAggregatorId); + + ZkCoProcessorConfig memory config = verifier.getZkConfig(ZkCoProcessorType.RiscZero); + assertEq(config.aggregatorId, newAggregatorId); + assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, newAggregatorId)); + assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, AGGREGATOR_ID)); + } + + function testUpdateAggregatorIdRevertsIfZero() public { + _setUpRiscZeroConfig(); + vm.expectRevert(NitroEnclaveVerifier.ZeroProgramId.selector); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, bytes32(0)); + } + + function testUpdateAggregatorIdRevertsIfSame() public { + _setUpRiscZeroConfig(); + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.ProgramIdAlreadyLatest.selector, ZkCoProcessorType.RiscZero, AGGREGATOR_ID + ) + ); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); + } + + function testUpdateAggregatorIdRevertsIfNotOwner() public { + _setUpRiscZeroConfig(); + vm.prank(submitter); + vm.expectRevert(); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, keccak256("new")); + } + + // ============ removeVerifierId Tests ============ + + function testRemoveVerifierId() public { + _setUpRiscZeroConfig(); + + bytes32 newId = keccak256("new-verifier-id"); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, newId, keccak256("proof")); + + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID); + assertFalse(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, VERIFIER_ID)); + assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, newId)); + } + + function testRemoveVerifierIdRevertsIfLatest() public { + _setUpRiscZeroConfig(); + + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.CannotRemoveLatestProgramId.selector, ZkCoProcessorType.RiscZero, VERIFIER_ID + ) + ); + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID); + } + + function testRemoveVerifierIdRevertsIfNotExists() public { + _setUpRiscZeroConfig(); + bytes32 nonexistent = keccak256("nonexistent"); + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.ProgramIdNotFound.selector, ZkCoProcessorType.RiscZero, nonexistent + ) + ); + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, nonexistent); + } + + function testRemoveVerifierIdRevertsIfNotOwner() public { + _setUpRiscZeroConfig(); + vm.prank(submitter); + vm.expectRevert(); + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID); + } + + // ============ removeAggregatorId Tests ============ + + function testRemoveAggregatorId() public { + _setUpRiscZeroConfig(); + + bytes32 newId = keccak256("new-aggregator-id"); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, newId); + + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); + assertFalse(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, AGGREGATOR_ID)); + assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, newId)); + } + + function testRemoveAggregatorIdRevertsIfLatest() public { + _setUpRiscZeroConfig(); + + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.CannotRemoveLatestProgramId.selector, ZkCoProcessorType.RiscZero, AGGREGATOR_ID + ) + ); + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); + } + + function testRemoveAggregatorIdRevertsIfNotExists() public { + _setUpRiscZeroConfig(); + bytes32 nonexistent = keccak256("nonexistent"); + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.ProgramIdNotFound.selector, ZkCoProcessorType.RiscZero, nonexistent + ) + ); + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, nonexistent); + } + + function testRemoveAggregatorIdRevertsIfNotOwner() public { + _setUpRiscZeroConfig(); + vm.prank(submitter); + vm.expectRevert(); + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); + } + + // ============ addVerifyRoute / freezeVerifyRoute Tests ============ + + function testAddVerifyRoute() public { + bytes4 selector = bytes4(keccak256("test")); + address routeVerifier = makeAddr("route-verifier"); + + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector), routeVerifier); + } + + function testAddVerifyRouteRevertsIfZeroAddress() public { + vm.expectRevert(NitroEnclaveVerifier.ZeroVerifierAddress.selector); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, bytes4(uint32(0x01)), address(0)); + } + + function testAddVerifyRouteRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, bytes4(keccak256("test")), makeAddr("v")); + } + + function testFreezeVerifyRoute() public { + bytes4 selector = bytes4(keccak256("test")); + address routeVerifier = makeAddr("route-verifier"); + + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + + vm.expectRevert( + abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + ); + verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector); + } + + function testAddVerifyRouteRevertsIfFrozen() public { + bytes4 selector = bytes4(keccak256("test")); + address routeVerifier = makeAddr("route-verifier"); + + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + + vm.expectRevert( + abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + ); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + } + + function testFreezeVerifyRouteRevertsIfAlreadyFrozen() public { + bytes4 selector = bytes4(keccak256("test")); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("v")); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + + vm.expectRevert( + abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + ); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + } + + function testFreezeVerifyRouteRevertsIfNotOwner() public { + bytes4 selector = bytes4(keccak256("test")); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("v")); + + vm.prank(submitter); + vm.expectRevert(); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + } + + // ============ getZkVerifier Tests ============ + + function testGetZkVerifierFallsBackToDefault() public { + _setUpRiscZeroConfig(); + + bytes4 unknownSelector = bytes4(0xdeadbeef); + assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, unknownSelector), mockRiscZeroVerifier); + } + + function testGetZkVerifierReturnsRouteSpecific() public { + _setUpRiscZeroConfig(); + + bytes4 selector = bytes4(keccak256("special")); + address routeVerifier = makeAddr("route-verifier"); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + + assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector), routeVerifier); + } + + // ============ checkTrustedIntermediateCerts Tests ============ + + function testCheckTrustedIntermediateCerts() public view { + bytes32[][] memory reportCerts = new bytes32[][](1); + reportCerts[0] = new bytes32[](3); + reportCerts[0][0] = ROOT_CERT; + reportCerts[0][1] = INTERMEDIATE_CERT_1; + reportCerts[0][2] = INTERMEDIATE_CERT_2; // not trusted + + uint8[] memory results = verifier.checkTrustedIntermediateCerts(reportCerts); + assertEq(results[0], 2); // root + 1 intermediate trusted + } + + function testCheckTrustedIntermediateCertsRevertsIfWrongRoot() public { + bytes32 wrongRoot = keccak256("wrong-root"); + bytes32[][] memory reportCerts = new bytes32[][](1); + reportCerts[0] = new bytes32[](1); + reportCerts[0][0] = wrongRoot; + + vm.expectRevert(abi.encodeWithSelector(NitroEnclaveVerifier.RootCertMismatch.selector, ROOT_CERT, wrongRoot)); + verifier.checkTrustedIntermediateCerts(reportCerts); + } + + // ============ verify — access control ============ + + function testVerifyRevertsIfNotProofSubmitter() public { + vm.expectRevert(NitroEnclaveVerifier.CallerNotProofSubmitter.selector); + verifier.verify("", ZkCoProcessorType.RiscZero, ""); + } + + // ============ verify — ZkVerifierNotConfigured ============ + + function testVerifyRevertsIfZkVerifierNotConfigured() public { + // Set up config WITHOUT a zkVerifier address (zero) + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: address(0) }); + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + vm.prank(submitter); + vm.expectRevert( + abi.encodeWithSelector(NitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.RiscZero) + ); + verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + } + + // ============ verify — Unknown_Zk_Coprocessor ============ + + function testVerifyRevertsForUnknownCoprocessor() public { + // Use ZkCoProcessorType.Unknown (0) — not RiscZero or Succinct + ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ + verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier + }); + verifier.setZkConfiguration(ZkCoProcessorType.Unknown, config, VERIFIER_PROOF_ID); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + vm.prank(submitter); + vm.expectRevert(NitroEnclaveVerifier.Unknown_Zk_Coprocessor.selector); + verifier.verify(output, ZkCoProcessorType.Unknown, proofBytes); + } + + // ============ verify — ZkRouteFrozen during verify() ============ + + function testVerifyRevertsIfRouteFrozen() public { + _setUpRiscZeroConfig(); + + bytes4 selector = bytes4(0); // matches the selector in our proofBytes + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("route-v")); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + vm.prank(submitter); + vm.expectRevert( + abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + ); + verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + } + + // ============ verify — RiscZero happy path ============ + + function testVerifySuccessfulJournal() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.Success)); + } + + function testVerifyJournalRootCertNotTrusted() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + journal.certs[0] = keccak256("wrong-root"); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.RootCertNotTrusted)); + } + + function testVerifyJournalRootCertNotTrustedZeroPrefixLen() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + journal.trustedCertsPrefixLen = 0; + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.RootCertNotTrusted)); + } + + function testVerifyJournalIntermediateCertNotTrusted() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + // Replace trusted intermediate with untrusted one, but keep trustedCertsPrefixLen = 2 + journal.certs[1] = keccak256("untrusted-intermediate"); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + } + + function testVerifyJournalInvalidTimestampTooOld() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + // Set timestamp far in the past — more than maxTimeDiff seconds ago (in ms) + journal.timestamp = uint64(block.timestamp - MAX_TIME_DIFF - 1) * 1000; + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.InvalidTimestamp)); + } + + function testVerifyJournalInvalidTimestampFuture() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + // Set timestamp in the future (converted to ms) + journal.timestamp = uint64(block.timestamp + 100) * 1000; + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.InvalidTimestamp)); + } + + function testVerifyCachesNewCerts() public { + _setUpRiscZeroConfig(); + + bytes32 newCert = keccak256("new-leaf-cert"); + assertFalse(verifier.trustedIntermediateCerts(newCert)); + + VerifierJournal memory journal = _createSuccessJournal(); + // Add a new cert beyond the trusted prefix that will get cached + bytes32[] memory certs = new bytes32[](3); + certs[0] = ROOT_CERT; + certs[1] = INTERMEDIATE_CERT_1; + certs[2] = newCert; + journal.certs = certs; + journal.trustedCertsPrefixLen = 2; // only root + 1 intermediate are pre-trusted + + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertTrue(verifier.trustedIntermediateCerts(newCert)); + } + + function testVerifyJournalPassesThroughFailedResult() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + journal.result = VerificationResult.IntermediateCertsNotTrusted; + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + } + + // ============ verify — Succinct SP1 happy path ============ + + function testVerifySuccessfulJournalSP1() public { + _setUpSP1Config(); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockSP1Verify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.Success)); + } + + function testVerifyRevertsIfNotProofSubmitterSP1() public { + vm.expectRevert(NitroEnclaveVerifier.CallerNotProofSubmitter.selector); + verifier.verify("", ZkCoProcessorType.Succinct, ""); + } + + function testVerifyRevertsIfZkVerifierNotConfiguredSP1() public { + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: address(0) }); + verifier.setZkConfiguration(ZkCoProcessorType.Succinct, config, VERIFIER_PROOF_ID); + + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + vm.prank(submitter); + vm.expectRevert( + abi.encodeWithSelector(NitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.Succinct) + ); + verifier.verify(abi.encode(_createSuccessJournal()), ZkCoProcessorType.Succinct, proofBytes); + } + + // ============ batchVerify Tests ============ + + function testBatchVerifyRevertsIfNotProofSubmitter() public { + vm.expectRevert(NitroEnclaveVerifier.CallerNotProofSubmitter.selector); + verifier.batchVerify("", ZkCoProcessorType.RiscZero, ""); + } + + function testBatchVerifySuccess() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + VerifierJournal[] memory outputs = new VerifierJournal[](2); + outputs[0] = journal; + outputs[1] = journal; + + BatchVerifierJournal memory batchJournal = + BatchVerifierJournal({ verifierVk: VERIFIER_PROOF_ID, outputs: outputs }); + + bytes memory output = abi.encode(batchJournal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal[] memory results = verifier.batchVerify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(results.length, 2); + assertEq(uint8(results[0].result), uint8(VerificationResult.Success)); + assertEq(uint8(results[1].result), uint8(VerificationResult.Success)); + } + + function testBatchVerifyRevertsIfVerifierVkMismatch() public { + _setUpRiscZeroConfig(); + + bytes32 wrongVk = keccak256("wrong-vk"); + VerifierJournal[] memory outputs = new VerifierJournal[](1); + outputs[0] = _createSuccessJournal(); + + BatchVerifierJournal memory batchJournal = BatchVerifierJournal({ verifierVk: wrongVk, outputs: outputs }); + + bytes memory output = abi.encode(batchJournal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); + + vm.prank(submitter); + vm.expectRevert( + abi.encodeWithSelector(NitroEnclaveVerifier.VerifierVkMismatch.selector, VERIFIER_PROOF_ID, wrongVk) + ); + verifier.batchVerify(output, ZkCoProcessorType.RiscZero, proofBytes); + } + + function testBatchVerifySuccessSP1() public { + _setUpSP1Config(); + + VerifierJournal memory journal = _createSuccessJournal(); + VerifierJournal[] memory outputs = new VerifierJournal[](1); + outputs[0] = journal; + + BatchVerifierJournal memory batchJournal = + BatchVerifierJournal({ verifierVk: VERIFIER_PROOF_ID, outputs: outputs }); + + bytes memory output = abi.encode(batchJournal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockSP1Verify(AGGREGATOR_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal[] memory results = verifier.batchVerify(output, ZkCoProcessorType.Succinct, proofBytes); + + assertEq(results.length, 1); + assertEq(uint8(results[0].result), uint8(VerificationResult.Success)); + } + + // ============ Revoked Cert Invalidates Journal ============ + + function testRevokedCertInvalidatesVerification() public { + _setUpRiscZeroConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + // Revoke the intermediate cert before verification + verifier.revokeCert(INTERMEDIATE_CERT_1); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + } + + // ============ Helpers ============ + + function _setUpRiscZeroConfig() internal { + ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ + verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier + }); + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + } + + function _setUpSP1Config() internal { + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockSP1Verifier }); + verifier.setZkConfiguration(ZkCoProcessorType.Succinct, config, VERIFIER_PROOF_ID); + } + + function _createSuccessJournal() internal view returns (VerifierJournal memory) { + bytes32[] memory certs = new bytes32[](2); + certs[0] = ROOT_CERT; + certs[1] = INTERMEDIATE_CERT_1; + + Pcr[] memory pcrs = new Pcr[](0); + + return VerifierJournal({ + result: VerificationResult.Success, + trustedCertsPrefixLen: 2, + timestamp: uint64(block.timestamp) * 1000, + certs: certs, + userData: "", + nonce: "", + publicKey: "", + pcrs: pcrs, + moduleId: "test-module" + }); + } + + function _mockRiscZeroVerify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal { + // IRiscZeroVerifier.verify(proofBytes, programId, sha256(output)) + vm.mockCall( + mockRiscZeroVerifier, + abi.encodeWithSelector( + bytes4(keccak256("verify(bytes,bytes32,bytes32)")), proofBytes, programId, sha256(output) + ), + "" + ); + } + + function _mockSP1Verify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal { + // ISP1Verifier.verifyProof(programVKey, publicValues, proofBytes) + vm.mockCall( + mockSP1Verifier, + abi.encodeWithSelector( + bytes4(keccak256("verifyProof(bytes32,bytes,bytes)")), programId, output, proofBytes + ), + "" + ); + } +} diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol new file mode 100644 index 000000000..18ff74f37 --- /dev/null +++ b/test/multiproof/Nullify.t.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { ClaimAlreadyResolved } 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); + + // expectedResolution is uint64.max (no proofs left), so must wait 14 days from creation + vm.warp(block.timestamp + 14 days); + + 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.IN_PROGRESS)); + assertEq(game1.bondRecipient(), ZK_PROVER); + assertEq(game1.proofCount(), 0); + assertEq(game1.expectedResolution().raw(), type(uint64).max); + + // expectedResolution is uint64.max (no proofs left), so must wait 14 days from creation + vm.warp(block.timestamp + 14 days); + + 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(ClaimAlreadyResolved.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 with ZK proof + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + + vm.prank(ZK_PROVER); + game1.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + // Nullify can override challenge + bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); + game1.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); + + assertEq(game1.bondRecipient(), TEE_PROVER); + + // After nullify, only TEE proof remains; expectedResolution = now + 7 days + vm.warp(block.timestamp + 7 days); + game1.resolve(); + + 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); + } +} diff --git a/test/multiproof/TEEProverRegistry.t.sol b/test/multiproof/TEEProverRegistry.t.sol new file mode 100644 index 000000000..b2adc64f6 --- /dev/null +++ b/test/multiproof/TEEProverRegistry.t.sol @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: MIT +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 "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; + +import { DevTEEProverRegistry } from "src/multiproof/mocks/MockDevTEEProverRegistry.sol"; +import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol"; + +/// @notice Tests for TEEProverRegistry and DevTEEProverRegistry contracts. +/// @dev IMPORTANT: This test file uses DevTEEProverRegistry as the implementation because +/// registering signers on the production TEEProverRegistry requires a ZK proof of a valid +/// AWS Nitro attestation, which cannot be generated in a test environment. DevTEEProverRegistry extends +/// TEEProverRegistry with an `addDevSigner` function that bypasses attestation verification, +/// allowing us to test all signer-related functionality. All tests for base TEEProverRegistry +/// functionality (PCR0 management, ownership, proposer, etc.) are equally valid since +/// DevTEEProverRegistry inherits from TEEProverRegistry without modifying those functions. +contract TEEProverRegistryTest is Test { + DevTEEProverRegistry public teeProverRegistry; + 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 DevTEEProverRegistry for test flexibility) + // NitroEnclaveVerifier is not needed since tests use addDevSigner(), so pass address(0). + DevTEEProverRegistry impl = new DevTEEProverRegistry(INitroEnclaveVerifier(address(0))); + + // Deploy proxy admin + proxyAdmin = new ProxyAdmin(address(this)); + + // Deploy proxy + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), address(proxyAdmin), abi.encodeCall(TEEProverRegistry.initialize, (owner, manager)) + ); + + teeProverRegistry = DevTEEProverRegistry(address(proxy)); + } + + // ============ Initialization Tests ============ + + function testInitialization() public view { + assertEq(teeProverRegistry.owner(), owner); + assertEq(teeProverRegistry.manager(), manager); + assertEq(teeProverRegistry.version(), "0.2.0"); + } + + // ============ PCR0 Registration Tests ============ + + function testRegisterPCR0() public { + vm.expectEmit(true, false, false, false); + emit PCR0Registered(pcr0Hash); + + vm.prank(owner); + teeProverRegistry.registerPCR0(TEST_PCR0); + + assertTrue(teeProverRegistry.validPCR0s(pcr0Hash)); + } + + function testRegisterPCR0FailsIfNotOwner() public { + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + teeProverRegistry.registerPCR0(TEST_PCR0); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + teeProverRegistry.registerPCR0(TEST_PCR0); + } + + function testDeregisterPCR0() public { + // First register + vm.prank(owner); + teeProverRegistry.registerPCR0(TEST_PCR0); + assertTrue(teeProverRegistry.validPCR0s(pcr0Hash)); + + // Then deregister + vm.expectEmit(true, false, false, false); + emit PCR0Deregistered(pcr0Hash); + + vm.prank(owner); + teeProverRegistry.deregisterPCR0(TEST_PCR0); + + assertFalse(teeProverRegistry.validPCR0s(pcr0Hash)); + } + + function testDeregisterPCR0FailsIfNotOwner() public { + vm.prank(owner); + teeProverRegistry.registerPCR0(TEST_PCR0); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + teeProverRegistry.deregisterPCR0(TEST_PCR0); + } + + // ============ Signer Deregistration Tests ============ + + function testDeregisterSignerAsOwner() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + // Add signer via DevTEEProverRegistry + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, signerPcr0); + + // Verify signer is registered + assertTrue(teeProverRegistry.isValidSigner(signer)); + + // Deregister as owner + vm.expectEmit(true, false, false, false); + emit SignerDeregistered(signer); + + vm.prank(owner); + teeProverRegistry.deregisterSigner(signer); + + assertFalse(teeProverRegistry.isValidSigner(signer)); + assertEq(teeProverRegistry.signerPCR0(signer), bytes32(0)); + } + + function testDeregisterSignerAsManager() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + // Add signer via DevTEEProverRegistry + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, signerPcr0); + + assertTrue(teeProverRegistry.isValidSigner(signer)); + + vm.prank(manager); + teeProverRegistry.deregisterSigner(signer); + + assertFalse(teeProverRegistry.isValidSigner(signer)); + } + + function testDeregisterSignerFailsIfUnauthorized() public { + address signer = makeAddr("signer"); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner or the manager"); + teeProverRegistry.deregisterSigner(signer); + } + + // ============ Proposer Tests ============ + + function testSetProposer() public { + address newProposer = makeAddr("proposer"); + + vm.expectEmit(true, false, false, false); + emit ProposerSet(newProposer, true); + + vm.prank(owner); + teeProverRegistry.setProposer(newProposer, true); + + assertTrue(teeProverRegistry.isValidProposer(newProposer)); + } + + function testSetProposerFailsIfNotOwner() public { + address newProposer = makeAddr("proposer"); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + teeProverRegistry.setProposer(newProposer, true); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + teeProverRegistry.setProposer(newProposer, true); + } + + // ============ isValidSigner Tests ============ + + function testIsValidSignerReturnsFalseForUnregistered() public { + address unregistered = makeAddr("unregistered"); + assertFalse(teeProverRegistry.isValidSigner(unregistered)); + } + + function testIsValidSignerReturnsTrueForRegistered() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, signerPcr0); + + assertTrue(teeProverRegistry.isValidSigner(signer)); + } + + // ============ signerPCR0 Tests ============ + + function testSignerPCR0ReturnsZeroForUnregistered() public { + address unregistered = makeAddr("unregistered"); + assertEq(teeProverRegistry.signerPCR0(unregistered), bytes32(0)); + } + + function testSignerPCR0ReturnsCorrectValue() public { + address signer = makeAddr("signer"); + bytes32 expectedPcr0 = keccak256("signer-pcr0"); + + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, expectedPcr0); + + assertEq(teeProverRegistry.signerPCR0(signer), expectedPcr0); + } + + // ============ MAX_AGE Tests ============ + + function testMaxAgeConstant() public view { + assertEq(teeProverRegistry.MAX_AGE(), 60 minutes); + } + + // ============ Ownership Transfer Tests ============ + + function testTransferOwnership() public { + address newOwner = makeAddr("newOwner"); + + vm.prank(owner); + teeProverRegistry.transferOwnership(newOwner); + + assertEq(teeProverRegistry.owner(), newOwner); + } + + function testTransferOwnershipFailsIfNotOwner() public { + address newOwner = makeAddr("newOwner"); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + teeProverRegistry.transferOwnership(newOwner); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + teeProverRegistry.transferOwnership(newOwner); + } + + function testTransferOwnershipFailsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert("OwnableManaged: new owner is the zero address"); + teeProverRegistry.transferOwnership(address(0)); + } + + // ============ Management Transfer Tests ============ + + function testTransferManagementAsOwner() public { + address newManager = makeAddr("newManager"); + + vm.prank(owner); + teeProverRegistry.transferManagement(newManager); + + assertEq(teeProverRegistry.manager(), newManager); + } + + function testTransferManagementAsManager() public { + address newManager = makeAddr("newManager"); + + vm.prank(manager); + teeProverRegistry.transferManagement(newManager); + + assertEq(teeProverRegistry.manager(), newManager); + } + + function testTransferManagementFailsIfUnauthorized() public { + address newManager = makeAddr("newManager"); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner or the manager"); + teeProverRegistry.transferManagement(newManager); + } + + function testTransferManagementFailsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert("OwnableManaged: new manager is the zero address"); + teeProverRegistry.transferManagement(address(0)); + } + + // ============ Renounce Tests ============ + + function testRenounceOwnership() public { + vm.prank(owner); + teeProverRegistry.renounceOwnership(); + + assertEq(teeProverRegistry.owner(), address(0)); + } + + function testRenounceManagementAsOwner() public { + vm.prank(owner); + teeProverRegistry.renounceManagement(); + + assertEq(teeProverRegistry.manager(), address(0)); + } + + function testRenounceManagementAsManager() public { + vm.prank(manager); + teeProverRegistry.renounceManagement(); + + assertEq(teeProverRegistry.manager(), address(0)); + } + + // ============ DevTEEProverRegistry: 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); + teeProverRegistry.addDevSigner(signer, devPcr0Hash); + + assertTrue(teeProverRegistry.isValidSigner(signer)); + assertEq(teeProverRegistry.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"); + teeProverRegistry.addDevSigner(signer, devPcr0Hash); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + teeProverRegistry.addDevSigner(signer, devPcr0Hash); + } + + function testAddDevSignerCanOverwriteExisting() public { + address signer = makeAddr("dev-signer"); + bytes32 firstPcr0 = keccak256("first-pcr0"); + bytes32 secondPcr0 = keccak256("second-pcr0"); + + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, firstPcr0); + assertEq(teeProverRegistry.signerPCR0(signer), firstPcr0); + + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, secondPcr0); + assertEq(teeProverRegistry.signerPCR0(signer), secondPcr0); + } + + function testAddDevSignerWithZeroPcr0() public { + address signer = makeAddr("dev-signer"); + + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, bytes32(0)); + + // Signer should not be valid because PCR0 is zero + assertFalse(teeProverRegistry.isValidSigner(signer)); + assertEq(teeProverRegistry.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); + teeProverRegistry.addDevSigner(signer1, pcr0Hash1); + teeProverRegistry.addDevSigner(signer2, pcr0Hash2); + teeProverRegistry.addDevSigner(signer3, pcr0Hash3); + vm.stopPrank(); + + assertTrue(teeProverRegistry.isValidSigner(signer1)); + assertTrue(teeProverRegistry.isValidSigner(signer2)); + assertTrue(teeProverRegistry.isValidSigner(signer3)); + + assertEq(teeProverRegistry.signerPCR0(signer1), pcr0Hash1); + assertEq(teeProverRegistry.signerPCR0(signer2), pcr0Hash2); + assertEq(teeProverRegistry.signerPCR0(signer3), pcr0Hash3); + } + + // ============ getRegisteredSigners Tests ============ + + function testGetRegisteredSignersEmpty() public view { + address[] memory signers = teeProverRegistry.getRegisteredSigners(); + assertEq(signers.length, 0); + } + + function testGetRegisteredSignersAfterRegister() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("pcr0"); + + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, signerPcr0); + + address[] memory signers = teeProverRegistry.getRegisteredSigners(); + assertEq(signers.length, 1); + assertEq(signers[0], signer); + } + + function testGetRegisteredSignersAfterDeregister() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("pcr0"); + + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, signerPcr0); + + assertEq(teeProverRegistry.getRegisteredSigners().length, 1); + + vm.prank(owner); + teeProverRegistry.deregisterSigner(signer); + + address[] memory signers = teeProverRegistry.getRegisteredSigners(); + assertEq(signers.length, 0); + } + + function testGetRegisteredSignersMultiple() public { + address signer1 = makeAddr("signer-1"); + address signer2 = makeAddr("signer-2"); + address signer3 = makeAddr("signer-3"); + + bytes32 sharedPcr0 = keccak256("pcr0"); + + vm.startPrank(owner); + teeProverRegistry.addDevSigner(signer1, sharedPcr0); + teeProverRegistry.addDevSigner(signer2, sharedPcr0); + teeProverRegistry.addDevSigner(signer3, sharedPcr0); + vm.stopPrank(); + + address[] memory signers = teeProverRegistry.getRegisteredSigners(); + assertEq(signers.length, 3); + + // Verify all three are present (order not guaranteed) + bool foundSigner1; + bool foundSigner2; + bool foundSigner3; + for (uint256 i = 0; i < signers.length; i++) { + if (signers[i] == signer1) foundSigner1 = true; + if (signers[i] == signer2) foundSigner2 = true; + if (signers[i] == signer3) foundSigner3 = true; + } + assertTrue(foundSigner1); + assertTrue(foundSigner2); + assertTrue(foundSigner3); + } + + function testGetRegisteredSignersConsistencyAfterMixedOperations() public { + address signer1 = makeAddr("signer-1"); + address signer2 = makeAddr("signer-2"); + address signer3 = makeAddr("signer-3"); + + bytes32 sharedPcr0 = keccak256("pcr0"); + + // Register three signers + vm.startPrank(owner); + teeProverRegistry.addDevSigner(signer1, sharedPcr0); + teeProverRegistry.addDevSigner(signer2, sharedPcr0); + teeProverRegistry.addDevSigner(signer3, sharedPcr0); + vm.stopPrank(); + + assertEq(teeProverRegistry.getRegisteredSigners().length, 3); + + // Deregister the middle one + vm.prank(manager); + teeProverRegistry.deregisterSigner(signer2); + + address[] memory signers = teeProverRegistry.getRegisteredSigners(); + assertEq(signers.length, 2); + + // Mapping and set stay consistent + for (uint256 i = 0; i < signers.length; i++) { + assertTrue(teeProverRegistry.isValidSigner(signers[i])); + assertNotEq(signers[i], signer2); + } + + // Deregistered signer not in mapping either + assertFalse(teeProverRegistry.isValidSigner(signer2)); + assertEq(teeProverRegistry.signerPCR0(signer2), bytes32(0)); + } + + function testGetRegisteredSignersDeregisterIdempotent() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("pcr0"); + + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, signerPcr0); + + vm.prank(owner); + teeProverRegistry.deregisterSigner(signer); + + // Deregistering again should not revert and set should still be empty + vm.prank(owner); + teeProverRegistry.deregisterSigner(signer); + + assertEq(teeProverRegistry.getRegisteredSigners().length, 0); + } +} diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol new file mode 100644 index 000000000..d92e2304a --- /dev/null +++ b/test/multiproof/TEEVerifier.t.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +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 "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; + +import { MockAnchorStateRegistry } from "scripts/multiproof/mocks/MockAnchorStateRegistry.sol"; +import { DevTEEProverRegistry } from "src/multiproof/mocks/MockDevTEEProverRegistry.sol"; +import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; + +contract TEEVerifierTest is Test { + TEEVerifier public verifier; + DevTEEProverRegistry public teeProverRegistry; + ProxyAdmin public proxyAdmin; + MockAnchorStateRegistry public anchorStateRegistry; + + // 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) + DevTEEProverRegistry impl = new DevTEEProverRegistry(INitroEnclaveVerifier(address(0))); + + // Deploy proxy admin + proxyAdmin = new ProxyAdmin(address(this)); + + // Deploy proxy + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), address(proxyAdmin), abi.encodeCall(TEEProverRegistry.initialize, (owner, owner)) + ); + + teeProverRegistry = DevTEEProverRegistry(address(proxy)); + + // Register the signer with PCR0 hash + teeProverRegistry.addDevSigner(signerAddress, PCR0_HASH); + + // Set the proposer as valid + teeProverRegistry.setProposer(PROPOSER, true); + + // Deploy TEEVerifier + anchorStateRegistry = new MockAnchorStateRegistry(); + verifier = new TEEVerifier( + TEEProverRegistry(address(teeProverRegistry)), IAnchorStateRegistry(address(anchorStateRegistry)) + ); + } + + 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.TEE_PROVER_REGISTRY()), address(teeProverRegistry)); + } +} 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..d7eb18e44 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 { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.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; + TEEProverRegistry teeProverRegistry; /// @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")); + teeProverRegistry = TEEProverRegistry(artifacts.mustGetAddress("TEEProverRegistry")); if (deploy.cfg().useAltDA()) { dataAvailabilityChallenge = diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 1e2425daa..9a02d48d7 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 { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.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. + + // TEEProverRegistryImpl contracts.push( InitializeableContract({ - name: "ETHLockboxProxy", - target: address(ethLockbox), - initCalldata: abi.encodeCall( - ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) - ) + name: "TEEProverRegistryImpl", + target: address(teeProverRegistry), + initCalldata: abi.encodeCall(TEEProverRegistry.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);