From 50419359542d454cbd620f135333ad7b3c58ec5f Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 4 Dec 2025 16:23:28 -0500 Subject: [PATCH 001/125] initial commit --- interfaces/mutliproof/ITEEVerifier.sol | 8 + interfaces/mutliproof/IZKVerifier.sol | 8 + src/multiproof/AggregateVerifier.sol | 586 +++++++++++++++++++++++ src/multiproof/mock/MockSystemConfig.sol | 15 + src/multiproof/mock/MockTEEVerifier.sol | 11 + src/multiproof/mock/MockZKVerifier.sol | 11 + test/multiproof/AggregateVerifier.t.sol | 524 ++++++++++++++++++++ test/multiproof/SetupTest.t.sol | 117 +++++ 8 files changed, 1280 insertions(+) create mode 100644 interfaces/mutliproof/ITEEVerifier.sol create mode 100644 interfaces/mutliproof/IZKVerifier.sol create mode 100644 src/multiproof/AggregateVerifier.sol create mode 100644 src/multiproof/mock/MockSystemConfig.sol create mode 100644 src/multiproof/mock/MockTEEVerifier.sol create mode 100644 src/multiproof/mock/MockZKVerifier.sol create mode 100644 test/multiproof/AggregateVerifier.t.sol create mode 100644 test/multiproof/SetupTest.t.sol diff --git a/interfaces/mutliproof/ITEEVerifier.sol b/interfaces/mutliproof/ITEEVerifier.sol new file mode 100644 index 00000000..918fded1 --- /dev/null +++ b/interfaces/mutliproof/ITEEVerifier.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Claim } from "optimism/src/dispute/lib/Types.sol"; + +interface ITEEVerifier { + function verify(bytes calldata proofBytes, Claim rootClaim, uint256 l2SequenceNumber) external view returns (bool); +} \ No newline at end of file diff --git a/interfaces/mutliproof/IZKVerifier.sol b/interfaces/mutliproof/IZKVerifier.sol new file mode 100644 index 00000000..f531d84d --- /dev/null +++ b/interfaces/mutliproof/IZKVerifier.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Claim } from "optimism/src/dispute/lib/Types.sol"; + +interface IZKVerifier { + function verify(bytes calldata proofBytes, Claim rootClaim, uint256 l2SequenceNumber) external view returns (bool); +} \ No newline at end of file diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol new file mode 100644 index 00000000..1eda786b --- /dev/null +++ b/src/multiproof/AggregateVerifier.sol @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; +// Libraries +import { Clone } from "solady/utils/Clone.sol"; +import { + Claim, + GameType, + Hash, + Proposal, + Timestamp +} from "optimism/src/dispute/lib/Types.sol"; +import { + AlreadyInitialized, + ClaimAlreadyResolved, + NoCreditToClaim, + BondTransferFailed, + GamePaused, + GameNotResolved, + GameNotFinalized +} from "optimism/src/dispute/lib/Errors.sol"; +import "./Errors.sol"; + +// Interfaces +import { GameStatus, IDisputeGame, IDisputeGameFactory } from "optimism/src/dispute/AnchorStateRegistry.sol"; +import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; +import {ITEEVerifier} from "./interfaces/ITEEVerifier.sol"; +import {IZKVerifier} from "./interfaces/IZKVerifier.sol"; + +contract AggregateVerifier is Clone, IDisputeGame { + //////////////////////////////////////////////////////////////// + // Enums // + //////////////////////////////////////////////////////////////// + + /// @notice The type of proof. Can be expanded for different types of ZK proofs. + enum ProofType { + TEE, + ZK + } + + //////////////////////////////////////////////////////////////// + // Structs // + //////////////////////////////////////////////////////////////// + + /// @notice The `ProvingData` struct represents the data associated with the proofs for a claim. + /// @param counteredByGameAddress The address of the game that countered this game. + /// @param teeProver The address that provided a TEE proof. + /// @param zkProver The address that provided a ZK proof. + /// @param lastProvedAt The timestamp of the last provided proof. + struct ProvingData { + address counteredByGameAddress; + address teeProver; + address zkProver; + Timestamp lastProvedAt; + } + + //////////////////////////////////////////////////////////////// + // Events // + //////////////////////////////////////////////////////////////// + + /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. + /// @param challenger The address of the challenger. + /// @param game The game used to challenge this proposal. + event Challenged(address indexed challenger, IDisputeGame game); + + /// @notice Emitted when the game is proved. + /// @param prover The address of the prover. + /// @param proofType The type of proof. + event Proved(address indexed prover, ProofType indexed proofType); + + /// @notice Emitted when the game is nullified. + /// @param nullifier The address of the nullifier. + /// @param game The game used to nullify this proposal. + event Nullified(address indexed nullifier, IDisputeGame game); + + //////////////////////////////////////////////////////////////// + // State Vars // + //////////////////////////////////////////////////////////////// + /// @notice The slow finalization delay. + uint256 public constant SLOW_FINALIZATION_DELAY = 7 days; + + /// @notice The fast finalization delay. + uint256 public constant FAST_FINALIZATION_DELAY = 1 days; + + /// @notice The anchor state registry. + IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; + + /// @notice The dispute game factory. + IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY; + + /// @notice The TEE prover. + ITEEVerifier public immutable TEE_VERIFIER; + + /// @notice The ZK prover. + IZKVerifier public immutable ZK_VERIFIER; + + /// @notice The address that can submit a TEE proof. + address public immutable TEE_PROPOSER; + + /// @notice The game type ID. + GameType internal immutable GAME_TYPE; + + /// @notice The chain ID of the L2 network this contract argues about. + uint256 internal 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 internal immutable BLOCK_INTERVAL; + + /// @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 The claim made by the proposer. + ProvingData public provingData; + + /// @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 A boolean for whether or not the game type was respected when the game was created. + bool public wasRespectedGameTypeWhenCreated; + + address public bondRecipient; + + /// @param _gameType The game type. + /// @param _anchorStateRegistry The anchor state registry. + /// @param _teeVerifier The TEE verifier. + /// @param _zkVerifier The ZK verifier. + /// @param _teeProposer The address that can submit a TEE proof. + /// @param _l2ChainId The chain ID of the L2 network. + /// @param _blockInterval The block interval. + constructor( + GameType _gameType, + IAnchorStateRegistry _anchorStateRegistry, + ITEEVerifier _teeVerifier, + IZKVerifier _zkVerifier, + address _teeProposer, + uint256 _l2ChainId, + uint256 _blockInterval + ) { + // Set up initial game state. + GAME_TYPE = _gameType; + ANCHOR_STATE_REGISTRY = _anchorStateRegistry; + DISPUTE_GAME_FACTORY = ANCHOR_STATE_REGISTRY.disputeGameFactory(); + TEE_VERIFIER = _teeVerifier; + ZK_VERIFIER = _zkVerifier; + TEE_PROPOSER = _teeProposer; + L2_CHAIN_ID = _l2ChainId; + BLOCK_INTERVAL = _blockInterval; + } + + /// @notice Initializes the contract. + /// @dev This function may only be called once. + function initialize() 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) + // - 0x02 CWIA bytes + assembly { + if iszero(eq(calldatasize(), 0x7E)) { + // Store the selector for `BadExtraData()` & revert + mstore(0x00, 0x9824bdab) + revert(0x1C, 0x04) + } + } + + // 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, and not retired. + if ( + !ANCHOR_STATE_REGISTRY.isGameRespected(parentGame) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(parentGame) + || ANCHOR_STATE_REGISTRY.isGameRetired(parentGame) + ) { + revert InvalidParentGame(); + } + + // The parent game must be a valid game. + if (parentGame.status() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); + + startingOutputRoot = Proposal({ + l2SequenceNumber: parentGame.l2SequenceNumber(), + root: Hash.wrap(parentGame.rootClaim().raw()) + }); + } else { + // When there is no parent game, the starting output root is the anchor state for the game type. + (startingOutputRoot.root, startingOutputRoot.l2SequenceNumber) = + ANCHOR_STATE_REGISTRY.getAnchorRoot(); + } + + // The block number must be BLOCK_INTERVAL blocks after the starting block number. + if (l2SequenceNumber() != startingOutputRoot.l2SequenceNumber + BLOCK_INTERVAL) { + revert UnexpectedBlockNumber(startingOutputRoot.l2SequenceNumber + BLOCK_INTERVAL, l2SequenceNumber()); + } + + // Set the game as initialized. + initialized = true; + + // Set the game's starting timestamp + createdAt = Timestamp.wrap(uint64(block.timestamp)); + + wasRespectedGameTypeWhenCreated = + GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); + } + + /// @notice The L2 block number for which this game is proposing an output root. + function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) { + l2BlockNumber_ = _getArgUint256(0x54); + } + + function l2SequenceNumber() public pure returns (uint256 l2SequenceNumber_) { + l2SequenceNumber_ = l2BlockNumber(); + } + + /// @notice The parent index of the game. + function parentIndex() public pure returns (uint32 parentIndex_) { + parentIndex_ = _getArgUint32(0x74); + } + + /// @notice The starting block number of the game. + function startingBlockNumber() external view returns (uint256 startingBlockNumber_) { + startingBlockNumber_ = startingOutputRoot.l2SequenceNumber; + } + + /// @notice The starting output root of the game. + function startingRootHash() external view returns (Hash startingRootHash_) { + startingRootHash_ = startingOutputRoot.root; + } + + //////////////////////////////////////////////////////////////// + // Proving Methods // + //////////////////////////////////////////////////////////////// + + /// @notice Verifies a TEE proof for the current game. + /// @param proofBytes The proof bytes. + function verifyTeeProof(bytes calldata proofBytes) external { + // The caller must be the TEE proposer. + if (msg.sender != TEE_PROPOSER) revert NotAuthorized(); + + // Verify the proof. + _verifyTeeProof(proofBytes, msg.sender); + } + + /// @notice Verifies a TEE proof for the current game. + /// @param proofBytes The proof bytes. + /// @param prover The address of the prover. + function _verifyTeeProof(bytes memory proofBytes, address prover) internal { + // Only one TEE proof can be submitted. + if (provingData.teeProver != address(0)) revert AlreadyProven(); + + // The game must be in progress. + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + + // The game must not be over. + if (gameOver()) revert GameOver(); + + // Validate the proof. + if (!TEE_VERIFIER.verify(proofBytes, rootClaim(), l2SequenceNumber())) revert InvalidProof(); + + // Update proving data. + provingData.teeProver = prover; + provingData.lastProvedAt = Timestamp.wrap(uint64(block.timestamp)); + + // Emit the proved event. + emit Proved(prover, ProofType.TEE); + } + + /// @notice Verifies a ZK proof for the current game. + /// @param proofBytes The proof bytes. + function verifyZkProof(bytes calldata proofBytes) external { + _verifyZkProof(proofBytes, msg.sender); + } + + /// @notice Verifies a ZK proof for the current game. + /// @param proofBytes The proof bytes. + /// @param prover The address of the prover. + function _verifyZkProof(bytes memory proofBytes, address prover) internal { + // Only one ZK proof can be submitted. + if (provingData.zkProver != address(0)) revert AlreadyProven(); + + // The game must be in progress or challenged (to allow nullification). + if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); + + // The game must not be over. + if (gameOver()) revert GameOver(); + + // Validate the proof. + if (!ZK_VERIFIER.verify(proofBytes, rootClaim(), l2SequenceNumber())) revert InvalidProof(); + + // Update proving data. + provingData.zkProver = prover; + provingData.lastProvedAt = Timestamp.wrap(uint64(block.timestamp)); + + // Emit the proved event. + emit Proved(prover, ProofType.ZK); + } + + /// @notice Resolves the game after enough time has passed. + function resolve() external returns (GameStatus) { + // The game must be in progress. + if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); + + GameStatus parentGameStatus = getParentGameStatus(); + // The parent game must have resolved. + if (parentGameStatus == GameStatus.IN_PROGRESS) revert ParentGameNotResolved(); + + // If the parent game's claim is invalid, blacklisted, or retired, then the current game's claim is invalid. + if (parentGameStatus == GameStatus.CHALLENGER_WINS) { + status = GameStatus.CHALLENGER_WINS; + } else { + // Game must be completed with a valid proof. + if (!gameOver()) revert GameNotOver(); + status = GameStatus.DEFENDER_WINS; + } + + // Bond is refunded as no challenge was made or parent is invalid. + bondRecipient = gameCreator(); + // Mark the game as resolved. + resolvedAt = Timestamp.wrap(uint64(block.timestamp)); + emit Resolved(status); + + return status; + } + + /// @notice Challenges the TEE proof with a ZK proof. + /// @param gameIndex The index of the game used to challenge. + /// @dev The game used to challenge must have a ZK proof for the same + /// block number but a different root claim as the current game. + function challenge(uint256 gameIndex) external { + // Can only challenge a game that has not been challenged or resolved yet. + if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); + // The TEE prover must not be empty. You should nullify the game if you want to challenge. + if (provingData.teeProver == address(0)) revert MissingTEEProof(); + if (provingData.zkProver != address(0)) revert AlreadyProven(); + + (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); + + AggregateVerifier challengingGame = AggregateVerifier(address(game)); + // The parent index must be the same. + if (challengingGame.parentIndex() != parentIndex()) revert IncorrectParentIndex(); + + // The block number must be the same. + if (challengingGame.l2SequenceNumber() != l2SequenceNumber()) revert IncorrectBlockNumber(); + + // The root claim must be different. + // Not actually reachable as the factory prevents the same proposal from being created. + if (challengingGame.rootClaim().raw() == rootClaim().raw()) revert IncorrectRootClaim(); + + // The ZK prover must not be empty. + if (challengingGame.zkProver() == address(0)) revert MissingZKProof(); + + // The game must be respected, not blacklisted, and not retired. + if (!ANCHOR_STATE_REGISTRY.isGameRespected(game) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) || ANCHOR_STATE_REGISTRY.isGameRetired(game)) { + revert InvalidGame(); + } + + // Update the counteredBy address + provingData.counteredByGameAddress = address(challengingGame); + + // Set the game as challenged. + status = GameStatus.CHALLENGER_WINS; + + // Set the bond recipient. + // Bond cannot be claimed until the game used to challenge resolves as DEFENDER_WINS. + bondRecipient = challengingGame.zkProver(); + + // Emit the challenged event + emit Challenged(challengingGame.zkProver(), game); + } + + /// @notice Nullifies the game if a soundness issue is found. + /// @param gameIndex The index of the game used to nullify. + /// @param proofType The type of proof used to nullify. + /// @dev The game used to nullify must have a proof for the same + /// block number but a different root claim as the current game. + function nullify(uint256 gameIndex, ProofType proofType) external { + // Can only nullify a game that has not resolved yet. + // We can nullify a challenged game in case of a soundness issue + if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); + + (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); + + // Can only nullify a game that has a proof of the same type. + if (proofType == ProofType.TEE) { + if (provingData.teeProver == address(0) || AggregateVerifier(address(game)).teeProver() == address(0)) revert MissingTEEProof(); + } else if (proofType == ProofType.ZK) { + if (provingData.zkProver == address(0) || AggregateVerifier(address(game)).zkProver() == address(0)) revert MissingZKProof(); + } + else { + revert InvalidProofType(); + } + + // The parent index must be the same. + if (AggregateVerifier(address(game)).parentIndex() != parentIndex()) revert IncorrectParentIndex(); + + // The block number must be the same. + if (game.l2SequenceNumber() != l2SequenceNumber()) revert IncorrectBlockNumber(); + + // The root claim must be different. + // Not actually reachable as the factory prevents the same proposal from being created. + if (game.rootClaim().raw() == rootClaim().raw()) revert IncorrectRootClaim(); + + // The game must be respected, not blacklisted, and not retired. + if (!ANCHOR_STATE_REGISTRY.isGameRespected(game) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) || ANCHOR_STATE_REGISTRY.isGameRetired(game)) { + revert InvalidGame(); + } + + // Set the game as challenged so that child games can't resolve. + status = GameStatus.CHALLENGER_WINS; + // Refund the bond. This can override a challenge. + bondRecipient = gameCreator(); + + emit Nullified(msg.sender, game); + } + + /// @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 { + // The bond recipient must not be empty. + if (bondRecipient == address(0)) revert BondRecipientEmpty(); + + // If this game was challenged, the countered by game must be valid. + if (provingData.counteredByGameAddress != address(0)) { + if (IDisputeGame(provingData.counteredByGameAddress).status() != GameStatus.DEFENDER_WINS) revert InvalidCounteredByGame(); + } + + // The game must have credit to claim. + if (address(this).balance == 0) revert NoCreditToClaim(); + + // Transfer the credit to the bond recipient. + (bool success,) = bondRecipient.call{value: address(this).balance}(hex""); + if (!success) revert BondTransferFailed(); + } + + function closeGame() public { + + // 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 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() private 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(); + } else { + // 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 Determines if the game is finished. + function gameOver() public view returns (bool) { + if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { + return provingData.lastProvedAt.raw() + FAST_FINALIZATION_DELAY < uint64(block.timestamp); + } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { + return provingData.lastProvedAt.raw() + SLOW_FINALIZATION_DELAY < uint64(block.timestamp); + } + // No proof was provided so don't view the game as over. + return false; + } + + /// @notice Getter for the game type. + /// @dev The reference impl should be entirely different depending on the type (fault, validity) + /// i.e. The game type should indicate the security model. + /// @return gameType_ The type of proof system being used. + function gameType() public view returns (GameType gameType_) { + gameType_ = GAME_TYPE; + } + + /// @notice Getter for the creator of the dispute game. + /// @dev `clones-with-immutable-args` argument #1 + /// @return creator_ The creator of the dispute game. + function gameCreator() public pure returns (address creator_) { + creator_ = _getArgAddress(0x00); + } + + /// @notice Getter for the root claim. + /// @dev `clones-with-immutable-args` argument #2 + /// @return rootClaim_ The root claim of the DisputeGame. + function rootClaim() public pure returns (Claim rootClaim_) { + rootClaim_ = Claim.wrap(_getArgBytes32(0x14)); + } + + /// @notice Getter for the parent hash of the L1 block when the dispute game was created. + /// @dev `clones-with-immutable-args` argument #3 + /// @return l1Head_ The parent hash of the L1 block when the dispute game was created. + function l1Head() public pure returns (Hash l1Head_) { + l1Head_ = Hash.wrap(_getArgBytes32(0x34)); + } + + /// @notice Getter for the extra data. + /// @dev `clones-with-immutable-args` argument #4 + /// @return extraData_ Any extra data supplied to the dispute game contract by the creator. + function extraData() public pure returns (bytes memory extraData_) { + // The extra data starts at the second word within the cwia calldata and + // is 36 bytes long. + // 32 bytes are for the l2BlockNumber + // 4 bytes are for the parentIndex + extraData_ = _getArgBytes(0x54, 0x24); + } + + /// @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 gameType_, Claim rootClaim_, bytes memory extraData_) { + gameType_ = gameType(); + rootClaim_ = rootClaim(); + extraData_ = extraData(); + } + + function teeProver() external view returns (address teeProver_) { + teeProver_ = provingData.teeProver; + } + + function zkProver() external view returns (address zkProver_) { + zkProver_ = provingData.zkProver; + } + + //////////////////////////////////////////////////////////////// + // IMMUTABLE GETTERS // + //////////////////////////////////////////////////////////////// + + function l2ChainId() external view returns (uint256 l2ChainId_) { + l2ChainId_ = L2_CHAIN_ID; + } + + function blockInterval() external view returns (uint256 blockInterval_) { + blockInterval_ = BLOCK_INTERVAL; + } + + function anchorStateRegistry() external view returns (IAnchorStateRegistry anchorStateRegistry_) { + anchorStateRegistry_ = ANCHOR_STATE_REGISTRY; + } +} diff --git a/src/multiproof/mock/MockSystemConfig.sol b/src/multiproof/mock/MockSystemConfig.sol new file mode 100644 index 00000000..36df5361 --- /dev/null +++ b/src/multiproof/mock/MockSystemConfig.sol @@ -0,0 +1,15 @@ +// 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; + } +} \ No newline at end of file diff --git a/src/multiproof/mock/MockTEEVerifier.sol b/src/multiproof/mock/MockTEEVerifier.sol new file mode 100644 index 00000000..69b36592 --- /dev/null +++ b/src/multiproof/mock/MockTEEVerifier.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {ITEEVerifier} from "../interfaces/ITEEVerifier.sol"; +import {Claim} from "optimism/src/dispute/lib/Types.sol"; + +contract MockTEEVerifier is ITEEVerifier { + function verify(bytes calldata , Claim , uint256 ) external pure returns (bool) { + return true; + } +} \ No newline at end of file diff --git a/src/multiproof/mock/MockZKVerifier.sol b/src/multiproof/mock/MockZKVerifier.sol new file mode 100644 index 00000000..5ae64b69 --- /dev/null +++ b/src/multiproof/mock/MockZKVerifier.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {IZKVerifier} from "../interfaces/IZKVerifier.sol"; +import {Claim} from "optimism/src/dispute/lib/Types.sol"; + +contract MockZKVerifier is IZKVerifier { + function verify(bytes calldata , Claim , uint256 ) external pure returns (bool) { + return true; + } +} \ No newline at end of file diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol new file mode 100644 index 00000000..6d549e9e --- /dev/null +++ b/test/multiproof/AggregateVerifier.t.sol @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import "test/SetupTest.t.sol"; +import {GameStatus, Timestamp, AggregateVerifier} from "src/AggregateVerifier.sol"; +import { + MissingTEEProof, + MissingZKProof, + IncorrectRootClaim, + IncorrectBlockNumber, + IncorrectParentIndex, + NotAuthorized +} from "src/Errors.sol"; + +// Optimism +import { ClaimAlreadyResolved, GameAlreadyExists } from "optimism/src/dispute/lib/Errors.sol"; + +contract AggregateVerifierTest is SetupTest { + function setUp() public override { + super.setUp(); + anchorStateRegistry.setRespectedGameType(AGGREGATE_VERIFIER_GAME_TYPE); + } + + function testInitializeWithTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = "tee-proof"; + + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game, TEE_PROVER, true, 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)); + assertEq(game.bondRecipient(), address(0)); + assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); + assertEq(address(game).balance, INIT_BOND); + } + + function testInitializeWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = "zk-proof"; + + AggregateVerifier game = _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); + _provideProof(game, ZK_PROVER, false, 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)); + assertEq(game.bondRecipient(), address(0)); + assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); + assertEq(address(game).balance, INIT_BOND); + } + + function testInitializeFailsIfNotTEEProposer() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = "tee-proof"; + + AggregateVerifier game = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + + vm.expectRevert(NotAuthorized.selector); + _provideProof(game, ZK_PROVER, true, proof); + } + + function testChallengeTEEProofWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // Create first game with TEE proof + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, TEE_PROVER, true, teeProof); + + // Create second game with different root claim and ZK proof + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game2, ZK_PROVER, false, zkProof); + + // Get game index from factory + uint256 gameIndex = factory.gameCount() - 1; + + // Challenge game1 with game2 + game1.challenge(gameIndex); + + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game1.bondRecipient(), ZK_PROVER); + (address counteredBy,,,) = game1.provingData(); + assertEq(counteredBy, address(game2)); + + // Retrieve bond after challenge + vm.warp(block.timestamp + 7 days + 1); + game2.resolve(); + assertEq(uint8(game2.status()), uint8(GameStatus.DEFENDER_WINS)); + assertEq(ZK_PROVER.balance, 0); + assertEq(address(game1).balance, INIT_BOND); + vm.prank(ZK_PROVER); + game1.claimCredit(); + assertEq(ZK_PROVER.balance, INIT_BOND); + assertEq(address(game1).balance, 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 = "zk-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, ZK_PROVER, false, zkProof1); + + // Create second game with different root claim and ZK proof + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + bytes memory zkProof2 = "zk-proof-2"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game2, ZK_PROVER, false, zkProof2); + + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert(MissingTEEProof.selector); + game1.challenge(gameIndex); + } + + function testCannotCreateSameProposal() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + _createAggregateVerifierGame( + TEE_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max); + + Hash uuid = factory.getGameUUID(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max)); + vm.expectRevert(abi.encodeWithSelector(GameAlreadyExists.selector, uuid)); + _createAggregateVerifierGame( + ZK_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + } + + function testChallengeFailsIfDifferentParentIndex() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, TEE_PROVER, true, teeProof); + + // Create game2 with game1 as parent + uint256 game1Index = factory.gameCount() - 1; + uint256 nextBlockNumber = currentL2BlockNumber + BLOCK_INTERVAL; + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + nextBlockNumber, + uint32(game1Index) + ); + + _provideProof(game2, ZK_PROVER, false, zkProof); + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert(IncorrectParentIndex.selector); + game1.challenge(gameIndex); + } + + function testChallengeFailsIfChallengingGameHasNoZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = "tee-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, TEE_PROVER, true, teeProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = "tee-proof-2"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game2, TEE_PROVER, true, teeProof2); + + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert(MissingZKProof.selector); + game1.challenge(gameIndex); + } + + function testChallengeFailsIfGameAlreadyResolved() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, TEE_PROVER, true, 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 = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game2, ZK_PROVER, false, zkProof); + + uint256 challengeIndex1 = factory.gameCount() - 1; + vm.expectRevert(ClaimAlreadyResolved.selector); + game1.challenge(challengeIndex1); + } + + function testNullifyWithTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = "tee-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, TEE_PROVER, true, teeProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = "tee-proof-2"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, TEE_PROVER, true, teeProof2); + + uint256 gameIndex = factory.gameCount() - 1; + game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); + + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game1.bondRecipient(), TEE_PROVER); + } + + function testNullifyWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof1 = "zk-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, ZK_PROVER, false, zkProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + bytes memory zkProof2 = "zk-proof-2"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, ZK_PROVER, false, zkProof2); + + uint256 gameIndex = factory.gameCount() - 1; + game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); + + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game1.bondRecipient(), ZK_PROVER); + } + + function testNullifyFailsIfNoTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, ZK_PROVER, false, zkProof); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, TEE_PROVER, true, teeProof); + + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(MissingTEEProof.selector); + game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); + } + + function testNullifyFailsIfNoZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, TEE_PROVER, true, teeProof); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, ZK_PROVER, false, zkProof); + + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(MissingZKProof.selector); + game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); + } + + function testNullifyFailsIfGameAlreadyResolved() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = "tee-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); + _provideProof(game1, TEE_PROVER, true, teeProof1); + + // Resolve game1 + vm.warp(block.timestamp + 7 days + 1); + game1.resolve(); + + // Try to nullify game1 + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory teeProof2 = "tee-proof-2"; + AggregateVerifier game2 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, TEE_PROVER, true, teeProof2); + + uint256 challengeIndex = factory.gameCount() - 1; + vm.expectRevert(ClaimAlreadyResolved.selector); + game1.nullify(challengeIndex, AggregateVerifier.ProofType.TEE); + } + + function testNullifyCanOverrideChallenge() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = "tee-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, TEE_PROVER, true, teeProof1); + + // Challenge game1 + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, ZK_PROVER, false, zkProof); + + uint256 challengeIndex = factory.gameCount() - 1; + game1.challenge(challengeIndex); + assertEq(game1.bondRecipient(), ZK_PROVER); + + // Nullify can override challenge + _provideProof(game1, ZK_PROVER, false, zkProof); + game1.nullify(challengeIndex, AggregateVerifier.ProofType.ZK); + + assertEq(game1.bondRecipient(), TEE_PROVER); + } + + // Helper function to create a game via factory + function _createAggregateVerifierGame( + address creator, + Claim rootClaim, + uint256 l2BlockNumber, + uint32 parentIndex + ) internal returns (AggregateVerifier game) { + bytes memory extraData = abi.encodePacked( + uint256(l2BlockNumber), + uint32(parentIndex) + ); + + vm.deal(creator, INIT_BOND); + vm.prank(creator); + return AggregateVerifier(address(factory.create{value: INIT_BOND}( + AGGREGATE_VERIFIER_GAME_TYPE, + rootClaim, + extraData + ))); + } + + function _provideProof(AggregateVerifier game, address prover, bool isTeeProof, bytes memory proof) internal { + vm.prank(prover); + if (isTeeProof) { + game.verifyTeeProof(proof); + } else { + game.verifyZkProof(proof); + } + } +} diff --git a/test/multiproof/SetupTest.t.sol b/test/multiproof/SetupTest.t.sol new file mode 100644 index 00000000..41005fe0 --- /dev/null +++ b/test/multiproof/SetupTest.t.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Test, console2 } from "forge-std/Test.sol"; +import { AggregateVerifier, IAnchorStateRegistry } from "src/AggregateVerifier.sol"; + +import {ITEEVerifier} from "src/interfaces/ITEEVerifier.sol"; +import {IZKVerifier} from "src/interfaces/IZKVerifier.sol"; + +// Mocks +import { MockTEEVerifier } from "src/mocks/MockTEEVerifier.sol"; +import { MockZKVerifier } from "src/mocks/MockZKVerifier.sol"; +import { MockSystemConfig } from "src/mocks/MockSystemConfig.sol"; + +// Optimism +import { IDisputeGame, DisputeGameFactory } from "optimism/src/dispute/DisputeGameFactory.sol"; +import { GameType, Duration, Claim } from "optimism/src/dispute/lib/Types.sol"; +import { ISystemConfig, IDisputeGameFactory, Hash, Proposal, AnchorStateRegistry } from "optimism/src/dispute/AnchorStateRegistry.sol"; + +// OpenZeppelin +import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +contract SetupTest is Test { + // Constants + GameType public constant AGGREGATE_VERIFIER_GAME_TYPE = GameType.wrap(621); + uint256 public constant L2_CHAIN_ID = 8453; + uint256 public constant BLOCK_INTERVAL = 100; + uint256 public constant INIT_BOND = 1 ether; + // Finality delay handled by the AggregateVerifier + uint256 public constant FINALITY_DELAY = 0 days; + + 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"); + + ProxyAdmin public proxyAdmin; + MockSystemConfig public systemConfig; + + DisputeGameFactory public factory; + AnchorStateRegistry public anchorStateRegistry; + + MockTEEVerifier public teeVerifier; + MockZKVerifier public zkVerifier; + + function setUp() public virtual { + _deployContractsAndProxies(); + _initializeProxies(); + + // Deploy the implementations + _deployAndSetAggregateVerifier(); + + // 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 dispute game factory + DisputeGameFactory _factory = new DisputeGameFactory(); + + // Deploy proxy admin + proxyAdmin = new ProxyAdmin(); + + // 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 the verifiers + teeVerifier = new MockTEEVerifier(); + zkVerifier = new MockZKVerifier(); + } + + 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)); + } + + function _deployAndSetAggregateVerifier() internal { + + // Deploy the dispute game relay implementation + AggregateVerifier aggregateVerifierImpl = new AggregateVerifier( + AGGREGATE_VERIFIER_GAME_TYPE, + IAnchorStateRegistry(address(anchorStateRegistry)), + ITEEVerifier(address(teeVerifier)), + IZKVerifier(address(zkVerifier)), + TEE_PROVER, + L2_CHAIN_ID, + BLOCK_INTERVAL + ); + + // 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); + } +} \ No newline at end of file From bd550fd34b02427e4fe419564ca697e2cd900f53 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 4 Dec 2025 16:57:39 -0500 Subject: [PATCH 002/125] Refactor tests and simplify verifier --- interfaces/mutliproof/ITEEVerifier.sol | 8 - .../{IZKVerifier.sol => IVerifier.sol} | 2 +- src/multiproof/AggregateVerifier.sol | 11 +- src/multiproof/mock/MockTEEVerifier.sol | 11 - .../{MockZKVerifier.sol => MockVerifier.sol} | 4 +- test/multiproof/AggregateVerifier.t.sol | 403 ------------------ test/multiproof/Challenge.t.sol | 220 ++++++++++ test/multiproof/Nullify.t.sol | 198 +++++++++ test/multiproof/SetupTest.t.sol | 18 +- 9 files changed, 434 insertions(+), 441 deletions(-) delete mode 100644 interfaces/mutliproof/ITEEVerifier.sol rename interfaces/mutliproof/{IZKVerifier.sol => IVerifier.sol} (90%) delete mode 100644 src/multiproof/mock/MockTEEVerifier.sol rename src/multiproof/mock/{MockZKVerifier.sol => MockVerifier.sol} (69%) create mode 100644 test/multiproof/Challenge.t.sol create mode 100644 test/multiproof/Nullify.t.sol diff --git a/interfaces/mutliproof/ITEEVerifier.sol b/interfaces/mutliproof/ITEEVerifier.sol deleted file mode 100644 index 918fded1..00000000 --- a/interfaces/mutliproof/ITEEVerifier.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { Claim } from "optimism/src/dispute/lib/Types.sol"; - -interface ITEEVerifier { - function verify(bytes calldata proofBytes, Claim rootClaim, uint256 l2SequenceNumber) external view returns (bool); -} \ No newline at end of file diff --git a/interfaces/mutliproof/IZKVerifier.sol b/interfaces/mutliproof/IVerifier.sol similarity index 90% rename from interfaces/mutliproof/IZKVerifier.sol rename to interfaces/mutliproof/IVerifier.sol index f531d84d..4933c7d2 100644 --- a/interfaces/mutliproof/IZKVerifier.sol +++ b/interfaces/mutliproof/IVerifier.sol @@ -3,6 +3,6 @@ pragma solidity 0.8.15; import { Claim } from "optimism/src/dispute/lib/Types.sol"; -interface IZKVerifier { +interface IVerifier { function verify(bytes calldata proofBytes, Claim rootClaim, uint256 l2SequenceNumber) external view returns (bool); } \ No newline at end of file diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 1eda786b..3e5bf4b5 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -23,8 +23,7 @@ import "./Errors.sol"; // Interfaces import { GameStatus, IDisputeGame, IDisputeGameFactory } from "optimism/src/dispute/AnchorStateRegistry.sol"; import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; -import {ITEEVerifier} from "./interfaces/ITEEVerifier.sol"; -import {IZKVerifier} from "./interfaces/IZKVerifier.sol"; +import {IVerifier} from "./interfaces/IVerifier.sol"; contract AggregateVerifier is Clone, IDisputeGame { //////////////////////////////////////////////////////////////// @@ -88,10 +87,10 @@ contract AggregateVerifier is Clone, IDisputeGame { IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY; /// @notice The TEE prover. - ITEEVerifier public immutable TEE_VERIFIER; + IVerifier public immutable TEE_VERIFIER; /// @notice The ZK prover. - IZKVerifier public immutable ZK_VERIFIER; + IVerifier public immutable ZK_VERIFIER; /// @notice The address that can submit a TEE proof. address public immutable TEE_PROPOSER; @@ -140,8 +139,8 @@ contract AggregateVerifier is Clone, IDisputeGame { constructor( GameType _gameType, IAnchorStateRegistry _anchorStateRegistry, - ITEEVerifier _teeVerifier, - IZKVerifier _zkVerifier, + IVerifier _teeVerifier, + IVerifier _zkVerifier, address _teeProposer, uint256 _l2ChainId, uint256 _blockInterval diff --git a/src/multiproof/mock/MockTEEVerifier.sol b/src/multiproof/mock/MockTEEVerifier.sol deleted file mode 100644 index 69b36592..00000000 --- a/src/multiproof/mock/MockTEEVerifier.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {ITEEVerifier} from "../interfaces/ITEEVerifier.sol"; -import {Claim} from "optimism/src/dispute/lib/Types.sol"; - -contract MockTEEVerifier is ITEEVerifier { - function verify(bytes calldata , Claim , uint256 ) external pure returns (bool) { - return true; - } -} \ No newline at end of file diff --git a/src/multiproof/mock/MockZKVerifier.sol b/src/multiproof/mock/MockVerifier.sol similarity index 69% rename from src/multiproof/mock/MockZKVerifier.sol rename to src/multiproof/mock/MockVerifier.sol index 5ae64b69..a1102b56 100644 --- a/src/multiproof/mock/MockZKVerifier.sol +++ b/src/multiproof/mock/MockVerifier.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {IZKVerifier} from "../interfaces/IZKVerifier.sol"; +import {IVerifier} from "../interfaces/IVerifier.sol"; import {Claim} from "optimism/src/dispute/lib/Types.sol"; -contract MockZKVerifier is IZKVerifier { +contract MockVerifier is IVerifier { function verify(bytes calldata , Claim , uint256 ) external pure returns (bool) { return true; } diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 6d549e9e..a0f86d9f 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -89,409 +89,6 @@ contract AggregateVerifierTest is SetupTest { _provideProof(game, ZK_PROVER, true, proof); } - function testChallengeTEEProofWithZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - // Create first game with TEE proof - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = "tee-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game1, TEE_PROVER, true, teeProof); - - // Create second game with different root claim and ZK proof - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory zkProof = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game2, ZK_PROVER, false, zkProof); - - // Get game index from factory - uint256 gameIndex = factory.gameCount() - 1; - - // Challenge game1 with game2 - game1.challenge(gameIndex); - - assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); - assertEq(game1.bondRecipient(), ZK_PROVER); - (address counteredBy,,,) = game1.provingData(); - assertEq(counteredBy, address(game2)); - - // Retrieve bond after challenge - vm.warp(block.timestamp + 7 days + 1); - game2.resolve(); - assertEq(uint8(game2.status()), uint8(GameStatus.DEFENDER_WINS)); - assertEq(ZK_PROVER.balance, 0); - assertEq(address(game1).balance, INIT_BOND); - vm.prank(ZK_PROVER); - game1.claimCredit(); - assertEq(ZK_PROVER.balance, INIT_BOND); - assertEq(address(game1).balance, 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 = "zk-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game1, ZK_PROVER, false, zkProof1); - - // Create second game with different root claim and ZK proof - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); - bytes memory zkProof2 = "zk-proof-2"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game2, ZK_PROVER, false, zkProof2); - - uint256 gameIndex = factory.gameCount() - 1; - - vm.expectRevert(MissingTEEProof.selector); - game1.challenge(gameIndex); - } - - function testCannotCreateSameProposal() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - - _createAggregateVerifierGame( - TEE_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max); - - Hash uuid = factory.getGameUUID(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max)); - vm.expectRevert(abi.encodeWithSelector(GameAlreadyExists.selector, uuid)); - _createAggregateVerifierGame( - ZK_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); - } - - function testChallengeFailsIfDifferentParentIndex() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = "tee-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game1, TEE_PROVER, true, teeProof); - - // Create game2 with game1 as parent - uint256 game1Index = factory.gameCount() - 1; - uint256 nextBlockNumber = currentL2BlockNumber + BLOCK_INTERVAL; - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); - bytes memory zkProof = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - nextBlockNumber, - uint32(game1Index) - ); - - _provideProof(game2, ZK_PROVER, false, zkProof); - uint256 gameIndex = factory.gameCount() - 1; - - vm.expectRevert(IncorrectParentIndex.selector); - game1.challenge(gameIndex); - } - - function testChallengeFailsIfChallengingGameHasNoZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = "tee-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game1, TEE_PROVER, true, teeProof1); - - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof2 = "tee-proof-2"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game2, TEE_PROVER, true, teeProof2); - - uint256 gameIndex = factory.gameCount() - 1; - - vm.expectRevert(MissingZKProof.selector); - game1.challenge(gameIndex); - } - - function testChallengeFailsIfGameAlreadyResolved() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = "tee-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game1, TEE_PROVER, true, 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 = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - - _provideProof(game2, ZK_PROVER, false, zkProof); - - uint256 challengeIndex1 = factory.gameCount() - 1; - vm.expectRevert(ClaimAlreadyResolved.selector); - game1.challenge(challengeIndex1); - } - - function testNullifyWithTEEProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = "tee-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game1, TEE_PROVER, true, teeProof1); - - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof2 = "tee-proof-2"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game2, TEE_PROVER, true, teeProof2); - - uint256 gameIndex = factory.gameCount() - 1; - game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); - - assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); - assertEq(game1.bondRecipient(), TEE_PROVER); - } - - function testNullifyWithZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof1 = "zk-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game1, ZK_PROVER, false, zkProof1); - - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); - bytes memory zkProof2 = "zk-proof-2"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game2, ZK_PROVER, false, zkProof2); - - uint256 gameIndex = factory.gameCount() - 1; - game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); - - assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); - assertEq(game1.bondRecipient(), ZK_PROVER); - } - - function testNullifyFailsIfNoTEEProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof = "zk-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game1, ZK_PROVER, false, zkProof); - - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof = "tee-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game2, TEE_PROVER, true, teeProof); - - uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(MissingTEEProof.selector); - game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); - } - - function testNullifyFailsIfNoZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof = "tee-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game1, TEE_PROVER, true, teeProof); - - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory zkProof = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game2, ZK_PROVER, false, zkProof); - - uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(MissingZKProof.selector); - game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); - } - - function testNullifyFailsIfGameAlreadyResolved() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = "tee-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - _provideProof(game1, TEE_PROVER, true, teeProof1); - - // Resolve game1 - vm.warp(block.timestamp + 7 days + 1); - game1.resolve(); - - // Try to nullify game1 - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory teeProof2 = "tee-proof-2"; - AggregateVerifier game2 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game2, TEE_PROVER, true, teeProof2); - - uint256 challengeIndex = factory.gameCount() - 1; - vm.expectRevert(ClaimAlreadyResolved.selector); - game1.nullify(challengeIndex, AggregateVerifier.ProofType.TEE); - } - - function testNullifyCanOverrideChallenge() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = "tee-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game1, TEE_PROVER, true, teeProof1); - - // Challenge game1 - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory zkProof = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); - _provideProof(game2, ZK_PROVER, false, zkProof); - - uint256 challengeIndex = factory.gameCount() - 1; - game1.challenge(challengeIndex); - assertEq(game1.bondRecipient(), ZK_PROVER); - - // Nullify can override challenge - _provideProof(game1, ZK_PROVER, false, zkProof); - game1.nullify(challengeIndex, AggregateVerifier.ProofType.ZK); - - assertEq(game1.bondRecipient(), TEE_PROVER); - } - // Helper function to create a game via factory function _createAggregateVerifierGame( address creator, diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol new file mode 100644 index 00000000..d98a8872 --- /dev/null +++ b/test/multiproof/Challenge.t.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import "test/AggregateVerifier.t.sol"; + +contract ChallengeTest is AggregateVerifierTest { + + function testChallengeTEEProofWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // Create first game with TEE proof + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, TEE_PROVER, true, teeProof); + + // Create second game with different root claim and ZK proof + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game2, ZK_PROVER, false, zkProof); + + // Get game index from factory + uint256 gameIndex = factory.gameCount() - 1; + + // Challenge game1 with game2 + game1.challenge(gameIndex); + + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game1.bondRecipient(), ZK_PROVER); + (address counteredBy,,,) = game1.provingData(); + assertEq(counteredBy, address(game2)); + + // Retrieve bond after challenge + vm.warp(block.timestamp + 7 days + 1); + game2.resolve(); + assertEq(uint8(game2.status()), uint8(GameStatus.DEFENDER_WINS)); + assertEq(ZK_PROVER.balance, 0); + assertEq(address(game1).balance, INIT_BOND); + vm.prank(ZK_PROVER); + game1.claimCredit(); + assertEq(ZK_PROVER.balance, INIT_BOND); + assertEq(address(game1).balance, 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 = "zk-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, ZK_PROVER, false, zkProof1); + + // Create second game with different root claim and ZK proof + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + bytes memory zkProof2 = "zk-proof-2"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game2, ZK_PROVER, false, zkProof2); + + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert(MissingTEEProof.selector); + game1.challenge(gameIndex); + } + + function testCannotCreateSameProposal() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + _createAggregateVerifierGame( + TEE_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max); + + Hash uuid = factory.getGameUUID(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max)); + vm.expectRevert(abi.encodeWithSelector(GameAlreadyExists.selector, uuid)); + _createAggregateVerifierGame( + ZK_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + } + + function testChallengeFailsIfDifferentParentIndex() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, TEE_PROVER, true, teeProof); + + // Create game2 with game1 as parent + uint256 game1Index = factory.gameCount() - 1; + uint256 nextBlockNumber = currentL2BlockNumber + BLOCK_INTERVAL; + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + nextBlockNumber, + uint32(game1Index) + ); + + _provideProof(game2, ZK_PROVER, false, zkProof); + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert(IncorrectParentIndex.selector); + game1.challenge(gameIndex); + } + + function testChallengeFailsIfChallengingGameHasNoZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = "tee-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, TEE_PROVER, true, teeProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = "tee-proof-2"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game2, TEE_PROVER, true, teeProof2); + + uint256 gameIndex = factory.gameCount() - 1; + + vm.expectRevert(MissingZKProof.selector); + game1.challenge(gameIndex); + } + + function testChallengeFailsIfGameAlreadyResolved() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game1, TEE_PROVER, true, 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 = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game2, ZK_PROVER, false, zkProof); + + uint256 challengeIndex1 = factory.gameCount() - 1; + vm.expectRevert(ClaimAlreadyResolved.selector); + game1.challenge(challengeIndex1); + } +} \ No newline at end of file diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol new file mode 100644 index 00000000..eea58115 --- /dev/null +++ b/test/multiproof/Nullify.t.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + + +import "test/AggregateVerifier.t.sol"; + +contract NullifyTest is AggregateVerifierTest { + + function testNullifyWithTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = "tee-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, TEE_PROVER, true, teeProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = "tee-proof-2"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, TEE_PROVER, true, teeProof2); + + uint256 gameIndex = factory.gameCount() - 1; + game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); + + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game1.bondRecipient(), TEE_PROVER); + } + + function testNullifyWithZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof1 = "zk-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, ZK_PROVER, false, zkProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + bytes memory zkProof2 = "zk-proof-2"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, ZK_PROVER, false, zkProof2); + + uint256 gameIndex = factory.gameCount() - 1; + game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); + + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game1.bondRecipient(), ZK_PROVER); + } + + function testNullifyFailsIfNoTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, ZK_PROVER, false, zkProof); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, TEE_PROVER, true, teeProof); + + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(MissingTEEProof.selector); + game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); + } + + function testNullifyFailsIfNoZKProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof = "tee-proof"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, TEE_PROVER, true, teeProof); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, ZK_PROVER, false, zkProof); + + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(MissingZKProof.selector); + game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); + } + + function testNullifyFailsIfGameAlreadyResolved() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = "tee-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); + _provideProof(game1, TEE_PROVER, true, teeProof1); + + // Resolve game1 + vm.warp(block.timestamp + 7 days + 1); + game1.resolve(); + + // Try to nullify game1 + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory teeProof2 = "tee-proof-2"; + AggregateVerifier game2 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, TEE_PROVER, true, teeProof2); + + uint256 challengeIndex = factory.gameCount() - 1; + vm.expectRevert(ClaimAlreadyResolved.selector); + game1.nullify(challengeIndex, AggregateVerifier.ProofType.TEE); + } + + function testNullifyCanOverrideChallenge() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = "tee-proof-1"; + + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game1, TEE_PROVER, true, teeProof1); + + // Challenge game1 + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim2, + currentL2BlockNumber, + type(uint32).max + ); + _provideProof(game2, ZK_PROVER, false, zkProof); + + uint256 challengeIndex = factory.gameCount() - 1; + game1.challenge(challengeIndex); + assertEq(game1.bondRecipient(), ZK_PROVER); + + // Nullify can override challenge + _provideProof(game1, ZK_PROVER, false, zkProof); + game1.nullify(challengeIndex, AggregateVerifier.ProofType.ZK); + + assertEq(game1.bondRecipient(), TEE_PROVER); + } +} \ No newline at end of file diff --git a/test/multiproof/SetupTest.t.sol b/test/multiproof/SetupTest.t.sol index 41005fe0..65901e07 100644 --- a/test/multiproof/SetupTest.t.sol +++ b/test/multiproof/SetupTest.t.sol @@ -4,12 +4,10 @@ pragma solidity ^0.8.0; import { Test, console2 } from "forge-std/Test.sol"; import { AggregateVerifier, IAnchorStateRegistry } from "src/AggregateVerifier.sol"; -import {ITEEVerifier} from "src/interfaces/ITEEVerifier.sol"; -import {IZKVerifier} from "src/interfaces/IZKVerifier.sol"; +import {IVerifier} from "src/interfaces/IVerifier.sol"; // Mocks -import { MockTEEVerifier } from "src/mocks/MockTEEVerifier.sol"; -import { MockZKVerifier } from "src/mocks/MockZKVerifier.sol"; +import { MockVerifier } from "src/mocks/MockVerifier.sol"; import { MockSystemConfig } from "src/mocks/MockSystemConfig.sol"; // Optimism @@ -42,8 +40,8 @@ contract SetupTest is Test { DisputeGameFactory public factory; AnchorStateRegistry public anchorStateRegistry; - MockTEEVerifier public teeVerifier; - MockZKVerifier public zkVerifier; + MockVerifier public teeVerifier; + MockVerifier public zkVerifier; function setUp() public virtual { _deployContractsAndProxies(); @@ -85,8 +83,8 @@ contract SetupTest is Test { factory = DisputeGameFactory(address(factoryProxy)); // Deploy the verifiers - teeVerifier = new MockTEEVerifier(); - zkVerifier = new MockZKVerifier(); + teeVerifier = new MockVerifier(); + zkVerifier = new MockVerifier(); } function _initializeProxies() internal { @@ -101,8 +99,8 @@ contract SetupTest is Test { AggregateVerifier aggregateVerifierImpl = new AggregateVerifier( AGGREGATE_VERIFIER_GAME_TYPE, IAnchorStateRegistry(address(anchorStateRegistry)), - ITEEVerifier(address(teeVerifier)), - IZKVerifier(address(zkVerifier)), + IVerifier(address(teeVerifier)), + IVerifier(address(zkVerifier)), TEE_PROVER, L2_CHAIN_ID, BLOCK_INTERVAL From e1a7654e0af0018c8638e0423da2f4c958bd8e8c Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Mon, 8 Dec 2025 12:37:59 -0500 Subject: [PATCH 003/125] Refactor proof verification methods to consolidate TEE and ZK proof handling into a single function, improving code clarity and maintainability. Update tests accordingly to reflect the new verification structure. --- src/multiproof/AggregateVerifier.sol | 54 ++++++++++--------------- test/multiproof/AggregateVerifier.t.sol | 4 +- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 3e5bf4b5..6b34e935 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -253,68 +253,56 @@ contract AggregateVerifier is Clone, IDisputeGame { // Proving Methods // //////////////////////////////////////////////////////////////// - /// @notice Verifies a TEE proof for the current game. - /// @param proofBytes The proof bytes. - function verifyTeeProof(bytes calldata proofBytes) external { - // The caller must be the TEE proposer. - if (msg.sender != TEE_PROPOSER) revert NotAuthorized(); + function verifyProof(bytes calldata proofBytes, ProofType proofType) external { + // The game must not be over. + if (gameOver()) revert GameOver(); + + if (proofType == ProofType.TEE) { + if (msg.sender != TEE_PROPOSER) revert NotAuthorized(); + _verifyTeeProof(proofBytes); + } else if (proofType == ProofType.ZK) { + _verifyZkProof(proofBytes); + } + else { + revert InvalidProofType(); + } + + provingData.lastProvedAt = Timestamp.wrap(uint64(block.timestamp)); - // Verify the proof. - _verifyTeeProof(proofBytes, msg.sender); + // Emit the proved event. + emit Proved(msg.sender, proofType); } /// @notice Verifies a TEE proof for the current game. /// @param proofBytes The proof bytes. - /// @param prover The address of the prover. - function _verifyTeeProof(bytes memory proofBytes, address prover) internal { + function _verifyTeeProof(bytes memory proofBytes) internal { // Only one TEE proof can be submitted. if (provingData.teeProver != address(0)) revert AlreadyProven(); // The game must be in progress. if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - // The game must not be over. - if (gameOver()) revert GameOver(); - // Validate the proof. if (!TEE_VERIFIER.verify(proofBytes, rootClaim(), l2SequenceNumber())) revert InvalidProof(); // Update proving data. - provingData.teeProver = prover; - provingData.lastProvedAt = Timestamp.wrap(uint64(block.timestamp)); - - // Emit the proved event. - emit Proved(prover, ProofType.TEE); - } - - /// @notice Verifies a ZK proof for the current game. - /// @param proofBytes The proof bytes. - function verifyZkProof(bytes calldata proofBytes) external { - _verifyZkProof(proofBytes, msg.sender); + provingData.teeProver = msg.sender; } /// @notice Verifies a ZK proof for the current game. /// @param proofBytes The proof bytes. - /// @param prover The address of the prover. - function _verifyZkProof(bytes memory proofBytes, address prover) internal { + function _verifyZkProof(bytes memory proofBytes) internal { // Only one ZK proof can be submitted. if (provingData.zkProver != address(0)) revert AlreadyProven(); // The game must be in progress or challenged (to allow nullification). if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); - // The game must not be over. - if (gameOver()) revert GameOver(); - // Validate the proof. if (!ZK_VERIFIER.verify(proofBytes, rootClaim(), l2SequenceNumber())) revert InvalidProof(); // Update proving data. - provingData.zkProver = prover; - provingData.lastProvedAt = Timestamp.wrap(uint64(block.timestamp)); - - // Emit the proved event. - emit Proved(prover, ProofType.ZK); + provingData.zkProver = msg.sender; } /// @notice Resolves the game after enough time has passed. diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index a0f86d9f..9cf372a7 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -113,9 +113,9 @@ contract AggregateVerifierTest is SetupTest { function _provideProof(AggregateVerifier game, address prover, bool isTeeProof, bytes memory proof) internal { vm.prank(prover); if (isTeeProof) { - game.verifyTeeProof(proof); + game.verifyProof(proof, AggregateVerifier.ProofType.TEE); } else { - game.verifyZkProof(proof); + game.verifyProof(proof, AggregateVerifier.ProofType.ZK); } } } From 2ea6dffc3496b935331e4f3bb54a1030c451caaa Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 9 Dec 2025 16:09:37 -0500 Subject: [PATCH 004/125] refactor tests and errors --- src/multiproof/AggregateVerifier.sol | 9 --- test/multiproof/AggregateVerifier.t.sol | 67 +++++++------------ .../{SetupTest.t.sol => BaseTest.t.sol} | 37 +++++++++- test/multiproof/Challenge.t.sol | 4 +- test/multiproof/Nullify.t.sol | 4 +- 5 files changed, 63 insertions(+), 58 deletions(-) rename test/multiproof/{SetupTest.t.sol => BaseTest.t.sol} (79%) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 6b34e935..8ea1cf40 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -9,15 +9,6 @@ import { Proposal, Timestamp } from "optimism/src/dispute/lib/Types.sol"; -import { - AlreadyInitialized, - ClaimAlreadyResolved, - NoCreditToClaim, - BondTransferFailed, - GamePaused, - GameNotResolved, - GameNotFinalized -} from "optimism/src/dispute/lib/Errors.sol"; import "./Errors.sol"; // Interfaces diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 9cf372a7..167f53df 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -1,25 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import "test/SetupTest.t.sol"; -import {GameStatus, Timestamp, AggregateVerifier} from "src/AggregateVerifier.sol"; -import { - MissingTEEProof, - MissingZKProof, - IncorrectRootClaim, - IncorrectBlockNumber, - IncorrectParentIndex, - NotAuthorized -} from "src/Errors.sol"; +import "test/BaseTest.t.sol"; -// Optimism -import { ClaimAlreadyResolved, GameAlreadyExists } from "optimism/src/dispute/lib/Errors.sol"; - -contract AggregateVerifierTest is SetupTest { - function setUp() public override { - super.setUp(); - anchorStateRegistry.setRespectedGameType(AGGREGATE_VERIFIER_GAME_TYPE); - } +contract AggregateVerifierTest is BaseTest { function testInitializeWithTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; @@ -89,33 +73,30 @@ contract AggregateVerifierTest is SetupTest { _provideProof(game, ZK_PROVER, true, proof); } - // Helper function to create a game via factory - function _createAggregateVerifierGame( - address creator, - Claim rootClaim, - uint256 l2BlockNumber, - uint32 parentIndex - ) internal returns (AggregateVerifier game) { - bytes memory extraData = abi.encodePacked( - uint256(l2BlockNumber), - uint32(parentIndex) - ); + function testUpdatingAnchorStateRegistryWithTEEProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = "tee-proof"; - vm.deal(creator, INIT_BOND); - vm.prank(creator); - return AggregateVerifier(address(factory.create{value: INIT_BOND}( - AGGREGATE_VERIFIER_GAME_TYPE, + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, rootClaim, - extraData - ))); - } + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game, TEE_PROVER, true, proof); + + // Resolve after 7 days + vm.warp(block.timestamp + 7 days + 1); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); - function _provideProof(AggregateVerifier game, address prover, bool isTeeProof, bytes memory proof) internal { - vm.prank(prover); - if (isTeeProof) { - game.verifyProof(proof, AggregateVerifier.ProofType.TEE); - } else { - game.verifyProof(proof, AggregateVerifier.ProofType.ZK); - } + // Update AnchorStateRegistry + vm.warp(block.timestamp + 1); + game.closeGame(); + (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), rootClaim.raw()); + assertEq(l2SequenceNumber, currentL2BlockNumber); } } diff --git a/test/multiproof/SetupTest.t.sol b/test/multiproof/BaseTest.t.sol similarity index 79% rename from test/multiproof/SetupTest.t.sol rename to test/multiproof/BaseTest.t.sol index 65901e07..73b1bfc3 100644 --- a/test/multiproof/SetupTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.0; import { Test, console2 } from "forge-std/Test.sol"; -import { AggregateVerifier, IAnchorStateRegistry } from "src/AggregateVerifier.sol"; +import "src/AggregateVerifier.sol"; +import "src/Errors.sol"; import {IVerifier} from "src/interfaces/IVerifier.sol"; @@ -19,7 +20,7 @@ import { ISystemConfig, IDisputeGameFactory, Hash, Proposal, AnchorStateRegistry import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -contract SetupTest is Test { +contract BaseTest is Test { // Constants GameType public constant AGGREGATE_VERIFIER_GAME_TYPE = GameType.wrap(621); uint256 public constant L2_CHAIN_ID = 8453; @@ -50,6 +51,8 @@ contract SetupTest is Test { // Deploy the implementations _deployAndSetAggregateVerifier(); + anchorStateRegistry.setRespectedGameType(AGGREGATE_VERIFIER_GAME_TYPE); + // Set the timestamp to after the retirement timestamp vm.warp(block.timestamp + 1); } @@ -112,4 +115,34 @@ contract SetupTest is Test { // 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 + ) internal returns (AggregateVerifier game) { + bytes memory extraData = abi.encodePacked( + uint256(l2BlockNumber), + uint32(parentIndex) + ); + + vm.deal(creator, INIT_BOND); + vm.prank(creator); + return AggregateVerifier(address(factory.create{value: INIT_BOND}( + AGGREGATE_VERIFIER_GAME_TYPE, + rootClaim, + extraData + ))); + } + + function _provideProof(AggregateVerifier game, address prover, bool isTeeProof, bytes memory proof) internal { + vm.prank(prover); + if (isTeeProof) { + game.verifyProof(proof, AggregateVerifier.ProofType.TEE); + } else { + game.verifyProof(proof, AggregateVerifier.ProofType.ZK); + } + } } \ No newline at end of file diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index d98a8872..a54d193d 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import "test/AggregateVerifier.t.sol"; +import "test/BaseTest.t.sol"; -contract ChallengeTest is AggregateVerifierTest { +contract ChallengeTest is BaseTest { function testChallengeTEEProofWithZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index eea58115..f89b745d 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.15; -import "test/AggregateVerifier.t.sol"; +import "test/BaseTest.t.sol"; -contract NullifyTest is AggregateVerifierTest { +contract NullifyTest is BaseTest { function testNullifyWithTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; From 70864f86af076b0ed3be995c8f1b15b7132ecb98 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 9 Dec 2025 16:31:10 -0500 Subject: [PATCH 005/125] Prevent proofs from extending resolution time --- src/multiproof/AggregateVerifier.sol | 31 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 8ea1cf40..2d1bd8b7 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -16,6 +16,8 @@ import { GameStatus, IDisputeGame, IDisputeGameFactory } from "optimism/src/disp import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; +import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; + contract AggregateVerifier is Clone, IDisputeGame { //////////////////////////////////////////////////////////////// // Enums // @@ -35,12 +37,12 @@ contract AggregateVerifier is Clone, IDisputeGame { /// @param counteredByGameAddress The address of the game that countered this game. /// @param teeProver The address that provided a TEE proof. /// @param zkProver The address that provided a ZK proof. - /// @param lastProvedAt The timestamp of the last provided proof. + /// @param expectedResolution The timestamp of the game's expected resolution. struct ProvingData { address counteredByGameAddress; address teeProver; address zkProver; - Timestamp lastProvedAt; + Timestamp expectedResolution; } //////////////////////////////////////////////////////////////// @@ -66,10 +68,10 @@ contract AggregateVerifier is Clone, IDisputeGame { // State Vars // //////////////////////////////////////////////////////////////// /// @notice The slow finalization delay. - uint256 public constant SLOW_FINALIZATION_DELAY = 7 days; + uint64 public constant SLOW_FINALIZATION_DELAY = 7 days; /// @notice The fast finalization delay. - uint256 public constant FAST_FINALIZATION_DELAY = 1 days; + uint64 public constant FAST_FINALIZATION_DELAY = 1 days; /// @notice The anchor state registry. IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; @@ -212,6 +214,9 @@ contract AggregateVerifier is Clone, IDisputeGame { // Set the game's starting timestamp createdAt = Timestamp.wrap(uint64(block.timestamp)); + // Game cannot resolve without a proof + provingData.expectedResolution = Timestamp.wrap(type(uint64).max); + wasRespectedGameTypeWhenCreated = GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); } @@ -258,7 +263,7 @@ contract AggregateVerifier is Clone, IDisputeGame { revert InvalidProofType(); } - provingData.lastProvedAt = Timestamp.wrap(uint64(block.timestamp)); + _updateExpectedResolution(); // Emit the proved event. emit Proved(msg.sender, proofType); @@ -476,13 +481,19 @@ contract AggregateVerifier is Clone, IDisputeGame { /// @notice Determines if the game is finished. function gameOver() public view returns (bool) { + return provingData.expectedResolution.raw() <= block.timestamp; + } + + function _updateExpectedResolution() internal { + uint64 newResolution = uint64(block.timestamp); if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { - return provingData.lastProvedAt.raw() + FAST_FINALIZATION_DELAY < uint64(block.timestamp); + newResolution += FAST_FINALIZATION_DELAY; } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { - return provingData.lastProvedAt.raw() + SLOW_FINALIZATION_DELAY < uint64(block.timestamp); - } - // No proof was provided so don't view the game as over. - return false; + newResolution += SLOW_FINALIZATION_DELAY; + } else { + revert NoProofProvided(); + } + provingData.expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); } /// @notice Getter for the game type. From 2ef75e4f4e2b01334d15e0ac3848bff5c9312509 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 9 Dec 2025 16:57:47 -0500 Subject: [PATCH 006/125] ZK proof allows game creator to immediately reclaim bond --- src/multiproof/AggregateVerifier.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 2d1bd8b7..955a2e81 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -299,6 +299,9 @@ contract AggregateVerifier is Clone, IDisputeGame { // Update proving data. provingData.zkProver = msg.sender; + + // Bond can be reclaimed after a ZK proof is provided. + bondRecipient = gameCreator(); } /// @notice Resolves the game after enough time has passed. From 7e70a7505b6db6c39e4318ae47aeb69cc232ba34 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 9 Dec 2025 16:57:53 -0500 Subject: [PATCH 007/125] Additional tests --- test/multiproof/AggregateVerifier.t.sol | 118 +++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 167f53df..74b8bc30 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -52,7 +52,7 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.gameType().raw(), AGGREGATE_VERIFIER_GAME_TYPE.raw()); assertEq(game.gameCreator(), ZK_PROVER); assertEq(game.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max)); - assertEq(game.bondRecipient(), address(0)); + assertEq(game.bondRecipient(), ZK_PROVER); assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); assertEq(address(game).balance, INIT_BOND); } @@ -87,11 +87,21 @@ contract AggregateVerifierTest is BaseTest { _provideProof(game, TEE_PROVER, true, proof); + // Cannot claim bond before resolving + vm.expectRevert(BondRecipientEmpty.selector); + game.claimCredit(); + // Resolve after 7 days - vm.warp(block.timestamp + 7 days + 1); + vm.warp(block.timestamp + 7 days); game.resolve(); assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + // Reclaim bond after resolving + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(address(game).balance, 0); + // Update AnchorStateRegistry vm.warp(block.timestamp + 1); game.closeGame(); @@ -99,4 +109,108 @@ contract AggregateVerifierTest is BaseTest { 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 = "zk-proof"; + + AggregateVerifier game = _createAggregateVerifierGame( + ZK_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game, ZK_PROVER, false, proof); + + // Reclaim bond + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(address(game).balance, 0); + + // Resolve after 7 days + vm.warp(block.timestamp + 7 days); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + + // Update AnchorStateRegistry + vm.warp(block.timestamp + 1); + game.closeGame(); + (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), rootClaim.raw()); + assertEq(l2SequenceNumber, currentL2BlockNumber); + } + + function testUpdatingAnchorStateRegistryWithBothProofs() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory teeProof = "tee-proof"; + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game, TEE_PROVER, true, teeProof); + _provideProof(game, ZK_PROVER, false, zkProof); + + // Reclaim bond + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(address(game).balance, 0); + + // Resolve after 1 day + vm.warp(block.timestamp + 1 days); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + + // Update AnchorStateRegistry + vm.warp(block.timestamp + 1); + game.closeGame(); + (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), rootClaim.raw()); + assertEq(l2SequenceNumber, currentL2BlockNumber); + } + + function testProofCannotIncreaseExpectedResolution() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory teeProof = "tee-proof"; + bytes memory zkProof = "zk-proof"; + + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(game, TEE_PROVER, true, teeProof); + + (, , , Timestamp originalExpectedResolution) = game.provingData(); + assertEq(originalExpectedResolution.raw(), block.timestamp + 7 days); + + vm.warp(block.timestamp + 7 days - 1); + // Cannot resolve yet + vm.expectRevert(GameNotOver.selector); + game.resolve(); + + // Provide ZK proof + _provideProof(game, ZK_PROVER, false, zkProof); + + // Proof should not have increased expected resolution + (, , , Timestamp expectedResolution) = game.provingData(); + assertEq(expectedResolution.raw(), originalExpectedResolution.raw()); + + // Resolve after 1 second + vm.warp(block.timestamp + 1); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + } } From ab2e4003f885f62d6128a5e55b97244c0a807f60 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Wed, 10 Dec 2025 11:00:50 -0500 Subject: [PATCH 008/125] Refactor test cases to standardize warp duration and improve naming conventions for clarity in Nullify tests. --- test/multiproof/Challenge.t.sol | 3 +-- test/multiproof/Nullify.t.sol | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index a54d193d..f3b32512 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -46,12 +46,11 @@ contract ChallengeTest is BaseTest { assertEq(counteredBy, address(game2)); // Retrieve bond after challenge - vm.warp(block.timestamp + 7 days + 1); + vm.warp(block.timestamp + 7 days); game2.resolve(); assertEq(uint8(game2.status()), uint8(GameStatus.DEFENDER_WINS)); assertEq(ZK_PROVER.balance, 0); assertEq(address(game1).balance, INIT_BOND); - vm.prank(ZK_PROVER); game1.claimCredit(); assertEq(ZK_PROVER.balance, INIT_BOND); assertEq(address(game1).balance, 0); diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index f89b745d..659640bc 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -70,7 +70,7 @@ contract NullifyTest is BaseTest { assertEq(game1.bondRecipient(), ZK_PROVER); } - function testNullifyFailsIfNoTEEProof() public { + function testTEENullifyFailsIfNoTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); @@ -100,7 +100,7 @@ contract NullifyTest is BaseTest { game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); } - function testNullifyFailsIfNoZKProof() public { + function testZKNullifyFailsIfNoZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); @@ -140,7 +140,7 @@ contract NullifyTest is BaseTest { _provideProof(game1, TEE_PROVER, true, teeProof1); // Resolve game1 - vm.warp(block.timestamp + 7 days + 1); + vm.warp(block.timestamp + 7 days); game1.resolve(); // Try to nullify game1 From aa2518df0807f4a81a0d9e6ccc4a49b1bbc94b5b Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Wed, 10 Dec 2025 17:06:07 -0500 Subject: [PATCH 009/125] Add validation to ensure parent game has a proof before creating a child game in AggregateVerifier. Implement corresponding test case to verify this behavior. --- src/multiproof/AggregateVerifier.sol | 3 ++ test/multiproof/AggregateVerifier.t.sol | 37 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 955a2e81..ac04990e 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -193,6 +193,9 @@ contract AggregateVerifier is Clone, IDisputeGame { // The parent game must be a valid game. if (parentGame.status() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); + // The parent game must have a proof. + if (AggregateVerifier(address(parentGame)).teeProver() == address(0) && AggregateVerifier(address(parentGame)).zkProver() == address(0)) revert InvalidParentGame(); + startingOutputRoot = Proposal({ l2SequenceNumber: parentGame.l2SequenceNumber(), root: Hash.wrap(parentGame.rootClaim().raw()) diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 74b8bc30..e9f0e5cc 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -213,4 +213,41 @@ contract AggregateVerifierTest is BaseTest { game.resolve(); assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); } + + function testParentGameMustHaveAProof() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes memory proof = "tee-proof"; + + AggregateVerifier parentGame = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + + uint256 parentGameIndex = factory.gameCount() - 1; + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaimChild = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + // Cannot create a child game without a proof for the parent + vm.expectRevert(InvalidParentGame.selector); + _createAggregateVerifierGame( + TEE_PROVER, + rootClaimChild, + currentL2BlockNumber, + uint32(parentGameIndex) + ); + + // Provide proof for the parent game + _provideProof(parentGame, TEE_PROVER, true, proof); + + // Create the child game + AggregateVerifier childGame = _createAggregateVerifierGame( + TEE_PROVER, + rootClaimChild, + currentL2BlockNumber, + uint32(parentGameIndex) + ); + } } From 1613646cf5eb39f39d113ad64722346ac4d0c02a Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 11 Dec 2025 11:52:06 -0500 Subject: [PATCH 010/125] Use calldata and remove unused variable --- src/multiproof/AggregateVerifier.sol | 4 ++-- test/multiproof/AggregateVerifier.t.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index ac04990e..acf0ca1d 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -274,7 +274,7 @@ contract AggregateVerifier is Clone, IDisputeGame { /// @notice Verifies a TEE proof for the current game. /// @param proofBytes The proof bytes. - function _verifyTeeProof(bytes memory proofBytes) internal { + function _verifyTeeProof(bytes calldata proofBytes) internal { // Only one TEE proof can be submitted. if (provingData.teeProver != address(0)) revert AlreadyProven(); @@ -290,7 +290,7 @@ contract AggregateVerifier is Clone, IDisputeGame { /// @notice Verifies a ZK proof for the current game. /// @param proofBytes The proof bytes. - function _verifyZkProof(bytes memory proofBytes) internal { + function _verifyZkProof(bytes calldata proofBytes) internal { // Only one ZK proof can be submitted. if (provingData.zkProver != address(0)) revert AlreadyProven(); diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index e9f0e5cc..7a92626e 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -243,7 +243,7 @@ contract AggregateVerifierTest is BaseTest { _provideProof(parentGame, TEE_PROVER, true, proof); // Create the child game - AggregateVerifier childGame = _createAggregateVerifierGame( + _createAggregateVerifierGame( TEE_PROVER, rootClaimChild, currentL2BlockNumber, From 5ba6ed74207a18daa77dca3daaf2aa836ee4cc12 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 11 Dec 2025 11:57:55 -0500 Subject: [PATCH 011/125] Make internal verify functions consistent --- src/multiproof/AggregateVerifier.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index acf0ca1d..2b9967d9 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -261,6 +261,9 @@ contract AggregateVerifier is Clone, IDisputeGame { _verifyTeeProof(proofBytes); } else if (proofType == ProofType.ZK) { _verifyZkProof(proofBytes); + + // Bond can be reclaimed after a ZK proof is provided. + bondRecipient = gameCreator(); } else { revert InvalidProofType(); @@ -302,9 +305,6 @@ contract AggregateVerifier is Clone, IDisputeGame { // Update proving data. provingData.zkProver = msg.sender; - - // Bond can be reclaimed after a ZK proof is provided. - bondRecipient = gameCreator(); } /// @notice Resolves the game after enough time has passed. From ab2d01bb14784dde9d43301a0f89cf1ebbb6bbdb Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 11 Dec 2025 15:45:28 -0500 Subject: [PATCH 012/125] fix: can claim bond after nullifying a challenge --- src/multiproof/AggregateVerifier.sol | 2 ++ test/multiproof/Nullify.t.sol | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 2b9967d9..2f632aeb 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -421,6 +421,8 @@ contract AggregateVerifier is Clone, IDisputeGame { status = GameStatus.CHALLENGER_WINS; // Refund the bond. This can override a challenge. bondRecipient = gameCreator(); + // To allow bond to be claimed in case challenging game is nullified + delete provingData.counteredByGameAddress; emit Nullified(msg.sender, game); } diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 659640bc..5e30bc3b 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -36,6 +36,11 @@ contract NullifyTest is BaseTest { assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(game1.bondRecipient(), TEE_PROVER); + + uint256 balanceBefore = game1.gameCreator().balance; + game1.claimCredit(); + assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(address(game1).balance, 0); } function testNullifyWithZKProof() public { @@ -68,6 +73,11 @@ contract NullifyTest is BaseTest { assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(game1.bondRecipient(), ZK_PROVER); + + uint256 balanceBefore = game1.gameCreator().balance; + game1.claimCredit(); + assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(address(game1).balance, 0); } function testTEENullifyFailsIfNoTEEProof() public { @@ -194,5 +204,10 @@ contract NullifyTest is BaseTest { game1.nullify(challengeIndex, AggregateVerifier.ProofType.ZK); assertEq(game1.bondRecipient(), TEE_PROVER); + + uint256 balanceBefore = game1.gameCreator().balance; + game1.claimCredit(); + assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(address(game1).balance, 0); } } \ No newline at end of file From 8e38be6716e8dc2b60317418146d3d07c6eef47d Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 6 Jan 2026 15:05:29 -0500 Subject: [PATCH 013/125] prevent challenges if parent or game itself is invalid --- src/multiproof/AggregateVerifier.sol | 7 ++++ test/multiproof/Challenge.t.sol | 62 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 2f632aeb..e6fabcaa 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -341,6 +341,13 @@ contract AggregateVerifier is Clone, IDisputeGame { function challenge(uint256 gameIndex) external { // Can only challenge a game that has not been challenged or resolved yet. if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); + + // This game cannot be blacklisted or retired. + if (ANCHOR_STATE_REGISTRY.isGameBlacklisted(IDisputeGame(address(this))) || ANCHOR_STATE_REGISTRY.isGameRetired(IDisputeGame(address(this)))) revert InvalidGame(); + + // The parent game cannot have been challenged + if (getParentGameStatus() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); + // The TEE prover must not be empty. You should nullify the game if you want to challenge. if (provingData.teeProver == address(0)) revert MissingTEEProof(); if (provingData.zkProver != address(0)) revert AlreadyProven(); diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index f3b32512..c94ff22e 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -216,4 +216,66 @@ contract ChallengeTest is BaseTest { vm.expectRevert(ClaimAlreadyResolved.selector); game1.challenge(challengeIndex1); } + + function testChallengeFailsIfParentGameStatusIsChallenged() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + // create parent game + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory parentProof = "parent-proof"; + + AggregateVerifier parentGame = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim1, + currentL2BlockNumber, + type(uint32).max + ); + + _provideProof(parentGame, TEE_PROVER, true, parentProof); + + uint256 parentGameIndex = factory.gameCount() - 1; + currentL2BlockNumber += BLOCK_INTERVAL; + + // create child game + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + bytes memory childProof = "child-proof"; + + AggregateVerifier childGame = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + uint32(parentGameIndex) + ); + + _provideProof(childGame, TEE_PROVER, true, childProof); + + // blacklist parent game + anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); + + // challenge child game + uint256 childGameIndex = factory.gameCount() - 1; + vm.expectRevert(InvalidParentGame.selector); + childGame.challenge(childGameIndex); + } + + function testChallengeFailsIfGameItselfIsBlacklisted() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, + rootClaim, + currentL2BlockNumber, + type(uint32).max + ); + + // blacklist game + anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); + + // challenge game + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(InvalidGame.selector); + game.challenge(gameIndex); + } } \ No newline at end of file From 3a2b31cf6efb5bc3f201ddda7f22a33a25f1b2db Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Mon, 12 Jan 2026 16:35:48 -0500 Subject: [PATCH 014/125] Enhance AggregateVerifier to include hashes for TEE and ZK images, and rollup configuration. Update verification methods to use a journal hash instead of root claims. Modify MockVerifier and tests accordingly. --- interfaces/mutliproof/IVerifier.sol | 2 +- src/multiproof/AggregateVerifier.sol | 44 ++++++++++++++++++++++++++-- src/multiproof/mock/MockVerifier.sol | 2 +- test/multiproof/BaseTest.t.sol | 7 +++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/interfaces/mutliproof/IVerifier.sol b/interfaces/mutliproof/IVerifier.sol index 4933c7d2..8fe0d487 100644 --- a/interfaces/mutliproof/IVerifier.sol +++ b/interfaces/mutliproof/IVerifier.sol @@ -4,5 +4,5 @@ pragma solidity 0.8.15; import { Claim } from "optimism/src/dispute/lib/Types.sol"; interface IVerifier { - function verify(bytes calldata proofBytes, Claim rootClaim, uint256 l2SequenceNumber) external view returns (bool); + function verify(bytes calldata proofBytes, bytes32 journal) external view returns (bool); } \ No newline at end of file diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index e6fabcaa..2825cfcd 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -82,9 +82,18 @@ contract AggregateVerifier is Clone, IDisputeGame { /// @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 address that can submit a TEE proof. address public immutable TEE_PROPOSER; @@ -126,6 +135,9 @@ contract AggregateVerifier is Clone, IDisputeGame { /// @param _anchorStateRegistry The anchor state registry. /// @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 _teeProposer The address that can submit a TEE proof. /// @param _l2ChainId The chain ID of the L2 network. /// @param _blockInterval The block interval. @@ -134,6 +146,9 @@ contract AggregateVerifier is Clone, IDisputeGame { IAnchorStateRegistry _anchorStateRegistry, IVerifier _teeVerifier, IVerifier _zkVerifier, + bytes32 _teeImageHash, + bytes32 _zkImageHash, + bytes32 _configHash, address _teeProposer, uint256 _l2ChainId, uint256 _blockInterval @@ -144,6 +159,9 @@ contract AggregateVerifier is Clone, IDisputeGame { DISPUTE_GAME_FACTORY = ANCHOR_STATE_REGISTRY.disputeGameFactory(); TEE_VERIFIER = _teeVerifier; ZK_VERIFIER = _zkVerifier; + TEE_IMAGE_HASH = _teeImageHash; + ZK_IMAGE_HASH = _zkImageHash; + CONFIG_HASH = _configHash; TEE_PROPOSER = _teeProposer; L2_CHAIN_ID = _l2ChainId; BLOCK_INTERVAL = _blockInterval; @@ -284,8 +302,19 @@ contract AggregateVerifier is Clone, IDisputeGame { // The game must be in progress. if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + bytes32 journal = keccak256(abi.encodePacked( + msg.sender, + l1Head(), + startingOutputRoot.root, + startingOutputRoot.l2SequenceNumber, + rootClaim(), + l2SequenceNumber(), + CONFIG_HASH, + TEE_IMAGE_HASH + )); + // Validate the proof. - if (!TEE_VERIFIER.verify(proofBytes, rootClaim(), l2SequenceNumber())) revert InvalidProof(); + if (!TEE_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); // Update proving data. provingData.teeProver = msg.sender; @@ -300,8 +329,19 @@ contract AggregateVerifier is Clone, IDisputeGame { // The game must be in progress or challenged (to allow nullification). if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); + bytes32 journal = keccak256(abi.encodePacked( + msg.sender, + l1Head(), + startingOutputRoot.root, + startingOutputRoot.l2SequenceNumber, + rootClaim(), + l2SequenceNumber(), + CONFIG_HASH, + ZK_IMAGE_HASH + )); + // Validate the proof. - if (!ZK_VERIFIER.verify(proofBytes, rootClaim(), l2SequenceNumber())) revert InvalidProof(); + if (!ZK_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); // Update proving data. provingData.zkProver = msg.sender; diff --git a/src/multiproof/mock/MockVerifier.sol b/src/multiproof/mock/MockVerifier.sol index a1102b56..8bc82302 100644 --- a/src/multiproof/mock/MockVerifier.sol +++ b/src/multiproof/mock/MockVerifier.sol @@ -5,7 +5,7 @@ import {IVerifier} from "../interfaces/IVerifier.sol"; import {Claim} from "optimism/src/dispute/lib/Types.sol"; contract MockVerifier is IVerifier { - function verify(bytes calldata , Claim , uint256 ) external pure returns (bool) { + function verify(bytes calldata , bytes32 ) external pure returns (bool) { return true; } } \ No newline at end of file diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 73b1bfc3..7877dd51 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -35,6 +35,10 @@ contract BaseTest is Test { 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; @@ -104,6 +108,9 @@ contract BaseTest is Test { IAnchorStateRegistry(address(anchorStateRegistry)), IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), + TEE_IMAGE_HASH, + ZK_IMAGE_HASH, + CONFIG_HASH, TEE_PROVER, L2_CHAIN_ID, BLOCK_INTERVAL From 0a053f121589c984ae3f419fc78a96a1754d6221 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 09:51:35 -0500 Subject: [PATCH 015/125] Add ReentrancyGuard to AggregateVerifier and protect claimCredit function --- src/multiproof/AggregateVerifier.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 2825cfcd..a9609825 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -17,8 +17,9 @@ import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegi import {IVerifier} from "./interfaces/IVerifier.sol"; import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; +import { ReentrancyGuard } from "solady/utils/ReentrancyGuard.sol"; -contract AggregateVerifier is Clone, IDisputeGame { +contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { //////////////////////////////////////////////////////////////// // Enums // //////////////////////////////////////////////////////////////// @@ -476,7 +477,7 @@ contract AggregateVerifier is Clone, IDisputeGame { /// @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 { + function claimCredit() nonReentrant external { // The bond recipient must not be empty. if (bondRecipient == address(0)) revert BondRecipientEmpty(); From 308f5919bb59daaf9ad850318abbf253365d05f1 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 09:52:44 -0500 Subject: [PATCH 016/125] Add constant for initialize calldata size in AggregateVerifier and update validation check to use this constant --- src/multiproof/AggregateVerifier.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index a9609825..25896902 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -74,6 +74,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice The fast finalization delay. uint64 public constant FAST_FINALIZATION_DELAY = 1 days; + /// @notice The size of the initialize call data. + uint256 private constant INITIALIZE_CALLDATA_SIZE = 0x7E; + /// @notice The anchor state registry. IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; @@ -189,7 +192,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // - 0x04 extraData (parentIndex) // - 0x02 CWIA bytes assembly { - if iszero(eq(calldatasize(), 0x7E)) { + if iszero(eq(calldatasize(), INITIALIZE_CALLDATA_SIZE)) { // Store the selector for `BadExtraData()` & revert mstore(0x00, 0x9824bdab) revert(0x1C, 0x04) From 9585f67590750e7418feeb04c094824ac13ad0da Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 09:56:15 -0500 Subject: [PATCH 017/125] Add test to validate failure on invalid calldata size in AggregateVerifier --- test/multiproof/AggregateVerifier.t.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 7a92626e..79d61d3f 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -57,6 +57,18 @@ contract AggregateVerifierTest is BaseTest { assertEq(address(game).balance, INIT_BOND); } + function testInitializeFailsIfInvalidCallDataSize() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + + vm.deal(TEE_PROVER, INIT_BOND); + bytes memory extraData = ""; + + vm.prank(TEE_PROVER); + vm.expectRevert(BadExtraData.selector); + factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData); + } + function testInitializeFailsIfNotTEEProposer() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); From 45c47f6933ea25fc61e14b32fb45c7567c26a1e7 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:22:29 -0500 Subject: [PATCH 018/125] Refactor l2BlockNumber function to l2SequenceNumber in AggregateVerifier, updating documentation and logic to clarify its purpose as the L2 sequence number. --- src/multiproof/AggregateVerifier.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 25896902..1cd751be 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -246,13 +246,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); } - /// @notice The L2 block number for which this game is proposing an output root. - function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) { - l2BlockNumber_ = _getArgUint256(0x54); - } - + /// @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 l2SequenceNumber_) { - l2SequenceNumber_ = l2BlockNumber(); + l2SequenceNumber_ = _getArgUint256(0x54); } /// @notice The parent index of the game. From 5cfe30befae19b2e8fd5d622e19451d845a8102f Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:29:24 -0500 Subject: [PATCH 019/125] Refactor visibility of state variables in AggregateVerifier from internal to public, enhancing accessibility for contract interactions. --- src/multiproof/AggregateVerifier.sol | 32 ++++------------------------ 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 1cd751be..f48a1f3e 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -78,7 +78,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { uint256 private constant INITIALIZE_CALLDATA_SIZE = 0x7E; /// @notice The anchor state registry. - IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; + IAnchorStateRegistry public immutable ANCHOR_STATE_REGISTRY; /// @notice The dispute game factory. IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY; @@ -102,14 +102,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { address public immutable TEE_PROPOSER; /// @notice The game type ID. - GameType internal immutable GAME_TYPE; + GameType public immutable GAME_TYPE; /// @notice The chain ID of the L2 network this contract argues about. - uint256 internal immutable L2_CHAIN_ID; + 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 internal immutable BLOCK_INTERVAL; + uint256 public immutable BLOCK_INTERVAL; /// @notice The starting timestamp of the game. Timestamp public createdAt; @@ -551,14 +551,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { provingData.expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); } - /// @notice Getter for the game type. - /// @dev The reference impl should be entirely different depending on the type (fault, validity) - /// i.e. The game type should indicate the security model. - /// @return gameType_ The type of proof system being used. - function gameType() public view returns (GameType gameType_) { - gameType_ = GAME_TYPE; - } - /// @notice Getter for the creator of the dispute game. /// @dev `clones-with-immutable-args` argument #1 /// @return creator_ The creator of the dispute game. @@ -611,20 +603,4 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { function zkProver() external view returns (address zkProver_) { zkProver_ = provingData.zkProver; } - - //////////////////////////////////////////////////////////////// - // IMMUTABLE GETTERS // - //////////////////////////////////////////////////////////////// - - function l2ChainId() external view returns (uint256 l2ChainId_) { - l2ChainId_ = L2_CHAIN_ID; - } - - function blockInterval() external view returns (uint256 blockInterval_) { - blockInterval_ = BLOCK_INTERVAL; - } - - function anchorStateRegistry() external view returns (IAnchorStateRegistry anchorStateRegistry_) { - anchorStateRegistry_ = ANCHOR_STATE_REGISTRY; - } } From b2dd1ff8ca26dcb20b09500175495bb78657bfd4 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:30:37 -0500 Subject: [PATCH 020/125] Moved wasRespectedGameTypeWhenCreated for storage optimization --- src/multiproof/AggregateVerifier.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index f48a1f3e..384cbda7 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -123,6 +123,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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 claim made by the proposer. ProvingData public provingData; @@ -130,8 +133,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @dev This should match the claim root of the parent game. Proposal public startingOutputRoot; - /// @notice A boolean for whether or not the game type was respected when the game was created. - bool public wasRespectedGameTypeWhenCreated; address public bondRecipient; From f0d3f54fc3c0fba0fecdff5d3fba1f001cc33782 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:33:54 -0500 Subject: [PATCH 021/125] Remove underscores from constructor variables --- src/multiproof/AggregateVerifier.sol | 60 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 384cbda7..464f7d5c 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -136,40 +136,40 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { address public bondRecipient; - /// @param _gameType The game type. - /// @param _anchorStateRegistry The anchor state registry. - /// @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 _teeProposer The address that can submit a TEE proof. - /// @param _l2ChainId The chain ID of the L2 network. - /// @param _blockInterval The block interval. + /// @param gameType The game type. + /// @param anchorStateRegistry The anchor state registry. + /// @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 teeProposer The address that can submit a TEE proof. + /// @param l2ChainId The chain ID of the L2 network. + /// @param blockInterval The block interval. constructor( - GameType _gameType, - IAnchorStateRegistry _anchorStateRegistry, - IVerifier _teeVerifier, - IVerifier _zkVerifier, - bytes32 _teeImageHash, - bytes32 _zkImageHash, - bytes32 _configHash, - address _teeProposer, - uint256 _l2ChainId, - uint256 _blockInterval + GameType gameType, + IAnchorStateRegistry anchorStateRegistry, + IVerifier teeVerifier, + IVerifier zkVerifier, + bytes32 teeImageHash, + bytes32 zkImageHash, + bytes32 configHash, + address teeProposer, + uint256 l2ChainId, + uint256 blockInterval ) { // Set up initial game state. - GAME_TYPE = _gameType; - ANCHOR_STATE_REGISTRY = _anchorStateRegistry; + GAME_TYPE = gameType; + ANCHOR_STATE_REGISTRY = anchorStateRegistry; DISPUTE_GAME_FACTORY = ANCHOR_STATE_REGISTRY.disputeGameFactory(); - TEE_VERIFIER = _teeVerifier; - ZK_VERIFIER = _zkVerifier; - TEE_IMAGE_HASH = _teeImageHash; - ZK_IMAGE_HASH = _zkImageHash; - CONFIG_HASH = _configHash; - TEE_PROPOSER = _teeProposer; - L2_CHAIN_ID = _l2ChainId; - BLOCK_INTERVAL = _blockInterval; + TEE_VERIFIER = teeVerifier; + ZK_VERIFIER = zkVerifier; + TEE_IMAGE_HASH = teeImageHash; + ZK_IMAGE_HASH = zkImageHash; + CONFIG_HASH = configHash; + TEE_PROPOSER = teeProposer; + L2_CHAIN_ID = l2ChainId; + BLOCK_INTERVAL = blockInterval; } /// @notice Initializes the contract. From 5fcecd5b0129ec14b470929ee0e98c7b341a8b87 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:36:08 -0500 Subject: [PATCH 022/125] IDIsputeGame compliance --- src/multiproof/AggregateVerifier.sol | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 464f7d5c..a742679c 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -75,7 +75,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { uint64 public constant FAST_FINALIZATION_DELAY = 1 days; /// @notice The size of the initialize call data. - uint256 private constant INITIALIZE_CALLDATA_SIZE = 0x7E; + uint256 internal constant INITIALIZE_CALLDATA_SIZE = 0x7E; /// @notice The anchor state registry. IAnchorStateRegistry public immutable ANCHOR_STATE_REGISTRY; @@ -102,7 +102,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { address public immutable TEE_PROPOSER; /// @notice The game type ID. - GameType public immutable GAME_TYPE; + GameType internal immutable GAME_TYPE; /// @notice The chain ID of the L2 network this contract argues about. uint256 public immutable L2_CHAIN_ID; @@ -592,7 +592,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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 gameType_, Claim rootClaim_, bytes memory extraData_) { - gameType_ = gameType(); + gameType_ = GAME_TYPE; rootClaim_ = rootClaim(); extraData_ = extraData(); } @@ -604,4 +604,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { function zkProver() external view returns (address zkProver_) { zkProver_ = provingData.zkProver; } + + /// @notice Getter for the game type. + /// @dev For compliance with the IDisputeGame interface. + /// @return gameType_ The type of proof system being used. + function gameType() external view returns (GameType gameType_) { + gameType_ = GAME_TYPE; + } } From ba44e138381ec663d70e46435281810a67fdfaa4 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:40:28 -0500 Subject: [PATCH 023/125] Remove implicit returns --- src/multiproof/AggregateVerifier.sol | 52 +++++++++++++--------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index a742679c..0e5e1c4b 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -248,23 +248,23 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } /// @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 l2SequenceNumber_) { - l2SequenceNumber_ = _getArgUint256(0x54); + function l2SequenceNumber() public pure returns (uint256) { + return _getArgUint256(0x54); } /// @notice The parent index of the game. - function parentIndex() public pure returns (uint32 parentIndex_) { - parentIndex_ = _getArgUint32(0x74); + function parentIndex() public pure returns (uint32) { + return _getArgUint32(0x74); } /// @notice The starting block number of the game. - function startingBlockNumber() external view returns (uint256 startingBlockNumber_) { - startingBlockNumber_ = startingOutputRoot.l2SequenceNumber; + function startingBlockNumber() external view returns (uint256) { + return startingOutputRoot.l2SequenceNumber; } /// @notice The starting output root of the game. - function startingRootHash() external view returns (Hash startingRootHash_) { - startingRootHash_ = startingOutputRoot.root; + function startingRootHash() external view returns (Hash) { + return startingOutputRoot.root; } //////////////////////////////////////////////////////////////// @@ -521,7 +521,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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() private view returns (GameStatus) { + 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)) { @@ -555,33 +555,33 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice Getter for the creator of the dispute game. /// @dev `clones-with-immutable-args` argument #1 /// @return creator_ The creator of the dispute game. - function gameCreator() public pure returns (address creator_) { - creator_ = _getArgAddress(0x00); + function gameCreator() public pure returns (address) { + return _getArgAddress(0x00); } /// @notice Getter for the root claim. /// @dev `clones-with-immutable-args` argument #2 /// @return rootClaim_ The root claim of the DisputeGame. - function rootClaim() public pure returns (Claim rootClaim_) { - rootClaim_ = Claim.wrap(_getArgBytes32(0x14)); + 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. /// @dev `clones-with-immutable-args` argument #3 /// @return l1Head_ The parent hash of the L1 block when the dispute game was created. - function l1Head() public pure returns (Hash l1Head_) { - l1Head_ = Hash.wrap(_getArgBytes32(0x34)); + function l1Head() public pure returns (Hash) { + return Hash.wrap(_getArgBytes32(0x34)); } /// @notice Getter for the extra data. /// @dev `clones-with-immutable-args` argument #4 /// @return extraData_ Any extra data supplied to the dispute game contract by the creator. - function extraData() public pure returns (bytes memory extraData_) { + function extraData() public pure returns (bytes memory) { // The extra data starts at the second word within the cwia calldata and // is 36 bytes long. // 32 bytes are for the l2BlockNumber // 4 bytes are for the parentIndex - extraData_ = _getArgBytes(0x54, 0x24); + return _getArgBytes(0x54, 0x24); } /// @notice A compliant implementation of this interface should return the components of the @@ -591,24 +591,22 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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 gameType_, Claim rootClaim_, bytes memory extraData_) { - gameType_ = GAME_TYPE; - rootClaim_ = rootClaim(); - extraData_ = extraData(); + function gameData() external view returns (GameType, Claim, bytes memory) { + return (GAME_TYPE, rootClaim(), extraData()); } - function teeProver() external view returns (address teeProver_) { - teeProver_ = provingData.teeProver; + function teeProver() external view returns (address) { + return provingData.teeProver; } - function zkProver() external view returns (address zkProver_) { - zkProver_ = provingData.zkProver; + function zkProver() external view returns (address) { + return provingData.zkProver; } /// @notice Getter for the game type. /// @dev For compliance with the IDisputeGame interface. /// @return gameType_ The type of proof system being used. - function gameType() external view returns (GameType gameType_) { - gameType_ = GAME_TYPE; + function gameType() external view returns (GameType) { + return GAME_TYPE; } } From 54c4192e8a7157bf266d8454301e9d0abb143aca Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:46:36 -0500 Subject: [PATCH 024/125] Add CreditClaimed event and update claimCredit logic in AggregateVerifier to emit event upon credit transfer --- src/multiproof/AggregateVerifier.sol | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 0e5e1c4b..87419c06 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -65,6 +65,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @param game The game used to nullify this proposal. event Nullified(address indexed nullifier, IDisputeGame game); + /// @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); + //////////////////////////////////////////////////////////////// // State Vars // //////////////////////////////////////////////////////////////// @@ -486,12 +491,17 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (IDisputeGame(provingData.counteredByGameAddress).status() != GameStatus.DEFENDER_WINS) revert InvalidCounteredByGame(); } + uint256 balanceToClaim = address(this).balance; + // The game must have credit to claim. - if (address(this).balance == 0) revert NoCreditToClaim(); + if (balanceToClaim == 0) revert NoCreditToClaim(); // Transfer the credit to the bond recipient. - (bool success,) = bondRecipient.call{value: address(this).balance}(hex""); + (bool success,) = bondRecipient.call{value: balanceToClaim}(hex""); if (!success) revert BondTransferFailed(); + + // Emit the credit claimed event. + emit CreditClaimed(bondRecipient, balanceToClaim); } function closeGame() public { From 49e2eeac688d3e3c52946ea15c3ccecf81282687 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:51:04 -0500 Subject: [PATCH 025/125] Rename getParentGameStatus to _getParentGameStatus in AggregateVerifier for consistency with internal function naming conventions. --- src/multiproof/AggregateVerifier.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 87419c06..97f8fecd 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -531,7 +531,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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) { + 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)) { From 2b3238d36ad68d430d0ddd940c8b26066786d8d6 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 10:52:00 -0500 Subject: [PATCH 026/125] Refactor return logic in getParentGameStatus to improve clarity by consolidating the return statement for the first dispute game scenario. --- src/multiproof/AggregateVerifier.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 97f8fecd..7518c0cd 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -538,11 +538,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { return GameStatus.CHALLENGER_WINS; } return parentGame.status(); - } else { - // 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; } + // 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 Determines if the game is finished. From 8d47e4871c302afae4e9034bd7ec9bc7f499fbc1 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 11:20:20 -0500 Subject: [PATCH 027/125] solidity style guide --- src/multiproof/AggregateVerifier.sol | 328 +++++++++++++-------------- 1 file changed, 164 insertions(+), 164 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 7518c0cd..ec95b014 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -19,6 +19,7 @@ import {IVerifier} from "./interfaces/IVerifier.sol"; import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; import { ReentrancyGuard } from "solady/utils/ReentrancyGuard.sol"; + contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { //////////////////////////////////////////////////////////////// // Enums // @@ -47,31 +48,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } //////////////////////////////////////////////////////////////// - // Events // - //////////////////////////////////////////////////////////////// - - /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. - /// @param challenger The address of the challenger. - /// @param game The game used to challenge this proposal. - event Challenged(address indexed challenger, IDisputeGame game); - - /// @notice Emitted when the game is proved. - /// @param prover The address of the prover. - /// @param proofType The type of proof. - event Proved(address indexed prover, ProofType indexed proofType); - - /// @notice Emitted when the game is nullified. - /// @param nullifier The address of the nullifier. - /// @param game The game used to nullify this proposal. - event Nullified(address indexed nullifier, IDisputeGame game); - - /// @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); - - //////////////////////////////////////////////////////////////// - // State Vars // + // Constants // //////////////////////////////////////////////////////////////// /// @notice The slow finalization delay. uint64 public constant SLOW_FINALIZATION_DELAY = 7 days; @@ -82,6 +59,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice The size of the initialize call data. uint256 internal constant INITIALIZE_CALLDATA_SIZE = 0x7E; + //////////////////////////////////////////////////////////////// + // Immutables // + //////////////////////////////////////////////////////////////// /// @notice The anchor state registry. IAnchorStateRegistry public immutable ANCHOR_STATE_REGISTRY; @@ -106,9 +86,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice The address that can submit a TEE proof. address public immutable TEE_PROPOSER; - /// @notice The game type ID. - GameType internal immutable GAME_TYPE; - /// @notice The chain ID of the L2 network this contract argues about. uint256 public immutable L2_CHAIN_ID; @@ -116,6 +93,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @dev The parent's block number + BLOCK_INTERVAL = this proposal's block number. uint256 public immutable BLOCK_INTERVAL; + /// @notice The game type ID. + GameType internal immutable GAME_TYPE; + + //////////////////////////////////////////////////////////////// + // State Vars // + //////////////////////////////////////////////////////////////// /// @notice The starting timestamp of the game. Timestamp public createdAt; @@ -138,9 +121,33 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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; + //////////////////////////////////////////////////////////////// + // Events // + //////////////////////////////////////////////////////////////// + + /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. + /// @param challenger The address of the challenger. + /// @param game The game used to challenge this proposal. + event Challenged(address indexed challenger, IDisputeGame game); + + /// @notice Emitted when the game is proved. + /// @param prover The address of the prover. + /// @param proofType The type of proof. + event Proved(address indexed prover, ProofType indexed proofType); + + /// @notice Emitted when the game is nullified. + /// @param nullifier The address of the nullifier. + /// @param game The game used to nullify this proposal. + event Nullified(address indexed nullifier, IDisputeGame game); + + /// @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); + /// @param gameType The game type. /// @param anchorStateRegistry The anchor state registry. /// @param teeVerifier The TEE verifier. @@ -252,30 +259,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); } - /// @notice The L2 sequence number for which this game is proposing an output root (in this case - the block number). - function l2SequenceNumber() public pure returns (uint256) { - return _getArgUint256(0x54); - } - - /// @notice The parent index of the game. - function parentIndex() public pure returns (uint32) { - return _getArgUint32(0x74); - } - - /// @notice 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; - } - - //////////////////////////////////////////////////////////////// - // Proving Methods // - //////////////////////////////////////////////////////////////// - + /// @notice Verifies a proof for the current game. + /// @param proofBytes The proof. + /// @param proofType The type of proof. function verifyProof(bytes calldata proofBytes, ProofType proofType) external { // The game must not be over. if (gameOver()) revert GameOver(); @@ -299,66 +285,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { emit Proved(msg.sender, proofType); } - /// @notice Verifies a TEE proof for the current game. - /// @param proofBytes The proof bytes. - function _verifyTeeProof(bytes calldata proofBytes) internal { - // Only one TEE proof can be submitted. - if (provingData.teeProver != address(0)) revert AlreadyProven(); - - // The game must be in progress. - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - bytes32 journal = keccak256(abi.encodePacked( - msg.sender, - l1Head(), - startingOutputRoot.root, - startingOutputRoot.l2SequenceNumber, - rootClaim(), - l2SequenceNumber(), - CONFIG_HASH, - TEE_IMAGE_HASH - )); - - // Validate the proof. - if (!TEE_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); - - // Update proving data. - provingData.teeProver = msg.sender; - } - - /// @notice Verifies a ZK proof for the current game. - /// @param proofBytes The proof bytes. - function _verifyZkProof(bytes calldata proofBytes) internal { - // Only one ZK proof can be submitted. - if (provingData.zkProver != address(0)) revert AlreadyProven(); - - // The game must be in progress or challenged (to allow nullification). - if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); - - bytes32 journal = keccak256(abi.encodePacked( - msg.sender, - l1Head(), - startingOutputRoot.root, - startingOutputRoot.l2SequenceNumber, - rootClaim(), - l2SequenceNumber(), - CONFIG_HASH, - ZK_IMAGE_HASH - )); - - // Validate the proof. - if (!ZK_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); - - // Update proving data. - provingData.zkProver = msg.sender; - } - - /// @notice Resolves the game after enough time has passed. + /// @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(); + GameStatus parentGameStatus = _getParentGameStatus(); // The parent game must have resolved. if (parentGameStatus == GameStatus.IN_PROGRESS) revert ParentGameNotResolved(); @@ -392,7 +324,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (ANCHOR_STATE_REGISTRY.isGameBlacklisted(IDisputeGame(address(this))) || ANCHOR_STATE_REGISTRY.isGameRetired(IDisputeGame(address(this)))) revert InvalidGame(); // The parent game cannot have been challenged - if (getParentGameStatus() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); + if (_getParentGameStatus() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); // The TEE prover must not be empty. You should nullify the game if you want to challenge. if (provingData.teeProver == address(0)) revert MissingTEEProof(); @@ -482,7 +414,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice Claim the credit belonging to the bond recipient. Reverts if the game isn't /// finalized or if the bond transfer fails. - function claimCredit() nonReentrant external { + function claimCredit() external nonReentrant { // The bond recipient must not be empty. if (bondRecipient == address(0)) revert BondRecipientEmpty(); @@ -504,8 +436,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { emit CreditClaimed(bondRecipient, balanceToClaim); } - function closeGame() public { - + /// @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(); @@ -529,19 +461,41 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { try ANCHOR_STATE_REGISTRY.setAnchorState(IDisputeGame(address(this))) { } catch { } } - /// @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 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 provingData.teeProver; + } + + /// @notice Address that provided a ZK proof. + function zkProver() external view returns (address) { + return provingData.zkProver; + } + + /// @notice Getter for the game type. + /// @dev For compliance with the IDisputeGame interface. + function gameType() external view returns (GameType) { + return GAME_TYPE; } /// @notice Determines if the game is finished. @@ -549,42 +503,22 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { return provingData.expectedResolution.raw() <= block.timestamp; } - function _updateExpectedResolution() internal { - uint64 newResolution = uint64(block.timestamp); - if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { - newResolution += FAST_FINALIZATION_DELAY; - } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { - newResolution += SLOW_FINALIZATION_DELAY; - } else { - revert NoProofProvided(); - } - provingData.expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); - } - /// @notice Getter for the creator of the dispute game. - /// @dev `clones-with-immutable-args` argument #1 - /// @return creator_ The creator of the dispute game. function gameCreator() public pure returns (address) { return _getArgAddress(0x00); } /// @notice Getter for the root claim. - /// @dev `clones-with-immutable-args` argument #2 - /// @return rootClaim_ The root claim of the DisputeGame. 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. - /// @dev `clones-with-immutable-args` argument #3 - /// @return l1Head_ 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 Getter for the extra data. - /// @dev `clones-with-immutable-args` argument #4 - /// @return extraData_ Any extra data supplied to the dispute game contract by the creator. function extraData() public pure returns (bytes memory) { // The extra data starts at the second word within the cwia calldata and // is 36 bytes long. @@ -592,30 +526,96 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // 4 bytes are for the parentIndex return _getArgBytes(0x54, 0x24); } + + /// @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 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 The parent index of the game. + function parentIndex() public pure returns (uint32) { + return _getArgUint32(0x74); } - function teeProver() external view returns (address) { - return provingData.teeProver; + /// @notice Verifies a TEE proof for the current game. + /// @param proofBytes The proof. + function _verifyTeeProof(bytes calldata proofBytes) internal { + // Only one TEE proof can be submitted. + if (provingData.teeProver != address(0)) revert AlreadyProven(); + + // The game must be in progress. + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); + + bytes32 journal = keccak256(abi.encodePacked( + msg.sender, + l1Head(), + startingOutputRoot.root, + startingOutputRoot.l2SequenceNumber, + rootClaim(), + l2SequenceNumber(), + CONFIG_HASH, + TEE_IMAGE_HASH + )); + + // Validate the proof. + if (!TEE_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); + + // Update proving data. + provingData.teeProver = msg.sender; } - function zkProver() external view returns (address) { - return provingData.zkProver; + /// @notice Verifies a ZK proof for the current game. + /// @param proofBytes The proof. + function _verifyZkProof(bytes calldata proofBytes) internal { + // Only one ZK proof can be submitted. + if (provingData.zkProver != address(0)) revert AlreadyProven(); + + // The game must be in progress or challenged (to allow nullification). + if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); + + bytes32 journal = keccak256(abi.encodePacked( + msg.sender, + l1Head(), + startingOutputRoot.root, + startingOutputRoot.l2SequenceNumber, + rootClaim(), + l2SequenceNumber(), + CONFIG_HASH, + ZK_IMAGE_HASH + )); + + // Validate the proof. + if (!ZK_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); + + // Update proving data. + provingData.zkProver = msg.sender; } - /// @notice Getter for the game type. - /// @dev For compliance with the IDisputeGame interface. - /// @return gameType_ The type of proof system being used. - function gameType() external view returns (GameType) { - return GAME_TYPE; + /// @notice Updates the expected resolution timestamp. + function _updateExpectedResolution() internal { + uint64 newResolution = uint64(block.timestamp); + if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { + newResolution += FAST_FINALIZATION_DELAY; + } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { + newResolution += SLOW_FINALIZATION_DELAY; + } else { + revert NoProofProvided(); + } + provingData.expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); + } + + /// @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; } } From abe2710ead5289957625426701185a01bc41eff3 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 11:21:00 -0500 Subject: [PATCH 028/125] forge fmt --- interfaces/mutliproof/IVerifier.sol | 4 +- src/multiproof/AggregateVerifier.sol | 136 +++++++++------- src/multiproof/mock/MockSystemConfig.sol | 3 +- src/multiproof/mock/MockVerifier.sol | 4 +- test/multiproof/AggregateVerifier.t.sol | 110 +++++-------- test/multiproof/BaseTest.t.sol | 75 +++++---- test/multiproof/Challenge.t.sol | 196 ++++++++--------------- test/multiproof/Nullify.t.sol | 159 +++++++----------- 8 files changed, 278 insertions(+), 409 deletions(-) diff --git a/interfaces/mutliproof/IVerifier.sol b/interfaces/mutliproof/IVerifier.sol index 8fe0d487..843ccf79 100644 --- a/interfaces/mutliproof/IVerifier.sol +++ b/interfaces/mutliproof/IVerifier.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { Claim } from "optimism/src/dispute/lib/Types.sol"; +import {Claim} from "optimism/src/dispute/lib/Types.sol"; interface IVerifier { function verify(bytes calldata proofBytes, bytes32 journal) external view returns (bool); -} \ No newline at end of file +} diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index ec95b014..b04dcbda 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -1,30 +1,23 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; // Libraries -import { Clone } from "solady/utils/Clone.sol"; -import { - Claim, - GameType, - Hash, - Proposal, - Timestamp -} from "optimism/src/dispute/lib/Types.sol"; +import {Clone} from "solady/utils/Clone.sol"; +import {Claim, GameType, Hash, Proposal, Timestamp} from "optimism/src/dispute/lib/Types.sol"; import "./Errors.sol"; // Interfaces -import { GameStatus, IDisputeGame, IDisputeGameFactory } from "optimism/src/dispute/AnchorStateRegistry.sol"; +import {GameStatus, IDisputeGame, IDisputeGameFactory} from "optimism/src/dispute/AnchorStateRegistry.sol"; import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; -import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; -import { ReentrancyGuard } from "solady/utils/ReentrancyGuard.sol"; - +import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol"; +import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { //////////////////////////////////////////////////////////////// // Enums // //////////////////////////////////////////////////////////////// - + /// @notice The type of proof. Can be expanded for different types of ZK proofs. enum ProofType { TEE, @@ -89,7 +82,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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. + /// @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; @@ -113,7 +106,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice A boolean for whether or not the game type was respected when the game was created. bool public wasRespectedGameTypeWhenCreated; - + /// @notice The claim made by the proposer. ProvingData public provingData; @@ -219,7 +212,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Parent game must be respected, not blacklisted, and not retired. if ( - !ANCHOR_STATE_REGISTRY.isGameRespected(parentGame) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(parentGame) + !ANCHOR_STATE_REGISTRY.isGameRespected(parentGame) + || ANCHOR_STATE_REGISTRY.isGameBlacklisted(parentGame) || ANCHOR_STATE_REGISTRY.isGameRetired(parentGame) ) { revert InvalidParentGame(); @@ -229,16 +223,17 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (parentGame.status() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); // The parent game must have a proof. - if (AggregateVerifier(address(parentGame)).teeProver() == address(0) && AggregateVerifier(address(parentGame)).zkProver() == address(0)) revert InvalidParentGame(); + if ( + AggregateVerifier(address(parentGame)).teeProver() == address(0) + && AggregateVerifier(address(parentGame)).zkProver() == address(0) + ) revert InvalidParentGame(); startingOutputRoot = Proposal({ - l2SequenceNumber: parentGame.l2SequenceNumber(), - root: Hash.wrap(parentGame.rootClaim().raw()) + l2SequenceNumber: parentGame.l2SequenceNumber(), root: Hash.wrap(parentGame.rootClaim().raw()) }); } else { // When there is no parent game, the starting output root is the anchor state for the game type. - (startingOutputRoot.root, startingOutputRoot.l2SequenceNumber) = - ANCHOR_STATE_REGISTRY.getAnchorRoot(); + (startingOutputRoot.root, startingOutputRoot.l2SequenceNumber) = ANCHOR_STATE_REGISTRY.getAnchorRoot(); } // The block number must be BLOCK_INTERVAL blocks after the starting block number. @@ -274,8 +269,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Bond can be reclaimed after a ZK proof is provided. bondRecipient = gameCreator(); - } - else { + } else { revert InvalidProofType(); } @@ -321,15 +315,18 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); // This game cannot be blacklisted or retired. - if (ANCHOR_STATE_REGISTRY.isGameBlacklisted(IDisputeGame(address(this))) || ANCHOR_STATE_REGISTRY.isGameRetired(IDisputeGame(address(this)))) revert InvalidGame(); + if ( + ANCHOR_STATE_REGISTRY.isGameBlacklisted(IDisputeGame(address(this))) + || ANCHOR_STATE_REGISTRY.isGameRetired(IDisputeGame(address(this))) + ) revert InvalidGame(); // The parent game cannot have been challenged if (_getParentGameStatus() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); - + // The TEE prover must not be empty. You should nullify the game if you want to challenge. if (provingData.teeProver == address(0)) revert MissingTEEProof(); if (provingData.zkProver != address(0)) revert AlreadyProven(); - + (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); AggregateVerifier challengingGame = AggregateVerifier(address(game)); @@ -347,9 +344,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (challengingGame.zkProver() == address(0)) revert MissingZKProof(); // The game must be respected, not blacklisted, and not retired. - if (!ANCHOR_STATE_REGISTRY.isGameRespected(game) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) || ANCHOR_STATE_REGISTRY.isGameRetired(game)) { + if ( + !ANCHOR_STATE_REGISTRY.isGameRespected(game) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) + || ANCHOR_STATE_REGISTRY.isGameRetired(game) + ) { revert InvalidGame(); - } + } // Update the counteredBy address provingData.counteredByGameAddress = address(challengingGame); @@ -379,11 +379,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Can only nullify a game that has a proof of the same type. if (proofType == ProofType.TEE) { - if (provingData.teeProver == address(0) || AggregateVerifier(address(game)).teeProver() == address(0)) revert MissingTEEProof(); + if (provingData.teeProver == address(0) || AggregateVerifier(address(game)).teeProver() == address(0)) { + revert MissingTEEProof(); + } } else if (proofType == ProofType.ZK) { - if (provingData.zkProver == address(0) || AggregateVerifier(address(game)).zkProver() == address(0)) revert MissingZKProof(); - } - else { + if (provingData.zkProver == address(0) || AggregateVerifier(address(game)).zkProver() == address(0)) { + revert MissingZKProof(); + } + } else { revert InvalidProofType(); } @@ -398,7 +401,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (game.rootClaim().raw() == rootClaim().raw()) revert IncorrectRootClaim(); // The game must be respected, not blacklisted, and not retired. - if (!ANCHOR_STATE_REGISTRY.isGameRespected(game) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) || ANCHOR_STATE_REGISTRY.isGameRetired(game)) { + if ( + !ANCHOR_STATE_REGISTRY.isGameRespected(game) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) + || ANCHOR_STATE_REGISTRY.isGameRetired(game) + ) { revert InvalidGame(); } @@ -413,14 +419,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } /// @notice Claim the credit belonging to the bond recipient. Reverts if the game isn't - /// finalized or if the bond transfer fails. + /// finalized or if the bond transfer fails. function claimCredit() external nonReentrant { // The bond recipient must not be empty. if (bondRecipient == address(0)) revert BondRecipientEmpty(); // If this game was challenged, the countered by game must be valid. if (provingData.counteredByGameAddress != address(0)) { - if (IDisputeGame(provingData.counteredByGameAddress).status() != GameStatus.DEFENDER_WINS) revert InvalidCounteredByGame(); + if (IDisputeGame(provingData.counteredByGameAddress).status() != GameStatus.DEFENDER_WINS) { + revert InvalidCounteredByGame(); + } } uint256 balanceToClaim = address(this).balance; @@ -438,7 +446,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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. + // We won't close the game if the system is currently paused. if (ANCHOR_STATE_REGISTRY.paused()) { revert GamePaused(); } @@ -458,7 +466,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // 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 { } + try ANCHOR_STATE_REGISTRY.setAnchorState(IDisputeGame(address(this))) {} catch {} } /// @notice The starting block number of the game. @@ -521,12 +529,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice Getter for the extra data. function extraData() public pure returns (bytes memory) { // The extra data starts at the second word within the cwia calldata and - // is 36 bytes long. + // is 36 bytes long. // 32 bytes are for the l2BlockNumber // 4 bytes are for the parentIndex return _getArgBytes(0x54, 0x24); } - + /// @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); @@ -546,16 +554,18 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // The game must be in progress. if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - bytes32 journal = keccak256(abi.encodePacked( - msg.sender, - l1Head(), - startingOutputRoot.root, - startingOutputRoot.l2SequenceNumber, - rootClaim(), - l2SequenceNumber(), - CONFIG_HASH, - TEE_IMAGE_HASH - )); + bytes32 journal = keccak256( + abi.encodePacked( + msg.sender, + l1Head(), + startingOutputRoot.root, + startingOutputRoot.l2SequenceNumber, + rootClaim(), + l2SequenceNumber(), + CONFIG_HASH, + TEE_IMAGE_HASH + ) + ); // Validate the proof. if (!TEE_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); @@ -573,16 +583,18 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // The game must be in progress or challenged (to allow nullification). if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); - bytes32 journal = keccak256(abi.encodePacked( - msg.sender, - l1Head(), - startingOutputRoot.root, - startingOutputRoot.l2SequenceNumber, - rootClaim(), - l2SequenceNumber(), - CONFIG_HASH, - ZK_IMAGE_HASH - )); + bytes32 journal = keccak256( + abi.encodePacked( + msg.sender, + l1Head(), + startingOutputRoot.root, + startingOutputRoot.l2SequenceNumber, + rootClaim(), + l2SequenceNumber(), + CONFIG_HASH, + ZK_IMAGE_HASH + ) + ); // Validate the proof. if (!ZK_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); @@ -601,7 +613,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } else { revert NoProofProvided(); } - provingData.expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); + provingData.expectedResolution = + Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); } /// @notice Returns the status of the parent game. @@ -609,7 +622,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { 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)) { + if (ANCHOR_STATE_REGISTRY.isGameBlacklisted(parentGame) || ANCHOR_STATE_REGISTRY.isGameRetired(parentGame)) + { return GameStatus.CHALLENGER_WINS; } return parentGame.status(); diff --git a/src/multiproof/mock/MockSystemConfig.sol b/src/multiproof/mock/MockSystemConfig.sol index 36df5361..5182c4c0 100644 --- a/src/multiproof/mock/MockSystemConfig.sol +++ b/src/multiproof/mock/MockSystemConfig.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.15; contract MockSystemConfig { - address public guardian; constructor() { @@ -12,4 +11,4 @@ contract MockSystemConfig { function paused() public pure returns (bool) { return false; } -} \ No newline at end of file +} diff --git a/src/multiproof/mock/MockVerifier.sol b/src/multiproof/mock/MockVerifier.sol index 8bc82302..9b39b6c1 100644 --- a/src/multiproof/mock/MockVerifier.sol +++ b/src/multiproof/mock/MockVerifier.sol @@ -5,7 +5,7 @@ import {IVerifier} from "../interfaces/IVerifier.sol"; import {Claim} from "optimism/src/dispute/lib/Types.sol"; contract MockVerifier is IVerifier { - function verify(bytes calldata , bytes32 ) external pure returns (bool) { + function verify(bytes calldata, bytes32) external pure returns (bool) { return true; } -} \ No newline at end of file +} diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 79d61d3f..5e4a697f 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -4,21 +4,16 @@ pragma solidity 0.8.15; import "test/BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { - function testInitializeWithTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "tee-proof"; - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); _provideProof(game, TEE_PROVER, true, proof); - + assertEq(game.wasRespectedGameTypeWhenCreated(), true); assertEq(address(game.teeProver()), TEE_PROVER); assertEq(address(game.zkProver()), address(0)); @@ -38,10 +33,11 @@ contract AggregateVerifierTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "zk-proof"; - - AggregateVerifier game = _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); + + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); _provideProof(game, ZK_PROVER, false, proof); - + assertEq(game.wasRespectedGameTypeWhenCreated(), true); assertEq(address(game.teeProver()), address(0)); assertEq(address(game.zkProver()), ZK_PROVER); @@ -60,7 +56,7 @@ contract AggregateVerifierTest is BaseTest { function testInitializeFailsIfInvalidCallDataSize() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - + vm.deal(TEE_PROVER, INIT_BOND); bytes memory extraData = ""; @@ -73,13 +69,9 @@ contract AggregateVerifierTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "tee-proof"; - - AggregateVerifier game = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); vm.expectRevert(NotAuthorized.selector); _provideProof(game, ZK_PROVER, true, proof); @@ -89,16 +81,12 @@ contract AggregateVerifierTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "tee-proof"; - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); _provideProof(game, TEE_PROVER, true, proof); - + // Cannot claim bond before resolving vm.expectRevert(BondRecipientEmpty.selector); game.claimCredit(); @@ -126,16 +114,12 @@ contract AggregateVerifierTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "zk-proof"; - - AggregateVerifier game = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); _provideProof(game, ZK_PROVER, false, proof); - + // Reclaim bond uint256 balanceBefore = game.gameCreator().balance; game.claimCredit(); @@ -160,17 +144,13 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory teeProof = "tee-proof"; bytes memory zkProof = "zk-proof"; - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); _provideProof(game, TEE_PROVER, true, teeProof); _provideProof(game, ZK_PROVER, false, zkProof); - + // Reclaim bond uint256 balanceBefore = game.gameCreator().balance; game.claimCredit(); @@ -195,17 +175,13 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory teeProof = "tee-proof"; bytes memory zkProof = "zk-proof"; - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); _provideProof(game, TEE_PROVER, true, teeProof); - (, , , Timestamp originalExpectedResolution) = game.provingData(); + (,,, Timestamp originalExpectedResolution) = game.provingData(); assertEq(originalExpectedResolution.raw(), block.timestamp + 7 days); vm.warp(block.timestamp + 7 days - 1); @@ -215,9 +191,9 @@ contract AggregateVerifierTest is BaseTest { // Provide ZK proof _provideProof(game, ZK_PROVER, false, zkProof); - + // Proof should not have increased expected resolution - (, , , Timestamp expectedResolution) = game.provingData(); + (,,, Timestamp expectedResolution) = game.provingData(); assertEq(expectedResolution.raw(), originalExpectedResolution.raw()); // Resolve after 1 second @@ -230,13 +206,9 @@ contract AggregateVerifierTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "tee-proof"; - - AggregateVerifier parentGame = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier parentGame = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); uint256 parentGameIndex = factory.gameCount() - 1; currentL2BlockNumber += BLOCK_INTERVAL; @@ -244,22 +216,12 @@ contract AggregateVerifierTest is BaseTest { // Cannot create a child game without a proof for the parent vm.expectRevert(InvalidParentGame.selector); - _createAggregateVerifierGame( - TEE_PROVER, - rootClaimChild, - currentL2BlockNumber, - uint32(parentGameIndex) - ); + _createAggregateVerifierGame(TEE_PROVER, rootClaimChild, currentL2BlockNumber, uint32(parentGameIndex)); // Provide proof for the parent game _provideProof(parentGame, TEE_PROVER, true, proof); - + // Create the child game - _createAggregateVerifierGame( - TEE_PROVER, - rootClaimChild, - currentL2BlockNumber, - uint32(parentGameIndex) - ); + _createAggregateVerifierGame(TEE_PROVER, rootClaimChild, currentL2BlockNumber, uint32(parentGameIndex)); } } diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 7877dd51..828e7966 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -1,24 +1,30 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { Test, console2 } from "forge-std/Test.sol"; +import {Test, console2} from "forge-std/Test.sol"; import "src/AggregateVerifier.sol"; import "src/Errors.sol"; import {IVerifier} from "src/interfaces/IVerifier.sol"; // Mocks -import { MockVerifier } from "src/mocks/MockVerifier.sol"; -import { MockSystemConfig } from "src/mocks/MockSystemConfig.sol"; +import {MockVerifier} from "src/mocks/MockVerifier.sol"; +import {MockSystemConfig} from "src/mocks/MockSystemConfig.sol"; // Optimism -import { IDisputeGame, DisputeGameFactory } from "optimism/src/dispute/DisputeGameFactory.sol"; -import { GameType, Duration, Claim } from "optimism/src/dispute/lib/Types.sol"; -import { ISystemConfig, IDisputeGameFactory, Hash, Proposal, AnchorStateRegistry } from "optimism/src/dispute/AnchorStateRegistry.sol"; +import {IDisputeGame, DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; +import {GameType, Duration, Claim} from "optimism/src/dispute/lib/Types.sol"; +import { + ISystemConfig, + IDisputeGameFactory, + Hash, + Proposal, + AnchorStateRegistry +} from "optimism/src/dispute/AnchorStateRegistry.sol"; // OpenZeppelin -import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; contract BaseTest is Test { // Constants @@ -74,19 +80,13 @@ contract BaseTest is Test { proxyAdmin = new ProxyAdmin(); // Deploy proxy for anchor state registry - TransparentUpgradeableProxy anchorStateRegistryProxy = new TransparentUpgradeableProxy( - address(_anchorStateRegistry), - address(proxyAdmin), - "" - ); + TransparentUpgradeableProxy anchorStateRegistryProxy = + new TransparentUpgradeableProxy(address(_anchorStateRegistry), address(proxyAdmin), ""); anchorStateRegistry = AnchorStateRegistry(address(anchorStateRegistryProxy)); - + // Deploy proxy for factory - TransparentUpgradeableProxy factoryProxy = new TransparentUpgradeableProxy( - address(_factory), - address(proxyAdmin), - "" - ); + TransparentUpgradeableProxy factoryProxy = + new TransparentUpgradeableProxy(address(_factory), address(proxyAdmin), ""); factory = DisputeGameFactory(address(factoryProxy)); // Deploy the verifiers @@ -96,12 +96,18 @@ contract BaseTest is Test { 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)); + 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)); } function _deployAndSetAggregateVerifier() internal { - // Deploy the dispute game relay implementation AggregateVerifier aggregateVerifierImpl = new AggregateVerifier( AGGREGATE_VERIFIER_GAME_TYPE, @@ -124,24 +130,17 @@ contract BaseTest is Test { } // Helper function to create a game via factory - function _createAggregateVerifierGame( - address creator, - Claim rootClaim, - uint256 l2BlockNumber, - uint32 parentIndex - ) internal returns (AggregateVerifier game) { - bytes memory extraData = abi.encodePacked( - uint256(l2BlockNumber), - uint32(parentIndex) - ); - + function _createAggregateVerifierGame(address creator, Claim rootClaim, uint256 l2BlockNumber, uint32 parentIndex) + internal + returns (AggregateVerifier game) + { + bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), uint32(parentIndex)); + vm.deal(creator, INIT_BOND); vm.prank(creator); - return AggregateVerifier(address(factory.create{value: INIT_BOND}( - AGGREGATE_VERIFIER_GAME_TYPE, - rootClaim, - extraData - ))); + return AggregateVerifier( + address(factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData)) + ); } function _provideProof(AggregateVerifier game, address prover, bool isTeeProof, bytes memory proof) internal { @@ -152,4 +151,4 @@ contract BaseTest is Test { game.verifyProof(proof, AggregateVerifier.ProofType.ZK); } } -} \ No newline at end of file +} diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index c94ff22e..8b27af0e 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -4,39 +4,30 @@ pragma solidity 0.8.15; import "test/BaseTest.t.sol"; contract ChallengeTest is BaseTest { - function testChallengeTEEProofWithZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; - + // Create first game with TEE proof Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory teeProof = "tee-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, TEE_PROVER, true, teeProof); - + // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, ZK_PROVER, false, zkProof); // Get game index from factory uint256 gameIndex = factory.gameCount() - 1; - + // Challenge game1 with game2 game1.challenge(gameIndex); @@ -58,73 +49,54 @@ contract ChallengeTest is BaseTest { 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 = "zk-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, ZK_PROVER, false, zkProof1); - + // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); bytes memory zkProof2 = "zk-proof-2"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, ZK_PROVER, false, zkProof2); - + uint256 gameIndex = factory.gameCount() - 1; - + vm.expectRevert(MissingTEEProof.selector); game1.challenge(gameIndex); } function testCannotCreateSameProposal() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - - _createAggregateVerifierGame( - TEE_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max); - - Hash uuid = factory.getGameUUID(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max)); - vm.expectRevert(abi.encodeWithSelector(GameAlreadyExists.selector, uuid)); - _createAggregateVerifierGame( - ZK_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max + + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); + + Hash uuid = factory.getGameUUID( + AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max) ); + vm.expectRevert(abi.encodeWithSelector(GameAlreadyExists.selector, uuid)); + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); } function testChallengeFailsIfDifferentParentIndex() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory teeProof = "tee-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); - + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); + _provideProof(game1, TEE_PROVER, true, teeProof); // Create game2 with game1 as parent @@ -132,86 +104,66 @@ contract ChallengeTest is BaseTest { uint256 nextBlockNumber = currentL2BlockNumber + BLOCK_INTERVAL; Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); bytes memory zkProof = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - nextBlockNumber, - uint32(game1Index) - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index)); _provideProof(game2, ZK_PROVER, false, zkProof); uint256 gameIndex = factory.gameCount() - 1; - + vm.expectRevert(IncorrectParentIndex.selector); game1.challenge(gameIndex); } function testChallengeFailsIfChallengingGameHasNoZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof1 = "tee-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, TEE_PROVER, true, teeProof1); - + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof2 = "tee-proof-2"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, TEE_PROVER, true, teeProof2); - + uint256 gameIndex = factory.gameCount() - 1; - + vm.expectRevert(MissingZKProof.selector); game1.challenge(gameIndex); } function testChallengeFailsIfGameAlreadyResolved() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory teeProof = "tee-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, TEE_PROVER, true, 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 = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, ZK_PROVER, false, zkProof); - + uint256 challengeIndex1 = factory.gameCount() - 1; vm.expectRevert(ClaimAlreadyResolved.selector); game1.challenge(challengeIndex1); @@ -219,17 +171,13 @@ contract ChallengeTest is BaseTest { function testChallengeFailsIfParentGameStatusIsChallenged() public { currentL2BlockNumber += BLOCK_INTERVAL; - + // create parent game Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory parentProof = "parent-proof"; - - AggregateVerifier parentGame = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier parentGame = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(parentGame, TEE_PROVER, true, parentProof); @@ -239,16 +187,12 @@ contract ChallengeTest is BaseTest { // create child game Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory childProof = "child-proof"; - - AggregateVerifier childGame = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - uint32(parentGameIndex) - ); + + AggregateVerifier childGame = + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex)); _provideProof(childGame, TEE_PROVER, true, childProof); - + // blacklist parent game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); @@ -260,15 +204,11 @@ contract ChallengeTest is BaseTest { function testChallengeFailsIfGameItselfIsBlacklisted() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); // blacklist game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); @@ -278,4 +218,4 @@ contract ChallengeTest is BaseTest { vm.expectRevert(InvalidGame.selector); game.challenge(gameIndex); } -} \ No newline at end of file +} diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 5e30bc3b..81b0b302 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -1,39 +1,29 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; - import "test/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 = "tee-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, TEE_PROVER, true, teeProof1); - + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof2 = "tee-proof-2"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, TEE_PROVER, true, teeProof2); - + uint256 gameIndex = factory.gameCount() - 1; game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); - + assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(game1.bondRecipient(), TEE_PROVER); @@ -45,29 +35,21 @@ contract NullifyTest is BaseTest { function testNullifyWithZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof1 = "zk-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, ZK_PROVER, false, zkProof1); - + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); bytes memory zkProof2 = "zk-proof-2"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, ZK_PROVER, false, zkProof2); - + uint256 gameIndex = factory.gameCount() - 1; game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); @@ -82,29 +64,21 @@ contract NullifyTest is BaseTest { function testTEENullifyFailsIfNoTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof = "zk-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, ZK_PROVER, false, zkProof); - + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof = "tee-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, TEE_PROVER, true, teeProof); - + uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(MissingTEEProof.selector); game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); @@ -112,29 +86,21 @@ contract NullifyTest is BaseTest { function testZKNullifyFailsIfNoZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof = "tee-proof"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, TEE_PROVER, true, teeProof); - + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory zkProof = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, ZK_PROVER, false, zkProof); - + uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(MissingZKProof.selector); game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); @@ -142,28 +108,25 @@ contract NullifyTest is BaseTest { function testNullifyFailsIfGameAlreadyResolved() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof1 = "tee-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, TEE_PROVER, true, 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 = "tee-proof-2"; - AggregateVerifier game2 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + AggregateVerifier game2 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, TEE_PROVER, true, teeProof2); - + uint256 challengeIndex = factory.gameCount() - 1; vm.expectRevert(ClaimAlreadyResolved.selector); game1.nullify(challengeIndex, AggregateVerifier.ProofType.TEE); @@ -171,38 +134,30 @@ contract NullifyTest is BaseTest { function testNullifyCanOverrideChallenge() public { currentL2BlockNumber += BLOCK_INTERVAL; - + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof1 = "tee-proof-1"; - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, - rootClaim1, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); _provideProof(game1, TEE_PROVER, true, teeProof1); - + // Challenge game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = "zk-proof"; - - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, - rootClaim2, - currentL2BlockNumber, - type(uint32).max - ); + + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); _provideProof(game2, ZK_PROVER, false, zkProof); - + uint256 challengeIndex = factory.gameCount() - 1; game1.challenge(challengeIndex); assertEq(game1.bondRecipient(), ZK_PROVER); - + // Nullify can override challenge _provideProof(game1, ZK_PROVER, false, zkProof); game1.nullify(challengeIndex, AggregateVerifier.ProofType.ZK); - + assertEq(game1.bondRecipient(), TEE_PROVER); uint256 balanceBefore = game1.gameCreator().balance; @@ -210,4 +165,4 @@ contract NullifyTest is BaseTest { assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); assertEq(address(game1).balance, 0); } -} \ No newline at end of file +} From c200cb7ef46fce9674059c741078fcc111984250 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 11:27:09 -0500 Subject: [PATCH 029/125] Fix tests --- src/multiproof/AggregateVerifier.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index b04dcbda..5acf70a0 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -56,7 +56,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Immutables // //////////////////////////////////////////////////////////////// /// @notice The anchor state registry. - IAnchorStateRegistry public immutable ANCHOR_STATE_REGISTRY; + IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; /// @notice The dispute game factory. IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY; @@ -500,12 +500,18 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { return provingData.zkProver; } - /// @notice Getter for the game type. + /// @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 provingData.expectedResolution.raw() <= block.timestamp; From 3fbec561a518daf91d2d8d4720498856cffde316 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 11:30:32 -0500 Subject: [PATCH 030/125] remove unused imports --- interfaces/mutliproof/IVerifier.sol | 2 -- src/multiproof/mock/MockVerifier.sol | 1 - test/multiproof/BaseTest.t.sol | 5 ++--- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/interfaces/mutliproof/IVerifier.sol b/interfaces/mutliproof/IVerifier.sol index 843ccf79..360776d0 100644 --- a/interfaces/mutliproof/IVerifier.sol +++ b/interfaces/mutliproof/IVerifier.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {Claim} from "optimism/src/dispute/lib/Types.sol"; - interface IVerifier { function verify(bytes calldata proofBytes, bytes32 journal) external view returns (bool); } diff --git a/src/multiproof/mock/MockVerifier.sol b/src/multiproof/mock/MockVerifier.sol index 9b39b6c1..65cdfcbc 100644 --- a/src/multiproof/mock/MockVerifier.sol +++ b/src/multiproof/mock/MockVerifier.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.15; import {IVerifier} from "../interfaces/IVerifier.sol"; -import {Claim} from "optimism/src/dispute/lib/Types.sol"; contract MockVerifier is IVerifier { function verify(bytes calldata, bytes32) external pure returns (bool) { diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 828e7966..36c5c796 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {Test, console2} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import "src/AggregateVerifier.sol"; -import "src/Errors.sol"; import {IVerifier} from "src/interfaces/IVerifier.sol"; @@ -13,7 +12,7 @@ import {MockSystemConfig} from "src/mocks/MockSystemConfig.sol"; // Optimism import {IDisputeGame, DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; -import {GameType, Duration, Claim} from "optimism/src/dispute/lib/Types.sol"; +import {GameType, Claim} from "optimism/src/dispute/lib/Types.sol"; import { ISystemConfig, IDisputeGameFactory, From 40b8bff160e90785943e1771800df1a1e08c41d7 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 17:00:08 -0500 Subject: [PATCH 031/125] Named imports, reorganized imports, defined errors directly --- src/multiproof/AggregateVerifier.sol | 82 ++++++++++++++++++++++--- test/multiproof/AggregateVerifier.t.sol | 16 +++-- test/multiproof/BaseTest.t.sol | 29 ++++----- test/multiproof/Challenge.t.sol | 22 ++++--- test/multiproof/Nullify.t.sol | 11 +++- 5 files changed, 122 insertions(+), 38 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 5acf70a0..c3915c44 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -1,18 +1,29 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -// Libraries -import {Clone} from "solady/utils/Clone.sol"; -import {Claim, GameType, Hash, Proposal, Timestamp} from "optimism/src/dispute/lib/Types.sol"; -import "./Errors.sol"; -// Interfaces -import {GameStatus, IDisputeGame, IDisputeGameFactory} from "optimism/src/dispute/AnchorStateRegistry.sol"; +// Optimism +import { + AlreadyInitialized, + BondTransferFailed, + ClaimAlreadyResolved, + GameNotFinalized, + GameNotInProgress, + GameNotResolved, + GamePaused, + NoCreditToClaim +} from "optimism/src/dispute/lib/Errors.sol"; +import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; -import {IVerifier} from "./interfaces/IVerifier.sol"; +import {Claim, GameStatus, GameType, Hash, Proposal, Timestamp} from "optimism/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/IVerifier.sol"; + contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { //////////////////////////////////////////////////////////////// // Enums // @@ -141,6 +152,63 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @param amount The amount of credit claimed. event CreditClaimed(address indexed recipient, uint256 amount); + //////////////////////////////////////////////////////////////// + // Errors // + //////////////////////////////////////////////////////////////// + /// @notice When the parent game is invalid. + error InvalidParentGame(); + + /// @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 parent game has not resolved. + error ParentGameNotResolved(); + + /// @notice When there is no TEE proof. + error MissingTEEProof(); + + /// @notice When there is no ZK proof. + error MissingZKProof(); + + /// @notice When the parent index is not the same. + error IncorrectParentIndex(); + + /// @notice When the block number is not the same. + error IncorrectBlockNumber(); + + /// @notice When the root claim is not different. + error IncorrectRootClaim(); + + /// @notice When the game is invalid. + error InvalidGame(); + + /// @notice When the caller is not authorized. + error NotAuthorized(); + + /// @notice When the proof has already been verified. + error AlreadyProven(); + + /// @notice When the proof is invalid. + error InvalidProof(); + + /// @notice When no proof was provided. + error NoProofProvided(); + + /// @notice When an invalid proof type is provided. + error InvalidProofType(); + + /// @notice When the bond recipient is empty. + error BondRecipientEmpty(); + + /// @notice When the countered by game is invalid. + error InvalidCounteredByGame(); + /// @param gameType The game type. /// @param anchorStateRegistry The anchor state registry. /// @param teeVerifier The TEE verifier. diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 5e4a697f..be1ec11c 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -1,7 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import "test/BaseTest.t.sol"; +import {BadExtraData} from "optimism/src/dispute/lib/Errors.sol"; +import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {Claim, GameStatus, Hash, Timestamp} from "optimism/src/dispute/lib/Types.sol"; + +import {AggregateVerifier} from "src/AggregateVerifier.sol"; + +import {BaseTest} from "test/BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { function testInitializeWithTEEProof() public { @@ -73,7 +79,7 @@ contract AggregateVerifierTest is BaseTest { AggregateVerifier game = _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - vm.expectRevert(NotAuthorized.selector); + vm.expectRevert(AggregateVerifier.NotAuthorized.selector); _provideProof(game, ZK_PROVER, true, proof); } @@ -88,7 +94,7 @@ contract AggregateVerifierTest is BaseTest { _provideProof(game, TEE_PROVER, true, proof); // Cannot claim bond before resolving - vm.expectRevert(BondRecipientEmpty.selector); + vm.expectRevert(AggregateVerifier.BondRecipientEmpty.selector); game.claimCredit(); // Resolve after 7 days @@ -186,7 +192,7 @@ contract AggregateVerifierTest is BaseTest { vm.warp(block.timestamp + 7 days - 1); // Cannot resolve yet - vm.expectRevert(GameNotOver.selector); + vm.expectRevert(AggregateVerifier.GameNotOver.selector); game.resolve(); // Provide ZK proof @@ -215,7 +221,7 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaimChild = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); // Cannot create a child game without a proof for the parent - vm.expectRevert(InvalidParentGame.selector); + vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); _createAggregateVerifierGame(TEE_PROVER, rootClaimChild, currentL2BlockNumber, uint32(parentGameIndex)); // Provide proof for the parent game diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 36c5c796..29787eec 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -2,29 +2,26 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; -import "src/AggregateVerifier.sol"; - -import {IVerifier} from "src/interfaces/IVerifier.sol"; - -// Mocks -import {MockVerifier} from "src/mocks/MockVerifier.sol"; -import {MockSystemConfig} from "src/mocks/MockSystemConfig.sol"; // Optimism -import {IDisputeGame, DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; -import {GameType, Claim} from "optimism/src/dispute/lib/Types.sol"; -import { - ISystemConfig, - IDisputeGameFactory, - Hash, - Proposal, - AnchorStateRegistry -} from "optimism/src/dispute/AnchorStateRegistry.sol"; +import {AnchorStateRegistry} from "optimism/src/dispute/AnchorStateRegistry.sol"; +import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; +import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; +import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; +import {ISystemConfig} from "optimism/interfaces/L1/ISystemConfig.sol"; +import {Claim, GameStatus, GameType, Hash, Proposal, Timestamp} from "optimism/src/dispute/lib/Types.sol"; // OpenZeppelin import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {AggregateVerifier} from "src/AggregateVerifier.sol"; +import {IVerifier} from "src/interfaces/IVerifier.sol"; + +import {MockSystemConfig} from "src/mocks/MockSystemConfig.sol"; +import {MockVerifier} from "src/mocks/MockVerifier.sol"; + contract BaseTest is Test { // Constants GameType public constant AGGREGATE_VERIFIER_GAME_TYPE = GameType.wrap(621); diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 8b27af0e..7bdc2b6f 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -1,7 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import "test/BaseTest.t.sol"; +import {ClaimAlreadyResolved} from "optimism/src/dispute/lib/Errors.sol"; +import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; +import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; +import {Claim, GameStatus, Hash} from "optimism/src/dispute/lib/Types.sol"; + +import {AggregateVerifier} from "src/AggregateVerifier.sol"; + +import {BaseTest} from "test/BaseTest.t.sol"; contract ChallengeTest is BaseTest { function testChallengeTEEProofWithZKProof() public { @@ -70,7 +78,7 @@ contract ChallengeTest is BaseTest { uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(MissingTEEProof.selector); + vm.expectRevert(AggregateVerifier.MissingTEEProof.selector); game1.challenge(gameIndex); } @@ -84,7 +92,7 @@ contract ChallengeTest is BaseTest { Hash uuid = factory.getGameUUID( AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max) ); - vm.expectRevert(abi.encodeWithSelector(GameAlreadyExists.selector, uuid)); + vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, uuid)); _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); } @@ -111,7 +119,7 @@ contract ChallengeTest is BaseTest { _provideProof(game2, ZK_PROVER, false, zkProof); uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(IncorrectParentIndex.selector); + vm.expectRevert(AggregateVerifier.IncorrectParentIndex.selector); game1.challenge(gameIndex); } @@ -136,7 +144,7 @@ contract ChallengeTest is BaseTest { uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(MissingZKProof.selector); + vm.expectRevert(AggregateVerifier.MissingZKProof.selector); game1.challenge(gameIndex); } @@ -198,7 +206,7 @@ contract ChallengeTest is BaseTest { // challenge child game uint256 childGameIndex = factory.gameCount() - 1; - vm.expectRevert(InvalidParentGame.selector); + vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); childGame.challenge(childGameIndex); } @@ -215,7 +223,7 @@ contract ChallengeTest is BaseTest { // challenge game uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(InvalidGame.selector); + vm.expectRevert(AggregateVerifier.InvalidGame.selector); game.challenge(gameIndex); } } diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 81b0b302..2608a4a3 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -1,7 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import "test/BaseTest.t.sol"; +import {ClaimAlreadyResolved} from "optimism/src/dispute/lib/Errors.sol"; +import {Claim, GameStatus} from "optimism/src/dispute/lib/Types.sol"; + +import {AggregateVerifier} from "src/AggregateVerifier.sol"; + +import {BaseTest} from "test/BaseTest.t.sol"; contract NullifyTest is BaseTest { function testNullifyWithTEEProof() public { @@ -80,7 +85,7 @@ contract NullifyTest is BaseTest { _provideProof(game2, TEE_PROVER, true, teeProof); uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(MissingTEEProof.selector); + vm.expectRevert(AggregateVerifier.MissingTEEProof.selector); game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); } @@ -102,7 +107,7 @@ contract NullifyTest is BaseTest { _provideProof(game2, ZK_PROVER, false, zkProof); uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(MissingZKProof.selector); + vm.expectRevert(AggregateVerifier.MissingZKProof.selector); game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); } From 59f58be5258fae263a605940f835cefa58e7e713 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 16 Jan 2026 17:23:41 -0500 Subject: [PATCH 032/125] linter --- test/multiproof/AggregateVerifier.t.sol | 2 ++ test/multiproof/Challenge.t.sol | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index be1ec11c..289f6135 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -222,12 +222,14 @@ contract AggregateVerifierTest is BaseTest { // Cannot create a child game without a proof for the parent vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); + // forge-lint: disable-next-line(unsafe-typecast) _createAggregateVerifierGame(TEE_PROVER, rootClaimChild, currentL2BlockNumber, uint32(parentGameIndex)); // Provide proof for the parent game _provideProof(parentGame, TEE_PROVER, true, proof); // Create the child game + // forge-lint: disable-next-line(unsafe-typecast) _createAggregateVerifierGame(TEE_PROVER, rootClaimChild, currentL2BlockNumber, uint32(parentGameIndex)); } } diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 7bdc2b6f..5f126821 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -114,6 +114,7 @@ contract ChallengeTest is BaseTest { bytes memory zkProof = "zk-proof"; AggregateVerifier game2 = + // forge-lint: disable-next-line(unsafe-typecast) _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index)); _provideProof(game2, ZK_PROVER, false, zkProof); @@ -197,6 +198,7 @@ contract ChallengeTest is BaseTest { bytes memory childProof = "child-proof"; AggregateVerifier childGame = + // forge-lint: disable-next-line(unsafe-typecast) _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex)); _provideProof(childGame, TEE_PROVER, true, childProof); From 79fbe4855df387b2df3ee51e41c9953f7ebf1b6b Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 20 Jan 2026 11:08:32 -0500 Subject: [PATCH 033/125] Add underscores to deal with compiler warnings --- src/multiproof/AggregateVerifier.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index c3915c44..5fe58006 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -209,8 +209,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice When the countered by game is invalid. error InvalidCounteredByGame(); - /// @param gameType The game type. - /// @param anchorStateRegistry The anchor state registry. + /// @param gameType_ The game type. + /// @param anchorStateRegistry_ The anchor state registry. /// @param teeVerifier The TEE verifier. /// @param zkVerifier The ZK verifier. /// @param teeImageHash The hash of the TEE image. @@ -220,8 +220,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @param l2ChainId The chain ID of the L2 network. /// @param blockInterval The block interval. constructor( - GameType gameType, - IAnchorStateRegistry anchorStateRegistry, + GameType gameType_, + IAnchorStateRegistry anchorStateRegistry_, IVerifier teeVerifier, IVerifier zkVerifier, bytes32 teeImageHash, @@ -232,8 +232,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { uint256 blockInterval ) { // Set up initial game state. - GAME_TYPE = gameType; - ANCHOR_STATE_REGISTRY = anchorStateRegistry; + GAME_TYPE = gameType_; + ANCHOR_STATE_REGISTRY = anchorStateRegistry_; DISPUTE_GAME_FACTORY = ANCHOR_STATE_REGISTRY.disputeGameFactory(); TEE_VERIFIER = teeVerifier; ZK_VERIFIER = zkVerifier; From 7991678a70cbac78082b4e88f17ac1d66074d661 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 22 Jan 2026 16:35:48 -0500 Subject: [PATCH 034/125] Refactor and have consistent comments --- src/multiproof/AggregateVerifier.sol | 103 ++++++++++----------------- test/multiproof/Challenge.t.sol | 6 +- 2 files changed, 42 insertions(+), 67 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 5fe58006..00836d6b 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -176,15 +176,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice When there is no ZK proof. error MissingZKProof(); - /// @notice When the parent index is not the same. - error IncorrectParentIndex(); - - /// @notice When the block number is not the same. - error IncorrectBlockNumber(); - - /// @notice When the root claim is not different. - error IncorrectRootClaim(); - /// @notice When the game is invalid. error InvalidGame(); @@ -267,25 +258,19 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // - 0x02 CWIA bytes assembly { if iszero(eq(calldatasize(), INITIALIZE_CALLDATA_SIZE)) { - // Store the selector for `BadExtraData()` & revert + // Store the selector for `BadExtraData()` & revert. mstore(0x00, 0x9824bdab) revert(0x1C, 0x04) } } - // The first game is initialized with a parent index of uint32.max + // 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 + // For subsequent games, get the parent game's information. (,, IDisputeGame parentGame) = DISPUTE_GAME_FACTORY.gameAtIndex(parentIndex()); // Parent game must be respected, not blacklisted, and not retired. - if ( - !ANCHOR_STATE_REGISTRY.isGameRespected(parentGame) - || ANCHOR_STATE_REGISTRY.isGameBlacklisted(parentGame) - || ANCHOR_STATE_REGISTRY.isGameRetired(parentGame) - ) { - revert InvalidParentGame(); - } + if (!_isValidGame(parentGame)) revert InvalidParentGame(); // The parent game must be a valid game. if (parentGame.status() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); @@ -312,10 +297,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Set the game as initialized. initialized = true; - // Set the game's starting timestamp + // Set the game's starting timestamp. createdAt = Timestamp.wrap(uint64(block.timestamp)); - // Game cannot resolve without a proof + // Game cannot resolve without a proof. provingData.expectedResolution = Timestamp.wrap(type(uint64).max); wasRespectedGameTypeWhenCreated = @@ -383,43 +368,27 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); // This game cannot be blacklisted or retired. - if ( - ANCHOR_STATE_REGISTRY.isGameBlacklisted(IDisputeGame(address(this))) - || ANCHOR_STATE_REGISTRY.isGameRetired(IDisputeGame(address(this))) - ) revert InvalidGame(); + if (!_isValidGame(IDisputeGame(address(this)))) revert InvalidGame(); - // The parent game cannot have been challenged + // The parent game cannot have been challenged. if (_getParentGameStatus() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); - // The TEE prover must not be empty. You should nullify the game if you want to challenge. + // The TEE prover must not be empty. + // You should nullify the game if a ZK proof has already been provided. if (provingData.teeProver == address(0)) revert MissingTEEProof(); if (provingData.zkProver != address(0)) revert AlreadyProven(); (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); AggregateVerifier challengingGame = AggregateVerifier(address(game)); - // The parent index must be the same. - if (challengingGame.parentIndex() != parentIndex()) revert IncorrectParentIndex(); - - // The block number must be the same. - if (challengingGame.l2SequenceNumber() != l2SequenceNumber()) revert IncorrectBlockNumber(); - // The root claim must be different. - // Not actually reachable as the factory prevents the same proposal from being created. - if (challengingGame.rootClaim().raw() == rootClaim().raw()) revert IncorrectRootClaim(); + // The game must be a valid game used to challenge. + if (!_isValidChallengingGame(challengingGame)) revert InvalidGame(); // The ZK prover must not be empty. if (challengingGame.zkProver() == address(0)) revert MissingZKProof(); - // The game must be respected, not blacklisted, and not retired. - if ( - !ANCHOR_STATE_REGISTRY.isGameRespected(game) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) - || ANCHOR_STATE_REGISTRY.isGameRetired(game) - ) { - revert InvalidGame(); - } - - // Update the counteredBy address + // Update the counteredBy address. provingData.counteredByGameAddress = address(challengingGame); // Set the game as challenged. @@ -429,7 +398,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Bond cannot be claimed until the game used to challenge resolves as DEFENDER_WINS. bondRecipient = challengingGame.zkProver(); - // Emit the challenged event + // Emit the challenged event. emit Challenged(challengingGame.zkProver(), game); } @@ -440,7 +409,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// block number but a different root claim as the current game. function nullify(uint256 gameIndex, ProofType proofType) external { // Can only nullify a game that has not resolved yet. - // We can nullify a challenged game in case of a soundness issue + // We can nullify a challenged game in case of a soundness issue. if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); @@ -458,29 +427,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { revert InvalidProofType(); } - // The parent index must be the same. - if (AggregateVerifier(address(game)).parentIndex() != parentIndex()) revert IncorrectParentIndex(); - - // The block number must be the same. - if (game.l2SequenceNumber() != l2SequenceNumber()) revert IncorrectBlockNumber(); - - // The root claim must be different. - // Not actually reachable as the factory prevents the same proposal from being created. - if (game.rootClaim().raw() == rootClaim().raw()) revert IncorrectRootClaim(); - - // The game must be respected, not blacklisted, and not retired. - if ( - !ANCHOR_STATE_REGISTRY.isGameRespected(game) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(game) - || ANCHOR_STATE_REGISTRY.isGameRetired(game) - ) { - revert InvalidGame(); - } + // The game must be a valid game used to nullify. + if (!_isValidChallengingGame(game)) revert InvalidGame(); // Set the game as challenged so that child games can't resolve. status = GameStatus.CHALLENGER_WINS; // Refund the bond. This can override a challenge. bondRecipient = gameCreator(); - // To allow bond to be claimed in case challenging game is nullified + // To allow bond to be refunded as the challenging game is no longer valid. delete provingData.counteredByGameAddress; emit Nullified(msg.sender, game); @@ -706,4 +660,25 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // 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); + } + + /// @notice Checks if the game is a valid game used to challenge or nullify. + /// @param game The game to check. + function _isValidChallengingGame(IDisputeGame game) internal view returns (bool) { + return + // The parent game must be the same. + AggregateVerifier(address(game)).parentIndex() == parentIndex() && + // The block number must be the same. + game.l2SequenceNumber() == l2SequenceNumber() && + // The root claim must be different. + game.rootClaim().raw() != rootClaim().raw() && + // The game must be valid. + _isValidGame(game); + } } diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 5f126821..a28e6ad8 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -115,12 +115,12 @@ contract ChallengeTest is BaseTest { AggregateVerifier game2 = // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index)); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index)); _provideProof(game2, ZK_PROVER, false, zkProof); uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(AggregateVerifier.IncorrectParentIndex.selector); + vm.expectRevert(AggregateVerifier.InvalidGame.selector); game1.challenge(gameIndex); } @@ -199,7 +199,7 @@ contract ChallengeTest is BaseTest { AggregateVerifier childGame = // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex)); + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex)); _provideProof(childGame, TEE_PROVER, true, childProof); From b188c31cc89fdf19027ea1d8fa4e0bd8c1729274 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 30 Jan 2026 11:31:49 -0500 Subject: [PATCH 035/125] Update verifier interface to include image ID --- interfaces/mutliproof/IVerifier.sol | 2 +- src/multiproof/AggregateVerifier.sol | 4 ++-- src/multiproof/mock/MockVerifier.sol | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interfaces/mutliproof/IVerifier.sol b/interfaces/mutliproof/IVerifier.sol index 360776d0..65d5df54 100644 --- a/interfaces/mutliproof/IVerifier.sol +++ b/interfaces/mutliproof/IVerifier.sol @@ -2,5 +2,5 @@ pragma solidity 0.8.15; interface IVerifier { - function verify(bytes calldata proofBytes, bytes32 journal) external view returns (bool); + function verify(bytes calldata proofBytes, bytes32 imageID, bytes32 journal) external view returns (bool); } diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 00836d6b..105a77e4 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -596,7 +596,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { ); // Validate the proof. - if (!TEE_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); + if (!TEE_VERIFIER.verify(proofBytes, TEE_IMAGE_HASH, journal)) revert InvalidProof(); // Update proving data. provingData.teeProver = msg.sender; @@ -625,7 +625,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { ); // Validate the proof. - if (!ZK_VERIFIER.verify(proofBytes, journal)) revert InvalidProof(); + if (!ZK_VERIFIER.verify(proofBytes, ZK_IMAGE_HASH, journal)) revert InvalidProof(); // Update proving data. provingData.zkProver = msg.sender; diff --git a/src/multiproof/mock/MockVerifier.sol b/src/multiproof/mock/MockVerifier.sol index 65cdfcbc..003b103a 100644 --- a/src/multiproof/mock/MockVerifier.sol +++ b/src/multiproof/mock/MockVerifier.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.15; import {IVerifier} from "../interfaces/IVerifier.sol"; contract MockVerifier is IVerifier { - function verify(bytes calldata, bytes32) external pure returns (bool) { + function verify(bytes calldata, bytes32, bytes32) external pure returns (bool) { return true; } } From e0dc966647d4ed330a3f6474cbcabef6d4dd4703 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Mon, 2 Feb 2026 17:41:32 -0500 Subject: [PATCH 036/125] Added feature where a proof is required for initialization --- src/multiproof/AggregateVerifier.sol | 83 ++++++++++++++++--------- test/multiproof/AggregateVerifier.t.sol | 63 +++++++------------ test/multiproof/BaseTest.t.sol | 13 ++-- test/multiproof/Challenge.t.sol | 70 +++++---------------- test/multiproof/Nullify.t.sol | 45 +++++--------- 5 files changed, 106 insertions(+), 168 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 105a77e4..5e622431 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -24,7 +24,7 @@ import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; -contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { +contract AggregateVerifier is Clone, ReentrancyGuard { //////////////////////////////////////////////////////////////// // Enums // //////////////////////////////////////////////////////////////// @@ -132,6 +132,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Events // //////////////////////////////////////////////////////////////// + /// @notice Emitted when the game is resolved. + /// @param status The status of the game. + event Resolved(GameStatus indexed status); + /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. /// @param challenger The address of the challenger. /// @param game The game used to challenge this proposal. @@ -237,8 +241,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } /// @notice Initializes the contract. + /// @param proof The proof. /// @dev This function may only be called once. - function initialize() external payable virtual { + /// @dev First byte of the proof is the proof type. + function initialize(bytes calldata proof) external payable virtual { // The game must not have already been initialized. if (initialized) revert AlreadyInitialized(); @@ -256,8 +262,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // - 0x20 extraData (l2BlockNumber) // - 0x04 extraData (parentIndex) // - 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; assembly { - if iszero(eq(calldatasize(), INITIALIZE_CALLDATA_SIZE)) { + if iszero(eq(calldatasize(), add(INITIALIZE_CALLDATA_SIZE, add(0x40, proofLength)))) { // Store the selector for `BadExtraData()` & revert. mstore(0x00, 0x9824bdab) revert(0x1C, 0x04) @@ -276,6 +287,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (parentGame.status() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); // The parent game must have a proof. + // Should not be reachable since a proof is required to initialize. if ( AggregateVerifier(address(parentGame)).teeProver() == address(0) && AggregateVerifier(address(parentGame)).zkProver() == address(0) @@ -300,11 +312,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Set the game's starting timestamp. createdAt = Timestamp.wrap(uint64(block.timestamp)); - // Game cannot resolve without a proof. - provingData.expectedResolution = Timestamp.wrap(type(uint64).max); - + // 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. + provingData.expectedResolution = Timestamp.wrap(type(uint64).max); + + // Verify the proof. + ProofType proofType = ProofType(uint8(proof[0])); + _verifyProof(proof[1:], proofType, gameCreator()); } /// @notice Verifies a proof for the current game. @@ -314,22 +331,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // The game must not be over. if (gameOver()) revert GameOver(); - if (proofType == ProofType.TEE) { - if (msg.sender != TEE_PROPOSER) revert NotAuthorized(); - _verifyTeeProof(proofBytes); - } else if (proofType == ProofType.ZK) { - _verifyZkProof(proofBytes); - - // Bond can be reclaimed after a ZK proof is provided. - bondRecipient = gameCreator(); - } else { - revert InvalidProofType(); - } - - _updateExpectedResolution(); - - // Emit the proved event. - emit Proved(msg.sender, proofType); + _verifyProof(proofBytes, proofType, msg.sender); } /// @notice Resolves the game after a proof has been provided and enough time has passed. @@ -380,10 +382,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); - AggregateVerifier challengingGame = AggregateVerifier(address(game)); - // The game must be a valid game used to challenge. - if (!_isValidChallengingGame(challengingGame)) revert InvalidGame(); + if (!_isValidChallengingGame(game)) revert InvalidGame(); + + AggregateVerifier challengingGame = AggregateVerifier(address(game)); // The ZK prover must not be empty. if (challengingGame.zkProver() == address(0)) revert MissingZKProof(); @@ -573,9 +575,28 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { return _getArgUint32(0x74); } + function _verifyProof(bytes calldata proofBytes, ProofType proofType, address prover) internal { + if (proofType == ProofType.TEE) { + if (prover != TEE_PROPOSER) revert NotAuthorized(); + _verifyTeeProof(proofBytes, prover); + } else if (proofType == ProofType.ZK) { + _verifyZkProof(proofBytes, prover); + + // Bond can be reclaimed after a ZK proof is provided. + bondRecipient = gameCreator(); + } else { + revert InvalidProofType(); + } + + _updateExpectedResolution(); + + // Emit the proved event. + emit Proved(prover, proofType); + } + /// @notice Verifies a TEE proof for the current game. /// @param proofBytes The proof. - function _verifyTeeProof(bytes calldata proofBytes) internal { + function _verifyTeeProof(bytes calldata proofBytes, address prover) internal { // Only one TEE proof can be submitted. if (provingData.teeProver != address(0)) revert AlreadyProven(); @@ -584,7 +605,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { bytes32 journal = keccak256( abi.encodePacked( - msg.sender, + prover, l1Head(), startingOutputRoot.root, startingOutputRoot.l2SequenceNumber, @@ -599,12 +620,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (!TEE_VERIFIER.verify(proofBytes, TEE_IMAGE_HASH, journal)) revert InvalidProof(); // Update proving data. - provingData.teeProver = msg.sender; + provingData.teeProver = prover; } /// @notice Verifies a ZK proof for the current game. /// @param proofBytes The proof. - function _verifyZkProof(bytes calldata proofBytes) internal { + function _verifyZkProof(bytes calldata proofBytes, address prover) internal { // Only one ZK proof can be submitted. if (provingData.zkProver != address(0)) revert AlreadyProven(); @@ -613,7 +634,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { bytes32 journal = keccak256( abi.encodePacked( - msg.sender, + prover, l1Head(), startingOutputRoot.root, startingOutputRoot.l2SequenceNumber, @@ -628,7 +649,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { if (!ZK_VERIFIER.verify(proofBytes, ZK_IMAGE_HASH, journal)) revert InvalidProof(); // Update proving data. - provingData.zkProver = msg.sender; + provingData.zkProver = prover; } /// @notice Updates the expected resolution timestamp. diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 289f6135..7e695e78 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; import {BadExtraData} from "optimism/src/dispute/lib/Errors.sol"; import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; import {Claim, GameStatus, Hash, Timestamp} from "optimism/src/dispute/lib/Types.sol"; import {AggregateVerifier} from "src/AggregateVerifier.sol"; @@ -16,9 +17,7 @@ contract AggregateVerifierTest is BaseTest { bytes memory proof = "tee-proof"; AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - - _provideProof(game, TEE_PROVER, true, proof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE); assertEq(game.wasRespectedGameTypeWhenCreated(), true); assertEq(address(game.teeProver()), TEE_PROVER); @@ -41,8 +40,7 @@ contract AggregateVerifierTest is BaseTest { bytes memory proof = "zk-proof"; AggregateVerifier game = - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - _provideProof(game, ZK_PROVER, false, proof); + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK); assertEq(game.wasRespectedGameTypeWhenCreated(), true); assertEq(address(game.teeProver()), address(0)); @@ -65,10 +63,11 @@ contract AggregateVerifierTest is BaseTest { vm.deal(TEE_PROVER, INIT_BOND); bytes memory extraData = ""; + bytes memory initData = ""; vm.prank(TEE_PROVER); vm.expectRevert(BadExtraData.selector); - factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData); + factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData); } function testInitializeFailsIfNotTEEProposer() public { @@ -76,11 +75,8 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "tee-proof"; - AggregateVerifier game = - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - vm.expectRevert(AggregateVerifier.NotAuthorized.selector); - _provideProof(game, ZK_PROVER, true, proof); + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE); } function testUpdatingAnchorStateRegistryWithTEEProof() public { @@ -89,9 +85,7 @@ contract AggregateVerifierTest is BaseTest { bytes memory proof = "tee-proof"; AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - - _provideProof(game, TEE_PROVER, true, proof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE); // Cannot claim bond before resolving vm.expectRevert(AggregateVerifier.BondRecipientEmpty.selector); @@ -122,9 +116,7 @@ contract AggregateVerifierTest is BaseTest { bytes memory proof = "zk-proof"; AggregateVerifier game = - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - - _provideProof(game, ZK_PROVER, false, proof); + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK); // Reclaim bond uint256 balanceBefore = game.gameCreator().balance; @@ -152,10 +144,9 @@ contract AggregateVerifierTest is BaseTest { bytes memory zkProof = "zk-proof"; AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); - _provideProof(game, TEE_PROVER, true, teeProof); - _provideProof(game, ZK_PROVER, false, zkProof); + _provideProof(game, ZK_PROVER, AggregateVerifier.ProofType.ZK, zkProof); // Reclaim bond uint256 balanceBefore = game.gameCreator().balance; @@ -183,9 +174,7 @@ contract AggregateVerifierTest is BaseTest { bytes memory zkProof = "zk-proof"; AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - - _provideProof(game, TEE_PROVER, true, teeProof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); (,,, Timestamp originalExpectedResolution) = game.provingData(); assertEq(originalExpectedResolution.raw(), block.timestamp + 7 days); @@ -196,7 +185,7 @@ contract AggregateVerifierTest is BaseTest { game.resolve(); // Provide ZK proof - _provideProof(game, ZK_PROVER, false, zkProof); + _provideProof(game, ZK_PROVER, AggregateVerifier.ProofType.ZK, zkProof); // Proof should not have increased expected resolution (,,, Timestamp expectedResolution) = game.provingData(); @@ -208,28 +197,18 @@ contract AggregateVerifierTest is BaseTest { assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); } - function testParentGameMustHaveAProof() public { + function testCannotCreateSameProposal() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = "tee-proof"; - - AggregateVerifier parentGame = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - - uint256 parentGameIndex = factory.gameCount() - 1; - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaimChild = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - - // Cannot create a child game without a proof for the parent - vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); - // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(TEE_PROVER, rootClaimChild, currentL2BlockNumber, uint32(parentGameIndex)); + bytes memory teeProof = "tee-proof"; + bytes memory zkProof = "zk-proof"; - // Provide proof for the parent game - _provideProof(parentGame, TEE_PROVER, true, proof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); - // Create the child game - // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(TEE_PROVER, rootClaimChild, currentL2BlockNumber, uint32(parentGameIndex)); + Hash uuid = factory.getGameUUID( + AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max) + ); + vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, uuid)); + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); } } diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 29787eec..7623dd9f 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -126,25 +126,22 @@ contract BaseTest is Test { } // Helper function to create a game via factory - function _createAggregateVerifierGame(address creator, Claim rootClaim, uint256 l2BlockNumber, uint32 parentIndex) + function _createAggregateVerifierGame(address creator, Claim rootClaim, uint256 l2BlockNumber, uint32 parentIndex, bytes memory proof, AggregateVerifier.ProofType proofType) internal returns (AggregateVerifier game) { bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), uint32(parentIndex)); + bytes memory initData = abi.encodePacked(uint8(proofType), proof); vm.deal(creator, INIT_BOND); vm.prank(creator); return AggregateVerifier( - address(factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData)) + address(factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData)) ); } - function _provideProof(AggregateVerifier game, address prover, bool isTeeProof, bytes memory proof) internal { + function _provideProof(AggregateVerifier game, address prover, AggregateVerifier.ProofType proofType, bytes memory proof) internal { vm.prank(prover); - if (isTeeProof) { - game.verifyProof(proof, AggregateVerifier.ProofType.TEE); - } else { - game.verifyProof(proof, AggregateVerifier.ProofType.ZK); - } + game.verifyProof(proof, proofType); } } diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index a28e6ad8..22c09f7a 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.15; import {ClaimAlreadyResolved} from "optimism/src/dispute/lib/Errors.sol"; import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; -import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; import {Claim, GameStatus, Hash} from "optimism/src/dispute/lib/Types.sol"; import {AggregateVerifier} from "src/AggregateVerifier.sol"; @@ -20,18 +19,14 @@ contract ChallengeTest is BaseTest { bytes memory teeProof = "tee-proof"; AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - - _provideProof(game1, TEE_PROVER, true, teeProof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = "zk-proof"; AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - - _provideProof(game2, ZK_PROVER, false, zkProof); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); // Get game index from factory uint256 gameIndex = factory.gameCount() - 1; @@ -63,18 +58,13 @@ contract ChallengeTest is BaseTest { bytes memory zkProof1 = "zk-proof-1"; AggregateVerifier game1 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - - _provideProof(game1, ZK_PROVER, false, zkProof1); + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK); // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); bytes memory zkProof2 = "zk-proof-2"; - AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - - _provideProof(game2, ZK_PROVER, false, zkProof2); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK); uint256 gameIndex = factory.gameCount() - 1; @@ -82,20 +72,6 @@ contract ChallengeTest is BaseTest { game1.challenge(gameIndex); } - function testCannotCreateSameProposal() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - - Hash uuid = factory.getGameUUID( - AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max) - ); - vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, uuid)); - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); - } - function testChallengeFailsIfDifferentParentIndex() public { currentL2BlockNumber += BLOCK_INTERVAL; @@ -103,9 +79,7 @@ contract ChallengeTest is BaseTest { bytes memory teeProof = "tee-proof"; AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - - _provideProof(game1, TEE_PROVER, true, teeProof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); // Create game2 with game1 as parent uint256 game1Index = factory.gameCount() - 1; @@ -113,11 +87,9 @@ contract ChallengeTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); bytes memory zkProof = "zk-proof"; - AggregateVerifier game2 = // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index)); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof, AggregateVerifier.ProofType.ZK); - _provideProof(game2, ZK_PROVER, false, zkProof); uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(AggregateVerifier.InvalidGame.selector); @@ -131,17 +103,12 @@ contract ChallengeTest is BaseTest { bytes memory teeProof1 = "tee-proof-1"; AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - - _provideProof(game1, TEE_PROVER, true, teeProof1); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof2 = "tee-proof-2"; - AggregateVerifier game2 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - - _provideProof(game2, TEE_PROVER, true, teeProof2); + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE); uint256 gameIndex = factory.gameCount() - 1; @@ -156,9 +123,7 @@ contract ChallengeTest is BaseTest { bytes memory teeProof = "tee-proof"; AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - - _provideProof(game1, TEE_PROVER, true, teeProof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); // Resolve game1 vm.warp(block.timestamp + 7 days + 1); @@ -168,10 +133,7 @@ contract ChallengeTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof = "zk-proof"; - AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - - _provideProof(game2, ZK_PROVER, false, zkProof); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); uint256 challengeIndex1 = factory.gameCount() - 1; vm.expectRevert(ClaimAlreadyResolved.selector); @@ -186,9 +148,7 @@ contract ChallengeTest is BaseTest { bytes memory parentProof = "parent-proof"; AggregateVerifier parentGame = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - - _provideProof(parentGame, TEE_PROVER, true, parentProof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, parentProof, AggregateVerifier.ProofType.TEE); uint256 parentGameIndex = factory.gameCount() - 1; currentL2BlockNumber += BLOCK_INTERVAL; @@ -199,9 +159,7 @@ contract ChallengeTest is BaseTest { AggregateVerifier childGame = // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex)); - - _provideProof(childGame, TEE_PROVER, true, childProof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex), childProof, AggregateVerifier.ProofType.TEE); // blacklist parent game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); @@ -214,11 +172,11 @@ contract ChallengeTest is BaseTest { function testChallengeFailsIfGameItselfIsBlacklisted() public { currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + bytes memory proof = "tee-proof"; AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE); // blacklist game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 2608a4a3..db03f83f 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -16,15 +16,12 @@ contract NullifyTest is BaseTest { bytes memory teeProof1 = "tee-proof-1"; AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - _provideProof(game1, TEE_PROVER, true, teeProof1); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof2 = "tee-proof-2"; - AggregateVerifier game2 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - _provideProof(game2, TEE_PROVER, true, teeProof2); + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE); uint256 gameIndex = factory.gameCount() - 1; game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); @@ -45,15 +42,12 @@ contract NullifyTest is BaseTest { bytes memory zkProof1 = "zk-proof-1"; AggregateVerifier game1 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - _provideProof(game1, ZK_PROVER, false, zkProof1); + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); bytes memory zkProof2 = "zk-proof-2"; - AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - _provideProof(game2, ZK_PROVER, false, zkProof2); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK); uint256 gameIndex = factory.gameCount() - 1; game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); @@ -74,15 +68,12 @@ contract NullifyTest is BaseTest { bytes memory zkProof = "zk-proof"; AggregateVerifier game1 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - _provideProof(game1, ZK_PROVER, false, zkProof); + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof = "tee-proof"; - AggregateVerifier game2 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - _provideProof(game2, TEE_PROVER, true, teeProof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(AggregateVerifier.MissingTEEProof.selector); @@ -96,15 +87,12 @@ contract NullifyTest is BaseTest { bytes memory teeProof = "tee-proof"; AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - _provideProof(game1, TEE_PROVER, true, teeProof); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory zkProof = "zk-proof"; - AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - _provideProof(game2, ZK_PROVER, false, zkProof); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(AggregateVerifier.MissingZKProof.selector); @@ -118,8 +106,7 @@ contract NullifyTest is BaseTest { bytes memory teeProof1 = "tee-proof-1"; AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - _provideProof(game1, TEE_PROVER, true, teeProof1); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE); // Resolve game1 vm.warp(block.timestamp + 7 days); @@ -128,9 +115,8 @@ contract NullifyTest is BaseTest { // Try to nullify game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory teeProof2 = "tee-proof-2"; - AggregateVerifier game2 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - _provideProof(game2, TEE_PROVER, true, teeProof2); + + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE); uint256 challengeIndex = factory.gameCount() - 1; vm.expectRevert(ClaimAlreadyResolved.selector); @@ -144,23 +130,20 @@ contract NullifyTest is BaseTest { bytes memory teeProof1 = "tee-proof-1"; AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max); - _provideProof(game1, TEE_PROVER, true, teeProof1); + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE); // Challenge game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = "zk-proof"; - AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max); - _provideProof(game2, ZK_PROVER, false, zkProof); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); uint256 challengeIndex = factory.gameCount() - 1; game1.challenge(challengeIndex); assertEq(game1.bondRecipient(), ZK_PROVER); // Nullify can override challenge - _provideProof(game1, ZK_PROVER, false, zkProof); + _provideProof(game1, ZK_PROVER, AggregateVerifier.ProofType.ZK, zkProof); game1.nullify(challengeIndex, AggregateVerifier.ProofType.ZK); assertEq(game1.bondRecipient(), TEE_PROVER); From 52d98451e5e86100ae09e171ff66b1a9011f228f Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Mon, 2 Feb 2026 17:41:32 -0500 Subject: [PATCH 037/125] Added feature where a proof is required for initialization --- patch/optimism.patch | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 patch/optimism.patch diff --git a/patch/optimism.patch b/patch/optimism.patch new file mode 100644 index 00000000..71d22f44 --- /dev/null +++ b/patch/optimism.patch @@ -0,0 +1,39 @@ +diff --git a/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol b/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol +index a3bb74b..bc998e1 100644 +--- a/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol ++++ b/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol +@@ -2,5 +2,5 @@ + pragma solidity ^0.8.0; + + interface IInitializable { +- function initialize() external payable; ++ function initialize(bytes calldata initData) external payable; + } +diff --git a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol +index eb92ca5..c3521f7 100644 +--- a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol ++++ b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol +@@ -143,11 +143,13 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable + /// @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. ++ /// @param initData The initialization data for the DisputeGame. + /// @return proxy_ The address of the created DisputeGame proxy. + function create( + GameType _gameType, + Claim _rootClaim, +- bytes calldata _extraData ++ bytes calldata _extraData, ++ bytes calldata initData + ) + external + payable +@@ -198,7 +200,7 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable + ) + ); + } +- proxy_.initialize{ value: msg.value }(); ++ proxy_.initialize{value: msg.value}(initData); + + // Compute the unique identifier for the dispute game. + Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData); From f099f9e213ca33d8a789df278db5fc5d3e359a51 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Mon, 2 Feb 2026 17:49:05 -0500 Subject: [PATCH 038/125] Add back in IDisputeGame inheritance --- src/multiproof/AggregateVerifier.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 5e622431..c94b4c33 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -24,7 +24,7 @@ import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; -contract AggregateVerifier is Clone, ReentrancyGuard { +contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { //////////////////////////////////////////////////////////////// // Enums // //////////////////////////////////////////////////////////////// @@ -132,10 +132,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // Events // //////////////////////////////////////////////////////////////// - /// @notice Emitted when the game is resolved. - /// @param status The status of the game. - event Resolved(GameStatus indexed status); - /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. /// @param challenger The address of the challenger. /// @param game The game used to challenge this proposal. From e4e32f75e982db1ad42bd6dee218203d7a4b6b58 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Mon, 2 Feb 2026 18:14:01 -0500 Subject: [PATCH 039/125] forge fmt --- test/multiproof/AggregateVerifier.t.sol | 42 ++++++++++------ test/multiproof/BaseTest.t.sol | 19 ++++++-- test/multiproof/Challenge.t.sol | 65 +++++++++++++++++-------- test/multiproof/Nullify.t.sol | 54 +++++++++++++------- 4 files changed, 121 insertions(+), 59 deletions(-) diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 7e695e78..3c597f38 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -16,8 +16,9 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "tee-proof"; - AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE + ); assertEq(game.wasRespectedGameTypeWhenCreated(), true); assertEq(address(game.teeProver()), TEE_PROVER); @@ -39,8 +40,9 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "zk-proof"; - AggregateVerifier game = - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK); + AggregateVerifier game = _createAggregateVerifierGame( + ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK + ); assertEq(game.wasRespectedGameTypeWhenCreated(), true); assertEq(address(game.teeProver()), address(0)); @@ -76,7 +78,9 @@ contract AggregateVerifierTest is BaseTest { bytes memory proof = "tee-proof"; vm.expectRevert(AggregateVerifier.NotAuthorized.selector); - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE); + _createAggregateVerifierGame( + ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE + ); } function testUpdatingAnchorStateRegistryWithTEEProof() public { @@ -84,8 +88,9 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "tee-proof"; - AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE + ); // Cannot claim bond before resolving vm.expectRevert(AggregateVerifier.BondRecipientEmpty.selector); @@ -115,8 +120,9 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = "zk-proof"; - AggregateVerifier game = - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK); + AggregateVerifier game = _createAggregateVerifierGame( + ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK + ); // Reclaim bond uint256 balanceBefore = game.gameCreator().balance; @@ -143,8 +149,9 @@ contract AggregateVerifierTest is BaseTest { bytes memory teeProof = "tee-proof"; bytes memory zkProof = "zk-proof"; - AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + ); _provideProof(game, ZK_PROVER, AggregateVerifier.ProofType.ZK, zkProof); @@ -173,8 +180,9 @@ contract AggregateVerifierTest is BaseTest { bytes memory teeProof = "tee-proof"; bytes memory zkProof = "zk-proof"; - AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + ); (,,, Timestamp originalExpectedResolution) = game.provingData(); assertEq(originalExpectedResolution.raw(), block.timestamp + 7 days); @@ -203,12 +211,16 @@ contract AggregateVerifierTest is BaseTest { bytes memory teeProof = "tee-proof"; bytes memory zkProof = "zk-proof"; - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); + _createAggregateVerifierGame( + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + ); Hash uuid = factory.getGameUUID( AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, type(uint32).max) ); vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, uuid)); - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); + _createAggregateVerifierGame( + ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ); } } diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 7623dd9f..fcd5f4ee 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -126,10 +126,14 @@ contract BaseTest is Test { } // Helper function to create a game via factory - function _createAggregateVerifierGame(address creator, Claim rootClaim, uint256 l2BlockNumber, uint32 parentIndex, bytes memory proof, AggregateVerifier.ProofType proofType) - internal - returns (AggregateVerifier game) - { + function _createAggregateVerifierGame( + address creator, + Claim rootClaim, + uint256 l2BlockNumber, + uint32 parentIndex, + bytes memory proof, + AggregateVerifier.ProofType proofType + ) internal returns (AggregateVerifier game) { bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), uint32(parentIndex)); bytes memory initData = abi.encodePacked(uint8(proofType), proof); @@ -140,7 +144,12 @@ contract BaseTest is Test { ); } - function _provideProof(AggregateVerifier game, address prover, AggregateVerifier.ProofType proofType, bytes memory proof) internal { + function _provideProof( + AggregateVerifier game, + address prover, + AggregateVerifier.ProofType proofType, + bytes memory proof + ) internal { vm.prank(prover); game.verifyProof(proof, proofType); } diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 22c09f7a..a3f8ce79 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -18,15 +18,17 @@ contract ChallengeTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory teeProof = "tee-proof"; - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + ); // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = "zk-proof"; - AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ); // Get game index from factory uint256 gameIndex = factory.gameCount() - 1; @@ -57,14 +59,17 @@ contract ChallengeTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof1 = "zk-proof-1"; - AggregateVerifier game1 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK); + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK + ); // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); bytes memory zkProof2 = "zk-proof-2"; - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK); + _createAggregateVerifierGame( + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK + ); uint256 gameIndex = factory.gameCount() - 1; @@ -78,8 +83,9 @@ contract ChallengeTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory teeProof = "tee-proof"; - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + ); // Create game2 with game1 as parent uint256 game1Index = factory.gameCount() - 1; @@ -88,7 +94,9 @@ contract ChallengeTest is BaseTest { bytes memory zkProof = "zk-proof"; // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof, AggregateVerifier.ProofType.ZK); + _createAggregateVerifierGame( + ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof, AggregateVerifier.ProofType.ZK + ); uint256 gameIndex = factory.gameCount() - 1; @@ -102,13 +110,16 @@ contract ChallengeTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof1 = "tee-proof-1"; - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE); + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE + ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof2 = "tee-proof-2"; - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE); + _createAggregateVerifierGame( + TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE + ); uint256 gameIndex = factory.gameCount() - 1; @@ -122,8 +133,9 @@ contract ChallengeTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory teeProof = "tee-proof"; - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + ); // Resolve game1 vm.warp(block.timestamp + 7 days + 1); @@ -133,7 +145,9 @@ contract ChallengeTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof = "zk-proof"; - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); + _createAggregateVerifierGame( + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ); uint256 challengeIndex1 = factory.gameCount() - 1; vm.expectRevert(ClaimAlreadyResolved.selector); @@ -147,8 +161,9 @@ contract ChallengeTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory parentProof = "parent-proof"; - AggregateVerifier parentGame = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, parentProof, AggregateVerifier.ProofType.TEE); + AggregateVerifier parentGame = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, parentProof, AggregateVerifier.ProofType.TEE + ); uint256 parentGameIndex = factory.gameCount() - 1; currentL2BlockNumber += BLOCK_INTERVAL; @@ -159,7 +174,14 @@ contract ChallengeTest is BaseTest { AggregateVerifier childGame = // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex), childProof, AggregateVerifier.ProofType.TEE); + _createAggregateVerifierGame( + TEE_PROVER, + rootClaim2, + currentL2BlockNumber, + uint32(parentGameIndex), + childProof, + AggregateVerifier.ProofType.TEE + ); // blacklist parent game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); @@ -175,8 +197,9 @@ contract ChallengeTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory proof = "tee-proof"; - AggregateVerifier game = - _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game = _createAggregateVerifierGame( + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE + ); // blacklist game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index db03f83f..52566c14 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -15,13 +15,16 @@ contract NullifyTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof1 = "tee-proof-1"; - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE); + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE + ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof2 = "tee-proof-2"; - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE); + _createAggregateVerifierGame( + TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE + ); uint256 gameIndex = factory.gameCount() - 1; game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); @@ -41,13 +44,16 @@ contract NullifyTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof1 = "zk-proof-1"; - AggregateVerifier game1 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK); + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK + ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); bytes memory zkProof2 = "zk-proof-2"; - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK); + _createAggregateVerifierGame( + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK + ); uint256 gameIndex = factory.gameCount() - 1; game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); @@ -67,13 +73,16 @@ contract NullifyTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof = "zk-proof"; - AggregateVerifier game1 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); + AggregateVerifier game1 = _createAggregateVerifierGame( + ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof = "tee-proof"; - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); + _createAggregateVerifierGame( + TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + ); uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(AggregateVerifier.MissingTEEProof.selector); @@ -86,13 +95,16 @@ contract NullifyTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof = "tee-proof"; - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE); + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory zkProof = "zk-proof"; - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); + _createAggregateVerifierGame( + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ); uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(AggregateVerifier.MissingZKProof.selector); @@ -105,8 +117,9 @@ contract NullifyTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof1 = "tee-proof-1"; - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE); + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE + ); // Resolve game1 vm.warp(block.timestamp + 7 days); @@ -116,7 +129,9 @@ contract NullifyTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory teeProof2 = "tee-proof-2"; - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE); + _createAggregateVerifierGame( + TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE + ); uint256 challengeIndex = factory.gameCount() - 1; vm.expectRevert(ClaimAlreadyResolved.selector); @@ -129,14 +144,17 @@ contract NullifyTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); bytes memory teeProof1 = "tee-proof-1"; - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE); + AggregateVerifier game1 = _createAggregateVerifierGame( + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE + ); // Challenge game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = "zk-proof"; - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK); + _createAggregateVerifierGame( + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ); uint256 challengeIndex = factory.gameCount() - 1; game1.challenge(challengeIndex); From 6e1ed51ac56879199305faa0ae32136d94a3aa19 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 3 Feb 2026 09:05:29 -0500 Subject: [PATCH 040/125] refactor proof format --- src/multiproof/AggregateVerifier.sol | 9 ++++----- test/multiproof/BaseTest.t.sol | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index c94b4c33..aaaa125d 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -316,18 +316,17 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { provingData.expectedResolution = Timestamp.wrap(type(uint64).max); // Verify the proof. - ProofType proofType = ProofType(uint8(proof[0])); - _verifyProof(proof[1:], proofType, gameCreator()); + _verifyProof(proof[1:], ProofType(uint8(proof[0])), gameCreator()); } /// @notice Verifies a proof for the current game. /// @param proofBytes The proof. - /// @param proofType The type of proof. - function verifyProof(bytes calldata proofBytes, ProofType proofType) external { + /// @dev The first byte of the proof is the proof type. + function verifyProof(bytes calldata proofBytes) external { // The game must not be over. if (gameOver()) revert GameOver(); - _verifyProof(proofBytes, proofType, msg.sender); + _verifyProof(proofBytes[1:], ProofType(uint8(proofBytes[0])), msg.sender); } /// @notice Resolves the game after a proof has been provided and enough time has passed. diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index fcd5f4ee..494b780b 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -151,6 +151,7 @@ contract BaseTest is Test { bytes memory proof ) internal { vm.prank(prover); - game.verifyProof(proof, proofType); + bytes memory proofBytes = abi.encodePacked(uint8(proofType), proof); + game.verifyProof(proofBytes); } } From 3d1f480d154a4fa8547e89c778d80900243362fe Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 3 Feb 2026 15:42:26 -0500 Subject: [PATCH 041/125] game type validation on challenging game --- src/multiproof/AggregateVerifier.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index aaaa125d..464ca9e6 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -688,8 +688,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @param game The game to check. function _isValidChallengingGame(IDisputeGame game) internal view returns (bool) { return - // The parent game must be the same. - AggregateVerifier(address(game)).parentIndex() == parentIndex() && + // The game type must be the same. + game.gameType().raw() == GAME_TYPE.raw() && + // The parent game must be the same. + AggregateVerifier(address(game)).parentIndex() == parentIndex() && // The block number must be the same. game.l2SequenceNumber() == l2SequenceNumber() && // The root claim must be different. From fd2b88e548b5238de0f6af07e99621a7a0280887 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Feb 2026 11:26:05 -0500 Subject: [PATCH 042/125] Add delayedWETH --- src/multiproof/AggregateVerifier.sol | 37 +++++++++++++++++++++---- test/multiproof/AggregateVerifier.t.sol | 28 ++++++++++++------- test/multiproof/BaseTest.t.sol | 14 +++++++++- test/multiproof/Challenge.t.sol | 6 ++-- test/multiproof/Nullify.t.sol | 12 ++++++-- 5 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 464ca9e6..fd3cfb2c 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -12,6 +12,7 @@ import { GamePaused, NoCreditToClaim } from "optimism/src/dispute/lib/Errors.sol"; +import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; @@ -72,6 +73,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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; @@ -128,6 +132,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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; + //////////////////////////////////////////////////////////////// // Events // //////////////////////////////////////////////////////////////// @@ -202,6 +215,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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. @@ -213,6 +227,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { constructor( GameType gameType_, IAnchorStateRegistry anchorStateRegistry_, + IDelayedWETH delayedWETH, IVerifier teeVerifier, IVerifier zkVerifier, bytes32 teeImageHash, @@ -226,6 +241,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { 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; @@ -315,6 +331,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Set expected resolution. provingData.expectedResolution = Timestamp.wrap(type(uint64).max); + // Deposit the bond. + bondAmount = msg.value; + DELAYED_WETH.deposit{ value: msg.value }(); + // Verify the proof. _verifyProof(proof[1:], ProofType(uint8(proof[0])), gameCreator()); } @@ -440,6 +460,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice Claim the credit belonging to the bond recipient. Reverts if the game isn't /// finalized or if the bond transfer fails. function claimCredit() external nonReentrant { + // The bond must not have been claimed yet. + if (bondClaimed) revert NoCreditToClaim(); + // The bond recipient must not be empty. if (bondRecipient == address(0)) revert BondRecipientEmpty(); @@ -450,17 +473,21 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } } - uint256 balanceToClaim = address(this).balance; + if (!bondUnlocked) { + DELAYED_WETH.unlock(bondRecipient, bondAmount); + bondUnlocked = true; + return; + } - // The game must have credit to claim. - if (balanceToClaim == 0) revert NoCreditToClaim(); + bondClaimed = true; + DELAYED_WETH.withdraw(bondRecipient, bondAmount); // Transfer the credit to the bond recipient. - (bool success,) = bondRecipient.call{value: balanceToClaim}(hex""); + (bool success,) = bondRecipient.call{value: bondAmount}(hex""); if (!success) revert BondTransferFailed(); // Emit the credit claimed event. - emit CreditClaimed(bondRecipient, balanceToClaim); + emit CreditClaimed(bondRecipient, bondAmount); } /// @notice Closes the game by trying to update the anchor state. diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 3c597f38..8336f027 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -32,7 +32,7 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max)); assertEq(game.bondRecipient(), address(0)); assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); - assertEq(address(game).balance, INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); } function testInitializeWithZKProof() public { @@ -56,7 +56,7 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max)); assertEq(game.bondRecipient(), ZK_PROVER); assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); - assertEq(address(game).balance, INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); } function testInitializeFailsIfInvalidCallDataSize() public { @@ -101,11 +101,13 @@ contract AggregateVerifierTest is BaseTest { game.resolve(); assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); - // Reclaim bond after resolving + // 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(address(game).balance, 0); + assertEq(delayedWETH.balanceOf(address(game)), 0); // Update AnchorStateRegistry vm.warp(block.timestamp + 1); @@ -124,13 +126,15 @@ contract AggregateVerifierTest is BaseTest { ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK ); - // Reclaim bond + // 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(address(game).balance, 0); + assertEq(delayedWETH.balanceOf(address(game)), 0); - // Resolve after 7 days + // Resolve after another 7 days vm.warp(block.timestamp + 7 days); game.resolve(); assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); @@ -155,11 +159,9 @@ contract AggregateVerifierTest is BaseTest { _provideProof(game, ZK_PROVER, AggregateVerifier.ProofType.ZK, zkProof); - // Reclaim bond + // Unlock bond uint256 balanceBefore = game.gameCreator().balance; game.claimCredit(); - assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(address(game).balance, 0); // Resolve after 1 day vm.warp(block.timestamp + 1 days); @@ -172,6 +174,12 @@ contract AggregateVerifierTest is BaseTest { (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); assertEq(root.raw(), rootClaim.raw()); assertEq(l2SequenceNumber, currentL2BlockNumber); + + // Unlock and reclaim bond after delay + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); } function testProofCannotIncreaseExpectedResolution() public { diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 494b780b..39bf01c3 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -5,8 +5,10 @@ import {Test} from "forge-std/Test.sol"; // Optimism import {AnchorStateRegistry} from "optimism/src/dispute/AnchorStateRegistry.sol"; +import {DelayedWETH} from "optimism/src/dispute/DelayedWETH.sol"; import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; +import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; import {ISystemConfig} from "optimism/interfaces/L1/ISystemConfig.sol"; @@ -28,6 +30,7 @@ contract BaseTest is Test { uint256 public constant L2_CHAIN_ID = 8453; uint256 public constant BLOCK_INTERVAL = 100; 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; @@ -46,6 +49,7 @@ contract BaseTest is Test { DisputeGameFactory public factory; AnchorStateRegistry public anchorStateRegistry; + DelayedWETH public delayedWETH; MockVerifier public teeVerifier; MockVerifier public zkVerifier; @@ -66,9 +70,10 @@ contract BaseTest is Test { 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(); @@ -85,6 +90,11 @@ contract BaseTest is Test { new TransparentUpgradeableProxy(address(_factory), address(proxyAdmin), ""); factory = DisputeGameFactory(address(factoryProxy)); + // Deploy proxy for delayed WETH + TransparentUpgradeableProxy delayedWETHProxy = + new TransparentUpgradeableProxy(address(_delayedWETH), address(proxyAdmin), ""); + delayedWETH = DelayedWETH(payable(address(delayedWETHProxy))); + // Deploy the verifiers teeVerifier = new MockVerifier(); zkVerifier = new MockVerifier(); @@ -101,6 +111,7 @@ contract BaseTest is Test { GameType.wrap(0) ); factory.initialize(address(this)); + delayedWETH.initialize(ISystemConfig(address(systemConfig))); } function _deployAndSetAggregateVerifier() internal { @@ -108,6 +119,7 @@ contract BaseTest is Test { AggregateVerifier aggregateVerifierImpl = new AggregateVerifier( AGGREGATE_VERIFIER_GAME_TYPE, IAnchorStateRegistry(address(anchorStateRegistry)), + IDelayedWETH(payable(address(delayedWETH))), IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), TEE_IMAGE_HASH, diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index a3f8ce79..1eced497 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -46,10 +46,12 @@ contract ChallengeTest is BaseTest { game2.resolve(); assertEq(uint8(game2.status()), uint8(GameStatus.DEFENDER_WINS)); assertEq(ZK_PROVER.balance, 0); - assertEq(address(game1).balance, INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game1)), INIT_BOND); + game1.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); game1.claimCredit(); assertEq(ZK_PROVER.balance, INIT_BOND); - assertEq(address(game1).balance, 0); + assertEq(delayedWETH.balanceOf(address(game1)), 0); } function testChallengeFailsIfNoTEEProof() public { diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 52566c14..fc6b6caa 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -34,8 +34,10 @@ contract NullifyTest is BaseTest { uint256 balanceBefore = game1.gameCreator().balance; game1.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game1.claimCredit(); assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(address(game1).balance, 0); + assertEq(delayedWETH.balanceOf(address(game1)), 0); } function testNullifyWithZKProof() public { @@ -63,8 +65,10 @@ contract NullifyTest is BaseTest { uint256 balanceBefore = game1.gameCreator().balance; game1.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game1.claimCredit(); assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(address(game1).balance, 0); + assertEq(delayedWETH.balanceOf(address(game1)), 0); } function testTEENullifyFailsIfNoTEEProof() public { @@ -168,7 +172,9 @@ contract NullifyTest is BaseTest { uint256 balanceBefore = game1.gameCreator().balance; game1.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game1.claimCredit(); assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(address(game1).balance, 0); + assertEq(delayedWETH.balanceOf(address(game1)), 0); } } From faebf6e72a45dda12849ed4fe6e2d313625276d7 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Feb 2026 11:26:31 -0500 Subject: [PATCH 043/125] forge fmt --- src/multiproof/AggregateVerifier.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index fd3cfb2c..5a597a19 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -333,7 +333,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Deposit the bond. bondAmount = msg.value; - DELAYED_WETH.deposit{ value: msg.value }(); + DELAYED_WETH.deposit{value: msg.value}(); // Verify the proof. _verifyProof(proof[1:], ProofType(uint8(proof[0])), gameCreator()); @@ -716,7 +716,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { function _isValidChallengingGame(IDisputeGame game) internal view returns (bool) { return // The game type must be the same. - game.gameType().raw() == GAME_TYPE.raw() && + game.gameType().raw() == GAME_TYPE.raw() && // The parent game must be the same. AggregateVerifier(address(game)).parentIndex() == parentIndex() && // The block number must be the same. From 9f4a3d9c0c9d211101b3518f74d76c76bd88ed47 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Feb 2026 11:38:17 -0500 Subject: [PATCH 044/125] Check challenging game validity when claiming credit --- src/multiproof/AggregateVerifier.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 5a597a19..0ebca91c 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -468,6 +468,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // If this game was challenged, the countered by game must be valid. if (provingData.counteredByGameAddress != address(0)) { + if (!_isValidChallengingGame(IDisputeGame(provingData.counteredByGameAddress))) { + revert InvalidCounteredByGame(); + } if (IDisputeGame(provingData.counteredByGameAddress).status() != GameStatus.DEFENDER_WINS) { revert InvalidCounteredByGame(); } From e9fa5a88cba83ce7d877cf6b2bcd1e2cbd2d102b Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Thu, 5 Feb 2026 19:57:01 +0000 Subject: [PATCH 045/125] Add TEEVerifier and SystemConfigGlobal contracts and update AggregateVerifier to include l1OriginNumber in journal --- deploy-config/sepolia.json | 9 + scripts/multiproof/DeployAllForTesting.s.sol | 292 +++++++++++++++++++ src/multiproof/AggregateVerifier.sol | 10 +- src/multiproof/tee/SystemConfigGlobal.sol | 148 ++++++++++ src/multiproof/tee/TEEVerifier.sol | 131 +++++++++ 5 files changed, 586 insertions(+), 4 deletions(-) create mode 100644 deploy-config/sepolia.json create mode 100644 scripts/multiproof/DeployAllForTesting.s.sol create mode 100644 src/multiproof/tee/SystemConfigGlobal.sol create mode 100644 src/multiproof/tee/TEEVerifier.sol diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json new file mode 100644 index 00000000..edd38e2a --- /dev/null +++ b/deploy-config/sepolia.json @@ -0,0 +1,9 @@ +{ + "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33", + "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "teeProposer": "0x74252ce52515d60dae157F90232AEc557A8DAB57", + "gameType": "621", + "genesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", + "genesisBlockNumber": "37223829", + "configHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360" +} diff --git a/scripts/multiproof/DeployAllForTesting.s.sol b/scripts/multiproof/DeployAllForTesting.s.sol new file mode 100644 index 00000000..ee81e62e --- /dev/null +++ b/scripts/multiproof/DeployAllForTesting.s.sol @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {console2 as console} from "forge-std/console2.sol"; +import {stdJson} from "forge-std/StdJson.sol"; + +// TEE contracts +import {CertManager} from "@nitro-validator/CertManager.sol"; +import {SystemConfigGlobal} from "../src/tee/SystemConfigGlobal.sol"; +import {TEEVerifier} from "../src/tee/TEEVerifier.sol"; + +// Dispute game +import {AggregateVerifier} from "../src/AggregateVerifier.sol"; +import {IVerifier} from "../src/interfaces/IVerifier.sol"; +import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; +import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {GameType, Hash} from "optimism/src/dispute/lib/Types.sol"; + +// Mocks +import {MockVerifier} from "../src/mocks/MockVerifier.sol"; + +// Proxy - using OpenZeppelin for simpler deployment +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.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; + + function initialize(address _factory, Hash _anchorRoot, uint256 _anchorL2BlockNumber, GameType _gameType) external { + factory = _factory; + anchorRoot = _anchorRoot; + anchorL2BlockNumber = _anchorL2BlockNumber; + respectedGameType = _gameType; + } + + // This is the key function AggregateVerifier calls + function getAnchorRoot() external view returns (Hash, uint256) { + return (anchorRoot, anchorL2BlockNumber); + } + + function disputeGameFactory() external view returns (address) { + return factory; + } + + function setRespectedGameType(GameType _gameType) external { + respectedGameType = _gameType; + } + + /// @notice Update the anchor state (for testing purposes) + function setAnchorState(Hash _anchorRoot, uint256 _anchorL2BlockNumber) external { + anchorRoot = _anchorRoot; + anchorL2BlockNumber = _anchorL2BlockNumber; + } + + // Stub implementations that AggregateVerifier may call + function isGameRegistered(IDisputeGame) external pure returns (bool) { + return true; + } + + function isGameBlacklisted(IDisputeGame) external pure returns (bool) { + return false; + } + + function isGameRetired(IDisputeGame) external pure returns (bool) { + return false; + } + + function isGameRespected(IDisputeGame) external pure returns (bool) { + return true; + } +} + +// Import the REAL DisputeGameFactory +import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; + +/// @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 _owner) { + owner = _owner; + } +} + +/// @title DeployAllForTesting +/// @notice Deploys everything needed for e2e testing, using mocks for optimism contracts. +contract DeployAllForTesting is Script { + using stdJson for string; + + uint256 public constant BLOCK_INTERVAL = 100; + uint256 public constant INIT_BOND = 0.001 ether; + + // Config struct to reduce stack variables + struct DeployConfig { + address owner; + bytes32 teeImageHash; + address teeProposer; + GameType gameType; + uint256 gameTypeRaw; + bytes32 genesisOutputRoot; + uint256 genesisBlockNumber; + bytes32 configHash; + } + + // Deployed addresses + address public certManager; + address public systemConfigGlobalProxy; + address public teeVerifier; + address public disputeGameFactory; + address public mockAnchorRegistry; + address public aggregateVerifier; + + function _loadConfig() internal view returns (DeployConfig memory cfg) { + string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia.json")); + string memory config = vm.readFile(configPath); + + cfg.owner = config.readAddress(".finalSystemOwner"); + cfg.teeImageHash = config.readBytes32(".teeImageHash"); + cfg.teeProposer = config.readAddressOr(".teeProposer", cfg.owner); + cfg.gameTypeRaw = config.readUintOr(".gameType", 621); + cfg.gameType = GameType.wrap(uint32(cfg.gameTypeRaw)); + cfg.genesisOutputRoot = config.readBytes32Or(".genesisOutputRoot", bytes32(uint256(1))); + cfg.genesisBlockNumber = config.readUintOr(".genesisBlockNumber", 0); + cfg.configHash = config.readBytes32Or(".configHash", bytes32(0)); + } + + function run() public { + DeployConfig memory cfg = _loadConfig(); + + console.log("=== Deploying Complete Test Infrastructure ==="); + console.log("Chain ID:", block.chainid); + console.log("Owner:", cfg.owner); + console.log("TEE Proposer:", cfg.teeProposer); + console.log("Game Type:", cfg.gameTypeRaw); + + vm.startBroadcast(); + + _deployTEEContracts(cfg.owner); + _deployInfrastructure(cfg); + _deployAggregateVerifier(cfg); + + vm.stopBroadcast(); + + _printSummary(cfg.teeImageHash, cfg.gameTypeRaw, cfg.configHash); + _writeOutput(); + } + + function _deployTEEContracts(address owner) internal { + // 1. CertManager + certManager = address(new CertManager()); + console.log("CertManager:", certManager); + + // 2. SystemConfigGlobal with proxy + address scgImpl = address(new SystemConfigGlobal(CertManager(certManager))); + systemConfigGlobalProxy = address( + new TransparentUpgradeableProxy( + scgImpl, + address(0xdead), // Non-upgradeable for testing + abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) + ) + ); + console.log("SystemConfigGlobal:", systemConfigGlobalProxy); + + // 3. TEEVerifier + teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); + console.log("TEEVerifier:", teeVerifier); + } + + // Constant from Optimism's Constants.sol - the storage slot for proxy admin + bytes32 constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + function _deployInfrastructure(DeployConfig memory cfg) internal { + // 4. REAL DisputeGameFactory (behind proxy) + // The constructor calls _disableInitializers(), so we must use a proxy + address factoryImpl = address(new DisputeGameFactory()); + + // Deploy a minimal proxy admin that returns cfg.owner as owner + MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.owner); + + // Deploy proxy - but DON'T initialize yet (empty initData) + // We need to set the proxy admin storage slot first + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + factoryImpl, + address(proxyAdmin), // Use our MinimalProxyAdmin as the admin + "" // Don't call initialize yet + ); + + // Set the PROXY_OWNER_ADDRESS slot on the proxy so initialize() passes the access check + vm.store(address(proxy), PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(address(proxyAdmin))))); + + // Now initialize - caller is checked against proxyAdmin or proxyAdminOwner + DisputeGameFactory(address(proxy)).initialize(cfg.owner); + + disputeGameFactory = address(proxy); + console.log("DisputeGameFactory (REAL):", disputeGameFactory); + + // 5. Mock AnchorStateRegistry - still mocked because: + // - The real one needs SystemConfig, SuperchainConfig, etc. + // - We only need getAnchorRoot() for the prover + // - Manual setAnchorState() is useful for testing + MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); + mockAnchorRegistry = address(asr); + asr.initialize(disputeGameFactory, Hash.wrap(cfg.genesisOutputRoot), cfg.genesisBlockNumber, cfg.gameType); + console.log("AnchorStateRegistry (mock):", mockAnchorRegistry); + } + + function _deployAggregateVerifier(DeployConfig memory cfg) internal { + // 6. Mock ZK Verifier + address zkVerifier = address(new MockVerifier()); + console.log("MockVerifier (ZK):", zkVerifier); + + // 7. AggregateVerifier + aggregateVerifier = address( + new AggregateVerifier( + cfg.gameType, + IAnchorStateRegistry(mockAnchorRegistry), + IVerifier(teeVerifier), + IVerifier(zkVerifier), + cfg.teeImageHash, + bytes32(0), // zkImageHash (unused for testing) + cfg.configHash, + cfg.teeProposer, + 8453, // l2ChainId (Base mainnet) + BLOCK_INTERVAL + ) + ); + console.log("AggregateVerifier:", aggregateVerifier); + console.log("ConfigHash:", vm.toString(cfg.configHash)); + + // 8. Register AggregateVerifier with the real factory + DisputeGameFactory(disputeGameFactory).setImplementation(cfg.gameType, IDisputeGame(aggregateVerifier)); + DisputeGameFactory(disputeGameFactory).setInitBond(cfg.gameType, INIT_BOND); + console.log("Registered AggregateVerifier with factory"); + } + + function _printSummary(bytes32 teeImageHash, uint256 gameType, bytes32 configHash) internal view { + console.log("\n========================================"); + console.log(" DEPLOYMENT COMPLETE"); + console.log("========================================"); + console.log("\nTEE Contracts:"); + console.log(" CertManager:", certManager); + console.log(" SystemConfigGlobal:", systemConfigGlobalProxy); + console.log(" TEEVerifier:", teeVerifier); + console.log("\nInfrastructure:"); + console.log(" DisputeGameFactory (real):", disputeGameFactory); + console.log(" AnchorStateRegistry (mock):", mockAnchorRegistry); + console.log("\nGame:"); + console.log(" AggregateVerifier:", aggregateVerifier); + console.log(" Game Type:", gameType); + console.log(" ConfigHash:", vm.toString(configHash)); + console.log("========================================"); + console.log("\nNEXT STEP - Register dev signer:"); + console.log("cast send", systemConfigGlobalProxy); + console.log(' "addDevSigner(address,bytes32)" '); + console.log(" ", vm.toString(teeImageHash)); + } + + function _writeOutput() internal { + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-all.json"); + string memory output = string.concat( + '{"CertManager":"', + vm.toString(certManager), + '","SystemConfigGlobal":"', + vm.toString(systemConfigGlobalProxy), + '","TEEVerifier":"', + vm.toString(teeVerifier), + '","DisputeGameFactory":"', + vm.toString(disputeGameFactory), + '","AnchorStateRegistry":"', + vm.toString(mockAnchorRegistry), + '","AggregateVerifier":"', + vm.toString(aggregateVerifier), + '"}' + ); + vm.writeFile(outPath, output); + console.log("\nDeployment saved to:", outPath); + } +} diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index c94b4c33..10439c17 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -591,7 +591,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } /// @notice Verifies a TEE proof for the current game. - /// @param proofBytes The proof. + /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + signature (65). function _verifyTeeProof(bytes calldata proofBytes, address prover) internal { // Only one TEE proof can be submitted. if (provingData.teeProver != address(0)) revert AlreadyProven(); @@ -602,7 +602,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { bytes32 journal = keccak256( abi.encodePacked( prover, - l1Head(), + bytes32(proofBytes[0:32]), + uint256(bytes32(proofBytes[32:64])), startingOutputRoot.root, startingOutputRoot.l2SequenceNumber, rootClaim(), @@ -620,7 +621,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } /// @notice Verifies a ZK proof for the current game. - /// @param proofBytes The proof. + /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + zkProof (variable). function _verifyZkProof(bytes calldata proofBytes, address prover) internal { // Only one ZK proof can be submitted. if (provingData.zkProver != address(0)) revert AlreadyProven(); @@ -631,7 +632,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { bytes32 journal = keccak256( abi.encodePacked( prover, - l1Head(), + bytes32(proofBytes[0:32]), + uint256(bytes32(proofBytes[32:64])), startingOutputRoot.root, startingOutputRoot.l2SequenceNumber, rootClaim(), diff --git a/src/multiproof/tee/SystemConfigGlobal.sol b/src/multiproof/tee/SystemConfigGlobal.sol new file mode 100644 index 00000000..51a33569 --- /dev/null +++ b/src/multiproof/tee/SystemConfigGlobal.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {OwnableManagedUpgradeable} from "@op-enclave/OwnableManagedUpgradeable.sol"; +import {NitroValidator} from "@nitro-validator/NitroValidator.sol"; +import {LibBytes} from "@nitro-validator/LibBytes.sol"; +import {LibCborElement, CborElement, CborDecode} from "@nitro-validator/CborDecode.sol"; +import {ICertManager} from "@nitro-validator/ICertManager.sol"; + +/// @title SystemConfigGlobal +/// @notice Manages TEE signer registration via AWS Nitro attestation. +/// @dev Signers are registered by providing a valid AWS Nitro attestation document. +/// Each signer is associated with the PCR0 (enclave image hash) from their attestation, +/// which allows TEEVerifier to validate that a signer was registered with a specific image. +contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { + using LibBytes for bytes; + using CborDecode for bytes; + using LibCborElement for CborElement; + + /// @notice Maximum age of an attestation document (60 minutes). + uint256 public constant MAX_AGE = 60 minutes; + + /// @notice The address of the proposer. + address public proposer; + + /// @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. + /// This replaces the old validSigners(address => bool) mapping to enable imageID validation. + mapping(address => bytes32) public signerPCR0; + + /// @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 Semantic version. + /// @custom:semver 0.1.0 + function version() public pure virtual returns (string memory) { + return "0.1.0"; + } + + constructor(ICertManager certManager) NitroValidator(certManager) { + // On test networks, skip auto-initialization to allow manual initialization. + // On production, disable the implementation by setting dead addresses. + bool isTestnet = block.chainid == 31337 // Anvil + || block.chainid == 1337 // Ganache + || block.chainid == 11155111 // Sepolia + || block.chainid == 84532; // Base Sepolia + if (!isTestnet) { + initialize({_owner: address(0xdEaD), _manager: address(0xdEaD)}); + } + } + + function initialize(address _owner, address _manager) public initializer { + __OwnableManaged_init(); + transferOwnership(_owner); + transferManagement(_manager); + } + + /// @notice Sets the proposer address. + function setProposer(address _proposer) external onlyOwner { + proposer = _proposer; + } + + /// @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 an AWS Nitro attestation document. + /// @dev The attestation must: + /// 1. Be signed by a valid AWS Nitro certificate chain + /// 2. Contain a PCR0 that has been pre-registered via registerPCR0 + /// 3. Be less than MAX_AGE old + /// @param attestationTbs The TBS (to-be-signed) portion of the attestation document. + /// @param signature The signature over the attestation. + function registerSigner(bytes calldata attestationTbs, bytes calldata signature) external onlyOwnerOrManager { + Ptrs memory ptrs = validateAttestation(attestationTbs, signature); + bytes32 pcr0Hash = attestationTbs.keccak(ptrs.pcrs[0]); + require(validPCR0s[pcr0Hash], "invalid pcr0 in attestation"); + + require(ptrs.timestamp + MAX_AGE > block.timestamp, "attestation too old"); + + // The publicKey is encoded in the form specified in section 4.3.6 of ANSI X9.62, + // which is a 0x04 byte followed by the x and y coordinates of the public key. + // We ignore the first byte when hashing. + bytes32 publicKeyHash = attestationTbs.keccak(ptrs.publicKey.start() + 1, ptrs.publicKey.length() - 1); + address enclaveAddress = address(uint160(uint256(publicKeyHash))); + + // Store the PCR0 hash for this signer (enables imageID validation) + signerPCR0[enclaveAddress] = pcr0Hash; + emit SignerRegistered(enclaveAddress, pcr0Hash); + } + + /// @notice Deregisters a signer. + /// @param signer The address of the signer to deregister. + function deregisterSigner(address signer) external onlyOwnerOrManager { + delete signerPCR0[signer]; + emit SignerDeregistered(signer); + } + + /// @notice Registers a signer for testing (bypasses attestation verification). + /// @dev Only callable by owner, only works on test networks. + /// Allowed chains: Anvil (31337), Ganache (1337), Sepolia (11155111), Base Sepolia (84532). + /// DO NOT deploy this to production networks. + /// @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 { + require( + block.chainid == 31337 // Anvil + || block.chainid == 1337 // Ganache + || block.chainid == 11155111 // Sepolia + || block.chainid == 84532, // Base Sepolia + "dev only: test chains only" + ); + signerPCR0[signer] = pcr0Hash; + emit SignerRegistered(signer, pcr0Hash); + } + + /// @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); + } +} diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol new file mode 100644 index 00000000..2441804f --- /dev/null +++ b/src/multiproof/tee/TEEVerifier.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {IVerifier} from "../interfaces/IVerifier.sol"; +import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; + +/// @title IEIP2935 +/// @notice Interface for the EIP-2935 blockhash history contract. +interface IEIP2935 { + function get(uint256 blockNumber) external view returns (bytes32); +} + +/// @title TEEVerifier +/// @notice Stateless TEE proof verifier that validates signatures against registered signers. +/// @dev This contract is designed to be used as the TEE_VERIFIER in the AggregateVerifier. +/// It verifies that proofs are signed by enclave addresses registered in SystemConfigGlobal +/// via AWS Nitro attestation, and that the signer's PCR0 matches the claimed imageID. +/// Additionally, it verifies that the L1 origin block referenced in the proof actually exists +/// by checking against blockhash() or the EIP-2935 history contract. +/// The contract is intentionally stateless - all state related to output proposals is +/// managed by the calling contract (e.g., AggregateVerifier). +contract TEEVerifier is IVerifier { + /// @notice The SystemConfigGlobal contract that manages valid TEE signers. + /// @dev Signers are registered via AWS Nitro attestation in SystemConfigGlobal. + SystemConfigGlobal public immutable systemConfigGlobal; + + /// @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 = 0x0F792be4B0c0cb4DAE440Ef133E90C0eCD48CCCC; + + /// @notice The maximum number of blocks that blockhash() can look back. + uint256 public constant BLOCKHASH_WINDOW = 256; + + /// @notice The maximum number of blocks that EIP-2935 can look back (~8192). + uint256 public constant EIP2935_WINDOW = 8191; + + /// @notice Emitted when a proof is successfully verified. + /// @param signer The address that signed the proof. + /// @param imageID The image ID (PCR0 hash) that was validated. + /// @param journal The journal hash that was signed. + event ProofVerified(address indexed signer, bytes32 indexed imageID, bytes32 indexed journal); + + /// @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 L1 origin block is too old to verify. + error L1OriginTooOld(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 the proof format is invalid. + error InvalidProofFormat(); + + /// @notice Constructs the TEEVerifier contract. + /// @param _systemConfigGlobal The SystemConfigGlobal contract address. + constructor(SystemConfigGlobal _systemConfigGlobal) { + systemConfigGlobal = _systemConfigGlobal; + } + + /// @notice Verifies a TEE proof for a state transition. + /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + signature (65) = 129 bytes. + /// @param imageID The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. + /// @param journal The keccak256 hash of the proof's public inputs. + /// @return valid Whether the proof is valid. + function verify(bytes calldata proofBytes, bytes32 imageID, bytes32 journal) external view override returns (bool) { + if (proofBytes.length < 129) revert InvalidProofFormat(); + + bytes32 l1OriginHash = bytes32(proofBytes[0:32]); + uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); + bytes calldata signature = proofBytes[64:129]; + + // Verify claimed L1 origin hash matches actual blockhash + _verifyL1Origin(l1OriginHash, l1OriginNumber); + + // 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(); + } + + // Get the PCR0 the signer was registered with + bytes32 registeredPCR0 = systemConfigGlobal.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 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 { + bytes32 actualHash; + + if (block.number > l1OriginNumber && block.number - l1OriginNumber <= BLOCKHASH_WINDOW) { + actualHash = blockhash(l1OriginNumber); + } else if (block.number > l1OriginNumber && block.number - l1OriginNumber <= EIP2935_WINDOW) { + actualHash = IEIP2935(EIP2935_CONTRACT).get(l1OriginNumber); + } else { + revert L1OriginTooOld(l1OriginNumber, block.number); + } + + if (actualHash == bytes32(0)) { + revert L1OriginTooOld(l1OriginNumber, block.number); + } + + if (actualHash != l1OriginHash) { + revert L1OriginHashMismatch(l1OriginHash, actualHash); + } + } +} From 307b6fcab88be0c5fdcd7614b27a45a19a5092bb Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Thu, 5 Feb 2026 22:32:55 +0000 Subject: [PATCH 046/125] fix(TEEVerifier): use correct EIP-2935 address and raw calldata for blockhash lookups --- scripts/multiproof/DeployAllForTesting.s.sol | 27 ++++++++++++++++++++ src/multiproof/tee/TEEVerifier.sol | 17 ++++++------ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/scripts/multiproof/DeployAllForTesting.s.sol b/scripts/multiproof/DeployAllForTesting.s.sol index ee81e62e..c455e1b2 100644 --- a/scripts/multiproof/DeployAllForTesting.s.sol +++ b/scripts/multiproof/DeployAllForTesting.s.sol @@ -14,6 +14,7 @@ import {TEEVerifier} from "../src/tee/TEEVerifier.sol"; import {AggregateVerifier} from "../src/AggregateVerifier.sol"; import {IVerifier} from "../src/interfaces/IVerifier.sol"; import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; +import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; import {GameType, Hash} from "optimism/src/dispute/lib/Types.sol"; @@ -81,6 +82,25 @@ contract MockAnchorStateRegistry { } } +/// @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 + function withdraw(address recipient, uint256 amount) external { + payable(recipient).transfer(amount); + } + + /// @notice Allow contract to receive ETH + receive() external payable {} +} + // Import the REAL DisputeGameFactory import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; @@ -122,6 +142,7 @@ contract DeployAllForTesting is Script { address public teeVerifier; address public disputeGameFactory; address public mockAnchorRegistry; + address public mockDelayedWETH; address public aggregateVerifier; function _loadConfig() internal view returns (DeployConfig memory cfg) { @@ -223,11 +244,16 @@ contract DeployAllForTesting is Script { address zkVerifier = address(new MockVerifier()); console.log("MockVerifier (ZK):", zkVerifier); + // 6.5. Mock DelayedWETH for bond handling + mockDelayedWETH = address(new MockDelayedWETH()); + console.log("MockDelayedWETH:", mockDelayedWETH); + // 7. AggregateVerifier aggregateVerifier = address( new AggregateVerifier( cfg.gameType, IAnchorStateRegistry(mockAnchorRegistry), + IDelayedWETH(payable(mockDelayedWETH)), IVerifier(teeVerifier), IVerifier(zkVerifier), cfg.teeImageHash, @@ -258,6 +284,7 @@ contract DeployAllForTesting is Script { console.log("\nInfrastructure:"); console.log(" DisputeGameFactory (real):", disputeGameFactory); console.log(" AnchorStateRegistry (mock):", mockAnchorRegistry); + console.log(" DelayedWETH (mock):", mockDelayedWETH); console.log("\nGame:"); console.log(" AggregateVerifier:", aggregateVerifier); console.log(" Game Type:", gameType); diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 2441804f..0c10c84a 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -5,12 +5,6 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {IVerifier} from "../interfaces/IVerifier.sol"; import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; -/// @title IEIP2935 -/// @notice Interface for the EIP-2935 blockhash history contract. -interface IEIP2935 { - function get(uint256 blockNumber) external view returns (bytes32); -} - /// @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. @@ -28,7 +22,7 @@ contract TEEVerifier is IVerifier { /// @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 = 0x0F792be4B0c0cb4DAE440Ef133E90C0eCD48CCCC; + address public constant EIP2935_CONTRACT = 0x0000F90827F1C53a10cb7A02335B175320002935; /// @notice The maximum number of blocks that blockhash() can look back. uint256 public constant BLOCKHASH_WINDOW = 256; @@ -115,7 +109,14 @@ contract TEEVerifier is IVerifier { if (block.number > l1OriginNumber && block.number - l1OriginNumber <= BLOCKHASH_WINDOW) { actualHash = blockhash(l1OriginNumber); } else if (block.number > l1OriginNumber && block.number - l1OriginNumber <= EIP2935_WINDOW) { - actualHash = IEIP2935(EIP2935_CONTRACT).get(l1OriginNumber); + // 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); } From 98693e521c99144ecd2ce7648e5813b13a677b1b Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sat, 7 Feb 2026 01:55:24 +0000 Subject: [PATCH 047/125] Refactor TEE contracts and deployment script based on PR review feedback --- interfaces/mutliproof/IVerifier.sol | 2 +- scripts/multiproof/DeployAllForTesting.s.sol | 162 ++++-------------- .../multiproof/mocks/MinimalProxyAdmin.sol | 14 ++ .../mocks/MockAnchorStateRegistry.sol | 83 +++++++++ scripts/multiproof/mocks/MockDelayedWETH.sol | 23 +++ src/multiproof/tee/DevSystemConfigGlobal.sol | 23 +++ src/multiproof/tee/SystemConfigGlobal.sol | 85 ++++----- src/multiproof/tee/TEEVerifier.sol | 53 +++--- 8 files changed, 245 insertions(+), 200 deletions(-) create mode 100644 scripts/multiproof/mocks/MinimalProxyAdmin.sol create mode 100644 scripts/multiproof/mocks/MockAnchorStateRegistry.sol create mode 100644 scripts/multiproof/mocks/MockDelayedWETH.sol create mode 100644 src/multiproof/tee/DevSystemConfigGlobal.sol diff --git a/interfaces/mutliproof/IVerifier.sol b/interfaces/mutliproof/IVerifier.sol index 65d5df54..d6a6b874 100644 --- a/interfaces/mutliproof/IVerifier.sol +++ b/interfaces/mutliproof/IVerifier.sol @@ -2,5 +2,5 @@ pragma solidity 0.8.15; interface IVerifier { - function verify(bytes calldata proofBytes, bytes32 imageID, bytes32 journal) external view returns (bool); + function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view returns (bool); } diff --git a/scripts/multiproof/DeployAllForTesting.s.sol b/scripts/multiproof/DeployAllForTesting.s.sol index c455e1b2..8719d5bd 100644 --- a/scripts/multiproof/DeployAllForTesting.s.sol +++ b/scripts/multiproof/DeployAllForTesting.s.sol @@ -1,130 +1,41 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +import {CertManager} from "@nitro-validator/CertManager.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 {stdJson} from "forge-std/StdJson.sol"; - -// TEE contracts -import {CertManager} from "@nitro-validator/CertManager.sol"; -import {SystemConfigGlobal} from "../src/tee/SystemConfigGlobal.sol"; -import {TEEVerifier} from "../src/tee/TEEVerifier.sol"; - -// Dispute game -import {AggregateVerifier} from "../src/AggregateVerifier.sol"; -import {IVerifier} from "../src/interfaces/IVerifier.sol"; +import {console2 as console} from "forge-std/console2.sol"; import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; import {GameType, Hash} from "optimism/src/dispute/lib/Types.sol"; -// Mocks +import {AggregateVerifier} from "../src/AggregateVerifier.sol"; +import {IVerifier} from "../src/interfaces/IVerifier.sol"; import {MockVerifier} from "../src/mocks/MockVerifier.sol"; +import {DevSystemConfigGlobal} from "../src/tee/DevSystemConfigGlobal.sol"; +import {SystemConfigGlobal} from "../src/tee/SystemConfigGlobal.sol"; +import {TEEVerifier} from "../src/tee/TEEVerifier.sol"; -// Proxy - using OpenZeppelin for simpler deployment -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.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; - - function initialize(address _factory, Hash _anchorRoot, uint256 _anchorL2BlockNumber, GameType _gameType) external { - factory = _factory; - anchorRoot = _anchorRoot; - anchorL2BlockNumber = _anchorL2BlockNumber; - respectedGameType = _gameType; - } - - // This is the key function AggregateVerifier calls - function getAnchorRoot() external view returns (Hash, uint256) { - return (anchorRoot, anchorL2BlockNumber); - } - - function disputeGameFactory() external view returns (address) { - return factory; - } - - function setRespectedGameType(GameType _gameType) external { - respectedGameType = _gameType; - } - - /// @notice Update the anchor state (for testing purposes) - function setAnchorState(Hash _anchorRoot, uint256 _anchorL2BlockNumber) external { - anchorRoot = _anchorRoot; - anchorL2BlockNumber = _anchorL2BlockNumber; - } - - // Stub implementations that AggregateVerifier may call - function isGameRegistered(IDisputeGame) external pure returns (bool) { - return true; - } - - function isGameBlacklisted(IDisputeGame) external pure returns (bool) { - return false; - } - - function isGameRetired(IDisputeGame) external pure returns (bool) { - return false; - } - - function isGameRespected(IDisputeGame) external pure returns (bool) { - return true; - } -} - -/// @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 - function withdraw(address recipient, uint256 amount) external { - payable(recipient).transfer(amount); - } - - /// @notice Allow contract to receive ETH - receive() external payable {} -} - -// Import the REAL DisputeGameFactory -import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; - -/// @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 _owner) { - owner = _owner; - } -} +import {MinimalProxyAdmin} from "./mocks/MinimalProxyAdmin.sol"; +import {MockAnchorStateRegistry} from "./mocks/MockAnchorStateRegistry.sol"; +import {MockDelayedWETH} from "./mocks/MockDelayedWETH.sol"; /// @title DeployAllForTesting /// @notice Deploys everything needed for e2e testing, using mocks for optimism contracts. +/// @dev Uses the REAL DisputeGameFactory but mocks AnchorStateRegistry and DelayedWETH. contract DeployAllForTesting is Script { using stdJson for string; + /// @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 INIT_BOND = 0.001 ether; - // Config struct to reduce stack variables + /// @notice Config struct to reduce stack variables. struct DeployConfig { address owner; bytes32 teeImageHash; @@ -145,20 +56,6 @@ contract DeployAllForTesting is Script { address public mockDelayedWETH; address public aggregateVerifier; - function _loadConfig() internal view returns (DeployConfig memory cfg) { - string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia.json")); - string memory config = vm.readFile(configPath); - - cfg.owner = config.readAddress(".finalSystemOwner"); - cfg.teeImageHash = config.readBytes32(".teeImageHash"); - cfg.teeProposer = config.readAddressOr(".teeProposer", cfg.owner); - cfg.gameTypeRaw = config.readUintOr(".gameType", 621); - cfg.gameType = GameType.wrap(uint32(cfg.gameTypeRaw)); - cfg.genesisOutputRoot = config.readBytes32Or(".genesisOutputRoot", bytes32(uint256(1))); - cfg.genesisBlockNumber = config.readUintOr(".genesisBlockNumber", 0); - cfg.configHash = config.readBytes32Or(".configHash", bytes32(0)); - } - function run() public { DeployConfig memory cfg = _loadConfig(); @@ -180,13 +77,27 @@ contract DeployAllForTesting is Script { _writeOutput(); } + function _loadConfig() internal view returns (DeployConfig memory cfg) { + string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia.json")); + string memory config = vm.readFile(configPath); + + cfg.owner = config.readAddress(".finalSystemOwner"); + cfg.teeImageHash = config.readBytes32(".teeImageHash"); + cfg.teeProposer = config.readAddressOr(".teeProposer", cfg.owner); + cfg.gameTypeRaw = config.readUintOr(".gameType", 621); + cfg.gameType = GameType.wrap(uint32(cfg.gameTypeRaw)); + cfg.genesisOutputRoot = config.readBytes32Or(".genesisOutputRoot", bytes32(uint256(1))); + cfg.genesisBlockNumber = config.readUintOr(".genesisBlockNumber", 0); + cfg.configHash = config.readBytes32Or(".configHash", bytes32(0)); + } + function _deployTEEContracts(address owner) internal { // 1. CertManager certManager = address(new CertManager()); console.log("CertManager:", certManager); - // 2. SystemConfigGlobal with proxy - address scgImpl = address(new SystemConfigGlobal(CertManager(certManager))); + // 2. DevSystemConfigGlobal (dev version) with proxy + address scgImpl = address(new DevSystemConfigGlobal(CertManager(certManager))); systemConfigGlobalProxy = address( new TransparentUpgradeableProxy( scgImpl, @@ -194,16 +105,13 @@ contract DeployAllForTesting is Script { abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) ) ); - console.log("SystemConfigGlobal:", systemConfigGlobalProxy); + console.log("DevSystemConfigGlobal:", systemConfigGlobalProxy); // 3. TEEVerifier teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); console.log("TEEVerifier:", teeVerifier); } - // Constant from Optimism's Constants.sol - the storage slot for proxy admin - bytes32 constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; - function _deployInfrastructure(DeployConfig memory cfg) internal { // 4. REAL DisputeGameFactory (behind proxy) // The constructor calls _disableInitializers(), so we must use a proxy @@ -279,7 +187,7 @@ contract DeployAllForTesting is Script { console.log("========================================"); console.log("\nTEE Contracts:"); console.log(" CertManager:", certManager); - console.log(" SystemConfigGlobal:", systemConfigGlobalProxy); + console.log(" DevSystemConfigGlobal:", systemConfigGlobalProxy); console.log(" TEEVerifier:", teeVerifier); console.log("\nInfrastructure:"); console.log(" DisputeGameFactory (real):", disputeGameFactory); diff --git a/scripts/multiproof/mocks/MinimalProxyAdmin.sol b/scripts/multiproof/mocks/MinimalProxyAdmin.sol new file mode 100644 index 00000000..da9d950e --- /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 _owner) { + owner = _owner; + } +} diff --git a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol new file mode 100644 index 00000000..5b9532f9 --- /dev/null +++ b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {GameType, Hash} from "optimism/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 _factory The dispute game factory address. + /// @param _anchorRoot The initial anchor root. + /// @param _anchorL2BlockNumber The initial anchor L2 block number. + /// @param _gameType The respected game type. + function initialize(address _factory, Hash _anchorRoot, uint256 _anchorL2BlockNumber, GameType _gameType) external { + factory = _factory; + anchorRoot = _anchorRoot; + anchorL2BlockNumber = _anchorL2BlockNumber; + 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 _anchorRoot The new anchor root. + /// @param _anchorL2BlockNumber The new anchor L2 block number. + function setAnchorState(Hash _anchorRoot, uint256 _anchorL2BlockNumber) external { + anchorRoot = _anchorRoot; + anchorL2BlockNumber = _anchorL2BlockNumber; + } + + /// @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 00000000..a82c6724 --- /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/src/multiproof/tee/DevSystemConfigGlobal.sol b/src/multiproof/tee/DevSystemConfigGlobal.sol new file mode 100644 index 00000000..92d68c30 --- /dev/null +++ b/src/multiproof/tee/DevSystemConfigGlobal.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {ICertManager} from "@nitro-validator/ICertManager.sol"; + +import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; + +/// @title DevSystemConfigGlobal +/// @notice Development version of SystemConfigGlobal with bypassed attestation for testing. +/// @dev This contract adds addDevSigner() which bypasses AWS Nitro attestation verification. +/// DO NOT deploy this contract to production networks. +contract DevSystemConfigGlobal is SystemConfigGlobal { + constructor(ICertManager certManager) SystemConfigGlobal(certManager) {} + + /// @notice Registers a signer for testing (bypasses attestation verification). + /// @dev Only callable by owner. For development/testing use only. + /// @param signer The address of the signer to register. + /// @param pcr0Hash The PCR0 hash to associate with this signer. + function addDevSigner(address signer, bytes32 pcr0Hash) external onlyOwner { + signerPCR0[signer] = pcr0Hash; + emit SignerRegistered(signer, pcr0Hash); + } +} diff --git a/src/multiproof/tee/SystemConfigGlobal.sol b/src/multiproof/tee/SystemConfigGlobal.sol index 51a33569..a6d487e1 100644 --- a/src/multiproof/tee/SystemConfigGlobal.sol +++ b/src/multiproof/tee/SystemConfigGlobal.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {OwnableManagedUpgradeable} from "@op-enclave/OwnableManagedUpgradeable.sol"; -import {NitroValidator} from "@nitro-validator/NitroValidator.sol"; -import {LibBytes} from "@nitro-validator/LibBytes.sol"; import {LibCborElement, CborElement, CborDecode} from "@nitro-validator/CborDecode.sol"; import {ICertManager} from "@nitro-validator/ICertManager.sol"; +import {LibBytes} from "@nitro-validator/LibBytes.sol"; +import {NitroValidator} from "@nitro-validator/NitroValidator.sol"; +import {OwnableManagedUpgradeable} from "@op-enclave/OwnableManagedUpgradeable.sol"; /// @title SystemConfigGlobal /// @notice Manages TEE signer registration via AWS Nitro attestation. @@ -29,7 +29,7 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { /// @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. - /// This replaces the old validSigners(address => bool) mapping to enable imageID validation. + /// This replaces the old validSigners(address => bool) mapping to enable imageId validation. mapping(address => bytes32) public signerPCR0; /// @notice Emitted when a signer is registered. @@ -44,33 +44,26 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { /// @notice Emitted when a PCR0 is deregistered. event PCR0Deregistered(bytes32 indexed pcr0Hash); - /// @notice Semantic version. - /// @custom:semver 0.1.0 - function version() public pure virtual returns (string memory) { - return "0.1.0"; - } + /// @notice Emitted when the proposer is set. + event ProposerSet(address indexed proposer); - constructor(ICertManager certManager) NitroValidator(certManager) { - // On test networks, skip auto-initialization to allow manual initialization. - // On production, disable the implementation by setting dead addresses. - bool isTestnet = block.chainid == 31337 // Anvil - || block.chainid == 1337 // Ganache - || block.chainid == 11155111 // Sepolia - || block.chainid == 84532; // Base Sepolia - if (!isTestnet) { - initialize({_owner: address(0xdEaD), _manager: address(0xdEaD)}); - } - } + /// @notice Thrown when the PCR0 in the attestation is not registered as valid. + error InvalidPCR0(); - function initialize(address _owner, address _manager) public initializer { - __OwnableManaged_init(); - transferOwnership(_owner); - transferManagement(_manager); + /// @notice Thrown when the attestation document is too old. + error AttestationTooOld(); + + constructor(ICertManager certManager) NitroValidator(certManager) { + // Always disable the implementation contract by setting dead addresses. + // Proxies will call initialize() to set the real owner/manager. + initialize({initialOwner: address(0xdEaD), initialManager: address(0xdEaD)}); } /// @notice Sets the proposer address. - function setProposer(address _proposer) external onlyOwner { - proposer = _proposer; + /// @param newProposer The new proposer address. + function setProposer(address newProposer) external onlyOwner { + proposer = newProposer; + emit ProposerSet(newProposer); } /// @notice Registers a PCR0 (enclave image hash) as valid. @@ -99,9 +92,8 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { function registerSigner(bytes calldata attestationTbs, bytes calldata signature) external onlyOwnerOrManager { Ptrs memory ptrs = validateAttestation(attestationTbs, signature); bytes32 pcr0Hash = attestationTbs.keccak(ptrs.pcrs[0]); - require(validPCR0s[pcr0Hash], "invalid pcr0 in attestation"); - - require(ptrs.timestamp + MAX_AGE > block.timestamp, "attestation too old"); + if (!validPCR0s[pcr0Hash]) revert InvalidPCR0(); + if (ptrs.timestamp + MAX_AGE <= block.timestamp) revert AttestationTooOld(); // The publicKey is encoded in the form specified in section 4.3.6 of ANSI X9.62, // which is a 0x04 byte followed by the x and y coordinates of the public key. @@ -109,7 +101,7 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { bytes32 publicKeyHash = attestationTbs.keccak(ptrs.publicKey.start() + 1, ptrs.publicKey.length() - 1); address enclaveAddress = address(uint160(uint256(publicKeyHash))); - // Store the PCR0 hash for this signer (enables imageID validation) + // Store the PCR0 hash for this signer (enables imageId validation) signerPCR0[enclaveAddress] = pcr0Hash; emit SignerRegistered(enclaveAddress, pcr0Hash); } @@ -121,28 +113,25 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { emit SignerDeregistered(signer); } - /// @notice Registers a signer for testing (bypasses attestation verification). - /// @dev Only callable by owner, only works on test networks. - /// Allowed chains: Anvil (31337), Ganache (1337), Sepolia (11155111), Base Sepolia (84532). - /// DO NOT deploy this to production networks. - /// @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 { - require( - block.chainid == 31337 // Anvil - || block.chainid == 1337 // Ganache - || block.chainid == 11155111 // Sepolia - || block.chainid == 84532, // Base Sepolia - "dev only: test chains only" - ); - signerPCR0[signer] = pcr0Hash; - emit SignerRegistered(signer, pcr0Hash); - } - /// @notice Checks if an address is a valid signer. /// @param signer The address to check. /// @return True if the signer is registered, false otherwise. function isValidSigner(address signer) external view returns (bool) { return signerPCR0[signer] != bytes32(0); } + + /// @notice Initializes the contract with owner and manager. + /// @param initialOwner The initial owner address. + /// @param initialManager The initial manager address. + function initialize(address initialOwner, address initialManager) public initializer { + __OwnableManaged_init(); + transferOwnership(initialOwner); + transferManagement(initialManager); + } + + /// @notice Semantic version. + /// @custom:semver 0.1.0 + function version() public pure virtual returns (string memory) { + return "0.1.0"; + } } diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 0c10c84a..c73bb439 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -2,23 +2,21 @@ pragma solidity 0.8.15; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + import {IVerifier} from "../interfaces/IVerifier.sol"; + import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; /// @title TEEVerifier /// @notice Stateless TEE proof verifier that validates signatures against registered signers. /// @dev This contract is designed to be used as the TEE_VERIFIER in the AggregateVerifier. /// It verifies that proofs are signed by enclave addresses registered in SystemConfigGlobal -/// via AWS Nitro attestation, and that the signer's PCR0 matches the claimed imageID. +/// via AWS Nitro attestation, and that the signer's PCR0 matches the claimed imageId. /// Additionally, it verifies that the L1 origin block referenced in the proof actually exists /// by checking against blockhash() or the EIP-2935 history contract. /// The contract is intentionally stateless - all state related to output proposals is /// managed by the calling contract (e.g., AggregateVerifier). contract TEEVerifier is IVerifier { - /// @notice The SystemConfigGlobal contract that manages valid TEE signers. - /// @dev Signers are registered via AWS Nitro attestation in SystemConfigGlobal. - SystemConfigGlobal public immutable systemConfigGlobal; - /// @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. @@ -30,11 +28,9 @@ contract TEEVerifier is IVerifier { /// @notice The maximum number of blocks that EIP-2935 can look back (~8192). uint256 public constant EIP2935_WINDOW = 8191; - /// @notice Emitted when a proof is successfully verified. - /// @param signer The address that signed the proof. - /// @param imageID The image ID (PCR0 hash) that was validated. - /// @param journal The journal hash that was signed. - event ProofVerified(address indexed signer, bytes32 indexed imageID, bytes32 indexed journal); + /// @notice The SystemConfigGlobal contract that manages valid TEE signers. + /// @dev Signers are registered via AWS Nitro attestation in SystemConfigGlobal. + SystemConfigGlobal public immutable SYSTEM_CONFIG_GLOBAL; /// @notice Thrown when the recovered signer is not a valid registered signer. error InvalidSigner(address signer); @@ -42,12 +38,15 @@ contract TEEVerifier is IVerifier { /// @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 signer's registered PCR0 does not match the claimed imageId. + error ImageIdMismatch(bytes32 signerPCR0, bytes32 claimedImageId); /// @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); @@ -55,17 +54,17 @@ contract TEEVerifier is IVerifier { error InvalidProofFormat(); /// @notice Constructs the TEEVerifier contract. - /// @param _systemConfigGlobal The SystemConfigGlobal contract address. - constructor(SystemConfigGlobal _systemConfigGlobal) { - systemConfigGlobal = _systemConfigGlobal; + /// @param systemConfigGlobal The SystemConfigGlobal contract address. + constructor(SystemConfigGlobal systemConfigGlobal) { + SYSTEM_CONFIG_GLOBAL = systemConfigGlobal; } /// @notice Verifies a TEE proof for a state transition. /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + signature (65) = 129 bytes. - /// @param imageID The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. + /// @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. /// @param journal The keccak256 hash of the proof's public inputs. /// @return valid Whether the proof is valid. - function verify(bytes calldata proofBytes, bytes32 imageID, bytes32 journal) external view override returns (bool) { + function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view override returns (bool) { if (proofBytes.length < 129) revert InvalidProofFormat(); bytes32 l1OriginHash = bytes32(proofBytes[0:32]); @@ -84,17 +83,17 @@ contract TEEVerifier is IVerifier { } // Get the PCR0 the signer was registered with - bytes32 registeredPCR0 = systemConfigGlobal.signerPCR0(signer); + bytes32 registeredPCR0 = SYSTEM_CONFIG_GLOBAL.signerPCR0(signer); // Check that the signer is registered (PCR0 != 0) if (registeredPCR0 == bytes32(0)) { revert InvalidSigner(signer); } - // Check that the signer's registered PCR0 matches the claimed imageID + // 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); + if (registeredPCR0 != imageId) { + revert ImageIdMismatch(registeredPCR0, imageId); } return true; @@ -103,12 +102,18 @@ contract TEEVerifier is IVerifier { /// @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 { + function _verifyL1Origin(bytes32 l1OriginHash, uint256 l1OriginNumber) private view { + // Check for future block + if (l1OriginNumber >= block.number) { + revert L1OriginInFuture(l1OriginNumber, block.number); + } + bytes32 actualHash; + uint256 blockAge = block.number - l1OriginNumber; - if (block.number > l1OriginNumber && block.number - l1OriginNumber <= BLOCKHASH_WINDOW) { + if (blockAge <= BLOCKHASH_WINDOW) { actualHash = blockhash(l1OriginNumber); - } else if (block.number > l1OriginNumber && block.number - l1OriginNumber <= EIP2935_WINDOW) { + } 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. From b9e101d0a30ede4fc751a8b21f0aa34351d1483e Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sat, 7 Feb 2026 01:55:24 +0000 Subject: [PATCH 048/125] Refactor TEE contracts and deployment script based on PR review feedback --- patch/optimism.patch | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/patch/optimism.patch b/patch/optimism.patch index 71d22f44..a1823132 100644 --- a/patch/optimism.patch +++ b/patch/optimism.patch @@ -10,30 +10,47 @@ index a3bb74b..bc998e1 100644 + function initialize(bytes calldata initData) external payable; } diff --git a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol -index eb92ca5..c3521f7 100644 +index eb92ca5..abcdef1 100644 --- a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol +++ b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol @@ -143,11 +143,13 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable /// @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. -+ /// @param initData The initialization data for the DisputeGame. ++ /// @param _initData The initialization data for the DisputeGame. /// @return proxy_ The address of the created DisputeGame proxy. function create( GameType _gameType, Claim _rootClaim, - bytes calldata _extraData + bytes calldata _extraData, -+ bytes calldata initData ++ bytes calldata _initData ) external payable -@@ -198,7 +200,7 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable - ) +@@ -165,7 +167,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: +@@ -193,12 +197,10 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable + // │ [88 + n, 88 + n + m) │ Implementation args (opaque) │ + // └──────────────────────┴─────────────────────────────────────┘ + proxy_ = IDisputeGame( +- address(impl).clone( +- abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, gameArgs[_gameType]) +- ) ++ address(impl).clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) ); } - proxy_.initialize{ value: msg.value }(); -+ proxy_.initialize{value: msg.value}(initData); ++ proxy_.initialize{ value: msg.value }(_initData); // Compute the unique identifier for the dispute game. Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData); From 9ff1818226e78f69e5d71a0a368a9a47fb3f1513 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sat, 7 Feb 2026 02:36:05 +0000 Subject: [PATCH 049/125] Remove underscore-prefixed parameters from mock contracts per style guide --- .../multiproof/mocks/MinimalProxyAdmin.sol | 4 +- .../mocks/MockAnchorStateRegistry.sol | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/scripts/multiproof/mocks/MinimalProxyAdmin.sol b/scripts/multiproof/mocks/MinimalProxyAdmin.sol index da9d950e..ad46f7a4 100644 --- a/scripts/multiproof/mocks/MinimalProxyAdmin.sol +++ b/scripts/multiproof/mocks/MinimalProxyAdmin.sol @@ -8,7 +8,7 @@ pragma solidity 0.8.15; contract MinimalProxyAdmin { address public owner; - constructor(address _owner) { - owner = _owner; + constructor(address initialOwner) { + owner = initialOwner; } } diff --git a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol index 5b9532f9..894142ab 100644 --- a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol +++ b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol @@ -20,15 +20,20 @@ contract MockAnchorStateRegistry { GameType public respectedGameType; /// @notice Initializes the mock registry. - /// @param _factory The dispute game factory address. - /// @param _anchorRoot The initial anchor root. - /// @param _anchorL2BlockNumber The initial anchor L2 block number. - /// @param _gameType The respected game type. - function initialize(address _factory, Hash _anchorRoot, uint256 _anchorL2BlockNumber, GameType _gameType) external { - factory = _factory; - anchorRoot = _anchorRoot; - anchorL2BlockNumber = _anchorL2BlockNumber; - respectedGameType = _gameType; + /// @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. @@ -44,17 +49,17 @@ contract MockAnchorStateRegistry { } /// @notice Sets the respected game type. - /// @param _gameType The new game type. - function setRespectedGameType(GameType _gameType) external { - respectedGameType = _gameType; + /// @param gameType The new game type. + function setRespectedGameType(GameType gameType) external { + respectedGameType = gameType; } /// @notice Updates the anchor state (for testing purposes). - /// @param _anchorRoot The new anchor root. - /// @param _anchorL2BlockNumber The new anchor L2 block number. - function setAnchorState(Hash _anchorRoot, uint256 _anchorL2BlockNumber) external { - anchorRoot = _anchorRoot; - anchorL2BlockNumber = _anchorL2BlockNumber; + /// @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. From f57741cc3c33161a7d77e3bd234d315c2b4118ee Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sat, 7 Feb 2026 13:42:59 +0000 Subject: [PATCH 050/125] Add comprehensive tests for TEEVerifier, SystemConfigGlobal, and DevSystemConfigGlobal; fix proof format in existing tests --- .../mocks/MockAnchorStateRegistry.sol | 10 +- src/multiproof/mock/MockCertManager.sol | 16 + src/multiproof/tee/TEEVerifier.sol | 1 + test/multiproof/AggregateVerifier.t.sol | 22 +- test/multiproof/BaseTest.t.sol | 16 + test/multiproof/Challenge.t.sol | 26 +- test/multiproof/Nullify.t.sol | 24 +- test/multiproof/SystemConfigGlobal.t.sol | 400 ++++++++++++++++++ test/multiproof/TEEVerifier.t.sol | 245 +++++++++++ 9 files changed, 717 insertions(+), 43 deletions(-) create mode 100644 src/multiproof/mock/MockCertManager.sol create mode 100644 test/multiproof/SystemConfigGlobal.t.sol create mode 100644 test/multiproof/TEEVerifier.t.sol diff --git a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol index 894142ab..272d9fc9 100644 --- a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol +++ b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.15; import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; import {GameType, Hash} from "optimism/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: @@ -24,12 +23,9 @@ contract MockAnchorStateRegistry { /// @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 { + function initialize(address newFactory, Hash newAnchorRoot, uint256 newAnchorL2BlockNumber, GameType gameType) + external + { factory = newFactory; anchorRoot = newAnchorRoot; anchorL2BlockNumber = newAnchorL2BlockNumber; diff --git a/src/multiproof/mock/MockCertManager.sol b/src/multiproof/mock/MockCertManager.sol new file mode 100644 index 00000000..99aa0936 --- /dev/null +++ b/src/multiproof/mock/MockCertManager.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {ICertManager} from "@nitro-validator/ICertManager.sol"; + +/// @title MockCertManager +/// @notice Mock CertManager for testing SystemConfigGlobal. +contract MockCertManager is ICertManager { + function verifyCACert(bytes memory, bytes32) external pure returns (bytes32) { + return bytes32(0); + } + + function verifyClientCert(bytes memory, bytes32) external pure returns (VerifiedCert memory) { + return VerifiedCert({ca: false, notAfter: 0, maxPathLen: 0, subjectHash: bytes32(0), pubKey: ""}); + } +} diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index c73bb439..6d7fe6e7 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -111,6 +111,7 @@ contract TEEVerifier is IVerifier { 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) { diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 8336f027..f51b4603 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -14,7 +14,7 @@ contract AggregateVerifierTest is BaseTest { function testInitializeWithTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = "tee-proof"; + bytes memory proof = _generateProof("tee-proof"); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE @@ -38,7 +38,7 @@ contract AggregateVerifierTest is BaseTest { function testInitializeWithZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = "zk-proof"; + bytes memory proof = _generateProof("zk-proof"); AggregateVerifier game = _createAggregateVerifierGame( ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK @@ -75,7 +75,7 @@ contract AggregateVerifierTest is BaseTest { function testInitializeFailsIfNotTEEProposer() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = "tee-proof"; + bytes memory proof = _generateProof("tee-proof"); vm.expectRevert(AggregateVerifier.NotAuthorized.selector); _createAggregateVerifierGame( @@ -86,7 +86,7 @@ contract AggregateVerifierTest is BaseTest { function testUpdatingAnchorStateRegistryWithTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = "tee-proof"; + bytes memory proof = _generateProof("tee-proof"); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE @@ -120,7 +120,7 @@ contract AggregateVerifierTest is BaseTest { function testUpdatingAnchorStateRegistryWithZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = "zk-proof"; + bytes memory proof = _generateProof("zk-proof"); AggregateVerifier game = _createAggregateVerifierGame( ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK @@ -150,8 +150,8 @@ contract AggregateVerifierTest is BaseTest { function testUpdatingAnchorStateRegistryWithBothProofs() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory teeProof = "tee-proof"; - bytes memory zkProof = "zk-proof"; + bytes memory teeProof = _generateProof("tee-proof"); + bytes memory zkProof = _generateProof("zk-proof"); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE @@ -185,8 +185,8 @@ contract AggregateVerifierTest is BaseTest { function testProofCannotIncreaseExpectedResolution() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory teeProof = "tee-proof"; - bytes memory zkProof = "zk-proof"; + bytes memory teeProof = _generateProof("tee-proof"); + bytes memory zkProof = _generateProof("zk-proof"); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE @@ -216,8 +216,8 @@ contract AggregateVerifierTest is BaseTest { function testCannotCreateSameProposal() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory teeProof = "tee-proof"; - bytes memory zkProof = "zk-proof"; + bytes memory teeProof = _generateProof("tee-proof"); + bytes memory zkProof = _generateProof("zk-proof"); _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 39bf01c3..57714c89 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -166,4 +166,20 @@ contract BaseTest is Test { bytes memory proofBytes = abi.encodePacked(uint8(proofType), proof); game.verifyProof(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. + /// @return proof The formatted proof bytes. + function _generateProof(bytes memory salt) 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(l1OriginHash, l1OriginNumber, signature); + } } diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 1eced497..817a2be1 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -16,7 +16,7 @@ contract ChallengeTest is BaseTest { // Create first game with TEE proof Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = "tee-proof"; + bytes memory teeProof = _generateProof("tee-proof"); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE @@ -24,7 +24,7 @@ contract ChallengeTest is BaseTest { // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory zkProof = "zk-proof"; + bytes memory zkProof = _generateProof("zk-proof"); AggregateVerifier game2 = _createAggregateVerifierGame( ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK @@ -59,7 +59,7 @@ contract ChallengeTest is BaseTest { // Create first game with ZK proof (no TEE proof) Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof1 = "zk-proof-1"; + bytes memory zkProof1 = _generateProof("zk-proof-1"); AggregateVerifier game1 = _createAggregateVerifierGame( ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK @@ -67,7 +67,7 @@ contract ChallengeTest is BaseTest { // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); - bytes memory zkProof2 = "zk-proof-2"; + bytes memory zkProof2 = _generateProof("zk-proof-2"); _createAggregateVerifierGame( ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK @@ -83,7 +83,7 @@ contract ChallengeTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = "tee-proof"; + bytes memory teeProof = _generateProof("tee-proof"); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE @@ -93,7 +93,7 @@ contract ChallengeTest is BaseTest { uint256 game1Index = factory.gameCount() - 1; uint256 nextBlockNumber = currentL2BlockNumber + BLOCK_INTERVAL; Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); - bytes memory zkProof = "zk-proof"; + bytes memory zkProof = _generateProof("zk-proof"); // forge-lint: disable-next-line(unsafe-typecast) _createAggregateVerifierGame( @@ -110,14 +110,14 @@ contract ChallengeTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = "tee-proof-1"; + bytes memory teeProof1 = _generateProof("tee-proof-1"); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof2 = "tee-proof-2"; + bytes memory teeProof2 = _generateProof("tee-proof-2"); _createAggregateVerifierGame( TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE @@ -133,7 +133,7 @@ contract ChallengeTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = "tee-proof"; + bytes memory teeProof = _generateProof("tee-proof"); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE @@ -145,7 +145,7 @@ contract ChallengeTest is BaseTest { // Try to challenge game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof = "zk-proof"; + bytes memory zkProof = _generateProof("zk-proof"); _createAggregateVerifierGame( ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK @@ -161,7 +161,7 @@ contract ChallengeTest is BaseTest { // create parent game Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory parentProof = "parent-proof"; + bytes memory parentProof = _generateProof("parent-proof"); AggregateVerifier parentGame = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, parentProof, AggregateVerifier.ProofType.TEE @@ -172,7 +172,7 @@ contract ChallengeTest is BaseTest { // create child game Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory childProof = "child-proof"; + bytes memory childProof = _generateProof("child-proof"); AggregateVerifier childGame = // forge-lint: disable-next-line(unsafe-typecast) @@ -197,7 +197,7 @@ contract ChallengeTest is BaseTest { function testChallengeFailsIfGameItselfIsBlacklisted() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory proof = "tee-proof"; + bytes memory proof = _generateProof("tee-proof"); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index fc6b6caa..c4309409 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -13,14 +13,14 @@ contract NullifyTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = "tee-proof-1"; + bytes memory teeProof1 = _generateProof("tee-proof-1"); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof2 = "tee-proof-2"; + bytes memory teeProof2 = _generateProof("tee-proof-2"); _createAggregateVerifierGame( TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE @@ -44,14 +44,14 @@ contract NullifyTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof1 = "zk-proof-1"; + bytes memory zkProof1 = _generateProof("zk-proof-1"); AggregateVerifier game1 = _createAggregateVerifierGame( ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); - bytes memory zkProof2 = "zk-proof-2"; + bytes memory zkProof2 = _generateProof("zk-proof-2"); _createAggregateVerifierGame( ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK @@ -75,14 +75,14 @@ contract NullifyTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof = "zk-proof"; + bytes memory zkProof = _generateProof("zk-proof"); AggregateVerifier game1 = _createAggregateVerifierGame( ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof = "tee-proof"; + bytes memory teeProof = _generateProof("tee-proof"); _createAggregateVerifierGame( TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE @@ -97,14 +97,14 @@ contract NullifyTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof = "tee-proof"; + bytes memory teeProof = _generateProof("tee-proof"); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory zkProof = "zk-proof"; + bytes memory zkProof = _generateProof("zk-proof"); _createAggregateVerifierGame( ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK @@ -119,7 +119,7 @@ contract NullifyTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = "tee-proof-1"; + bytes memory teeProof1 = _generateProof("tee-proof-1"); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE @@ -131,7 +131,7 @@ contract NullifyTest is BaseTest { // Try to nullify game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory teeProof2 = "tee-proof-2"; + bytes memory teeProof2 = _generateProof("tee-proof-2"); _createAggregateVerifierGame( TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE @@ -146,7 +146,7 @@ contract NullifyTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = "tee-proof-1"; + bytes memory teeProof1 = _generateProof("tee-proof-1"); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE @@ -154,7 +154,7 @@ contract NullifyTest is BaseTest { // Challenge game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory zkProof = "zk-proof"; + bytes memory zkProof = _generateProof("zk-proof"); _createAggregateVerifierGame( ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol new file mode 100644 index 00000000..5940c926 --- /dev/null +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import {Test} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import {ICertManager} from "@nitro-validator/ICertManager.sol"; + +import {DevSystemConfigGlobal} from "src/tee/DevSystemConfigGlobal.sol"; +import {SystemConfigGlobal} from "src/tee/SystemConfigGlobal.sol"; + +import {MockCertManager} from "src/mocks/MockCertManager.sol"; + +/// @notice Tests for SystemConfigGlobal and DevSystemConfigGlobal contracts. +/// @dev IMPORTANT: This test file uses DevSystemConfigGlobal as the implementation because +/// registering signers on the production SystemConfigGlobal requires valid AWS Nitro attestation +/// documents, which cannot be generated in a test environment. DevSystemConfigGlobal extends +/// SystemConfigGlobal with an `addDevSigner` function that bypasses attestation verification, +/// allowing us to test all signer-related functionality. All tests for base SystemConfigGlobal +/// functionality (PCR0 management, ownership, proposer, etc.) are equally valid since +/// DevSystemConfigGlobal inherits from SystemConfigGlobal without modifying those functions. +contract SystemConfigGlobalTest is Test { + DevSystemConfigGlobal public systemConfigGlobal; + MockCertManager public certManager; + 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); + + function setUp() public { + owner = makeAddr("owner"); + manager = makeAddr("manager"); + unauthorized = makeAddr("unauthorized"); + + pcr0Hash = keccak256(TEST_PCR0); + + // Deploy mock cert manager + certManager = new MockCertManager(); + + // Deploy implementation (using DevSystemConfigGlobal for test flexibility) + DevSystemConfigGlobal impl = new DevSystemConfigGlobal(ICertManager(address(certManager))); + + // Deploy proxy admin + proxyAdmin = new ProxyAdmin(); + + // Deploy proxy + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), address(proxyAdmin), abi.encodeCall(SystemConfigGlobal.initialize, (owner, manager)) + ); + + systemConfigGlobal = DevSystemConfigGlobal(address(proxy)); + } + + // ============ Initialization Tests ============ + + function testInitialization() public view { + assertEq(systemConfigGlobal.owner(), owner); + assertEq(systemConfigGlobal.manager(), manager); + assertEq(systemConfigGlobal.version(), "0.1.0"); + } + + // ============ PCR0 Registration Tests ============ + + function testRegisterPCR0() public { + vm.expectEmit(true, false, false, false); + emit PCR0Registered(pcr0Hash); + + vm.prank(owner); + systemConfigGlobal.registerPCR0(TEST_PCR0); + + assertTrue(systemConfigGlobal.validPCR0s(pcr0Hash)); + } + + function testRegisterPCR0FailsIfNotOwner() public { + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.registerPCR0(TEST_PCR0); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.registerPCR0(TEST_PCR0); + } + + function testDeregisterPCR0() public { + // First register + vm.prank(owner); + systemConfigGlobal.registerPCR0(TEST_PCR0); + assertTrue(systemConfigGlobal.validPCR0s(pcr0Hash)); + + // Then deregister + vm.expectEmit(true, false, false, false); + emit PCR0Deregistered(pcr0Hash); + + vm.prank(owner); + systemConfigGlobal.deregisterPCR0(TEST_PCR0); + + assertFalse(systemConfigGlobal.validPCR0s(pcr0Hash)); + } + + function testDeregisterPCR0FailsIfNotOwner() public { + vm.prank(owner); + systemConfigGlobal.registerPCR0(TEST_PCR0); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.deregisterPCR0(TEST_PCR0); + } + + // ============ Signer Deregistration Tests ============ + + function testDeregisterSignerAsOwner() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + // Add signer via DevSystemConfigGlobal + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, signerPcr0); + + // Verify signer is registered + assertTrue(systemConfigGlobal.isValidSigner(signer)); + + // Deregister as owner + vm.expectEmit(true, false, false, false); + emit SignerDeregistered(signer); + + vm.prank(owner); + systemConfigGlobal.deregisterSigner(signer); + + assertFalse(systemConfigGlobal.isValidSigner(signer)); + assertEq(systemConfigGlobal.signerPCR0(signer), bytes32(0)); + } + + function testDeregisterSignerAsManager() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + // Add signer via DevSystemConfigGlobal + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, signerPcr0); + + assertTrue(systemConfigGlobal.isValidSigner(signer)); + + vm.prank(manager); + systemConfigGlobal.deregisterSigner(signer); + + assertFalse(systemConfigGlobal.isValidSigner(signer)); + } + + function testDeregisterSignerFailsIfUnauthorized() public { + address signer = makeAddr("signer"); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner or the manager"); + systemConfigGlobal.deregisterSigner(signer); + } + + // ============ Proposer Tests ============ + + function testSetProposer() public { + address newProposer = makeAddr("proposer"); + + vm.expectEmit(true, false, false, false); + emit ProposerSet(newProposer); + + vm.prank(owner); + systemConfigGlobal.setProposer(newProposer); + + assertEq(systemConfigGlobal.proposer(), newProposer); + } + + function testSetProposerFailsIfNotOwner() public { + address newProposer = makeAddr("proposer"); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.setProposer(newProposer); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.setProposer(newProposer); + } + + // ============ isValidSigner Tests ============ + + function testIsValidSignerReturnsFalseForUnregistered() public { + address unregistered = makeAddr("unregistered"); + assertFalse(systemConfigGlobal.isValidSigner(unregistered)); + } + + function testIsValidSignerReturnsTrueForRegistered() public { + address signer = makeAddr("signer"); + bytes32 signerPcr0 = keccak256("signer-pcr0"); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, signerPcr0); + + assertTrue(systemConfigGlobal.isValidSigner(signer)); + } + + // ============ signerPCR0 Tests ============ + + function testSignerPCR0ReturnsZeroForUnregistered() public { + address unregistered = makeAddr("unregistered"); + assertEq(systemConfigGlobal.signerPCR0(unregistered), bytes32(0)); + } + + function testSignerPCR0ReturnsCorrectValue() public { + address signer = makeAddr("signer"); + bytes32 expectedPcr0 = keccak256("signer-pcr0"); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, expectedPcr0); + + assertEq(systemConfigGlobal.signerPCR0(signer), expectedPcr0); + } + + // ============ MAX_AGE Tests ============ + + function testMaxAgeConstant() public view { + assertEq(systemConfigGlobal.MAX_AGE(), 60 minutes); + } + + // ============ Ownership Transfer Tests ============ + + function testTransferOwnership() public { + address newOwner = makeAddr("newOwner"); + + vm.prank(owner); + systemConfigGlobal.transferOwnership(newOwner); + + assertEq(systemConfigGlobal.owner(), newOwner); + } + + function testTransferOwnershipFailsIfNotOwner() public { + address newOwner = makeAddr("newOwner"); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.transferOwnership(newOwner); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.transferOwnership(newOwner); + } + + function testTransferOwnershipFailsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert("OwnableManaged: new owner is the zero address"); + systemConfigGlobal.transferOwnership(address(0)); + } + + // ============ Management Transfer Tests ============ + + function testTransferManagementAsOwner() public { + address newManager = makeAddr("newManager"); + + vm.prank(owner); + systemConfigGlobal.transferManagement(newManager); + + assertEq(systemConfigGlobal.manager(), newManager); + } + + function testTransferManagementAsManager() public { + address newManager = makeAddr("newManager"); + + vm.prank(manager); + systemConfigGlobal.transferManagement(newManager); + + assertEq(systemConfigGlobal.manager(), newManager); + } + + function testTransferManagementFailsIfUnauthorized() public { + address newManager = makeAddr("newManager"); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner or the manager"); + systemConfigGlobal.transferManagement(newManager); + } + + function testTransferManagementFailsForZeroAddress() public { + vm.prank(owner); + vm.expectRevert("OwnableManaged: new manager is the zero address"); + systemConfigGlobal.transferManagement(address(0)); + } + + // ============ Renounce Tests ============ + + function testRenounceOwnership() public { + vm.prank(owner); + systemConfigGlobal.renounceOwnership(); + + assertEq(systemConfigGlobal.owner(), address(0)); + } + + function testRenounceManagementAsOwner() public { + vm.prank(owner); + systemConfigGlobal.renounceManagement(); + + assertEq(systemConfigGlobal.manager(), address(0)); + } + + function testRenounceManagementAsManager() public { + vm.prank(manager); + systemConfigGlobal.renounceManagement(); + + assertEq(systemConfigGlobal.manager(), address(0)); + } + + // ============ DevSystemConfigGlobal: addDevSigner Tests ============ + + function testAddDevSigner() public { + address signer = makeAddr("dev-signer"); + bytes32 devPcr0Hash = keccak256("dev-pcr0"); + + vm.expectEmit(true, true, false, false); + emit SignerRegistered(signer, devPcr0Hash); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, devPcr0Hash); + + assertTrue(systemConfigGlobal.isValidSigner(signer)); + assertEq(systemConfigGlobal.signerPCR0(signer), devPcr0Hash); + } + + function testAddDevSignerFailsIfNotOwner() public { + address signer = makeAddr("dev-signer"); + bytes32 devPcr0Hash = keccak256("dev-pcr0"); + + vm.prank(manager); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.addDevSigner(signer, devPcr0Hash); + + vm.prank(unauthorized); + vm.expectRevert("OwnableManaged: caller is not the owner"); + systemConfigGlobal.addDevSigner(signer, devPcr0Hash); + } + + function testAddDevSignerCanOverwriteExisting() public { + address signer = makeAddr("dev-signer"); + bytes32 firstPcr0 = keccak256("first-pcr0"); + bytes32 secondPcr0 = keccak256("second-pcr0"); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, firstPcr0); + assertEq(systemConfigGlobal.signerPCR0(signer), firstPcr0); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, secondPcr0); + assertEq(systemConfigGlobal.signerPCR0(signer), secondPcr0); + } + + function testAddDevSignerWithZeroPcr0() public { + address signer = makeAddr("dev-signer"); + + vm.prank(owner); + systemConfigGlobal.addDevSigner(signer, bytes32(0)); + + // Signer should not be valid because PCR0 is zero + assertFalse(systemConfigGlobal.isValidSigner(signer)); + assertEq(systemConfigGlobal.signerPCR0(signer), bytes32(0)); + } + + function testAddMultipleDevSigners() public { + address signer1 = makeAddr("dev-signer-1"); + address signer2 = makeAddr("dev-signer-2"); + address signer3 = makeAddr("dev-signer-3"); + + bytes32 pcr0Hash1 = keccak256("pcr0-1"); + bytes32 pcr0Hash2 = keccak256("pcr0-2"); + bytes32 pcr0Hash3 = keccak256("pcr0-3"); + + vm.startPrank(owner); + systemConfigGlobal.addDevSigner(signer1, pcr0Hash1); + systemConfigGlobal.addDevSigner(signer2, pcr0Hash2); + systemConfigGlobal.addDevSigner(signer3, pcr0Hash3); + vm.stopPrank(); + + assertTrue(systemConfigGlobal.isValidSigner(signer1)); + assertTrue(systemConfigGlobal.isValidSigner(signer2)); + assertTrue(systemConfigGlobal.isValidSigner(signer3)); + + assertEq(systemConfigGlobal.signerPCR0(signer1), pcr0Hash1); + assertEq(systemConfigGlobal.signerPCR0(signer2), pcr0Hash2); + assertEq(systemConfigGlobal.signerPCR0(signer3), pcr0Hash3); + } +} diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol new file mode 100644 index 00000000..5b66e7ac --- /dev/null +++ b/test/multiproof/TEEVerifier.t.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import {Test} from "forge-std/Test.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import {ICertManager} from "@nitro-validator/ICertManager.sol"; + +import {DevSystemConfigGlobal} from "src/tee/DevSystemConfigGlobal.sol"; +import {SystemConfigGlobal} from "src/tee/SystemConfigGlobal.sol"; +import {TEEVerifier} from "src/tee/TEEVerifier.sol"; + +import {MockCertManager} from "src/mocks/MockCertManager.sol"; + +contract TEEVerifierTest is Test { + TEEVerifier public verifier; + DevSystemConfigGlobal public systemConfigGlobal; + MockCertManager public certManager; + ProxyAdmin public proxyAdmin; + + // Test signer - we'll derive address from private key + uint256 internal constant SIGNER_PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + address internal signerAddress; + + bytes32 internal constant PCR0_HASH = keccak256("test-pcr0"); + bytes32 internal constant IMAGE_ID = PCR0_HASH; // imageId must match PCR0 hash + + address internal owner; + + function setUp() public { + owner = address(this); + + // Derive signer address from private key + signerAddress = vm.addr(SIGNER_PRIVATE_KEY); + + // Deploy mock cert manager + certManager = new MockCertManager(); + + // Deploy implementation + DevSystemConfigGlobal impl = new DevSystemConfigGlobal(ICertManager(address(certManager))); + + // Deploy proxy admin + proxyAdmin = new ProxyAdmin(); + + // Deploy proxy + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), address(proxyAdmin), abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) + ); + + systemConfigGlobal = DevSystemConfigGlobal(address(proxy)); + + // Register the signer with PCR0 hash + systemConfigGlobal.addDevSigner(signerAddress, PCR0_HASH); + + // Deploy TEEVerifier + verifier = new TEEVerifier(SystemConfigGlobal(address(systemConfigGlobal))); + } + + function testVerifyValidSignature() public { + // Create a journal hash + bytes32 journal = keccak256("test-journal"); + + // Get current block info for L1 origin + uint256 l1OriginNumber = block.number - 1; + bytes32 l1OriginHash = blockhash(l1OriginNumber); + + // 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: l1OriginHash (32) + l1OriginNumber (32) + signature (65) + bytes memory proofBytes = abi.encodePacked(l1OriginHash, l1OriginNumber, signature); + + // Verify should return true + bool result = verifier.verify(proofBytes, IMAGE_ID, journal); + assertTrue(result); + } + + function testVerifyFailsWithInvalidSignature() public { + bytes32 journal = keccak256("test-journal"); + + uint256 l1OriginNumber = block.number - 1; + bytes32 l1OriginHash = blockhash(l1OriginNumber); + + // 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(l1OriginHash, l1OriginNumber, invalidSignature); + + vm.expectRevert(TEEVerifier.InvalidSignature.selector); + 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"); + + uint256 l1OriginNumber = block.number - 1; + bytes32 l1OriginHash = blockhash(l1OriginNumber); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(unregisteredKey, journal); + bytes memory signature = abi.encodePacked(r, s, v); + + bytes memory proofBytes = abi.encodePacked(l1OriginHash, l1OriginNumber, signature); + + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidSigner.selector, unregisteredSigner)); + verifier.verify(proofBytes, IMAGE_ID, journal); + } + + function testVerifyFailsWithImageIdMismatch() public { + bytes32 journal = keccak256("test-journal"); + + uint256 l1OriginNumber = block.number - 1; + bytes32 l1OriginHash = blockhash(l1OriginNumber); + + (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(l1OriginHash, l1OriginNumber, 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 testVerifyFailsWithL1OriginInFuture() public { + bytes32 journal = keccak256("test-journal"); + + // Use a future block number + uint256 l1OriginNumber = block.number + 1; + bytes32 l1OriginHash = bytes32(uint256(1)); // Fake hash + + (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(l1OriginHash, l1OriginNumber, signature); + + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number)); + verifier.verify(proofBytes, IMAGE_ID, journal); + } + + function testVerifyFailsWithL1OriginTooOld() public { + // Roll forward many blocks to make old blocks unavailable + vm.roll(block.number + 300); + + bytes32 journal = keccak256("test-journal"); + + // 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 + + (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(l1OriginHash, l1OriginNumber, signature); + + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); + verifier.verify(proofBytes, IMAGE_ID, journal); + } + + function testVerifyFailsWithL1OriginHashMismatch() public { + bytes32 journal = keccak256("test-journal"); + + uint256 l1OriginNumber = block.number - 1; + bytes32 wrongHash = bytes32(uint256(0xdeadbeef)); // Wrong hash + + (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(wrongHash, l1OriginNumber, signature); + + bytes32 actualHash = blockhash(l1OriginNumber); + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash)); + verifier.verify(proofBytes, IMAGE_ID, journal); + } + + function testVerifyFailsWithInvalidProofFormat() public { + bytes32 journal = keccak256("test-journal"); + + // Proof too short (less than 129 bytes) + bytes memory shortProof = new bytes(100); + + vm.expectRevert(TEEVerifier.InvalidProofFormat.selector); + verifier.verify(shortProof, IMAGE_ID, journal); + } + + function testVerifyWithBlockhashWindow() public { + // Test verification within the 256 block window + vm.roll(block.number + 100); + + bytes32 journal = keccak256("test-journal"); + + // Use a block that's within the 256 block window + uint256 l1OriginNumber = block.number - 50; + bytes32 l1OriginHash = blockhash(l1OriginNumber); + + (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(l1OriginHash, l1OriginNumber, signature); + + bool result = verifier.verify(proofBytes, IMAGE_ID, journal); + assertTrue(result); + } + + function testVerifyWithEIP2935Window() public { + // Roll forward past the 256 blockhash window + vm.roll(block.number + 300); + + bytes32 journal = keccak256("test-journal"); + + // 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)); + + // Mock the EIP-2935 contract response + vm.mockCall( + verifier.EIP2935_CONTRACT(), + abi.encode(l1OriginNumber), // raw 32-byte calldata + abi.encode(expectedHash) // returns the blockhash + ); + + (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(expectedHash, l1OriginNumber, signature); + + bool result = verifier.verify(proofBytes, IMAGE_ID, journal); + assertTrue(result); + } + + function testConstants() public view { + assertEq(verifier.EIP2935_CONTRACT(), 0x0000F90827F1C53a10cb7A02335B175320002935); + assertEq(verifier.BLOCKHASH_WINDOW(), 256); + assertEq(verifier.EIP2935_WINDOW(), 8191); + assertEq(address(verifier.SYSTEM_CONFIG_GLOBAL()), address(systemConfigGlobal)); + } +} From 01cd4425ba0bc287c0d167aabf3c71110977d8e2 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sun, 8 Feb 2026 17:22:41 +0000 Subject: [PATCH 051/125] Update teeProposer to match proposer address --- deploy-config/sepolia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index edd38e2a..173a04e6 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia.json @@ -1,7 +1,7 @@ { "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33", "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", - "teeProposer": "0x74252ce52515d60dae157F90232AEc557A8DAB57", + "teeProposer": "0x45efd28cCf55231a1F4976c9A503C59976Ed7520", "gameType": "621", "genesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", "genesisBlockNumber": "37223829", From db1107e8871f31be4e5f0b5b641badf9b8fc5519 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sun, 8 Feb 2026 23:39:19 +0000 Subject: [PATCH 052/125] correct teeImageHash in Sepolia deploy config --- deploy-config/sepolia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index 173a04e6..5a1e059d 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia.json @@ -1,6 +1,6 @@ { "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33", - "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "teeImageHash": "0x692a590aa58825d31ccfa165913f0a915c02ca58607b0f787a149179d2b69ec1", "teeProposer": "0x45efd28cCf55231a1F4976c9A503C59976Ed7520", "gameType": "621", "genesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", From 26eac98265513dc5e9b2ebe1a31e053cbc4f0c13 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Mon, 9 Feb 2026 14:35:28 -0500 Subject: [PATCH 053/125] Moving TEE proposer to TEEVerifier --- scripts/multiproof/DeployAllForTesting.s.sol | 1 - src/multiproof/AggregateVerifier.sol | 13 +----- src/multiproof/tee/SystemConfigGlobal.sol | 17 ++++---- src/multiproof/tee/TEEVerifier.sol | 18 +++++--- test/multiproof/AggregateVerifier.t.sol | 11 ----- test/multiproof/BaseTest.t.sol | 1 - test/multiproof/SystemConfigGlobal.t.sol | 12 +++--- test/multiproof/TEEVerifier.t.sol | 43 +++++++++++++++----- 8 files changed, 63 insertions(+), 53 deletions(-) diff --git a/scripts/multiproof/DeployAllForTesting.s.sol b/scripts/multiproof/DeployAllForTesting.s.sol index 8719d5bd..dddf60de 100644 --- a/scripts/multiproof/DeployAllForTesting.s.sol +++ b/scripts/multiproof/DeployAllForTesting.s.sol @@ -167,7 +167,6 @@ contract DeployAllForTesting is Script { cfg.teeImageHash, bytes32(0), // zkImageHash (unused for testing) cfg.configHash, - cfg.teeProposer, 8453, // l2ChainId (Base mainnet) BLOCK_INTERVAL ) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 640a980d..74faebc0 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -91,9 +91,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice The hash of the rollup configuration. bytes32 public immutable CONFIG_HASH; - /// @notice The address that can submit a TEE proof. - address public immutable TEE_PROPOSER; - /// @notice The chain ID of the L2 network this contract argues about. uint256 public immutable L2_CHAIN_ID; @@ -192,9 +189,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice When the game is invalid. error InvalidGame(); - /// @notice When the caller is not authorized. - error NotAuthorized(); - /// @notice When the proof has already been verified. error AlreadyProven(); @@ -221,7 +215,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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 teeProposer The address that can submit a TEE proof. /// @param l2ChainId The chain ID of the L2 network. /// @param blockInterval The block interval. constructor( @@ -233,7 +226,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { bytes32 teeImageHash, bytes32 zkImageHash, bytes32 configHash, - address teeProposer, uint256 l2ChainId, uint256 blockInterval ) { @@ -247,7 +239,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { TEE_IMAGE_HASH = teeImageHash; ZK_IMAGE_HASH = zkImageHash; CONFIG_HASH = configHash; - TEE_PROPOSER = teeProposer; L2_CHAIN_ID = l2ChainId; BLOCK_INTERVAL = blockInterval; } @@ -602,7 +593,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { function _verifyProof(bytes calldata proofBytes, ProofType proofType, address prover) internal { if (proofType == ProofType.TEE) { - if (prover != TEE_PROPOSER) revert NotAuthorized(); _verifyTeeProof(proofBytes, prover); } else if (proofType == ProofType.ZK) { _verifyZkProof(proofBytes, prover); @@ -643,7 +633,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { ); // Validate the proof. - if (!TEE_VERIFIER.verify(proofBytes, TEE_IMAGE_HASH, journal)) revert InvalidProof(); + bytes memory proof = abi.encodePacked(prover, proofBytes); + if (!TEE_VERIFIER.verify(proof, TEE_IMAGE_HASH, journal)) revert InvalidProof(); // Update proving data. provingData.teeProver = prover; diff --git a/src/multiproof/tee/SystemConfigGlobal.sol b/src/multiproof/tee/SystemConfigGlobal.sol index a6d487e1..9bd4dd7d 100644 --- a/src/multiproof/tee/SystemConfigGlobal.sol +++ b/src/multiproof/tee/SystemConfigGlobal.sol @@ -20,9 +20,6 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { /// @notice Maximum age of an attestation document (60 minutes). uint256 public constant MAX_AGE = 60 minutes; - /// @notice The address of the proposer. - address public proposer; - /// @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; @@ -32,6 +29,9 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { /// This replaces the old validSigners(address => bool) mapping to enable imageId validation. mapping(address => bytes32) public signerPCR0; + /// @notice Mapping of whether an address is a valid proposer. + mapping(address => bool) public isValidProposer; + /// @notice Emitted when a signer is registered. event SignerRegistered(address indexed signer, bytes32 indexed pcr0); @@ -45,7 +45,7 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { event PCR0Deregistered(bytes32 indexed pcr0Hash); /// @notice Emitted when the proposer is set. - event ProposerSet(address indexed proposer); + event ProposerSet(address indexed proposer, bool isValid); /// @notice Thrown when the PCR0 in the attestation is not registered as valid. error InvalidPCR0(); @@ -60,10 +60,11 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { } /// @notice Sets the proposer address. - /// @param newProposer The new proposer address. - function setProposer(address newProposer) external onlyOwner { - proposer = newProposer; - emit ProposerSet(newProposer); + /// @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. diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 6d7fe6e7..62ff0125 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -53,6 +53,9 @@ contract TEEVerifier is IVerifier { /// @notice Thrown when the proof format is invalid. error InvalidProofFormat(); + /// @notice Thrown when the proposer is not a valid registered proposer. + error InvalidProposer(address proposer); + /// @notice Constructs the TEEVerifier contract. /// @param systemConfigGlobal The SystemConfigGlobal contract address. constructor(SystemConfigGlobal systemConfigGlobal) { @@ -60,16 +63,17 @@ contract TEEVerifier is IVerifier { } /// @notice Verifies a TEE proof for a state transition. - /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + signature (65) = 129 bytes. + /// @param proofBytes The proof: proposer (20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65) = 149 bytes. /// @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. /// @param journal The keccak256 hash of the proof's public inputs. /// @return valid Whether the proof is valid. function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view override returns (bool) { - if (proofBytes.length < 129) revert InvalidProofFormat(); + if (proofBytes.length < 149) revert InvalidProofFormat(); - bytes32 l1OriginHash = bytes32(proofBytes[0:32]); - uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); - bytes calldata signature = proofBytes[64:129]; + address proposer = address(bytes20(proofBytes[0:20])); + bytes32 l1OriginHash = bytes32(proofBytes[20:52]); + uint256 l1OriginNumber = uint256(bytes32(proofBytes[52:84])); + bytes calldata signature = proofBytes[84:149]; // Verify claimed L1 origin hash matches actual blockhash _verifyL1Origin(l1OriginHash, l1OriginNumber); @@ -82,6 +86,10 @@ contract TEEVerifier is IVerifier { revert InvalidSignature(); } + if (!SYSTEM_CONFIG_GLOBAL.isValidProposer(proposer)) { + revert InvalidProposer(proposer); + } + // Get the PCR0 the signer was registered with bytes32 registeredPCR0 = SYSTEM_CONFIG_GLOBAL.signerPCR0(signer); diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index f51b4603..081c7d13 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -72,17 +72,6 @@ contract AggregateVerifierTest is BaseTest { factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData); } - function testInitializeFailsIfNotTEEProposer() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = _generateProof("tee-proof"); - - vm.expectRevert(AggregateVerifier.NotAuthorized.selector); - _createAggregateVerifierGame( - ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE - ); - } - function testUpdatingAnchorStateRegistryWithTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 57714c89..5c28a25e 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -125,7 +125,6 @@ contract BaseTest is Test { TEE_IMAGE_HASH, ZK_IMAGE_HASH, CONFIG_HASH, - TEE_PROVER, L2_CHAIN_ID, BLOCK_INTERVAL ); diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol index 5940c926..893a8b28 100644 --- a/test/multiproof/SystemConfigGlobal.t.sol +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -39,7 +39,7 @@ contract SystemConfigGlobalTest is Test { event SignerDeregistered(address indexed signer); event PCR0Registered(bytes32 indexed pcr0Hash); event PCR0Deregistered(bytes32 indexed pcr0Hash); - event ProposerSet(address indexed proposer); + event ProposerSet(address indexed proposer, bool isValid); function setUp() public { owner = makeAddr("owner"); @@ -174,12 +174,12 @@ contract SystemConfigGlobalTest is Test { address newProposer = makeAddr("proposer"); vm.expectEmit(true, false, false, false); - emit ProposerSet(newProposer); + emit ProposerSet(newProposer, true); vm.prank(owner); - systemConfigGlobal.setProposer(newProposer); + systemConfigGlobal.setProposer(newProposer, true); - assertEq(systemConfigGlobal.proposer(), newProposer); + assertTrue(systemConfigGlobal.isValidProposer(newProposer)); } function testSetProposerFailsIfNotOwner() public { @@ -187,11 +187,11 @@ contract SystemConfigGlobalTest is Test { vm.prank(manager); vm.expectRevert("OwnableManaged: caller is not the owner"); - systemConfigGlobal.setProposer(newProposer); + systemConfigGlobal.setProposer(newProposer, true); vm.prank(unauthorized); vm.expectRevert("OwnableManaged: caller is not the owner"); - systemConfigGlobal.setProposer(newProposer); + systemConfigGlobal.setProposer(newProposer, true); } // ============ isValidSigner Tests ============ diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 5b66e7ac..8193a39f 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -26,6 +26,7 @@ contract TEEVerifierTest is Test { 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; @@ -54,6 +55,9 @@ contract TEEVerifierTest is Test { // Register the signer with PCR0 hash systemConfigGlobal.addDevSigner(signerAddress, PCR0_HASH); + // Set the proposer as valid + systemConfigGlobal.setProposer(PROPOSER, true); + // Deploy TEEVerifier verifier = new TEEVerifier(SystemConfigGlobal(address(systemConfigGlobal))); } @@ -70,8 +74,8 @@ contract TEEVerifierTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal); bytes memory signature = abi.encodePacked(r, s, v); - // Construct proof: l1OriginHash (32) + l1OriginNumber (32) + signature (65) - bytes memory proofBytes = abi.encodePacked(l1OriginHash, l1OriginNumber, signature); + // Construct proof: proposer (20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65) + bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, signature); // Verify should return true bool result = verifier.verify(proofBytes, IMAGE_ID, journal); @@ -88,12 +92,31 @@ contract TEEVerifierTest is Test { bytes memory invalidSignature = new bytes(65); invalidSignature[64] = bytes1(uint8(27)); // Set v to 27 - bytes memory proofBytes = abi.encodePacked(l1OriginHash, l1OriginNumber, invalidSignature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, invalidSignature); vm.expectRevert(TEEVerifier.InvalidSignature.selector); verifier.verify(proofBytes, IMAGE_ID, journal); } + function testVerifyFailsWithInvalidProposer() public { + // Create a journal hash + bytes32 journal = keccak256("test-journal"); + + // Get current block info for L1 origin + uint256 l1OriginNumber = block.number - 1; + bytes32 l1OriginHash = blockhash(l1OriginNumber); + + // 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) + l1OriginHash (32) + l1OriginNumber (32) + signature (65) + bytes memory proofBytes = abi.encodePacked(address(0), l1OriginHash, l1OriginNumber, 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; @@ -107,7 +130,7 @@ contract TEEVerifierTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(unregisteredKey, journal); bytes memory signature = abi.encodePacked(r, s, v); - bytes memory proofBytes = abi.encodePacked(l1OriginHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, signature); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidSigner.selector, unregisteredSigner)); verifier.verify(proofBytes, IMAGE_ID, journal); @@ -122,7 +145,7 @@ contract TEEVerifierTest is Test { (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(l1OriginHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, signature); // Use a different imageId that doesn't match the registered PCR0 bytes32 wrongImageId = keccak256("wrong-image-id"); @@ -141,7 +164,7 @@ contract TEEVerifierTest is Test { (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(l1OriginHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, signature); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number)); verifier.verify(proofBytes, IMAGE_ID, journal); @@ -160,7 +183,7 @@ contract TEEVerifierTest is Test { (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(l1OriginHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, signature); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); verifier.verify(proofBytes, IMAGE_ID, journal); @@ -175,7 +198,7 @@ contract TEEVerifierTest is Test { (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(wrongHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, wrongHash, l1OriginNumber, signature); bytes32 actualHash = blockhash(l1OriginNumber); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash)); @@ -205,7 +228,7 @@ contract TEEVerifierTest is Test { (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(l1OriginHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, signature); bool result = verifier.verify(proofBytes, IMAGE_ID, journal); assertTrue(result); @@ -230,7 +253,7 @@ contract TEEVerifierTest is Test { (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(expectedHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, expectedHash, l1OriginNumber, signature); bool result = verifier.verify(proofBytes, IMAGE_ID, journal); assertTrue(result); From b3e4392e00928bf8cfeb0c6987a370292919a473 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 10 Feb 2026 10:44:59 -0500 Subject: [PATCH 054/125] fix TEE comment --- src/multiproof/AggregateVerifier.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 74faebc0..0d9091c9 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -610,7 +610,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } /// @notice Verifies a TEE proof for the current game. - /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + signature (65). + /// @param proofBytes The proof: prover(20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65). function _verifyTeeProof(bytes calldata proofBytes, address prover) internal { // Only one TEE proof can be submitted. if (provingData.teeProver != address(0)) revert AlreadyProven(); From c192c9b7e359b24de6fc19f765ad02284faa33e9 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 17 Feb 2026 15:36:14 -0500 Subject: [PATCH 055/125] feat: intermediate roots for nullification --- src/multiproof/AggregateVerifier.sol | 220 ++++++++++++++++----------- 1 file changed, 134 insertions(+), 86 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 0d9091c9..b886241b 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -25,7 +25,7 @@ import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; -contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { +contract AggregateVerifier is Clone, ReentrancyGuard { //////////////////////////////////////////////////////////////// // Enums // //////////////////////////////////////////////////////////////// @@ -61,9 +61,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice The fast finalization delay. uint64 public constant FAST_FINALIZATION_DELAY = 1 days; - /// @notice The size of the initialize call data. - uint256 internal constant INITIALIZE_CALLDATA_SIZE = 0x7E; - //////////////////////////////////////////////////////////////// // Immutables // //////////////////////////////////////////////////////////////// @@ -98,6 +95,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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; @@ -142,6 +146,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Events // //////////////////////////////////////////////////////////////// + /// @notice Emitted when the game is resolved. + /// @param status The status of the game. + event Resolved(GameStatus status); + /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. /// @param challenger The address of the challenger. /// @param game The game used to challenge this proposal. @@ -154,8 +162,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice Emitted when the game is nullified. /// @param nullifier The address of the nullifier. - /// @param game The game used to nullify this proposal. - event Nullified(address indexed nullifier, IDisputeGame game); + /// @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. @@ -207,6 +216,18 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @notice When the countered by game is invalid. error InvalidCounteredByGame(); + /// @notice When the block interval or intermediate block interval is invalid. + error InvalidBlockInterval(uint256 blockInterval, uint256 intermediateBlockInterval); + + /// @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 countered by game is not resolved. + error CounteredByGameNotResolved(); + /// @param gameType_ The game type. /// @param anchorStateRegistry_ The anchor state registry. /// @param delayedWETH The delayed WETH contract. @@ -227,7 +248,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { bytes32 zkImageHash, bytes32 configHash, uint256 l2ChainId, - uint256 blockInterval + uint256 blockInterval, + uint256 intermediateBlockInterval ) { // Set up initial game state. GAME_TYPE = gameType_; @@ -241,6 +263,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { CONFIG_HASH = configHash; L2_CHAIN_ID = l2ChainId; BLOCK_INTERVAL = blockInterval; + INTERMEDIATE_BLOCK_INTERVAL = intermediateBlockInterval; + + if (BLOCK_INTERVAL == 0 || intermediateBlockInterval == 0 || BLOCK_INTERVAL % INTERMEDIATE_BLOCK_INTERVAL != 0) { + revert InvalidBlockInterval(BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + } + + INITIALIZE_CALLDATA_SIZE = 0x7E + 0x20 * intermediateOutputRootsCount(); } /// @notice Initializes the contract. @@ -264,14 +293,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // - 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(), add(INITIALIZE_CALLDATA_SIZE, add(0x40, proofLength)))) { + if iszero(eq(calldatasize(), expectedCallDataSize)) { // Store the selector for `BadExtraData()` & revert. mstore(0x00, 0x9824bdab) revert(0x1C, 0x04) @@ -283,12 +314,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // For subsequent games, get the parent game's information. (,, IDisputeGame parentGame) = DISPUTE_GAME_FACTORY.gameAtIndex(parentIndex()); - // Parent game must be respected, not blacklisted, and not retired. + // Parent game must be respected, not blacklisted, not retired, and not challenged. if (!_isValidGame(parentGame)) revert InvalidParentGame(); - // The parent game must be a valid game. - if (parentGame.status() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); - // The parent game must have a proof. // Should not be reachable since a proof is required to initialize. if ( @@ -327,17 +355,20 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { DELAYED_WETH.deposit{value: msg.value}(); // Verify the proof. - _verifyProof(proof[1:], ProofType(uint8(proof[0])), gameCreator()); + _verifyProposalProof(proof[1:], ProofType(uint8(proof[0])), gameCreator()); } /// @notice Verifies a proof for the current game. /// @param proofBytes The proof. /// @dev The first byte of the proof is the proof type. - function verifyProof(bytes calldata proofBytes) external { + 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(); - _verifyProof(proofBytes[1:], ProofType(uint8(proofBytes[0])), msg.sender); + _verifyProposalProof(proofBytes[1:], ProofType(uint8(proofBytes[0])), msg.sender); } /// @notice Resolves the game after a proof has been provided and enough time has passed. @@ -411,41 +442,44 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } /// @notice Nullifies the game if a soundness issue is found. - /// @param gameIndex The index of the game used to nullify. - /// @param proofType The type of proof used to nullify. - /// @dev The game used to nullify must have a proof for the same - /// block number but a different root claim as the current game. - function nullify(uint256 gameIndex, ProofType proofType) external { - // Can only nullify a game that has not resolved yet. - // We can nullify a challenged game in case of a soundness issue. - if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); + /// @param proofBytes The proof. + /// @param intermediateRootIndex Index of the intermediate root to challenge. + /// @param intermediateRootToProve The intermediate root that the proof claims to be correct. + /// @dev The first byte of the proof is the proof type. + function nullify(bytes calldata proofBytes, uint256 intermediateRootIndex, bytes32 intermediateRootToProve) external { + if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); + if (intermediateRootIndex >= intermediateOutputRootsCount()) revert InvalidIntermediateRootIndex(); + + bytes32 proposedIntermediateRoot = intermediateOutputRoot(intermediateRootIndex); + if (proposedIntermediateRoot == intermediateRootToProve) revert IntermediateRootSameAsProposed(); + bytes32 startingRoot = intermediateRootIndex == 0 ? startingOutputRoot.root.raw() : intermediateOutputRoot(intermediateRootIndex - 1); + uint256 startingL2SequenceNumber = startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL; + uint256 endingL2SequenceNumber = startingL2SequenceNumber + INTERMEDIATE_BLOCK_INTERVAL; + + ProofType proofType = ProofType(uint8(proofBytes[0])); // Can only nullify a game that has a proof of the same type. if (proofType == ProofType.TEE) { - if (provingData.teeProver == address(0) || AggregateVerifier(address(game)).teeProver() == address(0)) { + if (provingData.teeProver == address(0)) { revert MissingTEEProof(); } + _verifyTeeProof(proofBytes[1:], msg.sender, startingRoot, startingL2SequenceNumber, intermediateRootToProve, endingL2SequenceNumber, abi.encodePacked(intermediateRootToProve)); } else if (proofType == ProofType.ZK) { - if (provingData.zkProver == address(0) || AggregateVerifier(address(game)).zkProver() == address(0)) { + if (provingData.zkProver == address(0)) { revert MissingZKProof(); } + _verifyZkProof(proofBytes[1:], msg.sender, startingRoot, startingL2SequenceNumber, intermediateRootToProve, endingL2SequenceNumber, abi.encodePacked(intermediateRootToProve)); } else { revert InvalidProofType(); } - // The game must be a valid game used to nullify. - if (!_isValidChallengingGame(game)) revert InvalidGame(); - // Set the game as challenged so that child games can't resolve. status = GameStatus.CHALLENGER_WINS; // Refund the bond. This can override a challenge. bondRecipient = gameCreator(); - // To allow bond to be refunded as the challenging game is no longer valid. - delete provingData.counteredByGameAddress; - emit Nullified(msg.sender, game); + emit Nullified(msg.sender, intermediateRootIndex, intermediateRootToProve); } /// @notice Claim the credit belonging to the bond recipient. Reverts if the game isn't @@ -457,13 +491,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // The bond recipient must not be empty. if (bondRecipient == address(0)) revert BondRecipientEmpty(); - // If this game was challenged, the countered by game must be valid. + // If this game was challenged, the countered by game must be valid or else the bond is refunded. if (provingData.counteredByGameAddress != address(0)) { - if (!_isValidChallengingGame(IDisputeGame(provingData.counteredByGameAddress))) { - revert InvalidCounteredByGame(); + GameStatus counteredByGameStatus = IDisputeGame(provingData.counteredByGameAddress).status(); + if (counteredByGameStatus == GameStatus.IN_PROGRESS) { + revert CounteredByGameNotResolved(); } - if (IDisputeGame(provingData.counteredByGameAddress).status() != GameStatus.DEFENDER_WINS) { - revert InvalidCounteredByGame(); + // If the countered by game is invalid or not resolved, the bond is refunded. + if (!_isValidChallengingGame(IDisputeGame(provingData.counteredByGameAddress))) { + bondRecipient = gameCreator(); } } @@ -474,6 +510,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } bondClaimed = true; + // This can fail if this game was challenged and the countered by game is blacklisted/retired after it resolved to DEFENDER_WINS. The centralized functions in DELAYED_WETH will handle this as it's a already a very centralized action to blacklist/retire a valid challenging game. DELAYED_WETH.withdraw(bondRecipient, bondAmount); // Transfer the credit to the bond recipient. @@ -557,6 +594,24 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { return provingData.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(0x94, 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(0x94 + 0x20 * index); + } + /// @notice Getter for the creator of the dispute game. function gameCreator() public pure returns (address) { return _getArgAddress(0x00); @@ -573,12 +628,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { } /// @notice Getter for the extra data. - function extraData() public pure returns (bytes memory) { + function extraData() public view returns (bytes memory) { // The extra data starts at the second word within the cwia calldata and - // is 36 bytes long. + // is 36 + 32 x intermediateRootsCount() bytes long. // 32 bytes are for the l2BlockNumber // 4 bytes are for the parentIndex - return _getArgBytes(0x54, 0x24); + // 32 bytes are for each intermediate root + return _getArgBytes(0x54, 0x24 + 0x20 * intermediateOutputRootsCount()); } /// @notice The L2 sequence number for which this game is proposing an output root (in this case - the block number). @@ -591,12 +647,20 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { return _getArgUint32(0x74); } - function _verifyProof(bytes calldata proofBytes, ProofType proofType, address prover) internal { + function _verifyProposalProof(bytes calldata proofBytes, ProofType proofType, address prover) internal { if (proofType == ProofType.TEE) { - _verifyTeeProof(proofBytes, prover); + // Only one TEE proof can be submitted. + if (provingData.teeProver != address(0)) revert AlreadyProven(); + _verifyTeeProof(proofBytes, prover, startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), l2SequenceNumber(), intermediateOutputRoots()); + // Update proving data. + provingData.teeProver = prover; } else if (proofType == ProofType.ZK) { - _verifyZkProof(proofBytes, prover); + // Only one ZK proof can be submitted. + if (provingData.zkProver != address(0)) revert AlreadyProven(); + _verifyZkProof(proofBytes, prover, startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), l2SequenceNumber(), intermediateOutputRoots()); + // Update proving data. + provingData.zkProver = prover; // Bond can be reclaimed after a ZK proof is provided. bondRecipient = gameCreator(); } else { @@ -609,24 +673,33 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { emit Proved(prover, proofType); } + /// @notice Updates the expected resolution timestamp. + function _updateExpectedResolution() internal { + uint64 newResolution = uint64(block.timestamp); + if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { + newResolution += FAST_FINALIZATION_DELAY; + } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { + newResolution += SLOW_FINALIZATION_DELAY; + } else { + revert NoProofProvided(); + } + provingData.expectedResolution = + Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); + } + /// @notice Verifies a TEE proof for the current game. /// @param proofBytes The proof: prover(20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65). - function _verifyTeeProof(bytes calldata proofBytes, address prover) internal { - // Only one TEE proof can be submitted. - if (provingData.teeProver != address(0)) revert AlreadyProven(); - - // The game must be in progress. - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - + function _verifyTeeProof(bytes calldata proofBytes, address prover, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots) internal view { bytes32 journal = keccak256( abi.encodePacked( prover, bytes32(proofBytes[0:32]), uint256(bytes32(proofBytes[32:64])), - startingOutputRoot.root, - startingOutputRoot.l2SequenceNumber, - rootClaim(), - l2SequenceNumber(), + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots, CONFIG_HASH, TEE_IMAGE_HASH ) @@ -635,29 +708,21 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Validate the proof. bytes memory proof = abi.encodePacked(prover, proofBytes); if (!TEE_VERIFIER.verify(proof, TEE_IMAGE_HASH, journal)) revert InvalidProof(); - - // Update proving data. - provingData.teeProver = prover; } /// @notice Verifies a ZK proof for the current game. /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + zkProof (variable). - function _verifyZkProof(bytes calldata proofBytes, address prover) internal { - // Only one ZK proof can be submitted. - if (provingData.zkProver != address(0)) revert AlreadyProven(); - - // The game must be in progress or challenged (to allow nullification). - if (status == GameStatus.DEFENDER_WINS) revert ClaimAlreadyResolved(); - + function _verifyZkProof(bytes calldata proofBytes, address prover, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots) internal view { bytes32 journal = keccak256( abi.encodePacked( prover, bytes32(proofBytes[0:32]), uint256(bytes32(proofBytes[32:64])), - startingOutputRoot.root, - startingOutputRoot.l2SequenceNumber, - rootClaim(), - l2SequenceNumber(), + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots, CONFIG_HASH, ZK_IMAGE_HASH ) @@ -665,23 +730,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { // Validate the proof. if (!ZK_VERIFIER.verify(proofBytes, ZK_IMAGE_HASH, journal)) revert InvalidProof(); - - // Update proving data. - provingData.zkProver = prover; - } - - /// @notice Updates the expected resolution timestamp. - function _updateExpectedResolution() internal { - uint64 newResolution = uint64(block.timestamp); - if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { - newResolution += FAST_FINALIZATION_DELAY; - } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { - newResolution += SLOW_FINALIZATION_DELAY; - } else { - revert NoProofProvided(); - } - provingData.expectedResolution = - Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); } /// @notice Returns the status of the parent game. @@ -704,7 +752,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, IDisputeGame { /// @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); + && !ANCHOR_STATE_REGISTRY.isGameRetired(game) && (game.status() != GameStatus.CHALLENGER_WINS); } /// @notice Checks if the game is a valid game used to challenge or nullify. From f0b333f182c3fc99bfeb2f8b3e0f5d397a99bea4 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 17 Feb 2026 15:48:06 -0500 Subject: [PATCH 056/125] move L1 origin hash check from TEEVerifier to AggregateVerifier --- src/multiproof/AggregateVerifier.sol | 75 ++++++++++++++++++++++++++-- src/multiproof/tee/TEEVerifier.sol | 62 ----------------------- 2 files changed, 71 insertions(+), 66 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index b886241b..6ea0c3b8 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -61,6 +61,17 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @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 // //////////////////////////////////////////////////////////////// @@ -228,6 +239,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice When the countered by game is not resolved. error CounteredByGameNotResolved(); + /// @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); + /// @param gameType_ The game type. /// @param anchorStateRegistry_ The anchor state registry. /// @param delayedWETH The delayed WETH contract. @@ -690,11 +710,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Verifies a TEE proof for the current game. /// @param proofBytes The proof: prover(20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65). function _verifyTeeProof(bytes calldata proofBytes, address prover, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots) internal view { + bytes32 l1OriginHash = bytes32(proofBytes[:32]); + uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); + // Verify claimed L1 origin hash matches actual blockhash + _verifyL1Origin(l1OriginHash, l1OriginNumber); + bytes32 journal = keccak256( abi.encodePacked( prover, - bytes32(proofBytes[0:32]), - uint256(bytes32(proofBytes[32:64])), + l1OriginHash, + l1OriginNumber, startingRoot, startingL2SequenceNumber, endingRoot, @@ -713,11 +738,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Verifies a ZK proof for the current game. /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + zkProof (variable). function _verifyZkProof(bytes calldata proofBytes, address prover, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots) internal view { + bytes32 l1OriginHash = bytes32(proofBytes[:32]); + uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); + // Verify claimed L1 origin hash matches actual blockhash + _verifyL1Origin(l1OriginHash, l1OriginNumber); + bytes32 journal = keccak256( abi.encodePacked( prover, - bytes32(proofBytes[0:32]), - uint256(bytes32(proofBytes[32:64])), + l1OriginHash, + l1OriginNumber, startingRoot, startingL2SequenceNumber, endingRoot, @@ -770,4 +800,41 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // The game must be valid. _isValidGame(game); } + + /// @notice Verifies that the claimed L1 origin hash matches the actual blockhash. + /// @param l1OriginHash The L1 block hash claimed in the proof. + /// @param l1OriginNumber The L1 block number claimed in the proof. + function _verifyL1Origin(bytes32 l1OriginHash, uint256 l1OriginNumber) internal view { + // Check for future block + if (l1OriginNumber >= block.number) { + revert L1OriginInFuture(l1OriginNumber, block.number); + } + + bytes32 actualHash; + uint256 blockAge = block.number - l1OriginNumber; + + // Prefer blockhash() over EIP-2935 when possible since it's cheaper (no external call). + if (blockAge <= BLOCKHASH_WINDOW) { + actualHash = blockhash(l1OriginNumber); + } else if (blockAge <= EIP2935_WINDOW) { + // EIP-2935 expects raw calldata: exactly 32 bytes containing the block number. + // Using a Solidity interface would add a 4-byte function selector, causing a revert. + // We use a low-level staticcall with raw 32-byte calldata instead. + (bool success, bytes memory result) = EIP2935_CONTRACT.staticcall(abi.encode(l1OriginNumber)); + if (!success || result.length != 32) { + revert L1OriginTooOld(l1OriginNumber, block.number); + } + actualHash = abi.decode(result, (bytes32)); + } else { + revert L1OriginTooOld(l1OriginNumber, block.number); + } + + if (actualHash == bytes32(0)) { + revert L1OriginTooOld(l1OriginNumber, block.number); + } + + if (actualHash != l1OriginHash) { + revert L1OriginHashMismatch(l1OriginHash, actualHash); + } + } } diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 62ff0125..7082d3c3 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -17,17 +17,6 @@ import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; /// The contract is intentionally stateless - all state related to output proposals is /// managed by the calling contract (e.g., AggregateVerifier). contract TEEVerifier is IVerifier { - /// @notice The EIP-2935 blockhash history contract address (deployed post-Pectra). - /// @dev This contract stores blockhashes for the last ~8192 blocks, extending the - /// 256-block window of the native blockhash() opcode. - address public constant EIP2935_CONTRACT = 0x0000F90827F1C53a10cb7A02335B175320002935; - - /// @notice The maximum number of blocks that blockhash() can look back. - uint256 public constant BLOCKHASH_WINDOW = 256; - - /// @notice The maximum number of blocks that EIP-2935 can look back (~8192). - uint256 public constant EIP2935_WINDOW = 8191; - /// @notice The SystemConfigGlobal contract that manages valid TEE signers. /// @dev Signers are registered via AWS Nitro attestation in SystemConfigGlobal. SystemConfigGlobal public immutable SYSTEM_CONFIG_GLOBAL; @@ -41,15 +30,6 @@ contract TEEVerifier is IVerifier { /// @notice Thrown when the signer's registered PCR0 does not match the claimed imageId. error ImageIdMismatch(bytes32 signerPCR0, bytes32 claimedImageId); - /// @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 the proof format is invalid. error InvalidProofFormat(); @@ -71,13 +51,8 @@ contract TEEVerifier is IVerifier { if (proofBytes.length < 149) revert InvalidProofFormat(); address proposer = address(bytes20(proofBytes[0:20])); - bytes32 l1OriginHash = bytes32(proofBytes[20:52]); - uint256 l1OriginNumber = uint256(bytes32(proofBytes[52:84])); bytes calldata signature = proofBytes[84:149]; - // Verify claimed L1 origin hash matches actual blockhash - _verifyL1Origin(l1OriginHash, l1OriginNumber); - // 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); @@ -106,41 +81,4 @@ contract TEEVerifier is IVerifier { return true; } - - /// @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) private 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); - } - } } From c7b1d8110decf0cd20cd9d26a1c9650650e35ea6 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 17 Feb 2026 17:14:22 -0500 Subject: [PATCH 057/125] fix tests --- scripts/multiproof/DeployAllForTesting.s.sol | 4 +- src/multiproof/AggregateVerifier.sol | 32 +++-- test/multiproof/AggregateVerifier.t.sol | 141 +++++++++++++++---- test/multiproof/BaseTest.t.sol | 38 +++-- test/multiproof/Challenge.t.sol | 53 ++++--- test/multiproof/Nullify.t.sol | 101 +++++-------- test/multiproof/TEEVerifier.t.sol | 98 ------------- 7 files changed, 228 insertions(+), 239 deletions(-) diff --git a/scripts/multiproof/DeployAllForTesting.s.sol b/scripts/multiproof/DeployAllForTesting.s.sol index dddf60de..1e950321 100644 --- a/scripts/multiproof/DeployAllForTesting.s.sol +++ b/scripts/multiproof/DeployAllForTesting.s.sol @@ -33,6 +33,7 @@ contract DeployAllForTesting is Script { bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; uint256 public constant BLOCK_INTERVAL = 100; + uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; uint256 public constant INIT_BOND = 0.001 ether; /// @notice Config struct to reduce stack variables. @@ -168,7 +169,8 @@ contract DeployAllForTesting is Script { bytes32(0), // zkImageHash (unused for testing) cfg.configHash, 8453, // l2ChainId (Base mainnet) - BLOCK_INTERVAL + BLOCK_INTERVAL, + INTERMEDIATE_BLOCK_INTERVAL ) ); console.log("AggregateVerifier:", aggregateVerifier); diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 6ea0c3b8..68d3ca72 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -236,6 +236,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @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 When the countered by game is not resolved. error CounteredByGameNotResolved(); @@ -329,6 +332,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard { } } + // 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. @@ -622,14 +630,24 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice The intermediate output roots of the game. function intermediateOutputRoots() public view returns (bytes memory) { - return _getArgBytes(0x94, 0x20 * intermediateOutputRootsCount()); + 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(0x94 + 0x20 * index); + 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. @@ -647,16 +665,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { return Hash.wrap(_getArgBytes32(0x34)); } - /// @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 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); diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 081c7d13..51dd36e7 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -14,10 +14,10 @@ contract AggregateVerifierTest is BaseTest { function testInitializeWithTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = _generateProof("tee-proof"); + bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof ); assertEq(game.wasRespectedGameTypeWhenCreated(), true); @@ -29,7 +29,7 @@ contract AggregateVerifierTest is BaseTest { 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)); + assertEq(game.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max, game.intermediateOutputRoots())); assertEq(game.bondRecipient(), address(0)); assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); @@ -38,10 +38,10 @@ contract AggregateVerifierTest is BaseTest { function testInitializeWithZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = _generateProof("zk-proof"); + bytes memory proof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); AggregateVerifier game = _createAggregateVerifierGame( - ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof ); assertEq(game.wasRespectedGameTypeWhenCreated(), true); @@ -53,7 +53,7 @@ contract AggregateVerifierTest is BaseTest { 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)); + 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); @@ -75,10 +75,10 @@ contract AggregateVerifierTest is BaseTest { function testUpdatingAnchorStateRegistryWithTEEProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = _generateProof("tee-proof"); + bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof ); // Cannot claim bond before resolving @@ -109,10 +109,10 @@ contract AggregateVerifierTest is BaseTest { function testUpdatingAnchorStateRegistryWithZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proof = _generateProof("zk-proof"); + bytes memory proof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); AggregateVerifier game = _createAggregateVerifierGame( - ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof ); // Unlock and reclaim bond after delay @@ -139,14 +139,14 @@ contract AggregateVerifierTest is BaseTest { function testUpdatingAnchorStateRegistryWithBothProofs() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory teeProof = _generateProof("tee-proof"); - bytes memory zkProof = _generateProof("zk-proof"); + 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, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof ); - _provideProof(game, ZK_PROVER, AggregateVerifier.ProofType.ZK, zkProof); + _provideProof(game, ZK_PROVER, zkProof); // Unlock bond uint256 balanceBefore = game.gameCreator().balance; @@ -174,11 +174,11 @@ contract AggregateVerifierTest is BaseTest { function testProofCannotIncreaseExpectedResolution() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory teeProof = _generateProof("tee-proof"); - bytes memory zkProof = _generateProof("zk-proof"); + 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, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof ); (,,, Timestamp originalExpectedResolution) = game.provingData(); @@ -190,7 +190,7 @@ contract AggregateVerifierTest is BaseTest { game.resolve(); // Provide ZK proof - _provideProof(game, ZK_PROVER, AggregateVerifier.ProofType.ZK, zkProof); + _provideProof(game, ZK_PROVER, zkProof); // Proof should not have increased expected resolution (,,, Timestamp expectedResolution) = game.provingData(); @@ -205,19 +205,112 @@ contract AggregateVerifierTest is BaseTest { function testCannotCreateSameProposal() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory teeProof = _generateProof("tee-proof"); - bytes memory zkProof = _generateProof("zk-proof"); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + 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) + 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, AggregateVerifier.ProofType.ZK + 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); + + 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); + + 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); + + 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); + + _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); + + _createAggregateVerifierGame( + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes ); } } diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 5c28a25e..be1cc567 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -28,7 +28,11 @@ 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 @@ -126,7 +130,8 @@ contract BaseTest is Test { ZK_IMAGE_HASH, CONFIG_HASH, L2_CHAIN_ID, - BLOCK_INTERVAL + BLOCK_INTERVAL, + INTERMEDIATE_BLOCK_INTERVAL ); // Set the implementation for the aggregate verifier @@ -142,36 +147,34 @@ contract BaseTest is Test { Claim rootClaim, uint256 l2BlockNumber, uint32 parentIndex, - bytes memory proof, - AggregateVerifier.ProofType proofType + bytes memory proof ) internal returns (AggregateVerifier game) { - bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), uint32(parentIndex)); - bytes memory initData = abi.encodePacked(uint8(proofType), proof); + 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.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData)) + address(factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, proof)) ); } function _provideProof( AggregateVerifier game, address prover, - AggregateVerifier.ProofType proofType, - bytes memory proof + bytes memory proofBytes ) internal { vm.prank(prover); - bytes memory proofBytes = abi.encodePacked(uint8(proofType), proof); - game.verifyProof(proofBytes); + 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) internal view returns (bytes memory) { + 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 @@ -179,6 +182,15 @@ contract BaseTest is Test { // 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(l1OriginHash, l1OriginNumber, signature); + 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; } -} +} \ No newline at end of file diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 817a2be1..e92125e5 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -16,18 +16,18 @@ contract ChallengeTest is BaseTest { // Create first game with TEE proof Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = _generateProof("tee-proof"); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof ); // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory zkProof = _generateProof("zk-proof"); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof ); // Get game index from factory @@ -59,18 +59,18 @@ contract ChallengeTest is BaseTest { // 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"); + bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1 ); // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); - bytes memory zkProof2 = _generateProof("zk-proof-2"); + bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2 ); uint256 gameIndex = factory.gameCount() - 1; @@ -83,21 +83,21 @@ contract ChallengeTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = _generateProof("tee-proof"); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof ); // Create game2 with game1 as parent uint256 game1Index = factory.gameCount() - 1; uint256 nextBlockNumber = currentL2BlockNumber + BLOCK_INTERVAL; Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); - bytes memory zkProof = _generateProof("zk-proof"); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); // forge-lint: disable-next-line(unsafe-typecast) _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof ); uint256 gameIndex = factory.gameCount() - 1; @@ -110,17 +110,17 @@ contract ChallengeTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = _generateProof("tee-proof-1"); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1 ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof2 = _generateProof("tee-proof-2"); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); _createAggregateVerifierGame( - TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2 ); uint256 gameIndex = factory.gameCount() - 1; @@ -133,10 +133,10 @@ contract ChallengeTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = _generateProof("tee-proof"); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof ); // Resolve game1 @@ -145,10 +145,10 @@ contract ChallengeTest is BaseTest { // Try to challenge game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof = _generateProof("zk-proof"); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof ); uint256 challengeIndex1 = factory.gameCount() - 1; @@ -161,10 +161,10 @@ contract ChallengeTest is BaseTest { // create parent game Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory parentProof = _generateProof("parent-proof"); + bytes memory parentProof = _generateProof("parent-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier parentGame = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, parentProof, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, parentProof ); uint256 parentGameIndex = factory.gameCount() - 1; @@ -172,7 +172,7 @@ contract ChallengeTest is BaseTest { // create child game Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory childProof = _generateProof("child-proof"); + bytes memory childProof = _generateProof("child-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier childGame = // forge-lint: disable-next-line(unsafe-typecast) @@ -181,8 +181,7 @@ contract ChallengeTest is BaseTest { rootClaim2, currentL2BlockNumber, uint32(parentGameIndex), - childProof, - AggregateVerifier.ProofType.TEE + childProof ); // blacklist parent game @@ -197,10 +196,10 @@ contract ChallengeTest is BaseTest { function testChallengeFailsIfGameItselfIsBlacklisted() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory proof = _generateProof("tee-proof"); + bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof ); // blacklist game diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index c4309409..81bbec8f 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {ClaimAlreadyResolved} from "optimism/src/dispute/lib/Errors.sol"; +import {GameNotInProgress} from "optimism/src/dispute/lib/Errors.sol"; import {Claim, GameStatus} from "optimism/src/dispute/lib/Types.sol"; import {AggregateVerifier} from "src/AggregateVerifier.sol"; @@ -13,52 +13,42 @@ contract NullifyTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = _generateProof("tee-proof-1"); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, 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"); - - _createAggregateVerifierGame( - TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE - ); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - uint256 gameIndex = factory.gameCount() - 1; - game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); + game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); - assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); - assertEq(game1.bondRecipient(), TEE_PROVER); + assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game.bondRecipient(), TEE_PROVER); - uint256 balanceBefore = game1.gameCreator().balance; - game1.claimCredit(); + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game1.claimCredit(); - assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game1)), 0); + 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"); + bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1 ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); - bytes memory zkProof2 = _generateProof("zk-proof-2"); - - _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2, AggregateVerifier.ProofType.ZK - ); + bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); - uint256 gameIndex = factory.gameCount() - 1; - game1.nullify(gameIndex, AggregateVerifier.ProofType.ZK); + game1.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(game1.bondRecipient(), ZK_PROVER); @@ -75,54 +65,44 @@ contract NullifyTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof = _generateProof("zk-proof"); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof = _generateProof("tee-proof"); - - _createAggregateVerifierGame( - TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE - ); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(AggregateVerifier.MissingTEEProof.selector); - game1.nullify(gameIndex, AggregateVerifier.ProofType.TEE); + game1.nullify(teeProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } function testZKNullifyFailsIfNoZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof = _generateProof("tee-proof"); + bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof ); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory zkProof = _generateProof("zk-proof"); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK - ); - - uint256 gameIndex = factory.gameCount() - 1; vm.expectRevert(AggregateVerifier.MissingZKProof.selector); - game1.nullify(gameIndex, 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"); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1 ); // Resolve game1 @@ -131,33 +111,28 @@ contract NullifyTest is BaseTest { // Try to nullify game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory teeProof2 = _generateProof("tee-proof-2"); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2, AggregateVerifier.ProofType.TEE - ); - - uint256 challengeIndex = factory.gameCount() - 1; - vm.expectRevert(ClaimAlreadyResolved.selector); - game1.nullify(challengeIndex, AggregateVerifier.ProofType.TEE); + vm.expectRevert(GameNotInProgress.selector); + game1.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } function testNullifyCanOverrideChallenge() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = _generateProof("tee-proof-1"); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1, AggregateVerifier.ProofType.TEE + TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1 ); // Challenge game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory zkProof = _generateProof("zk-proof"); + bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof, AggregateVerifier.ProofType.ZK + AggregateVerifier game2 = _createAggregateVerifierGame( + ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof ); uint256 challengeIndex = factory.gameCount() - 1; @@ -165,13 +140,11 @@ contract NullifyTest is BaseTest { assertEq(game1.bondRecipient(), ZK_PROVER); // Nullify can override challenge - _provideProof(game1, ZK_PROVER, AggregateVerifier.ProofType.ZK, zkProof); - game1.nullify(challengeIndex, AggregateVerifier.ProofType.ZK); - - assertEq(game1.bondRecipient(), TEE_PROVER); + game2.nullify(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); uint256 balanceBefore = game1.gameCreator().balance; game1.claimCredit(); + assertEq(game1.bondRecipient(), TEE_PROVER); vm.warp(block.timestamp + DELAYED_WETH_DELAY); game1.claimCredit(); assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 8193a39f..86dbb486 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -154,57 +154,6 @@ contract TEEVerifierTest is Test { verifier.verify(proofBytes, wrongImageId, journal); } - function testVerifyFailsWithL1OriginInFuture() public { - bytes32 journal = keccak256("test-journal"); - - // Use a future block number - uint256 l1OriginNumber = block.number + 1; - bytes32 l1OriginHash = bytes32(uint256(1)); // Fake hash - - (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, l1OriginHash, l1OriginNumber, signature); - - vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number)); - verifier.verify(proofBytes, IMAGE_ID, journal); - } - - function testVerifyFailsWithL1OriginTooOld() public { - // Roll forward many blocks to make old blocks unavailable - vm.roll(block.number + 300); - - bytes32 journal = keccak256("test-journal"); - - // 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 - - (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, l1OriginHash, l1OriginNumber, signature); - - vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); - verifier.verify(proofBytes, IMAGE_ID, journal); - } - - function testVerifyFailsWithL1OriginHashMismatch() public { - bytes32 journal = keccak256("test-journal"); - - uint256 l1OriginNumber = block.number - 1; - bytes32 wrongHash = bytes32(uint256(0xdeadbeef)); // Wrong hash - - (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, wrongHash, l1OriginNumber, signature); - - bytes32 actualHash = blockhash(l1OriginNumber); - vm.expectRevert(abi.encodeWithSelector(TEEVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash)); - verifier.verify(proofBytes, IMAGE_ID, journal); - } - function testVerifyFailsWithInvalidProofFormat() public { bytes32 journal = keccak256("test-journal"); @@ -215,54 +164,7 @@ contract TEEVerifierTest is Test { verifier.verify(shortProof, IMAGE_ID, journal); } - function testVerifyWithBlockhashWindow() public { - // Test verification within the 256 block window - vm.roll(block.number + 100); - - bytes32 journal = keccak256("test-journal"); - - // Use a block that's within the 256 block window - uint256 l1OriginNumber = block.number - 50; - bytes32 l1OriginHash = blockhash(l1OriginNumber); - - (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, l1OriginHash, l1OriginNumber, signature); - - bool result = verifier.verify(proofBytes, IMAGE_ID, journal); - assertTrue(result); - } - - function testVerifyWithEIP2935Window() public { - // Roll forward past the 256 blockhash window - vm.roll(block.number + 300); - - bytes32 journal = keccak256("test-journal"); - - // 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)); - - // Mock the EIP-2935 contract response - vm.mockCall( - verifier.EIP2935_CONTRACT(), - abi.encode(l1OriginNumber), // raw 32-byte calldata - abi.encode(expectedHash) // returns the blockhash - ); - - (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, expectedHash, l1OriginNumber, signature); - - bool result = verifier.verify(proofBytes, IMAGE_ID, journal); - assertTrue(result); - } - function testConstants() public view { - assertEq(verifier.EIP2935_CONTRACT(), 0x0000F90827F1C53a10cb7A02335B175320002935); - assertEq(verifier.BLOCKHASH_WINDOW(), 256); - assertEq(verifier.EIP2935_WINDOW(), 8191); assertEq(address(verifier.SYSTEM_CONFIG_GLOBAL()), address(systemConfigGlobal)); } } From b38214b4e33d383f17750792faf00994b37014a5 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 17 Feb 2026 17:14:33 -0500 Subject: [PATCH 058/125] forge fmt --- src/multiproof/AggregateVerifier.sol | 82 +++++++++++++++++++++---- test/multiproof/AggregateVerifier.t.sol | 74 ++++++++++------------ test/multiproof/BaseTest.t.sol | 21 ++++--- test/multiproof/Challenge.t.sol | 64 +++++++------------ test/multiproof/Nullify.t.sol | 35 +++++------ 5 files changed, 147 insertions(+), 129 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 68d3ca72..be23086b 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -160,7 +160,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Emitted when the game is resolved. /// @param status The status of the game. event Resolved(GameStatus status); - + /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. /// @param challenger The address of the challenger. /// @param game The game used to challenge this proposal. @@ -288,7 +288,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard { BLOCK_INTERVAL = blockInterval; INTERMEDIATE_BLOCK_INTERVAL = intermediateBlockInterval; - if (BLOCK_INTERVAL == 0 || intermediateBlockInterval == 0 || BLOCK_INTERVAL % INTERMEDIATE_BLOCK_INTERVAL != 0) { + if (BLOCK_INTERVAL == 0 || intermediateBlockInterval == 0 || BLOCK_INTERVAL % INTERMEDIATE_BLOCK_INTERVAL != 0) + { revert InvalidBlockInterval(BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); } @@ -334,7 +335,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // Last intermediate root has to match the proposal's claim if (intermediateOutputRoot(intermediateOutputRootsCount() - 1) != rootClaim().raw()) { - revert IntermediateRootMismatch(intermediateOutputRoot(intermediateOutputRootsCount() - 1), rootClaim().raw()); + revert IntermediateRootMismatch( + intermediateOutputRoot(intermediateOutputRootsCount() - 1), rootClaim().raw() + ); } // The first game is initialized with a parent index of uint32.max. @@ -392,7 +395,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { 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(); @@ -474,7 +477,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @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 { + function nullify(bytes calldata proofBytes, uint256 intermediateRootIndex, bytes32 intermediateRootToProve) + external + { if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); if (intermediateRootIndex >= intermediateOutputRootsCount()) revert InvalidIntermediateRootIndex(); @@ -482,8 +487,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bytes32 proposedIntermediateRoot = intermediateOutputRoot(intermediateRootIndex); if (proposedIntermediateRoot == intermediateRootToProve) revert IntermediateRootSameAsProposed(); - bytes32 startingRoot = intermediateRootIndex == 0 ? startingOutputRoot.root.raw() : intermediateOutputRoot(intermediateRootIndex - 1); - uint256 startingL2SequenceNumber = startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL; + bytes32 startingRoot = intermediateRootIndex == 0 + ? startingOutputRoot.root.raw() + : intermediateOutputRoot(intermediateRootIndex - 1); + uint256 startingL2SequenceNumber = + startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL; uint256 endingL2SequenceNumber = startingL2SequenceNumber + INTERMEDIATE_BLOCK_INTERVAL; ProofType proofType = ProofType(uint8(proofBytes[0])); @@ -492,12 +500,28 @@ contract AggregateVerifier is Clone, ReentrancyGuard { if (provingData.teeProver == address(0)) { revert MissingTEEProof(); } - _verifyTeeProof(proofBytes[1:], msg.sender, startingRoot, startingL2SequenceNumber, intermediateRootToProve, endingL2SequenceNumber, abi.encodePacked(intermediateRootToProve)); + _verifyTeeProof( + proofBytes[1:], + msg.sender, + startingRoot, + startingL2SequenceNumber, + intermediateRootToProve, + endingL2SequenceNumber, + abi.encodePacked(intermediateRootToProve) + ); } else if (proofType == ProofType.ZK) { if (provingData.zkProver == address(0)) { revert MissingZKProof(); } - _verifyZkProof(proofBytes[1:], msg.sender, startingRoot, startingL2SequenceNumber, intermediateRootToProve, endingL2SequenceNumber, abi.encodePacked(intermediateRootToProve)); + _verifyZkProof( + proofBytes[1:], + msg.sender, + startingRoot, + startingL2SequenceNumber, + intermediateRootToProve, + endingL2SequenceNumber, + abi.encodePacked(intermediateRootToProve) + ); } else { revert InvalidProofType(); } @@ -679,13 +703,29 @@ contract AggregateVerifier is Clone, ReentrancyGuard { if (proofType == ProofType.TEE) { // Only one TEE proof can be submitted. if (provingData.teeProver != address(0)) revert AlreadyProven(); - _verifyTeeProof(proofBytes, prover, startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), l2SequenceNumber(), intermediateOutputRoots()); + _verifyTeeProof( + proofBytes, + prover, + startingOutputRoot.root.raw(), + startingOutputRoot.l2SequenceNumber, + rootClaim().raw(), + l2SequenceNumber(), + intermediateOutputRoots() + ); // Update proving data. provingData.teeProver = prover; } else if (proofType == ProofType.ZK) { // Only one ZK proof can be submitted. if (provingData.zkProver != address(0)) revert AlreadyProven(); - _verifyZkProof(proofBytes, prover, startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), l2SequenceNumber(), intermediateOutputRoots()); + _verifyZkProof( + proofBytes, + prover, + startingOutputRoot.root.raw(), + startingOutputRoot.l2SequenceNumber, + rootClaim().raw(), + l2SequenceNumber(), + intermediateOutputRoots() + ); // Update proving data. provingData.zkProver = prover; @@ -717,7 +757,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Verifies a TEE proof for the current game. /// @param proofBytes The proof: prover(20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65). - function _verifyTeeProof(bytes calldata proofBytes, address prover, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots) internal view { + function _verifyTeeProof( + bytes calldata proofBytes, + address prover, + bytes32 startingRoot, + uint256 startingL2SequenceNumber, + bytes32 endingRoot, + uint256 endingL2SequenceNumber, + bytes memory intermediateRoots + ) internal view { bytes32 l1OriginHash = bytes32(proofBytes[:32]); uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); // Verify claimed L1 origin hash matches actual blockhash @@ -745,7 +793,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Verifies a ZK proof for the current game. /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + zkProof (variable). - function _verifyZkProof(bytes calldata proofBytes, address prover, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots) internal view { + function _verifyZkProof( + bytes calldata proofBytes, + address prover, + bytes32 startingRoot, + uint256 startingL2SequenceNumber, + bytes32 endingRoot, + uint256 endingL2SequenceNumber, + bytes memory intermediateRoots + ) internal view { bytes32 l1OriginHash = bytes32(proofBytes[:32]); uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); // Verify claimed L1 origin hash matches actual blockhash diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 51dd36e7..9fc159ae 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -16,9 +16,8 @@ contract AggregateVerifierTest is BaseTest { 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 - ); + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); assertEq(game.wasRespectedGameTypeWhenCreated(), true); assertEq(address(game.teeProver()), TEE_PROVER); @@ -29,7 +28,9 @@ contract AggregateVerifierTest is BaseTest { 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.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max, game.intermediateOutputRoots()) + ); assertEq(game.bondRecipient(), address(0)); assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); @@ -40,9 +41,8 @@ contract AggregateVerifierTest is BaseTest { 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 - ); + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); assertEq(game.wasRespectedGameTypeWhenCreated(), true); assertEq(address(game.teeProver()), address(0)); @@ -53,7 +53,9 @@ contract AggregateVerifierTest is BaseTest { 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.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); @@ -77,9 +79,8 @@ contract AggregateVerifierTest is BaseTest { 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 - ); + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); // Cannot claim bond before resolving vm.expectRevert(AggregateVerifier.BondRecipientEmpty.selector); @@ -111,9 +112,8 @@ contract AggregateVerifierTest is BaseTest { 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 - ); + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); // Unlock and reclaim bond after delay uint256 balanceBefore = game.gameCreator().balance; @@ -142,9 +142,8 @@ contract AggregateVerifierTest is BaseTest { 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 - ); + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof); _provideProof(game, ZK_PROVER, zkProof); @@ -177,9 +176,8 @@ contract AggregateVerifierTest is BaseTest { 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 - ); + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof); (,,, Timestamp originalExpectedResolution) = game.provingData(); assertEq(originalExpectedResolution.raw(), block.timestamp + 7 days); @@ -208,17 +206,16 @@ contract AggregateVerifierTest is BaseTest { 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 - ); + 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()) + 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 - ); + _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, zkProof); } function testVerifyFailsWithL1OriginInFuture() public { @@ -230,10 +227,10 @@ contract AggregateVerifierTest is BaseTest { bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber); - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number)); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number) ); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } function testVerifyFailsWithL1OriginTooOld() public { @@ -250,9 +247,7 @@ contract AggregateVerifierTest is BaseTest { bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber); vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes - ); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } function testVerifyFailsWithL1OriginHashMismatch() public { @@ -265,9 +260,7 @@ contract AggregateVerifierTest is BaseTest { bytes32 actualHash = blockhash(l1OriginNumber); vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash)); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes - ); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } function testVerifyWithBlockhashWindow() public { @@ -283,9 +276,7 @@ contract AggregateVerifierTest is BaseTest { bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes - ); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } function testVerifyWithEIP2935Window() public { @@ -294,7 +285,6 @@ contract AggregateVerifierTest is BaseTest { // 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)); @@ -309,8 +299,6 @@ contract AggregateVerifierTest is BaseTest { bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes - ); + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } } diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index be1cc567..6993b712 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -149,7 +149,8 @@ contract BaseTest is Test { uint32 parentIndex, bytes memory proof ) internal returns (AggregateVerifier game) { - bytes memory intermediateRoots = abi.encodePacked(_generateIntermediateRootsExceptLast(l2BlockNumber), rootClaim.raw()); + bytes memory intermediateRoots = + abi.encodePacked(_generateIntermediateRootsExceptLast(l2BlockNumber), rootClaim.raw()); bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), uint32(parentIndex), intermediateRoots); vm.deal(creator, INIT_BOND); @@ -159,11 +160,7 @@ contract BaseTest is Test { ); } - function _provideProof( - AggregateVerifier game, - address prover, - bytes memory proofBytes - ) internal { + function _provideProof(AggregateVerifier game, address prover, bytes memory proofBytes) internal { vm.prank(prover); game.verifyProposalProof(proofBytes); } @@ -174,7 +171,11 @@ contract BaseTest is Test { /// @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) { + 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 @@ -189,8 +190,10 @@ contract BaseTest is Test { 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))); + intermediateRoots = abi.encodePacked( + intermediateRoots, keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)) + ); } return intermediateRoots; } -} \ No newline at end of file +} diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index e92125e5..b40303ca 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -18,17 +18,15 @@ contract ChallengeTest is BaseTest { 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 - ); + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof - ); + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); // Get game index from factory uint256 gameIndex = factory.gameCount() - 1; @@ -61,17 +59,14 @@ contract ChallengeTest is BaseTest { 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 - ); + AggregateVerifier game1 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1); // Create second game with different root claim and ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); - _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2 - ); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2); uint256 gameIndex = factory.gameCount() - 1; @@ -85,9 +80,8 @@ contract ChallengeTest is BaseTest { 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 - ); + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); // Create game2 with game1 as parent uint256 game1Index = factory.gameCount() - 1; @@ -96,9 +90,7 @@ contract ChallengeTest is BaseTest { bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof - ); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof); uint256 gameIndex = factory.gameCount() - 1; @@ -112,16 +104,13 @@ contract ChallengeTest is BaseTest { 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 - ); + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2 - ); + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2); uint256 gameIndex = factory.gameCount() - 1; @@ -135,9 +124,8 @@ contract ChallengeTest is BaseTest { 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 - ); + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); // Resolve game1 vm.warp(block.timestamp + 7 days + 1); @@ -147,9 +135,7 @@ contract ChallengeTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof - ); + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); uint256 challengeIndex1 = factory.gameCount() - 1; vm.expectRevert(ClaimAlreadyResolved.selector); @@ -163,9 +149,8 @@ contract ChallengeTest is BaseTest { 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 - ); + AggregateVerifier parentGame = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, parentProof); uint256 parentGameIndex = factory.gameCount() - 1; currentL2BlockNumber += BLOCK_INTERVAL; @@ -176,13 +161,7 @@ contract ChallengeTest is BaseTest { AggregateVerifier childGame = // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame( - TEE_PROVER, - rootClaim2, - currentL2BlockNumber, - uint32(parentGameIndex), - childProof - ); + _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, uint32(parentGameIndex), childProof); // blacklist parent game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); @@ -198,9 +177,8 @@ contract ChallengeTest is BaseTest { 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 - ); + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); // blacklist game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 81bbec8f..5f02efa2 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -15,9 +15,8 @@ contract NullifyTest is BaseTest { 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 - ); + 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); @@ -41,9 +40,8 @@ contract NullifyTest is BaseTest { 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 - ); + 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); @@ -67,9 +65,8 @@ contract NullifyTest is BaseTest { Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof - ); + AggregateVerifier game1 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof); Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); @@ -84,9 +81,8 @@ contract NullifyTest is BaseTest { 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 - ); + 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); @@ -101,9 +97,8 @@ contract NullifyTest is BaseTest { 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 - ); + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); // Resolve game1 vm.warp(block.timestamp + 7 days); @@ -123,17 +118,15 @@ contract NullifyTest is BaseTest { 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 - ); + AggregateVerifier game1 = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); // Challenge game1 Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - AggregateVerifier game2 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof - ); + AggregateVerifier game2 = + _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); uint256 challengeIndex = factory.gameCount() - 1; game1.challenge(challengeIndex); From f82f98330d192b36d1a653c16190106bf80da5f6 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Thu, 19 Feb 2026 22:44:49 +0000 Subject: [PATCH 059/125] feat: split deploy configs and scripts into no-nitro and with-nitro variants --- deploy-config/sepolia-no-nitro.json | 9 + .../{sepolia.json => sepolia-with-nitro.json} | 2 +- ...orTesting.s.sol => DeployDevNoNitro.s.sol} | 112 ++++-- scripts/multiproof/DeployDevWithNitro.s.sol | 343 ++++++++++++++++++ src/multiproof/tee/SystemConfigGlobal.sol | 10 +- 5 files changed, 440 insertions(+), 36 deletions(-) create mode 100644 deploy-config/sepolia-no-nitro.json rename deploy-config/{sepolia.json => sepolia-with-nitro.json} (86%) rename scripts/multiproof/{DeployAllForTesting.s.sol => DeployDevNoNitro.s.sol} (61%) create mode 100644 scripts/multiproof/DeployDevWithNitro.s.sol diff --git a/deploy-config/sepolia-no-nitro.json b/deploy-config/sepolia-no-nitro.json new file mode 100644 index 00000000..a88b1352 --- /dev/null +++ b/deploy-config/sepolia-no-nitro.json @@ -0,0 +1,9 @@ +{ + "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33", + "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "teeProposer": "0xb28E6890Cffa969dA9851c1BF1Ac34B76EbFEe98", + "gameType": "621", + "genesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", + "genesisBlockNumber": "37223829", + "configHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360" +} diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia-with-nitro.json similarity index 86% rename from deploy-config/sepolia.json rename to deploy-config/sepolia-with-nitro.json index 5a1e059d..ee0ad04e 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia-with-nitro.json @@ -1,7 +1,7 @@ { "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33", "teeImageHash": "0x692a590aa58825d31ccfa165913f0a915c02ca58607b0f787a149179d2b69ec1", - "teeProposer": "0x45efd28cCf55231a1F4976c9A503C59976Ed7520", + "teeProposer": "0xb28E6890Cffa969dA9851c1BF1Ac34B76EbFEe98", "gameType": "621", "genesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", "genesisBlockNumber": "37223829", diff --git a/scripts/multiproof/DeployAllForTesting.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol similarity index 61% rename from scripts/multiproof/DeployAllForTesting.s.sol rename to scripts/multiproof/DeployDevNoNitro.s.sol index 8719d5bd..09e1d94d 100644 --- a/scripts/multiproof/DeployAllForTesting.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -1,6 +1,57 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +/** + * @title DeployDevNoNitro + * @notice Development deployment WITHOUT AWS Nitro attestation validation. + * + * ══════════════════════════════════════════════════════════════════════════════════ + * DEPLOYMENT TYPE: DEV (NO NITRO) + * ══════════════════════════════════════════════════════════════════════════════════ + * + * This script deploys infrastructure using DevSystemConfigGlobal, which BYPASSES + * AWS Nitro attestation validation. Signers can be registered with a simple call + * to addDevSigner() without needing a real Nitro enclave or attestation document. + * + * USE THIS SCRIPT WHEN: + * - Running local development or testing + * - You don't have access to an AWS Nitro enclave + * - You want to quickly test the prover without attestation overhead + * + * DO NOT USE THIS SCRIPT FOR: + * - Production deployments + * - Security testing of the attestation flow + * + * ───────────────────────────────────────────────────────────────────────────────── + * SIGNER REGISTRATION (SIMPLIFIED) + * ───────────────────────────────────────────────────────────────────────────────── + * + * After deployment, register a signer with a single call: + * + * cast send $SYSTEM_CONFIG_GLOBAL \ + * "addDevSigner(address,bytes32)" $SIGNER_ADDRESS $TEE_IMAGE_HASH \ + * --private-key $OWNER_KEY --rpc-url $RPC_URL + * + * No attestation, PCR0 registration, or certificate validation required. + * + * ───────────────────────────────────────────────────────────────────────────────── + * COMPARISON WITH DeployDevWithNitro + * ───────────────────────────────────────────────────────────────────────────────── + * + * | Feature | DeployDevNoNitro | DeployDevWithNitro | + * |----------------------------|----------------------|------------------------| + * | SystemConfigGlobal | DevSystemConfigGlobal | SystemConfigGlobal | + * | Signer registration | addDevSigner() | registerSigner() | + * | Requires Nitro enclave | No | Yes | + * | Validates AWS cert chain | No | Yes | + * | PCR0 pre-registration | No | Yes | + * | Attestation freshness | N/A | < 60 minutes | + * + * Both scripts use mocks for AnchorStateRegistry, DelayedWETH, and ZK Verifier. + * + * ══════════════════════════════════════════════════════════════════════════════════ + */ + import {CertManager} from "@nitro-validator/CertManager.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {Script} from "forge-std/Script.sol"; @@ -23,10 +74,10 @@ import {MinimalProxyAdmin} from "./mocks/MinimalProxyAdmin.sol"; import {MockAnchorStateRegistry} from "./mocks/MockAnchorStateRegistry.sol"; import {MockDelayedWETH} from "./mocks/MockDelayedWETH.sol"; -/// @title DeployAllForTesting -/// @notice Deploys everything needed for e2e testing, using mocks for optimism contracts. -/// @dev Uses the REAL DisputeGameFactory but mocks AnchorStateRegistry and DelayedWETH. -contract DeployAllForTesting is Script { +/// @title DeployDevNoNitro +/// @notice Development deployment WITHOUT AWS Nitro attestation validation. +/// @dev Uses DevSystemConfigGlobal which allows addDevSigner() to bypass attestation. +contract DeployDevNoNitro is Script { using stdJson for string; /// @notice Constant from Optimism's Constants.sol - the storage slot for proxy admin. @@ -59,11 +110,13 @@ contract DeployAllForTesting is Script { function run() public { DeployConfig memory cfg = _loadConfig(); - console.log("=== Deploying Complete Test Infrastructure ==="); + console.log("=== Deploying Dev Infrastructure (NO NITRO) ==="); console.log("Chain ID:", block.chainid); console.log("Owner:", cfg.owner); console.log("TEE Proposer:", cfg.teeProposer); console.log("Game Type:", cfg.gameTypeRaw); + console.log(""); + console.log("NOTE: Using DevSystemConfigGlobal - NO attestation required."); vm.startBroadcast(); @@ -78,7 +131,7 @@ contract DeployAllForTesting is Script { } function _loadConfig() internal view returns (DeployConfig memory cfg) { - string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia.json")); + string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia-no-nitro.json")); string memory config = vm.readFile(configPath); cfg.owner = config.readAddress(".finalSystemOwner"); @@ -92,11 +145,11 @@ contract DeployAllForTesting is Script { } function _deployTEEContracts(address owner) internal { - // 1. CertManager + // 1. CertManager (not used in dev mode, but deployed for interface compatibility) certManager = address(new CertManager()); console.log("CertManager:", certManager); - // 2. DevSystemConfigGlobal (dev version) with proxy + // 2. DevSystemConfigGlobal - allows addDevSigner() to bypass attestation address scgImpl = address(new DevSystemConfigGlobal(CertManager(certManager))); systemConfigGlobalProxy = address( new TransparentUpgradeableProxy( @@ -114,33 +167,22 @@ contract DeployAllForTesting is Script { function _deployInfrastructure(DeployConfig memory cfg) internal { // 4. REAL DisputeGameFactory (behind proxy) - // The constructor calls _disableInitializers(), so we must use a proxy address factoryImpl = address(new DisputeGameFactory()); - - // Deploy a minimal proxy admin that returns cfg.owner as owner MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.owner); - // Deploy proxy - but DON'T initialize yet (empty initData) - // We need to set the proxy admin storage slot first TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( factoryImpl, - address(proxyAdmin), // Use our MinimalProxyAdmin as the admin - "" // Don't call initialize yet + address(proxyAdmin), + "" ); - // Set the PROXY_OWNER_ADDRESS slot on the proxy so initialize() passes the access check vm.store(address(proxy), PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(address(proxyAdmin))))); - - // Now initialize - caller is checked against proxyAdmin or proxyAdminOwner DisputeGameFactory(address(proxy)).initialize(cfg.owner); disputeGameFactory = address(proxy); - console.log("DisputeGameFactory (REAL):", disputeGameFactory); + console.log("DisputeGameFactory:", disputeGameFactory); - // 5. Mock AnchorStateRegistry - still mocked because: - // - The real one needs SystemConfig, SuperchainConfig, etc. - // - We only need getAnchorRoot() for the prover - // - Manual setAnchorState() is useful for testing + // 5. Mock AnchorStateRegistry MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); mockAnchorRegistry = address(asr); asr.initialize(disputeGameFactory, Hash.wrap(cfg.genesisOutputRoot), cfg.genesisBlockNumber, cfg.gameType); @@ -165,7 +207,7 @@ contract DeployAllForTesting is Script { IVerifier(teeVerifier), IVerifier(zkVerifier), cfg.teeImageHash, - bytes32(0), // zkImageHash (unused for testing) + bytes32(0), // zkImageHash (unused) cfg.configHash, cfg.teeProposer, 8453, // l2ChainId (Base mainnet) @@ -173,9 +215,8 @@ contract DeployAllForTesting is Script { ) ); console.log("AggregateVerifier:", aggregateVerifier); - console.log("ConfigHash:", vm.toString(cfg.configHash)); - // 8. Register AggregateVerifier with the real factory + // 8. Register AggregateVerifier with the factory DisputeGameFactory(disputeGameFactory).setImplementation(cfg.gameType, IDisputeGame(aggregateVerifier)); DisputeGameFactory(disputeGameFactory).setInitBond(cfg.gameType, INIT_BOND); console.log("Registered AggregateVerifier with factory"); @@ -183,29 +224,32 @@ contract DeployAllForTesting is Script { function _printSummary(bytes32 teeImageHash, uint256 gameType, bytes32 configHash) internal view { console.log("\n========================================"); - console.log(" DEPLOYMENT COMPLETE"); + console.log(" DEV DEPLOYMENT COMPLETE (NO NITRO)"); console.log("========================================"); console.log("\nTEE Contracts:"); console.log(" CertManager:", certManager); console.log(" DevSystemConfigGlobal:", systemConfigGlobalProxy); console.log(" TEEVerifier:", teeVerifier); console.log("\nInfrastructure:"); - console.log(" DisputeGameFactory (real):", disputeGameFactory); + 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:", gameType); - console.log(" ConfigHash:", vm.toString(configHash)); + console.log(" TEE Image Hash:", vm.toString(teeImageHash)); + console.log(" Config Hash:", vm.toString(configHash)); console.log("========================================"); - console.log("\nNEXT STEP - Register dev signer:"); - console.log("cast send", systemConfigGlobalProxy); + console.log("\n>>> NEXT STEP - Register dev signer (NO ATTESTATION NEEDED) <<<"); + console.log("\ncast send", systemConfigGlobalProxy); console.log(' "addDevSigner(address,bytes32)" '); console.log(" ", vm.toString(teeImageHash)); + console.log(" --private-key --rpc-url "); + console.log("\n========================================\n"); } function _writeOutput() internal { - string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-all.json"); + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-dev-no-nitro.json"); string memory output = string.concat( '{"CertManager":"', vm.toString(certManager), @@ -217,11 +261,13 @@ contract DeployAllForTesting is Script { vm.toString(disputeGameFactory), '","AnchorStateRegistry":"', vm.toString(mockAnchorRegistry), + '","DelayedWETH":"', + vm.toString(mockDelayedWETH), '","AggregateVerifier":"', vm.toString(aggregateVerifier), '"}' ); vm.writeFile(outPath, output); - console.log("\nDeployment saved to:", outPath); + 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 00000000..49875a9e --- /dev/null +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +/** + * @title DeployDevWithNitro + * @notice Development deployment WITH AWS Nitro attestation validation. + * + * ══════════════════════════════════════════════════════════════════════════════════ + * DEPLOYMENT TYPE: DEV (WITH NITRO) + * ══════════════════════════════════════════════════════════════════════════════════ + * + * This script deploys infrastructure using the REAL SystemConfigGlobal, which + * REQUIRES valid AWS Nitro attestation for signer registration. You cannot use + * addDevSigner() - you must go through the full registerSigner() flow. + * + * USE THIS SCRIPT WHEN: + * - Testing the full Nitro attestation flow end-to-end + * - You have access to a real AWS Nitro enclave + * - You want to validate the production registration process + * + * DO NOT USE THIS SCRIPT IF: + * - You don't have a Nitro enclave available + * - You just want quick local testing (use DeployDevNoNitro instead) + * + * NOTE: This still uses mocks for AnchorStateRegistry, DelayedWETH, and ZK Verifier, + * so it's not a full production deployment - just production-like for the + * Nitro attestation flow. + * + * ───────────────────────────────────────────────────────────────────────────────── + * STEP 1: Register the PCR0 (enclave image hash) - OWNER ONLY + * ───────────────────────────────────────────────────────────────────────────────── + * + * The PCR0 is the raw 48-byte hash of the enclave image. You must register it + * before any signers using that image can be registered. + * + * # Get raw PCR0 bytes from the enclave (48 bytes, hex-encoded) + * PCR0_RAW="0x<48_bytes_hex>" + * + * # Register it (only owner can do this) + * cast send $SYSTEM_CONFIG_GLOBAL "registerPCR0(bytes)" $PCR0_RAW \ + * --private-key $OWNER_KEY --rpc-url $RPC_URL + * + * Note: The teeImageHash in deploy-config is keccak256(PCR0_RAW), not the raw bytes. + * + * ───────────────────────────────────────────────────────────────────────────────── + * STEP 2: Get fresh attestation from the enclave + * ───────────────────────────────────────────────────────────────────────────────── + * + * Query the Nitro enclave for a signed attestation document. This contains: + * - The enclave's public key (from which the signer address is derived) + * - PCR0 (image hash) + * - Timestamp + * - AWS certificate chain signature + * + * curl -s -X POST $ENCLAVE_URL \ + * -H "Content-Type: application/json" \ + * -d '{"jsonrpc":"2.0","method":"enclave_signerAttestation","id":1}' \ + * | jq -r '.result' + * + * IMPORTANT: The attestation is only valid for 60 minutes! You must complete + * Step 3 within that window. + * + * ───────────────────────────────────────────────────────────────────────────────── + * STEP 3: Register the signer using the attestation - OWNER OR MANAGER + * ───────────────────────────────────────────────────────────────────────────────── + * + * Option A: Use the register-signer tool from base/op-enclave + * + * go install github.com/base/op-enclave/tools/register-signer@latest + * + * register-signer \ + * -attestation $ATTESTATION_HEX \ + * -deployment deployments/-dev-with-nitro.json \ + * -rpc $RPC_URL \ + * -private-key $OWNER_OR_MANAGER_KEY + * + * Option B: Call the contract directly (requires parsing attestation into TBS + sig) + * + * # The attestation is a COSE Sign1 structure. You need to split it into: + * # - attestationTbs: The "to-be-signed" payload + * # - signature: The ECDSA signature over the payload + * # + * # See: https://github.com/base/op-enclave/tree/main/tools/register-signer + * + * cast send $SYSTEM_CONFIG_GLOBAL \ + * "registerSigner(bytes,bytes)" $ATTESTATION_TBS $SIGNATURE \ + * --private-key $OWNER_OR_MANAGER_KEY --rpc-url $RPC_URL + * + * ───────────────────────────────────────────────────────────────────────────────── + * VERIFICATION + * ───────────────────────────────────────────────────────────────────────────────── + * + * After registration, verify the signer is registered: + * + * # Check if signer is valid + * cast call $SYSTEM_CONFIG_GLOBAL "isValidSigner(address)(bool)" $SIGNER_ADDRESS + * + * # Get the PCR0 hash associated with the signer + * cast call $SYSTEM_CONFIG_GLOBAL "signerPCR0(address)(bytes32)" $SIGNER_ADDRESS + * + * ───────────────────────────────────────────────────────────────────────────────── + * COMPARISON WITH DeployDevNoNitro + * ───────────────────────────────────────────────────────────────────────────────── + * + * | Feature | DeployDevNoNitro | DeployDevWithNitro | + * |----------------------------|----------------------|------------------------| + * | SystemConfigGlobal | DevSystemConfigGlobal | SystemConfigGlobal | + * | Signer registration | addDevSigner() | registerSigner() | + * | Requires Nitro enclave | No | Yes | + * | Validates AWS cert chain | No | Yes | + * | PCR0 pre-registration | No | Yes | + * | Attestation freshness | N/A | < 60 minutes | + * + * Both scripts use mocks for AnchorStateRegistry, DelayedWETH, and ZK Verifier. + * + * ══════════════════════════════════════════════════════════════════════════════════ + */ + +import {CertManager} from "@nitro-validator/CertManager.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {Script} from "forge-std/Script.sol"; +import {stdJson} from "forge-std/StdJson.sol"; +import {console2 as console} from "forge-std/console2.sol"; +import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; +import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; +import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; +import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; +import {GameType, Hash} from "optimism/src/dispute/lib/Types.sol"; + +import {AggregateVerifier} from "../src/AggregateVerifier.sol"; +import {IVerifier} from "../src/interfaces/IVerifier.sol"; +import {MockVerifier} from "../src/mocks/MockVerifier.sol"; +import {SystemConfigGlobal} from "../src/tee/SystemConfigGlobal.sol"; +import {TEEVerifier} from "../src/tee/TEEVerifier.sol"; + +import {MinimalProxyAdmin} from "./mocks/MinimalProxyAdmin.sol"; +import {MockAnchorStateRegistry} from "./mocks/MockAnchorStateRegistry.sol"; +import {MockDelayedWETH} from "./mocks/MockDelayedWETH.sol"; + +/// @title DeployDevWithNitro +/// @notice Development deployment WITH AWS Nitro attestation validation. +/// @dev Uses real SystemConfigGlobal which requires registerSigner() with valid attestation. +contract DeployDevWithNitro is Script { + using stdJson for string; + + /// @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 INIT_BOND = 0.001 ether; + + /// @notice Config struct to reduce stack variables. + struct DeployConfig { + address owner; + bytes32 teeImageHash; + address teeProposer; + GameType gameType; + uint256 gameTypeRaw; + bytes32 genesisOutputRoot; + uint256 genesisBlockNumber; + bytes32 configHash; + } + + // Deployed addresses + address public certManager; + address public systemConfigGlobalProxy; + address public teeVerifier; + address public disputeGameFactory; + address public mockAnchorRegistry; + address public mockDelayedWETH; + address public aggregateVerifier; + + function run() public { + DeployConfig memory cfg = _loadConfig(); + + console.log("=== Deploying Dev Infrastructure (WITH NITRO) ==="); + console.log("Chain ID:", block.chainid); + console.log("Owner:", cfg.owner); + console.log("TEE Proposer:", cfg.teeProposer); + console.log("Game Type:", cfg.gameTypeRaw); + console.log(""); + console.log("NOTE: Using REAL SystemConfigGlobal - attestation REQUIRED."); + + vm.startBroadcast(); + + _deployTEEContracts(cfg.owner); + _deployInfrastructure(cfg); + _deployAggregateVerifier(cfg); + + vm.stopBroadcast(); + + _printSummary(cfg); + _writeOutput(); + } + + function _loadConfig() internal view returns (DeployConfig memory cfg) { + string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia-with-nitro.json")); + string memory config = vm.readFile(configPath); + + cfg.owner = config.readAddress(".finalSystemOwner"); + cfg.teeImageHash = config.readBytes32(".teeImageHash"); + cfg.teeProposer = config.readAddressOr(".teeProposer", cfg.owner); + cfg.gameTypeRaw = config.readUintOr(".gameType", 621); + cfg.gameType = GameType.wrap(uint32(cfg.gameTypeRaw)); + cfg.genesisOutputRoot = config.readBytes32Or(".genesisOutputRoot", bytes32(uint256(1))); + cfg.genesisBlockNumber = config.readUintOr(".genesisBlockNumber", 0); + cfg.configHash = config.readBytes32Or(".configHash", bytes32(0)); + } + + function _deployTEEContracts(address owner) internal { + // 1. CertManager - validates AWS Nitro certificate chains + certManager = address(new CertManager()); + console.log("CertManager:", certManager); + + // 2. SystemConfigGlobal (REAL version) - requires attestation for signer registration + address scgImpl = address(new SystemConfigGlobal(CertManager(certManager))); + systemConfigGlobalProxy = address( + new TransparentUpgradeableProxy( + scgImpl, + address(0xdead), // Non-upgradeable for testing + abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) + ) + ); + console.log("SystemConfigGlobal:", systemConfigGlobalProxy); + + // 3. TEEVerifier + teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); + console.log("TEEVerifier:", teeVerifier); + } + + function _deployInfrastructure(DeployConfig memory cfg) internal { + // 4. REAL DisputeGameFactory (behind proxy) + address factoryImpl = address(new DisputeGameFactory()); + MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.owner); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + factoryImpl, + address(proxyAdmin), + "" + ); + + vm.store(address(proxy), PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(address(proxyAdmin))))); + DisputeGameFactory(address(proxy)).initialize(cfg.owner); + + disputeGameFactory = address(proxy); + console.log("DisputeGameFactory:", disputeGameFactory); + + // 5. Mock AnchorStateRegistry + MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); + mockAnchorRegistry = address(asr); + asr.initialize(disputeGameFactory, Hash.wrap(cfg.genesisOutputRoot), cfg.genesisBlockNumber, cfg.gameType); + console.log("AnchorStateRegistry (mock):", mockAnchorRegistry); + } + + function _deployAggregateVerifier(DeployConfig memory cfg) internal { + // 6. Mock ZK Verifier + address zkVerifier = address(new MockVerifier()); + console.log("MockVerifier (ZK):", zkVerifier); + + // 6.5. Mock DelayedWETH for bond handling + mockDelayedWETH = address(new MockDelayedWETH()); + console.log("MockDelayedWETH:", mockDelayedWETH); + + // 7. AggregateVerifier + aggregateVerifier = address( + new AggregateVerifier( + cfg.gameType, + IAnchorStateRegistry(mockAnchorRegistry), + IDelayedWETH(payable(mockDelayedWETH)), + IVerifier(teeVerifier), + IVerifier(zkVerifier), + cfg.teeImageHash, + bytes32(0), // zkImageHash (unused) + cfg.configHash, + cfg.teeProposer, + 8453, // l2ChainId (Base mainnet) + BLOCK_INTERVAL + ) + ); + console.log("AggregateVerifier:", aggregateVerifier); + + // 8. Register AggregateVerifier with the factory + DisputeGameFactory(disputeGameFactory).setImplementation(cfg.gameType, IDisputeGame(aggregateVerifier)); + DisputeGameFactory(disputeGameFactory).setInitBond(cfg.gameType, INIT_BOND); + console.log("Registered AggregateVerifier with factory"); + } + + function _printSummary(DeployConfig memory cfg) internal view { + console.log("\n========================================"); + console.log(" DEV DEPLOYMENT COMPLETE (WITH NITRO)"); + console.log("========================================"); + console.log("\nTEE Contracts:"); + console.log(" CertManager:", certManager); + console.log(" SystemConfigGlobal:", systemConfigGlobalProxy); + console.log(" TEEVerifier:", teeVerifier); + console.log("\nInfrastructure:"); + console.log(" DisputeGameFactory:", disputeGameFactory); + console.log(" AnchorStateRegistry (mock):", mockAnchorRegistry); + console.log(" DelayedWETH (mock):", mockDelayedWETH); + console.log("\nGame:"); + console.log(" AggregateVerifier:", aggregateVerifier); + console.log(" Game Type:", cfg.gameTypeRaw); + console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash)); + console.log(" Config Hash:", vm.toString(cfg.configHash)); + console.log("========================================"); + console.log("\n>>> NEXT STEPS (ATTESTATION REQUIRED) <<<"); + console.log("\n1. Register the PCR0 (raw 48-byte enclave image hash):"); + console.log(" cast send", systemConfigGlobalProxy); + console.log(' "registerPCR0(bytes)" '); + console.log(" --private-key --rpc-url "); + console.log("\n2. Get attestation from enclave (valid for 60 min):"); + console.log(' curl -X POST -H "Content-Type: application/json"'); + console.log(" -d '{\"jsonrpc\":\"2.0\",\"method\":\"enclave_signerAttestation\",\"id\":1}'"); + console.log("\n3. Register signer with attestation:"); + console.log(" go install github.com/base/op-enclave/tools/register-signer@latest"); + console.log(" register-signer -attestation -rpc -private-key "); + 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( + '{"CertManager":"', + vm.toString(certManager), + '","SystemConfigGlobal":"', + vm.toString(systemConfigGlobalProxy), + '","TEEVerifier":"', + vm.toString(teeVerifier), + '","DisputeGameFactory":"', + vm.toString(disputeGameFactory), + '","AnchorStateRegistry":"', + vm.toString(mockAnchorRegistry), + '","DelayedWETH":"', + vm.toString(mockDelayedWETH), + '","AggregateVerifier":"', + vm.toString(aggregateVerifier), + '"}' + ); + vm.writeFile(outPath, output); + console.log("Deployment saved to:", outPath); + } +} diff --git a/src/multiproof/tee/SystemConfigGlobal.sol b/src/multiproof/tee/SystemConfigGlobal.sol index a6d487e1..05cd9504 100644 --- a/src/multiproof/tee/SystemConfigGlobal.sol +++ b/src/multiproof/tee/SystemConfigGlobal.sol @@ -17,9 +17,14 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { using CborDecode for bytes; using LibCborElement for CborElement; - /// @notice Maximum age of an attestation document (60 minutes). + /// @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 address of the proposer. address public proposer; @@ -93,7 +98,8 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { Ptrs memory ptrs = validateAttestation(attestationTbs, signature); bytes32 pcr0Hash = attestationTbs.keccak(ptrs.pcrs[0]); if (!validPCR0s[pcr0Hash]) revert InvalidPCR0(); - if (ptrs.timestamp + MAX_AGE <= block.timestamp) revert AttestationTooOld(); + // Convert attestation timestamp from milliseconds to seconds before comparing + if (ptrs.timestamp / MS_PER_SECOND + MAX_AGE <= block.timestamp) revert AttestationTooOld(); // The publicKey is encoded in the form specified in section 4.3.6 of ANSI X9.62, // which is a 0x04 byte followed by the x and y coordinates of the public key. From 1801c4951c4c97ad3d6752782d60a3ae4f568fe5 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 20 Feb 2026 15:28:36 -0500 Subject: [PATCH 060/125] refactor _verifyProof --- src/multiproof/AggregateVerifier.sol | 149 +++++++++++++-------------- 1 file changed, 73 insertions(+), 76 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index be23086b..b910301c 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -386,7 +386,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard { DELAYED_WETH.deposit{value: msg.value}(); // Verify the proof. - _verifyProposalProof(proof[1:], ProofType(uint8(proof[0])), gameCreator()); + ProofType proofType = ProofType(uint8(proof[0])); + _verifyProof(proof[1:], proofType, gameCreator(), startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), l2SequenceNumber(), intermediateOutputRoots()); + + _updateProvingData(proofType, gameCreator()); + + emit Proved(gameCreator(), proofType); } /// @notice Verifies a proof for the current game. @@ -399,7 +404,19 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // The game must not be over. if (gameOver()) revert GameOver(); - _verifyProposalProof(proofBytes[1:], ProofType(uint8(proofBytes[0])), msg.sender); + ProofType proofType = ProofType(uint8(proofBytes[0])); + if (proofType == ProofType.TEE) { + if (provingData.teeProver != address(0)) revert AlreadyProven(); + } else if (proofType == ProofType.ZK) { + if (provingData.zkProver != address(0)) revert AlreadyProven(); + } else { + revert InvalidProofType(); + } + + _verifyProof(proofBytes[1:], proofType, msg.sender, startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), l2SequenceNumber(), intermediateOutputRoots()); + _updateProvingData(proofType, msg.sender); + + emit Proved(msg.sender, proofType); } /// @notice Resolves the game after a proof has been provided and enough time has passed. @@ -495,36 +512,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard { uint256 endingL2SequenceNumber = startingL2SequenceNumber + INTERMEDIATE_BLOCK_INTERVAL; ProofType proofType = ProofType(uint8(proofBytes[0])); - // Can only nullify a game that has a proof of the same type. if (proofType == ProofType.TEE) { - if (provingData.teeProver == address(0)) { - revert MissingTEEProof(); - } - _verifyTeeProof( - proofBytes[1:], - msg.sender, - startingRoot, - startingL2SequenceNumber, - intermediateRootToProve, - endingL2SequenceNumber, - abi.encodePacked(intermediateRootToProve) - ); + if (provingData.teeProver == address(0)) revert MissingTEEProof(); } else if (proofType == ProofType.ZK) { - if (provingData.zkProver == address(0)) { - revert MissingZKProof(); - } - _verifyZkProof( - proofBytes[1:], - msg.sender, - startingRoot, - startingL2SequenceNumber, - intermediateRootToProve, - endingL2SequenceNumber, - abi.encodePacked(intermediateRootToProve) - ); + if (provingData.zkProver == address(0)) revert MissingZKProof(); } else { revert InvalidProofType(); } + + _verifyProof(proofBytes[1:], proofType, msg.sender, startingRoot, startingL2SequenceNumber, intermediateRootToProve, endingL2SequenceNumber, abi.encodePacked(intermediateRootToProve)); // Set the game as challenged so that child games can't resolve. status = GameStatus.CHALLENGER_WINS; @@ -699,35 +695,24 @@ contract AggregateVerifier is Clone, ReentrancyGuard { return _getArgUint32(0x74); } - function _verifyProposalProof(bytes calldata proofBytes, ProofType proofType, address prover) internal { + /// @notice Updates the expected resolution timestamp. + function _updateExpectedResolution() internal { + uint64 newResolution = uint64(block.timestamp); + if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { + newResolution += FAST_FINALIZATION_DELAY; + } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { + newResolution += SLOW_FINALIZATION_DELAY; + } else { + revert NoProofProvided(); + } + provingData.expectedResolution = + Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); + } + + function _updateProvingData(ProofType proofType, address prover) internal { if (proofType == ProofType.TEE) { - // Only one TEE proof can be submitted. - if (provingData.teeProver != address(0)) revert AlreadyProven(); - _verifyTeeProof( - proofBytes, - prover, - startingOutputRoot.root.raw(), - startingOutputRoot.l2SequenceNumber, - rootClaim().raw(), - l2SequenceNumber(), - intermediateOutputRoots() - ); - // Update proving data. provingData.teeProver = prover; } else if (proofType == ProofType.ZK) { - // Only one ZK proof can be submitted. - if (provingData.zkProver != address(0)) revert AlreadyProven(); - _verifyZkProof( - proofBytes, - prover, - startingOutputRoot.root.raw(), - startingOutputRoot.l2SequenceNumber, - rootClaim().raw(), - l2SequenceNumber(), - intermediateOutputRoots() - ); - - // Update proving data. provingData.zkProver = prover; // Bond can be reclaimed after a ZK proof is provided. bondRecipient = gameCreator(); @@ -736,23 +721,41 @@ contract AggregateVerifier is Clone, ReentrancyGuard { } _updateExpectedResolution(); - - // Emit the proved event. - emit Proved(prover, proofType); } - /// @notice Updates the expected resolution timestamp. - function _updateExpectedResolution() internal { - uint64 newResolution = uint64(block.timestamp); - if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { - newResolution += FAST_FINALIZATION_DELAY; - } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { - newResolution += SLOW_FINALIZATION_DELAY; + function _verifyProof(bytes calldata proofBytes, ProofType proofType, address prover, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots) internal view { + bytes32 l1OriginHash = bytes32(proofBytes[:32]); + uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); + // Verify claimed L1 origin hash matches actual blockhash + _verifyL1Origin(l1OriginHash, l1OriginNumber); + + if (proofType == ProofType.TEE) { + _verifyTeeProof( + proofBytes, + prover, + l1OriginHash, + l1OriginNumber, + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots + ); + } else if (proofType == ProofType.ZK) { + _verifyZkProof( + proofBytes, + prover, + l1OriginHash, + l1OriginNumber, + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots + ); } else { - revert NoProofProvided(); + revert InvalidProofType(); } - provingData.expectedResolution = - Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); } /// @notice Verifies a TEE proof for the current game. @@ -760,17 +763,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard { function _verifyTeeProof( bytes calldata proofBytes, address prover, + bytes32 l1OriginHash, + uint256 l1OriginNumber, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots ) internal view { - bytes32 l1OriginHash = bytes32(proofBytes[:32]); - uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); - // Verify claimed L1 origin hash matches actual blockhash - _verifyL1Origin(l1OriginHash, l1OriginNumber); - bytes32 journal = keccak256( abi.encodePacked( prover, @@ -796,17 +796,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard { function _verifyZkProof( bytes calldata proofBytes, address prover, + bytes32 l1OriginHash, + uint256 l1OriginNumber, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots ) internal view { - bytes32 l1OriginHash = bytes32(proofBytes[:32]); - uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); - // Verify claimed L1 origin hash matches actual blockhash - _verifyL1Origin(l1OriginHash, l1OriginNumber); - bytes32 journal = keccak256( abi.encodePacked( prover, From 85918eea8a46bb0370c2686254f0a1d30b9b62b5 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 20 Feb 2026 16:19:24 -0500 Subject: [PATCH 061/125] added block interval tests and cleaned up some errors --- src/multiproof/AggregateVerifier.sol | 65 ++++++++++++------------- test/multiproof/AggregateVerifier.t.sol | 53 ++++++++++++++++++++ test/multiproof/Challenge.t.sol | 4 +- test/multiproof/Nullify.t.sol | 4 +- 4 files changed, 88 insertions(+), 38 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index b910301c..cace0937 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -185,8 +185,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard { //////////////////////////////////////////////////////////////// // Errors // //////////////////////////////////////////////////////////////// - /// @notice When the parent game is invalid. - error InvalidParentGame(); + /// @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); @@ -197,38 +197,38 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice When the game is not over. error GameNotOver(); - /// @notice When the parent game has not resolved. - error ParentGameNotResolved(); + /// @notice When the game is invalid. + error InvalidGame(); - /// @notice When there is no TEE proof. - error MissingTEEProof(); + /// @notice When the parent game is invalid. + error InvalidParentGame(); - /// @notice When there is no ZK proof. - error MissingZKProof(); + /// @notice When the parent game has not resolved. + error ParentGameNotResolved(); - /// @notice When the game is invalid. - error InvalidGame(); + /// @notice When there is no proof of the given type. + error MissingProof(ProofType proofType); /// @notice When the proof has already been verified. - error AlreadyProven(); + error AlreadyProven(ProofType proofType); /// @notice When the proof is invalid. error InvalidProof(); - /// @notice When no proof was provided. - error NoProofProvided(); - /// @notice When an invalid proof type is provided. error InvalidProofType(); - /// @notice When the bond recipient is empty. - error BondRecipientEmpty(); + /// @notice When no proof was provided. + error NoProofProvided(); /// @notice When the countered by game is invalid. error InvalidCounteredByGame(); - /// @notice When the block interval or intermediate block interval is invalid. - error InvalidBlockInterval(uint256 blockInterval, uint256 intermediateBlockInterval); + /// @notice When the countered by game is not resolved. + error CounteredByGameNotResolved(); + + /// @notice When the bond recipient is empty. + error BondRecipientEmpty(); /// @notice When the intermediate root index is invalid. error InvalidIntermediateRootIndex(); @@ -239,9 +239,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice When the intermediate root does not match the claim. error IntermediateRootMismatch(bytes32 intermediateRoot, bytes32 claim); - /// @notice When the countered by game is not resolved. - error CounteredByGameNotResolved(); - /// @notice Thrown when the L1 origin block is too old to verify. error L1OriginTooOld(uint256 l1OriginNumber, uint256 currentBlock); @@ -274,6 +271,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard { uint256 blockInterval, uint256 intermediateBlockInterval ) { + if (blockInterval == 0 || intermediateBlockInterval == 0 || blockInterval % intermediateBlockInterval != 0) + { + revert InvalidBlockInterval(blockInterval, intermediateBlockInterval); + } + // Set up initial game state. GAME_TYPE = gameType_; ANCHOR_STATE_REGISTRY = anchorStateRegistry_; @@ -287,12 +289,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { L2_CHAIN_ID = l2ChainId; BLOCK_INTERVAL = blockInterval; INTERMEDIATE_BLOCK_INTERVAL = intermediateBlockInterval; - - if (BLOCK_INTERVAL == 0 || intermediateBlockInterval == 0 || BLOCK_INTERVAL % INTERMEDIATE_BLOCK_INTERVAL != 0) - { - revert InvalidBlockInterval(BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); - } - + INITIALIZE_CALLDATA_SIZE = 0x7E + 0x20 * intermediateOutputRootsCount(); } @@ -406,9 +403,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { ProofType proofType = ProofType(uint8(proofBytes[0])); if (proofType == ProofType.TEE) { - if (provingData.teeProver != address(0)) revert AlreadyProven(); + if (provingData.teeProver != address(0)) revert AlreadyProven(ProofType.TEE); } else if (proofType == ProofType.ZK) { - if (provingData.zkProver != address(0)) revert AlreadyProven(); + if (provingData.zkProver != address(0)) revert AlreadyProven(ProofType.ZK); } else { revert InvalidProofType(); } @@ -462,8 +459,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // The TEE prover must not be empty. // You should nullify the game if a ZK proof has already been provided. - if (provingData.teeProver == address(0)) revert MissingTEEProof(); - if (provingData.zkProver != address(0)) revert AlreadyProven(); + if (provingData.teeProver == address(0)) revert MissingProof(ProofType.TEE); + if (provingData.zkProver != address(0)) revert AlreadyProven(ProofType.ZK); (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); @@ -473,7 +470,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { AggregateVerifier challengingGame = AggregateVerifier(address(game)); // The ZK prover must not be empty. - if (challengingGame.zkProver() == address(0)) revert MissingZKProof(); + if (challengingGame.zkProver() == address(0)) revert MissingProof(ProofType.ZK); // Update the counteredBy address. provingData.counteredByGameAddress = address(challengingGame); @@ -513,9 +510,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { ProofType proofType = ProofType(uint8(proofBytes[0])); if (proofType == ProofType.TEE) { - if (provingData.teeProver == address(0)) revert MissingTEEProof(); + if (provingData.teeProver == address(0)) revert MissingProof(ProofType.TEE); } else if (proofType == ProofType.ZK) { - if (provingData.zkProver == address(0)) revert MissingZKProof(); + if (provingData.zkProver == address(0)) revert MissingProof(ProofType.ZK); } else { revert InvalidProofType(); } diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 9fc159ae..184d7654 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -2,11 +2,14 @@ pragma solidity 0.8.15; import {BadExtraData} from "optimism/src/dispute/lib/Errors.sol"; +import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; +import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; import {Claim, GameStatus, Hash, Timestamp} from "optimism/src/dispute/lib/Types.sol"; import {AggregateVerifier} from "src/AggregateVerifier.sol"; +import {IVerifier} from "src/interfaces/IVerifier.sol"; import {BaseTest} from "test/BaseTest.t.sol"; @@ -301,4 +304,54 @@ contract AggregateVerifierTest is BaseTest { _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 + ); + + // 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 + ); + + // 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 + ); + } } diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index b40303ca..7d853d5f 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -70,7 +70,7 @@ contract ChallengeTest is BaseTest { uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(AggregateVerifier.MissingTEEProof.selector); + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE)); game1.challenge(gameIndex); } @@ -114,7 +114,7 @@ contract ChallengeTest is BaseTest { uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(AggregateVerifier.MissingZKProof.selector); + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.ZK)); game1.challenge(gameIndex); } diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 5f02efa2..01a226b0 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -71,7 +71,7 @@ contract NullifyTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - vm.expectRevert(AggregateVerifier.MissingTEEProof.selector); + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE)); game1.nullify(teeProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } @@ -87,7 +87,7 @@ contract NullifyTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - vm.expectRevert(AggregateVerifier.MissingZKProof.selector); + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.ZK)); game1.nullify(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } From d9732f3baaa0f6cfe23a8c8479ddd461c2f884bc Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 20 Feb 2026 16:19:33 -0500 Subject: [PATCH 062/125] forge fmt --- src/multiproof/AggregateVerifier.sol | 51 +++++++++++++++++++++---- test/multiproof/AggregateVerifier.t.sol | 4 +- test/multiproof/Challenge.t.sol | 4 +- test/multiproof/Nullify.t.sol | 4 +- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index cace0937..c9b0b9e7 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -271,8 +271,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { uint256 blockInterval, uint256 intermediateBlockInterval ) { - if (blockInterval == 0 || intermediateBlockInterval == 0 || blockInterval % intermediateBlockInterval != 0) - { + if (blockInterval == 0 || intermediateBlockInterval == 0 || blockInterval % intermediateBlockInterval != 0) { revert InvalidBlockInterval(blockInterval, intermediateBlockInterval); } @@ -289,7 +288,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { L2_CHAIN_ID = l2ChainId; BLOCK_INTERVAL = blockInterval; INTERMEDIATE_BLOCK_INTERVAL = intermediateBlockInterval; - + INITIALIZE_CALLDATA_SIZE = 0x7E + 0x20 * intermediateOutputRootsCount(); } @@ -384,7 +383,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // Verify the proof. ProofType proofType = ProofType(uint8(proof[0])); - _verifyProof(proof[1:], proofType, gameCreator(), startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), l2SequenceNumber(), intermediateOutputRoots()); + _verifyProof( + proof[1:], + proofType, + gameCreator(), + startingOutputRoot.root.raw(), + startingOutputRoot.l2SequenceNumber, + rootClaim().raw(), + l2SequenceNumber(), + intermediateOutputRoots() + ); _updateProvingData(proofType, gameCreator()); @@ -410,7 +418,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard { revert InvalidProofType(); } - _verifyProof(proofBytes[1:], proofType, msg.sender, startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), l2SequenceNumber(), intermediateOutputRoots()); + _verifyProof( + proofBytes[1:], + proofType, + msg.sender, + startingOutputRoot.root.raw(), + startingOutputRoot.l2SequenceNumber, + rootClaim().raw(), + l2SequenceNumber(), + intermediateOutputRoots() + ); _updateProvingData(proofType, msg.sender); emit Proved(msg.sender, proofType); @@ -516,8 +533,17 @@ contract AggregateVerifier is Clone, ReentrancyGuard { } else { revert InvalidProofType(); } - - _verifyProof(proofBytes[1:], proofType, msg.sender, startingRoot, startingL2SequenceNumber, intermediateRootToProve, endingL2SequenceNumber, abi.encodePacked(intermediateRootToProve)); + + _verifyProof( + proofBytes[1:], + proofType, + msg.sender, + startingRoot, + startingL2SequenceNumber, + intermediateRootToProve, + endingL2SequenceNumber, + abi.encodePacked(intermediateRootToProve) + ); // Set the game as challenged so that child games can't resolve. status = GameStatus.CHALLENGER_WINS; @@ -720,7 +746,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard { _updateExpectedResolution(); } - function _verifyProof(bytes calldata proofBytes, ProofType proofType, address prover, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots) internal view { + function _verifyProof( + bytes calldata proofBytes, + ProofType proofType, + address prover, + bytes32 startingRoot, + uint256 startingL2SequenceNumber, + bytes32 endingRoot, + uint256 endingL2SequenceNumber, + bytes memory intermediateRoots + ) internal view { bytes32 l1OriginHash = bytes32(proofBytes[:32]); uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); // Verify claimed L1 origin hash matches actual blockhash diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 184d7654..e1550696 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -307,7 +307,9 @@ contract AggregateVerifierTest is BaseTest { function testDeployWithInvalidBlockIntervals() public { // Case 1: BLOCK_INTERVAL is 0 - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 0, INTERMEDIATE_BLOCK_INTERVAL)); + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 0, INTERMEDIATE_BLOCK_INTERVAL) + ); new AggregateVerifier( AGGREGATE_VERIFIER_GAME_TYPE, IAnchorStateRegistry(address(anchorStateRegistry)), diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 7d853d5f..3495ee19 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -70,7 +70,9 @@ contract ChallengeTest is BaseTest { uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE)); + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) + ); game1.challenge(gameIndex); } diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 01a226b0..d50a505b 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -71,7 +71,9 @@ contract NullifyTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE)); + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) + ); game1.nullify(teeProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } From 6119424e07d8e94a77c80dd5a703b52f569265fc Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 20 Feb 2026 16:49:15 -0500 Subject: [PATCH 063/125] verify proof earlier during initialize --- src/multiproof/AggregateVerifier.sol | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index c9b0b9e7..935ef6cf 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -364,23 +364,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { 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. - provingData.expectedResolution = Timestamp.wrap(type(uint64).max); - - // Deposit the bond. - bondAmount = msg.value; - DELAYED_WETH.deposit{value: msg.value}(); - // Verify the proof. ProofType proofType = ProofType(uint8(proof[0])); _verifyProof( @@ -397,6 +380,23 @@ contract AggregateVerifier is Clone, ReentrancyGuard { _updateProvingData(proofType, gameCreator()); emit Proved(gameCreator(), proofType); + + // 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. + provingData.expectedResolution = Timestamp.wrap(type(uint64).max); + + // Deposit the bond. + bondAmount = msg.value; + DELAYED_WETH.deposit{value: msg.value}(); } /// @notice Verifies a proof for the current game. From db1268ce84dfa1bf20b02b13ddb4c7f49c6d35bd Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 20 Feb 2026 16:52:32 -0500 Subject: [PATCH 064/125] revert last commit --- src/multiproof/AggregateVerifier.sol | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 935ef6cf..c9b0b9e7 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -364,23 +364,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { revert UnexpectedBlockNumber(startingOutputRoot.l2SequenceNumber + BLOCK_INTERVAL, l2SequenceNumber()); } - // Verify the proof. - ProofType proofType = ProofType(uint8(proof[0])); - _verifyProof( - proof[1:], - proofType, - gameCreator(), - startingOutputRoot.root.raw(), - startingOutputRoot.l2SequenceNumber, - rootClaim().raw(), - l2SequenceNumber(), - intermediateOutputRoots() - ); - - _updateProvingData(proofType, gameCreator()); - - emit Proved(gameCreator(), proofType); - // Set the game as initialized. initialized = true; @@ -397,6 +380,23 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // Deposit the bond. bondAmount = msg.value; DELAYED_WETH.deposit{value: msg.value}(); + + // Verify the proof. + ProofType proofType = ProofType(uint8(proof[0])); + _verifyProof( + proof[1:], + proofType, + gameCreator(), + startingOutputRoot.root.raw(), + startingOutputRoot.l2SequenceNumber, + rootClaim().raw(), + l2SequenceNumber(), + intermediateOutputRoots() + ); + + _updateProvingData(proofType, gameCreator()); + + emit Proved(gameCreator(), proofType); } /// @notice Verifies a proof for the current game. From 0633e550937e92cc939d9aedac6df39d3ade64f3 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 20 Feb 2026 16:54:01 -0500 Subject: [PATCH 065/125] refactor initialize --- src/multiproof/AggregateVerifier.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index c9b0b9e7..fd5af67f 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -377,10 +377,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // Set expected resolution. provingData.expectedResolution = Timestamp.wrap(type(uint64).max); - // Deposit the bond. - bondAmount = msg.value; - DELAYED_WETH.deposit{value: msg.value}(); - // Verify the proof. ProofType proofType = ProofType(uint8(proof[0])); _verifyProof( @@ -397,6 +393,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard { _updateProvingData(proofType, gameCreator()); emit Proved(gameCreator(), proofType); + + // Deposit the bond. + bondAmount = msg.value; + DELAYED_WETH.deposit{value: msg.value}(); } /// @notice Verifies a proof for the current game. From 50c088c69b775784910b22544a694f60dddaa612 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 20 Feb 2026 17:46:44 -0500 Subject: [PATCH 066/125] refactor --- src/multiproof/AggregateVerifier.sol | 112 +++++++++--------------- test/multiproof/AggregateVerifier.t.sol | 14 +-- test/multiproof/Challenge.t.sol | 2 +- 3 files changed, 50 insertions(+), 78 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index fd5af67f..8aea6ab1 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -36,22 +36,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { ZK } - //////////////////////////////////////////////////////////////// - // Structs // - //////////////////////////////////////////////////////////////// - - /// @notice The `ProvingData` struct represents the data associated with the proofs for a claim. - /// @param counteredByGameAddress The address of the game that countered this game. - /// @param teeProver The address that provided a TEE proof. - /// @param zkProver The address that provided a ZK proof. - /// @param expectedResolution The timestamp of the game's expected resolution. - struct ProvingData { - address counteredByGameAddress; - address teeProver; - address zkProver; - Timestamp expectedResolution; - } - //////////////////////////////////////////////////////////////// // Constants // //////////////////////////////////////////////////////////////// @@ -134,9 +118,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice A boolean for whether or not the game type was respected when the game was created. bool public wasRespectedGameTypeWhenCreated; - /// @notice The claim made by the proposer. - ProvingData public provingData; - /// @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; @@ -153,6 +134,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice The amount of the bond. uint256 public bondAmount; + /// @notice The address of the game that countered this game. + address public counteredByGameAddress; + + /// @notice The address that provided a proof of the given type. + mapping(ProofType => address) internal proofTypeToProver; + + /// @notice The timestamp of the game's expected resolution. + Timestamp public expectedResolution; + //////////////////////////////////////////////////////////////// // Events // //////////////////////////////////////////////////////////////// @@ -344,13 +334,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // Parent game must be respected, not blacklisted, not retired, and not challenged. if (!_isValidGame(parentGame)) revert InvalidParentGame(); - // The parent game must have a proof. - // Should not be reachable since a proof is required to initialize. - if ( - AggregateVerifier(address(parentGame)).teeProver() == address(0) - && AggregateVerifier(address(parentGame)).zkProver() == address(0) - ) revert InvalidParentGame(); - startingOutputRoot = Proposal({ l2SequenceNumber: parentGame.l2SequenceNumber(), root: Hash.wrap(parentGame.rootClaim().raw()) }); @@ -375,7 +358,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); // Set expected resolution. - provingData.expectedResolution = Timestamp.wrap(type(uint64).max); + expectedResolution = Timestamp.wrap(type(uint64).max); // Verify the proof. ProofType proofType = ProofType(uint8(proof[0])); @@ -410,13 +393,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { if (gameOver()) revert GameOver(); ProofType proofType = ProofType(uint8(proofBytes[0])); - if (proofType == ProofType.TEE) { - if (provingData.teeProver != address(0)) revert AlreadyProven(ProofType.TEE); - } else if (proofType == ProofType.ZK) { - if (provingData.zkProver != address(0)) revert AlreadyProven(ProofType.ZK); - } else { - revert InvalidProofType(); - } + if (proofTypeToProver[proofType] != address(0)) revert AlreadyProven(proofType); _verifyProof( proofBytes[1:], @@ -476,8 +453,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // The TEE prover must not be empty. // You should nullify the game if a ZK proof has already been provided. - if (provingData.teeProver == address(0)) revert MissingProof(ProofType.TEE); - if (provingData.zkProver != address(0)) revert AlreadyProven(ProofType.ZK); + if (proofTypeToProver[ProofType.TEE] == address(0)) revert MissingProof(ProofType.TEE); + if (proofTypeToProver[ProofType.ZK] != address(0)) revert AlreadyProven(ProofType.ZK); (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); @@ -490,7 +467,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { if (challengingGame.zkProver() == address(0)) revert MissingProof(ProofType.ZK); // Update the counteredBy address. - provingData.counteredByGameAddress = address(challengingGame); + counteredByGameAddress = address(challengingGame); // Set the game as challenged. status = GameStatus.CHALLENGER_WINS; @@ -518,6 +495,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bytes32 proposedIntermediateRoot = intermediateOutputRoot(intermediateRootIndex); if (proposedIntermediateRoot == intermediateRootToProve) revert IntermediateRootSameAsProposed(); + ProofType proofType = ProofType(uint8(proofBytes[0])); + if (proofTypeToProver[proofType] == address(0)) revert MissingProof(proofType); + bytes32 startingRoot = intermediateRootIndex == 0 ? startingOutputRoot.root.raw() : intermediateOutputRoot(intermediateRootIndex - 1); @@ -525,15 +505,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL; uint256 endingL2SequenceNumber = startingL2SequenceNumber + INTERMEDIATE_BLOCK_INTERVAL; - ProofType proofType = ProofType(uint8(proofBytes[0])); - if (proofType == ProofType.TEE) { - if (provingData.teeProver == address(0)) revert MissingProof(ProofType.TEE); - } else if (proofType == ProofType.ZK) { - if (provingData.zkProver == address(0)) revert MissingProof(ProofType.ZK); - } else { - revert InvalidProofType(); - } - _verifyProof( proofBytes[1:], proofType, @@ -563,13 +534,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard { if (bondRecipient == address(0)) revert BondRecipientEmpty(); // If this game was challenged, the countered by game must be valid or else the bond is refunded. - if (provingData.counteredByGameAddress != address(0)) { - GameStatus counteredByGameStatus = IDisputeGame(provingData.counteredByGameAddress).status(); + if (counteredByGameAddress != address(0)) { + GameStatus counteredByGameStatus = IDisputeGame(counteredByGameAddress).status(); if (counteredByGameStatus == GameStatus.IN_PROGRESS) { revert CounteredByGameNotResolved(); } // If the countered by game is invalid or not resolved, the bond is refunded. - if (!_isValidChallengingGame(IDisputeGame(provingData.counteredByGameAddress))) { + if (!_isValidChallengingGame(IDisputeGame(counteredByGameAddress))) { bondRecipient = gameCreator(); } } @@ -581,7 +552,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard { } bondClaimed = true; - // This can fail if this game was challenged and the countered by game is blacklisted/retired after it resolved to DEFENDER_WINS. The centralized functions in DELAYED_WETH will handle this as it's a already a very centralized action to blacklist/retire a valid challenging game. + // This can fail if this game was challenged and the countered by game is + // blacklisted/retired after it resolved to DEFENDER_WINS. + // The centralized functions in DELAYED_WETH will handle this as it's a already + // a very centralized action to blacklist/retire a valid challenging game. DELAYED_WETH.withdraw(bondRecipient, bondAmount); // Transfer the credit to the bond recipient. @@ -640,12 +614,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Address that provided a TEE proof. function teeProver() external view returns (address) { - return provingData.teeProver; + return proofTypeToProver[ProofType.TEE]; } /// @notice Address that provided a ZK proof. function zkProver() external view returns (address) { - return provingData.zkProver; + return proofTypeToProver[ProofType.ZK]; } /// @notice The game type. @@ -662,7 +636,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Determines if the game is finished. function gameOver() public view returns (bool) { - return provingData.expectedResolution.raw() <= block.timestamp; + return expectedResolution.raw() <= block.timestamp; } /// @notice The number of intermediate output roots. @@ -720,27 +694,23 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Updates the expected resolution timestamp. function _updateExpectedResolution() internal { - uint64 newResolution = uint64(block.timestamp); - if (provingData.teeProver != address(0) && provingData.zkProver != address(0)) { - newResolution += FAST_FINALIZATION_DELAY; - } else if (provingData.teeProver != address(0) || provingData.zkProver != address(0)) { - newResolution += SLOW_FINALIZATION_DELAY; - } else { - revert NoProofProvided(); - } - provingData.expectedResolution = - Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, provingData.expectedResolution.raw()))); + bool hasTee = proofTypeToProver[ProofType.TEE] != address(0); + bool hasZk = proofTypeToProver[ProofType.ZK] != address(0); + + if (!hasTee && !hasZk) revert NoProofProvided(); + + uint64 delay = (hasTee && hasZk) ? FAST_FINALIZATION_DELAY : SLOW_FINALIZATION_DELAY; + uint64 newResolution = uint64(block.timestamp) + delay; + expectedResolution = + Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, expectedResolution.raw()))); } function _updateProvingData(ProofType proofType, address prover) internal { - if (proofType == ProofType.TEE) { - provingData.teeProver = prover; - } else if (proofType == ProofType.ZK) { - provingData.zkProver = prover; - // Bond can be reclaimed after a ZK proof is provided. + proofTypeToProver[proofType] = prover; + + // Bond can be reclaimed after a ZK proof is provided. + if (proofType == ProofType.ZK) { bondRecipient = gameCreator(); - } else { - revert InvalidProofType(); } _updateExpectedResolution(); @@ -756,6 +726,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard { uint256 endingL2SequenceNumber, bytes memory intermediateRoots ) internal view { + if (proofBytes.length < 65) revert InvalidProof(); + bytes32 l1OriginHash = bytes32(proofBytes[:32]); uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); // Verify claimed L1 origin hash matches actual blockhash diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index e1550696..934f465c 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -182,7 +182,7 @@ contract AggregateVerifierTest is BaseTest { AggregateVerifier game = _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof); - (,,, Timestamp originalExpectedResolution) = game.provingData(); + Timestamp originalExpectedResolution = game.expectedResolution(); assertEq(originalExpectedResolution.raw(), block.timestamp + 7 days); vm.warp(block.timestamp + 7 days - 1); @@ -194,7 +194,7 @@ contract AggregateVerifierTest is BaseTest { _provideProof(game, ZK_PROVER, zkProof); // Proof should not have increased expected resolution - (,,, Timestamp expectedResolution) = game.provingData(); + Timestamp expectedResolution = game.expectedResolution(); assertEq(expectedResolution.raw(), originalExpectedResolution.raw()); // Resolve after 1 second @@ -228,7 +228,7 @@ contract AggregateVerifierTest is BaseTest { 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); + bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number) @@ -247,7 +247,7 @@ contract AggregateVerifierTest is BaseTest { 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); + 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); @@ -259,7 +259,7 @@ contract AggregateVerifierTest is BaseTest { 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); + 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)); @@ -277,7 +277,7 @@ contract AggregateVerifierTest is BaseTest { bytes32 l1OriginHash = blockhash(l1OriginNumber); Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber); + bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } @@ -300,7 +300,7 @@ contract AggregateVerifierTest is BaseTest { abi.encode(expectedHash) // returns the blockhash ); - bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber); + bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber, rootClaim.raw()); _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 3495ee19..b10e9a31 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -36,7 +36,7 @@ contract ChallengeTest is BaseTest { assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(game1.bondRecipient(), ZK_PROVER); - (address counteredBy,,,) = game1.provingData(); + address counteredBy = game1.counteredByGameAddress(); assertEq(counteredBy, address(game2)); // Retrieve bond after challenge From ccac6c3ce574b171014791bd2f2d4af08f8ab5b2 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sun, 22 Feb 2026 02:39:32 +0000 Subject: [PATCH 067/125] fix: register TEE proposer during deployment and add intermediateBlockInterval to WithNitro script --- scripts/multiproof/DeployDevNoNitro.s.sol | 6 ++++++ scripts/multiproof/DeployDevWithNitro.s.sol | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 7e040993..0db8df25 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -122,6 +122,7 @@ contract DeployDevNoNitro is Script { vm.startBroadcast(); _deployTEEContracts(cfg.owner); + _registerProposer(cfg.teeProposer); _deployInfrastructure(cfg); _deployAggregateVerifier(cfg); @@ -166,6 +167,11 @@ contract DeployDevNoNitro is Script { console.log("TEEVerifier:", teeVerifier); } + function _registerProposer(address teeProposer) internal { + SystemConfigGlobal(systemConfigGlobalProxy).setProposer(teeProposer, true); + console.log("Registered TEE proposer:", teeProposer); + } + function _deployInfrastructure(DeployConfig memory cfg) internal { // 4. REAL DisputeGameFactory (behind proxy) address factoryImpl = address(new DisputeGameFactory()); diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index 11e56474..e45f51e4 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -147,6 +147,7 @@ contract DeployDevWithNitro is Script { bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; uint256 public constant BLOCK_INTERVAL = 100; + uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; uint256 public constant INIT_BOND = 0.001 ether; /// @notice Config struct to reduce stack variables. @@ -184,6 +185,7 @@ contract DeployDevWithNitro is Script { vm.startBroadcast(); _deployTEEContracts(cfg.owner); + _registerProposer(cfg.teeProposer); _deployInfrastructure(cfg); _deployAggregateVerifier(cfg); @@ -228,6 +230,11 @@ contract DeployDevWithNitro is Script { console.log("TEEVerifier:", teeVerifier); } + function _registerProposer(address teeProposer) internal { + SystemConfigGlobal(systemConfigGlobalProxy).setProposer(teeProposer, true); + console.log("Registered TEE proposer:", teeProposer); + } + function _deployInfrastructure(DeployConfig memory cfg) internal { // 4. REAL DisputeGameFactory (behind proxy) address factoryImpl = address(new DisputeGameFactory()); @@ -273,7 +280,8 @@ contract DeployDevWithNitro is Script { bytes32(0), // zkImageHash (unused) cfg.configHash, 8453, // l2ChainId (Base mainnet) - BLOCK_INTERVAL + BLOCK_INTERVAL, + INTERMEDIATE_BLOCK_INTERVAL ) ); console.log("AggregateVerifier:", aggregateVerifier); From 6164a13d234778604021aab8e7ff3ffc210a6f7e Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 24 Feb 2026 15:09:14 -0500 Subject: [PATCH 068/125] use l1head for proofs after initialization --- src/multiproof/AggregateVerifier.sol | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 8aea6ab1..f042d2fb 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -362,10 +362,17 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // 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[1:], + proof[65:], proofType, gameCreator(), + l1OriginHash, startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), @@ -399,6 +406,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { proofBytes[1:], proofType, msg.sender, + l1Head().raw(), startingOutputRoot.root.raw(), startingOutputRoot.l2SequenceNumber, rootClaim().raw(), @@ -509,6 +517,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { proofBytes[1:], proofType, msg.sender, + l1Head().raw(), startingRoot, startingL2SequenceNumber, intermediateRootToProve, @@ -720,25 +729,20 @@ contract AggregateVerifier is Clone, ReentrancyGuard { 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 < 65) revert InvalidProof(); - - bytes32 l1OriginHash = bytes32(proofBytes[:32]); - uint256 l1OriginNumber = uint256(bytes32(proofBytes[32:64])); - // Verify claimed L1 origin hash matches actual blockhash - _verifyL1Origin(l1OriginHash, l1OriginNumber); + if (proofBytes.length < 1) revert InvalidProof(); if (proofType == ProofType.TEE) { _verifyTeeProof( proofBytes, prover, l1OriginHash, - l1OriginNumber, startingRoot, startingL2SequenceNumber, endingRoot, @@ -750,7 +754,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { proofBytes, prover, l1OriginHash, - l1OriginNumber, startingRoot, startingL2SequenceNumber, endingRoot, @@ -768,7 +771,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bytes calldata proofBytes, address prover, bytes32 l1OriginHash, - uint256 l1OriginNumber, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, @@ -779,7 +781,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { abi.encodePacked( prover, l1OriginHash, - l1OriginNumber, startingRoot, startingL2SequenceNumber, endingRoot, @@ -801,7 +802,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bytes calldata proofBytes, address prover, bytes32 l1OriginHash, - uint256 l1OriginNumber, bytes32 startingRoot, uint256 startingL2SequenceNumber, bytes32 endingRoot, @@ -812,7 +812,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard { abi.encodePacked( prover, l1OriginHash, - l1OriginNumber, startingRoot, startingL2SequenceNumber, endingRoot, From fd779bc8dea828f84011849c283611dd9f9d35f8 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Wed, 25 Feb 2026 13:58:50 -0500 Subject: [PATCH 069/125] forge fmt --- scripts/multiproof/DeployDevNoNitro.s.sol | 6 +----- scripts/multiproof/DeployDevWithNitro.s.sol | 6 +----- src/multiproof/AggregateVerifier.sol | 15 +++++++-------- test/multiproof/AggregateVerifier.t.sol | 15 ++++++++++----- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 0db8df25..6ac677ad 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -177,11 +177,7 @@ contract DeployDevNoNitro is Script { address factoryImpl = address(new DisputeGameFactory()); MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.owner); - TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( - factoryImpl, - address(proxyAdmin), - "" - ); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(factoryImpl, address(proxyAdmin), ""); vm.store(address(proxy), PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(address(proxyAdmin))))); DisputeGameFactory(address(proxy)).initialize(cfg.owner); diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index e45f51e4..85d3be30 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -240,11 +240,7 @@ contract DeployDevWithNitro is Script { address factoryImpl = address(new DisputeGameFactory()); MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.owner); - TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( - factoryImpl, - address(proxyAdmin), - "" - ); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(factoryImpl, address(proxyAdmin), ""); vm.store(address(proxy), PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(address(proxyAdmin))))); DisputeGameFactory(address(proxy)).initialize(cfg.owner); diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index f042d2fb..818b8fac 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -362,7 +362,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // 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 @@ -561,9 +561,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { } bondClaimed = true; - // This can fail if this game was challenged and the countered by game is - // blacklisted/retired after it resolved to DEFENDER_WINS. - // The centralized functions in DELAYED_WETH will handle this as it's a already + // This can fail if this game was challenged and the countered by game is + // blacklisted/retired after it resolved to DEFENDER_WINS. + // The centralized functions in DELAYED_WETH will handle this as it's a already // a very centralized action to blacklist/retire a valid challenging game. DELAYED_WETH.withdraw(bondRecipient, bondAmount); @@ -705,13 +705,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard { function _updateExpectedResolution() internal { bool hasTee = proofTypeToProver[ProofType.TEE] != address(0); bool hasZk = proofTypeToProver[ProofType.ZK] != address(0); - + if (!hasTee && !hasZk) revert NoProofProvided(); - + uint64 delay = (hasTee && hasZk) ? FAST_FINALIZATION_DELAY : SLOW_FINALIZATION_DELAY; uint64 newResolution = uint64(block.timestamp) + delay; - expectedResolution = - Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, expectedResolution.raw()))); + expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, expectedResolution.raw()))); } function _updateProvingData(ProofType proofType, address prover) internal { diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 934f465c..57058f52 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -228,7 +228,8 @@ contract AggregateVerifierTest is BaseTest { 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()); + bytes memory proofBytes = + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number) @@ -247,7 +248,8 @@ contract AggregateVerifierTest is BaseTest { 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()); + 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); @@ -259,7 +261,8 @@ contract AggregateVerifierTest is BaseTest { 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()); + 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)); @@ -277,7 +280,8 @@ contract AggregateVerifierTest is BaseTest { 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()); + bytes memory proofBytes = + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } @@ -300,7 +304,8 @@ contract AggregateVerifierTest is BaseTest { abi.encode(expectedHash) // returns the blockhash ); - bytes memory proofBytes = abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber, rootClaim.raw()); + bytes memory proofBytes = + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber, rootClaim.raw()); _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proofBytes); } From 780715dbb5eb6121b4b1a42fb3f378c1ffc4eff8 Mon Sep 17 00:00:00 2001 From: roger-bai-coinbase Date: Thu, 26 Feb 2026 18:09:31 -0500 Subject: [PATCH 070/125] Fix comments from PR feedback Co-authored-by: Leopold Joy --- src/multiproof/AggregateVerifier.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 818b8fac..40e6a49c 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -765,7 +765,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { } /// @notice Verifies a TEE proof for the current game. - /// @param proofBytes The proof: prover(20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65). + /// @param proofBytes The proof: prover(20) + signature (65). function _verifyTeeProof( bytes calldata proofBytes, address prover, @@ -796,7 +796,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { } /// @notice Verifies a ZK proof for the current game. - /// @param proofBytes The proof: l1OriginHash (32) + l1OriginNumber (32) + zkProof (variable). + /// @param proofBytes The proof: zkProof (variable). function _verifyZkProof( bytes calldata proofBytes, address prover, From 935b7eb86ff6380cac2095ab31aef7147318a038 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Fri, 27 Feb 2026 17:45:44 -0500 Subject: [PATCH 071/125] step 1 fix dependencies and op commit --- .env | 1 + Makefile | 18 ++++++++++++++++-- foundry.toml | 3 ++- src/multiproof/tee/TEEVerifier.sol | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.env b/.env index 00342e1e..de25d823 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ BASE_MAINNET_URL=https://mainnet.base.org +OP_COMMIT=d6aa14e96fe5001b5c23c233d0747078d7ea02fb diff --git a/Makefile b/Makefile index 2372124c..2875c829 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ install-foundry: ~/.foundry/bin/foundryup .PHONY: deps -deps: clean-lib +deps: clean-lib checkout-op-commit forge install --no-git \ github.com/foundry-rs/forge-std@6853b9ec7df5dc0c213b05ae67785ad4f4baa0ea \ github.com/runtimeverification/kontrol-cheatcodes@2c48ae1ab44228c199dca29414c0b4b18a3434e6 \ @@ -18,7 +18,9 @@ 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 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 @@ -44,3 +46,15 @@ bindings: abigen --abi out/BalanceTracker.sol/BalanceTracker.abi.json --pkg bindings --type BalanceTracker --out bindings/balance_tracker.go abigen --abi out/FeeDisburser.sol/FeeDisburser.abi.json --pkg bindings --type FeeDisburser --out bindings/fee_disburser.go cd bindings && go mod tidy + +.PHONY: checkout-op-commit +checkout-op-commit: + [ -n "$(OP_COMMIT)" ] || (echo "OP_COMMIT must be set in .env" && exit 1) + rm -rf lib/optimism + mkdir -p lib/optimism + cd lib/optimism; \ + git init; \ + git remote add origin https://github.com/ethereum-optimism/optimism.git; \ + git fetch --depth=1 origin $(OP_COMMIT); \ + git reset --hard FETCH_HEAD; \ + git apply ../../patch/optimism.patch; diff --git a/foundry.toml b/foundry.toml index 058cad23..e2ffb7e2 100644 --- a/foundry.toml +++ b/foundry.toml @@ -49,7 +49,8 @@ remappings = [ 'ds-test/=lib/forge-std/lib/ds-test/src', 'safe-contracts/=lib/safe-contracts/contracts', 'kontrol-cheatcodes/=lib/kontrol-cheatcodes/src', - 'interfaces/=interfaces' + 'interfaces/=interfaces', + 'optimism/=lib/optimism/packages/contracts-bedrock/', ] fs_permissions = [ diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 7082d3c3..be57d6dd 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.15; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {IVerifier} from "../interfaces/IVerifier.sol"; +import {IVerifier} from "../../interfaces/multiproof/IVerifier.sol"; import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; From 920d6e4b0c775f1f7bf059ad30224d22eacf10e5 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Fri, 27 Feb 2026 19:15:00 -0500 Subject: [PATCH 072/125] make multiproof compatible with base contracts --- Makefile | 2 +- foundry.toml | 4 ++- .../{mutliproof => multiproof}/IVerifier.sol | 0 src/multiproof/AggregateVerifier.sol | 20 +++++++------- src/multiproof/mock/MockCertManager.sol | 16 ------------ src/multiproof/mock/MockSystemConfig.sol | 14 ---------- src/multiproof/mock/MockVerifier.sol | 10 ------- src/multiproof/tee/TEEVerifier.sol | 2 +- test/libraries/SemverComp.t.sol | 2 +- test/multiproof/AggregateVerifier.t.sol | 18 ++++++------- test/multiproof/BaseTest.t.sol | 26 +++++++++---------- test/multiproof/Challenge.t.sol | 12 ++++----- test/multiproof/Nullify.t.sol | 8 +++--- test/multiproof/SystemConfigGlobal.t.sol | 6 ++--- test/multiproof/TEEVerifier.t.sol | 8 +++--- 15 files changed, 55 insertions(+), 93 deletions(-) rename interfaces/{mutliproof => multiproof}/IVerifier.sol (100%) delete mode 100644 src/multiproof/mock/MockCertManager.sol delete mode 100644 src/multiproof/mock/MockSystemConfig.sol delete mode 100644 src/multiproof/mock/MockVerifier.sol diff --git a/Makefile b/Makefile index 2875c829..2a3595f3 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ install-foundry: ~/.foundry/bin/foundryup .PHONY: deps -deps: clean-lib checkout-op-commit +deps: clean-lib forge install --no-git \ github.com/foundry-rs/forge-std@6853b9ec7df5dc0c213b05ae67785ad4f4baa0ea \ github.com/runtimeverification/kontrol-cheatcodes@2c48ae1ab44228c199dca29414c0b4b18a3434e6 \ diff --git a/foundry.toml b/foundry.toml index e2ffb7e2..bd9343ed 100644 --- a/foundry.toml +++ b/foundry.toml @@ -43,6 +43,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', @@ -50,7 +51,8 @@ remappings = [ 'safe-contracts/=lib/safe-contracts/contracts', 'kontrol-cheatcodes/=lib/kontrol-cheatcodes/src', 'interfaces/=interfaces', - 'optimism/=lib/optimism/packages/contracts-bedrock/', + '@nitro-validator/=lib/nitro-validator/src/', + '@op-enclave/=lib/op-enclave/contracts/src/' ] fs_permissions = [ diff --git a/interfaces/mutliproof/IVerifier.sol b/interfaces/multiproof/IVerifier.sol similarity index 100% rename from interfaces/mutliproof/IVerifier.sol rename to interfaces/multiproof/IVerifier.sol diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 40e6a49c..552a7289 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -11,19 +11,19 @@ import { GameNotResolved, GamePaused, NoCreditToClaim -} from "optimism/src/dispute/lib/Errors.sol"; -import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; -import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; -import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; -import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; -import {Claim, GameStatus, GameType, Hash, Proposal, Timestamp} from "optimism/src/dispute/lib/Types.sol"; +} 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 {Clone} from "@solady/utils/Clone.sol"; +import {FixedPointMathLib} from "@solady/utils/FixedPointMathLib.sol"; +import {ReentrancyGuard} from "@solady/utils/ReentrancyGuard.sol"; -import {IVerifier} from "./interfaces/IVerifier.sol"; +import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; contract AggregateVerifier is Clone, ReentrancyGuard { //////////////////////////////////////////////////////////////// diff --git a/src/multiproof/mock/MockCertManager.sol b/src/multiproof/mock/MockCertManager.sol deleted file mode 100644 index 99aa0936..00000000 --- a/src/multiproof/mock/MockCertManager.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {ICertManager} from "@nitro-validator/ICertManager.sol"; - -/// @title MockCertManager -/// @notice Mock CertManager for testing SystemConfigGlobal. -contract MockCertManager is ICertManager { - function verifyCACert(bytes memory, bytes32) external pure returns (bytes32) { - return bytes32(0); - } - - function verifyClientCert(bytes memory, bytes32) external pure returns (VerifiedCert memory) { - return VerifiedCert({ca: false, notAfter: 0, maxPathLen: 0, subjectHash: bytes32(0), pubKey: ""}); - } -} diff --git a/src/multiproof/mock/MockSystemConfig.sol b/src/multiproof/mock/MockSystemConfig.sol deleted file mode 100644 index 5182c4c0..00000000 --- a/src/multiproof/mock/MockSystemConfig.sol +++ /dev/null @@ -1,14 +0,0 @@ -// 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/mock/MockVerifier.sol b/src/multiproof/mock/MockVerifier.sol deleted file mode 100644 index 003b103a..00000000 --- a/src/multiproof/mock/MockVerifier.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import {IVerifier} from "../interfaces/IVerifier.sol"; - -contract MockVerifier is IVerifier { - function verify(bytes calldata, bytes32, bytes32) external pure returns (bool) { - return true; - } -} diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index be57d6dd..e76f9e4d 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.15; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {IVerifier} from "../../interfaces/multiproof/IVerifier.sol"; +import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; diff --git a/test/libraries/SemverComp.t.sol b/test/libraries/SemverComp.t.sol index b182af37..eb07ea14 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 index 57058f52..761cb414 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {BadExtraData} from "optimism/src/dispute/lib/Errors.sol"; -import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; -import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; -import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; -import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; -import {Claim, GameStatus, Hash, Timestamp} from "optimism/src/dispute/lib/Types.sol"; +import {BadExtraData} from "src/dispute/lib/Errors.sol"; +import {IAnchorStateRegistry} from "interfaces/dispute/IAnchorStateRegistry.sol"; +import {IDelayedWETH} from "interfaces/dispute/IDelayedWETH.sol"; +import {IDisputeGame} from "interfaces/dispute/IDisputeGame.sol"; +import {IDisputeGameFactory} from "interfaces/dispute/IDisputeGameFactory.sol"; +import {Claim, GameStatus, Hash, Timestamp} from "src/dispute/lib/Types.sol"; -import {AggregateVerifier} from "src/AggregateVerifier.sol"; -import {IVerifier} from "src/interfaces/IVerifier.sol"; +import {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; +import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; -import {BaseTest} from "test/BaseTest.t.sol"; +import {BaseTest} from "./BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { function testInitializeWithTEEProof() public { diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 6993b712..02deb1ae 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -4,25 +4,25 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; // Optimism -import {AnchorStateRegistry} from "optimism/src/dispute/AnchorStateRegistry.sol"; -import {DelayedWETH} from "optimism/src/dispute/DelayedWETH.sol"; -import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; -import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; -import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; -import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; -import {IDisputeGameFactory} from "optimism/interfaces/dispute/IDisputeGameFactory.sol"; -import {ISystemConfig} from "optimism/interfaces/L1/ISystemConfig.sol"; -import {Claim, GameStatus, GameType, Hash, Proposal, Timestamp} from "optimism/src/dispute/lib/Types.sol"; +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 "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {AggregateVerifier} from "src/AggregateVerifier.sol"; -import {IVerifier} from "src/interfaces/IVerifier.sol"; +import {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; +import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; -import {MockSystemConfig} from "src/mocks/MockSystemConfig.sol"; -import {MockVerifier} from "src/mocks/MockVerifier.sol"; +import {MockSystemConfig} from "src/multiproof/mocks/MockSystemConfig.sol"; +import {MockVerifier} from "src/multiproof/mocks/MockVerifier.sol"; contract BaseTest is Test { // Constants diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index b10e9a31..4d41278d 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {ClaimAlreadyResolved} from "optimism/src/dispute/lib/Errors.sol"; -import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; -import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; -import {Claim, GameStatus, Hash} from "optimism/src/dispute/lib/Types.sol"; +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/AggregateVerifier.sol"; +import {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; -import {BaseTest} from "test/BaseTest.t.sol"; +import {BaseTest} from "./BaseTest.t.sol"; contract ChallengeTest is BaseTest { function testChallengeTEEProofWithZKProof() public { diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index d50a505b..1af4aaae 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {GameNotInProgress} from "optimism/src/dispute/lib/Errors.sol"; -import {Claim, GameStatus} from "optimism/src/dispute/lib/Types.sol"; +import {GameNotInProgress} from "src/dispute/lib/Errors.sol"; +import {Claim, GameStatus} from "src/dispute/lib/Types.sol"; -import {AggregateVerifier} from "src/AggregateVerifier.sol"; +import {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; -import {BaseTest} from "test/BaseTest.t.sol"; +import {BaseTest} from "./BaseTest.t.sol"; contract NullifyTest is BaseTest { function testNullifyWithTEEProof() public { diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol index 893a8b28..4d6cf303 100644 --- a/test/multiproof/SystemConfigGlobal.t.sol +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -8,10 +8,10 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s import {ICertManager} from "@nitro-validator/ICertManager.sol"; -import {DevSystemConfigGlobal} from "src/tee/DevSystemConfigGlobal.sol"; -import {SystemConfigGlobal} from "src/tee/SystemConfigGlobal.sol"; +import {DevSystemConfigGlobal} from "src/multiproof/tee/DevSystemConfigGlobal.sol"; +import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; -import {MockCertManager} from "src/mocks/MockCertManager.sol"; +import {MockCertManager} from "src/multiproof/mocks/MockCertManager.sol"; /// @notice Tests for SystemConfigGlobal and DevSystemConfigGlobal contracts. /// @dev IMPORTANT: This test file uses DevSystemConfigGlobal as the implementation because diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 86dbb486..38e6e2ee 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -8,11 +8,11 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s import {ICertManager} from "@nitro-validator/ICertManager.sol"; -import {DevSystemConfigGlobal} from "src/tee/DevSystemConfigGlobal.sol"; -import {SystemConfigGlobal} from "src/tee/SystemConfigGlobal.sol"; -import {TEEVerifier} from "src/tee/TEEVerifier.sol"; +import {DevSystemConfigGlobal} from "src/multiproof/tee/DevSystemConfigGlobal.sol"; +import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; +import {TEEVerifier} from "src/multiproof/tee/TEEVerifier.sol"; -import {MockCertManager} from "src/mocks/MockCertManager.sol"; +import {MockCertManager} from "src/multiproof/mocks/MockCertManager.sol"; contract TEEVerifierTest is Test { TEEVerifier public verifier; From 351a9ba1d49ae04c67f8ead74a865aae437acc86 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Fri, 27 Feb 2026 21:40:30 -0500 Subject: [PATCH 073/125] fix imports in scripts --- scripts/multiproof/DeployDevNoNitro.s.sol | 24 +++++++++---------- scripts/multiproof/DeployDevWithNitro.s.sol | 22 ++++++++--------- .../mocks/MockAnchorStateRegistry.sol | 4 ++-- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 6ac677ad..05303694 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -57,18 +57,18 @@ import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transpa import {Script} from "forge-std/Script.sol"; import {stdJson} from "forge-std/StdJson.sol"; import {console2 as console} from "forge-std/console2.sol"; -import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; -import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; -import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; -import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; -import {GameType, Hash} from "optimism/src/dispute/lib/Types.sol"; - -import {AggregateVerifier} from "../src/AggregateVerifier.sol"; -import {IVerifier} from "../src/interfaces/IVerifier.sol"; -import {MockVerifier} from "../src/mocks/MockVerifier.sol"; -import {DevSystemConfigGlobal} from "../src/tee/DevSystemConfigGlobal.sol"; -import {SystemConfigGlobal} from "../src/tee/SystemConfigGlobal.sol"; -import {TEEVerifier} from "../src/tee/TEEVerifier.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 {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; +import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; +import {MockVerifier} from "src/multiproof/mocks/MockVerifier.sol"; +import {DevSystemConfigGlobal} from "src/multiproof/tee/DevSystemConfigGlobal.sol"; +import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; +import {TEEVerifier} from "src/multiproof/tee/TEEVerifier.sol"; import {MinimalProxyAdmin} from "./mocks/MinimalProxyAdmin.sol"; import {MockAnchorStateRegistry} from "./mocks/MockAnchorStateRegistry.sol"; diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index 85d3be30..6beba891 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -121,17 +121,17 @@ import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transpa import {Script} from "forge-std/Script.sol"; import {stdJson} from "forge-std/StdJson.sol"; import {console2 as console} from "forge-std/console2.sol"; -import {IAnchorStateRegistry} from "optimism/interfaces/dispute/IAnchorStateRegistry.sol"; -import {IDelayedWETH} from "optimism/interfaces/dispute/IDelayedWETH.sol"; -import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; -import {DisputeGameFactory} from "optimism/src/dispute/DisputeGameFactory.sol"; -import {GameType, Hash} from "optimism/src/dispute/lib/Types.sol"; - -import {AggregateVerifier} from "../src/AggregateVerifier.sol"; -import {IVerifier} from "../src/interfaces/IVerifier.sol"; -import {MockVerifier} from "../src/mocks/MockVerifier.sol"; -import {SystemConfigGlobal} from "../src/tee/SystemConfigGlobal.sol"; -import {TEEVerifier} from "../src/tee/TEEVerifier.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 {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; +import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; +import {MockVerifier} from "src/multiproof/mocks/MockVerifier.sol"; +import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; +import {TEEVerifier} from "src/multiproof/tee/TEEVerifier.sol"; import {MinimalProxyAdmin} from "./mocks/MinimalProxyAdmin.sol"; import {MockAnchorStateRegistry} from "./mocks/MockAnchorStateRegistry.sol"; diff --git a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol index 272d9fc9..b0c8d0c0 100644 --- a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol +++ b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {IDisputeGame} from "optimism/interfaces/dispute/IDisputeGame.sol"; -import {GameType, Hash} from "optimism/src/dispute/lib/Types.sol"; +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. From 48c06f907bfc1a8536862cd67e8439edc257044c Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Fri, 27 Feb 2026 21:45:52 -0500 Subject: [PATCH 074/125] fix mocks --- src/multiproof/mocks/MockCertManager.sol | 16 ++++++++++++++++ src/multiproof/mocks/MockSystemConfig.sol | 14 ++++++++++++++ src/multiproof/mocks/MockVerifier.sol | 10 ++++++++++ 3 files changed, 40 insertions(+) create mode 100644 src/multiproof/mocks/MockCertManager.sol create mode 100644 src/multiproof/mocks/MockSystemConfig.sol create mode 100644 src/multiproof/mocks/MockVerifier.sol diff --git a/src/multiproof/mocks/MockCertManager.sol b/src/multiproof/mocks/MockCertManager.sol new file mode 100644 index 00000000..99aa0936 --- /dev/null +++ b/src/multiproof/mocks/MockCertManager.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {ICertManager} from "@nitro-validator/ICertManager.sol"; + +/// @title MockCertManager +/// @notice Mock CertManager for testing SystemConfigGlobal. +contract MockCertManager is ICertManager { + function verifyCACert(bytes memory, bytes32) external pure returns (bytes32) { + return bytes32(0); + } + + function verifyClientCert(bytes memory, bytes32) external pure returns (VerifiedCert memory) { + return VerifiedCert({ca: false, notAfter: 0, maxPathLen: 0, subjectHash: bytes32(0), pubKey: ""}); + } +} diff --git a/src/multiproof/mocks/MockSystemConfig.sol b/src/multiproof/mocks/MockSystemConfig.sol new file mode 100644 index 00000000..5182c4c0 --- /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 00000000..68445c0c --- /dev/null +++ b/src/multiproof/mocks/MockVerifier.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; + +contract MockVerifier is IVerifier { + function verify(bytes calldata, bytes32, bytes32) external pure returns (bool) { + return true; + } +} From e986e12b00d4240719e8d32a5f4a021b017374a5 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Fri, 27 Feb 2026 22:15:03 -0500 Subject: [PATCH 075/125] format and seperate out multiproof tests --- .gitignore | 1 + Makefile | 10 ++++ foundry.toml | 9 +++ .../multiproof/AggregateVerifier.t.sol | 0 .../multiproof/BaseTest.t.sol | 0 .../multiproof/Challenge.t.sol | 0 .../multiproof/Nullify.t.sol | 0 .../multiproof/SystemConfigGlobal.t.sol | 0 .../multiproof/TEEVerifier.t.sol | 2 +- multiproof_tests/optimism.patch | 57 +++++++++++++++++++ scripts/multiproof/DeployDevNoNitro.s.sol | 42 +++++++------- scripts/multiproof/DeployDevWithNitro.s.sol | 40 ++++++------- .../mocks/MockAnchorStateRegistry.sol | 11 +++- scripts/multiproof/mocks/MockDelayedWETH.sol | 6 +- src/multiproof/AggregateVerifier.sol | 48 ++++++++++------ src/multiproof/mocks/MockCertManager.sol | 4 +- src/multiproof/mocks/MockVerifier.sol | 2 +- src/multiproof/tee/DevSystemConfigGlobal.sol | 6 +- src/multiproof/tee/SystemConfigGlobal.sol | 12 ++-- src/multiproof/tee/TEEVerifier.sol | 10 ++-- 20 files changed, 178 insertions(+), 82 deletions(-) rename {test => multiproof_tests}/multiproof/AggregateVerifier.t.sol (100%) rename {test => multiproof_tests}/multiproof/BaseTest.t.sol (100%) rename {test => multiproof_tests}/multiproof/Challenge.t.sol (100%) rename {test => multiproof_tests}/multiproof/Nullify.t.sol (100%) rename {test => multiproof_tests}/multiproof/SystemConfigGlobal.t.sol (100%) rename {test => multiproof_tests}/multiproof/TEEVerifier.t.sol (99%) create mode 100644 multiproof_tests/optimism.patch diff --git a/.gitignore b/.gitignore index 73a2e81b..cf6cbd81 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ kout-proofs test/kontrol/logs out lib +multiproof-out # Metrics coverage.out diff --git a/Makefile b/Makefile index 2a3595f3..7ebf6b5e 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,16 @@ deps: clean-lib test: forge test --ffi -vvv +.PHONY: test-multiproof +test-multiproof: + @set -e; \ + echo "Applying patch..."; \ + git apply multiproof_tests/optimism.patch; \ + trap 'echo "Reverting patch..."; git apply -R multiproof_tests/optimism.patch' EXIT; \ + echo "Running tests..."; \ + FOUNDRY_PROFILE=multiproof forge test --ffi -vvv + + .PHONY: clean-lib clean-lib: rm -rf lib diff --git a/foundry.toml b/foundry.toml index bd9343ed..78c76c54 100644 --- a/foundry.toml +++ b/foundry.toml @@ -176,3 +176,12 @@ src = 'test/kontrol/proofs' out = 'kout-proofs' test = 'test/kontrol/proofs' script = 'test/kontrol/proofs' + +################################################################ +# PROFILE: Multiproof # +################################################################ + +[profile.multiproof] +src = 'multiproof_tests' +out = 'multiproof-out' +test = 'multiproof_tests' diff --git a/test/multiproof/AggregateVerifier.t.sol b/multiproof_tests/multiproof/AggregateVerifier.t.sol similarity index 100% rename from test/multiproof/AggregateVerifier.t.sol rename to multiproof_tests/multiproof/AggregateVerifier.t.sol diff --git a/test/multiproof/BaseTest.t.sol b/multiproof_tests/multiproof/BaseTest.t.sol similarity index 100% rename from test/multiproof/BaseTest.t.sol rename to multiproof_tests/multiproof/BaseTest.t.sol diff --git a/test/multiproof/Challenge.t.sol b/multiproof_tests/multiproof/Challenge.t.sol similarity index 100% rename from test/multiproof/Challenge.t.sol rename to multiproof_tests/multiproof/Challenge.t.sol diff --git a/test/multiproof/Nullify.t.sol b/multiproof_tests/multiproof/Nullify.t.sol similarity index 100% rename from test/multiproof/Nullify.t.sol rename to multiproof_tests/multiproof/Nullify.t.sol diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/multiproof_tests/multiproof/SystemConfigGlobal.t.sol similarity index 100% rename from test/multiproof/SystemConfigGlobal.t.sol rename to multiproof_tests/multiproof/SystemConfigGlobal.t.sol diff --git a/test/multiproof/TEEVerifier.t.sol b/multiproof_tests/multiproof/TEEVerifier.t.sol similarity index 99% rename from test/multiproof/TEEVerifier.t.sol rename to multiproof_tests/multiproof/TEEVerifier.t.sol index 38e6e2ee..204fabb0 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/multiproof_tests/multiproof/TEEVerifier.t.sol @@ -62,7 +62,7 @@ contract TEEVerifierTest is Test { verifier = new TEEVerifier(SystemConfigGlobal(address(systemConfigGlobal))); } - function testVerifyValidSignature() public { + function testVerifyValidSignature() public view { // Create a journal hash bytes32 journal = keccak256("test-journal"); diff --git a/multiproof_tests/optimism.patch b/multiproof_tests/optimism.patch new file mode 100644 index 00000000..b75302b5 --- /dev/null +++ b/multiproof_tests/optimism.patch @@ -0,0 +1,57 @@ +diff --git a/interfaces/dispute/IInitializable.sol b/interfaces/dispute/IInitializable.sol +index a3bb74b8..bc998e1f 100644 +--- a/interfaces/dispute/IInitializable.sol ++++ b/interfaces/dispute/IInitializable.sol +@@ -2,5 +2,5 @@ + pragma solidity ^0.8.0; + + interface IInitializable { +- function initialize() external payable; ++ function initialize(bytes calldata initData) external payable; + } +diff --git a/src/dispute/DisputeGameFactory.sol b/src/dispute/DisputeGameFactory.sol +index 6129c830..851e898f 100644 +--- a/src/dispute/DisputeGameFactory.sol ++++ b/src/dispute/DisputeGameFactory.sol +@@ -143,11 +143,13 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable + /// @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. ++ /// @param _initData The initialization data for the DisputeGame. + /// @return proxy_ The address of the created DisputeGame proxy. + function create( + GameType _gameType, + Claim _rootClaim, +- bytes calldata _extraData ++ bytes calldata _extraData, ++ bytes calldata _initData + ) + external + payable +@@ -165,7 +167,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: +@@ -193,13 +197,10 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable + // │ [88 + n, 88 + n + m) │ Implementation args (opaque) │ + // └──────────────────────┴─────────────────────────────────────┘ + proxy_ = IDisputeGame( +- address(impl) +- .clone( +- abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, gameArgs[_gameType]) +- ) ++ address(impl).clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) + ); + } +- proxy_.initialize{ value: msg.value }(); ++ proxy_.initialize{ value: msg.value }(_initData); + + // Compute the unique identifier for the dispute game. + Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData); diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 05303694..a5481f33 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -52,27 +52,27 @@ pragma solidity 0.8.15; * ══════════════════════════════════════════════════════════════════════════════════ */ -import {CertManager} from "@nitro-validator/CertManager.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {Script} from "forge-std/Script.sol"; -import {stdJson} from "forge-std/StdJson.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 {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; -import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; -import {MockVerifier} from "src/multiproof/mocks/MockVerifier.sol"; -import {DevSystemConfigGlobal} from "src/multiproof/tee/DevSystemConfigGlobal.sol"; -import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; -import {TEEVerifier} from "src/multiproof/tee/TEEVerifier.sol"; - -import {MinimalProxyAdmin} from "./mocks/MinimalProxyAdmin.sol"; -import {MockAnchorStateRegistry} from "./mocks/MockAnchorStateRegistry.sol"; -import {MockDelayedWETH} from "./mocks/MockDelayedWETH.sol"; +import { CertManager } from "@nitro-validator/CertManager.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { Script } from "forge-std/Script.sol"; +import { stdJson } from "forge-std/StdJson.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 { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; +import { DevSystemConfigGlobal } from "src/multiproof/tee/DevSystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; + +import { MinimalProxyAdmin } from "./mocks/MinimalProxyAdmin.sol"; +import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; +import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; /// @title DeployDevNoNitro /// @notice Development deployment WITHOUT AWS Nitro attestation validation. diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index 6beba891..a8230ef1 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -116,26 +116,26 @@ pragma solidity 0.8.15; * ══════════════════════════════════════════════════════════════════════════════════ */ -import {CertManager} from "@nitro-validator/CertManager.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {Script} from "forge-std/Script.sol"; -import {stdJson} from "forge-std/StdJson.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 {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; -import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; -import {MockVerifier} from "src/multiproof/mocks/MockVerifier.sol"; -import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; -import {TEEVerifier} from "src/multiproof/tee/TEEVerifier.sol"; - -import {MinimalProxyAdmin} from "./mocks/MinimalProxyAdmin.sol"; -import {MockAnchorStateRegistry} from "./mocks/MockAnchorStateRegistry.sol"; -import {MockDelayedWETH} from "./mocks/MockDelayedWETH.sol"; +import { CertManager } from "@nitro-validator/CertManager.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { Script } from "forge-std/Script.sol"; +import { stdJson } from "forge-std/StdJson.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 { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; + +import { MinimalProxyAdmin } from "./mocks/MinimalProxyAdmin.sol"; +import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; +import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; /// @title DeployDevWithNitro /// @notice Development deployment WITH AWS Nitro attestation validation. diff --git a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol index b0c8d0c0..a4108dc4 100644 --- a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol +++ b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol @@ -1,8 +1,8 @@ // 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"; +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. @@ -23,7 +23,12 @@ contract MockAnchorStateRegistry { /// @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) + function initialize( + address newFactory, + Hash newAnchorRoot, + uint256 newAnchorL2BlockNumber, + GameType gameType + ) external { factory = newFactory; diff --git a/scripts/multiproof/mocks/MockDelayedWETH.sol b/scripts/multiproof/mocks/MockDelayedWETH.sol index a82c6724..ddffec0b 100644 --- a/scripts/multiproof/mocks/MockDelayedWETH.sol +++ b/scripts/multiproof/mocks/MockDelayedWETH.sol @@ -6,10 +6,10 @@ pragma solidity 0.8.15; /// @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 {} + function deposit() external payable { } /// @notice Mock unlock - no-op for testing. - function unlock(address, uint256) external {} + function unlock(address, uint256) external { } /// @notice Mock withdraw - transfers ETH back. /// @param recipient The address to send ETH to. @@ -19,5 +19,5 @@ contract MockDelayedWETH { } /// @notice Allows contract to receive ETH. - receive() external payable {} + receive() external payable { } } diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 552a7289..432fadb5 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -12,18 +12,18 @@ import { 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"; +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 { 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 { IVerifier } from "interfaces/multiproof/IVerifier.sol"; contract AggregateVerifier is Clone, ReentrancyGuard { //////////////////////////////////////////////////////////////// @@ -386,7 +386,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // Deposit the bond. bondAmount = msg.value; - DELAYED_WETH.deposit{value: msg.value}(); + DELAYED_WETH.deposit{ value: msg.value }(); } /// @notice Verifies a proof for the current game. @@ -493,7 +493,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @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) + function nullify( + bytes calldata proofBytes, + uint256 intermediateRootIndex, + bytes32 intermediateRootToProve + ) external { if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); @@ -568,7 +572,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { DELAYED_WETH.withdraw(bondRecipient, bondAmount); // Transfer the credit to the bond recipient. - (bool success,) = bondRecipient.call{value: bondAmount}(hex""); + (bool success,) = bondRecipient.call{ value: bondAmount }(hex""); if (!success) revert BondTransferFailed(); // Emit the credit claimed event. @@ -597,7 +601,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // 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 {} + try ANCHOR_STATE_REGISTRY.setAnchorState(IDisputeGame(address(this))) { } catch { } } /// @notice The starting block number of the game. @@ -691,7 +695,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard { 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). + /// @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); } @@ -734,7 +739,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots - ) internal view { + ) + internal + view + { if (proofBytes.length < 1) revert InvalidProof(); if (proofType == ProofType.TEE) { @@ -775,7 +783,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots - ) internal view { + ) + internal + view + { bytes32 journal = keccak256( abi.encodePacked( prover, @@ -806,7 +817,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bytes32 endingRoot, uint256 endingL2SequenceNumber, bytes memory intermediateRoots - ) internal view { + ) + internal + view + { bytes32 journal = keccak256( abi.encodePacked( prover, diff --git a/src/multiproof/mocks/MockCertManager.sol b/src/multiproof/mocks/MockCertManager.sol index 99aa0936..7cf70f01 100644 --- a/src/multiproof/mocks/MockCertManager.sol +++ b/src/multiproof/mocks/MockCertManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {ICertManager} from "@nitro-validator/ICertManager.sol"; +import { ICertManager } from "@nitro-validator/ICertManager.sol"; /// @title MockCertManager /// @notice Mock CertManager for testing SystemConfigGlobal. @@ -11,6 +11,6 @@ contract MockCertManager is ICertManager { } function verifyClientCert(bytes memory, bytes32) external pure returns (VerifiedCert memory) { - return VerifiedCert({ca: false, notAfter: 0, maxPathLen: 0, subjectHash: bytes32(0), pubKey: ""}); + return VerifiedCert({ ca: false, notAfter: 0, maxPathLen: 0, subjectHash: bytes32(0), pubKey: "" }); } } diff --git a/src/multiproof/mocks/MockVerifier.sol b/src/multiproof/mocks/MockVerifier.sol index 68445c0c..fff83d15 100644 --- a/src/multiproof/mocks/MockVerifier.sol +++ b/src/multiproof/mocks/MockVerifier.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; contract MockVerifier is IVerifier { function verify(bytes calldata, bytes32, bytes32) external pure returns (bool) { diff --git a/src/multiproof/tee/DevSystemConfigGlobal.sol b/src/multiproof/tee/DevSystemConfigGlobal.sol index 92d68c30..4ab84a2a 100644 --- a/src/multiproof/tee/DevSystemConfigGlobal.sol +++ b/src/multiproof/tee/DevSystemConfigGlobal.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {ICertManager} from "@nitro-validator/ICertManager.sol"; +import { ICertManager } from "@nitro-validator/ICertManager.sol"; -import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; /// @title DevSystemConfigGlobal /// @notice Development version of SystemConfigGlobal with bypassed attestation for testing. /// @dev This contract adds addDevSigner() which bypasses AWS Nitro attestation verification. /// DO NOT deploy this contract to production networks. contract DevSystemConfigGlobal is SystemConfigGlobal { - constructor(ICertManager certManager) SystemConfigGlobal(certManager) {} + constructor(ICertManager certManager) SystemConfigGlobal(certManager) { } /// @notice Registers a signer for testing (bypasses attestation verification). /// @dev Only callable by owner. For development/testing use only. diff --git a/src/multiproof/tee/SystemConfigGlobal.sol b/src/multiproof/tee/SystemConfigGlobal.sol index cfdfde93..f9695ecb 100644 --- a/src/multiproof/tee/SystemConfigGlobal.sol +++ b/src/multiproof/tee/SystemConfigGlobal.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {LibCborElement, CborElement, CborDecode} from "@nitro-validator/CborDecode.sol"; -import {ICertManager} from "@nitro-validator/ICertManager.sol"; -import {LibBytes} from "@nitro-validator/LibBytes.sol"; -import {NitroValidator} from "@nitro-validator/NitroValidator.sol"; -import {OwnableManagedUpgradeable} from "@op-enclave/OwnableManagedUpgradeable.sol"; +import { LibCborElement, CborElement, CborDecode } from "@nitro-validator/CborDecode.sol"; +import { ICertManager } from "@nitro-validator/ICertManager.sol"; +import { LibBytes } from "@nitro-validator/LibBytes.sol"; +import { NitroValidator } from "@nitro-validator/NitroValidator.sol"; +import { OwnableManagedUpgradeable } from "@op-enclave/OwnableManagedUpgradeable.sol"; /// @title SystemConfigGlobal /// @notice Manages TEE signer registration via AWS Nitro attestation. @@ -61,7 +61,7 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { constructor(ICertManager certManager) NitroValidator(certManager) { // Always disable the implementation contract by setting dead addresses. // Proxies will call initialize() to set the real owner/manager. - initialize({initialOwner: address(0xdEaD), initialManager: address(0xdEaD)}); + initialize({ initialOwner: address(0xdEaD), initialManager: address(0xdEaD) }); } /// @notice Sets the proposer address. diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index e76f9e4d..c3785776 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; -import {SystemConfigGlobal} from "./SystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; /// @title TEEVerifier /// @notice Stateless TEE proof verifier that validates signatures against registered signers. @@ -43,8 +43,8 @@ contract TEEVerifier is IVerifier { } /// @notice Verifies a TEE proof for a state transition. - /// @param proofBytes The proof: proposer (20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65) = 149 bytes. - /// @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. + /// @param proofBytes The proof: proposer (20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65) = 149 + /// bytes. @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. /// @param journal The keccak256 hash of the proof's public inputs. /// @return valid Whether the proof is valid. function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view override returns (bool) { From c0ee7accaa423450d9501a6b621426e23e4573f8 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Fri, 27 Feb 2026 22:45:45 -0500 Subject: [PATCH 076/125] add tests to just and gen semver --- .github/workflows/test.yml | 2 +- justfile | 9 +++++++++ snapshots/semver-lock.json | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b627c147..ecbb8d61 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,5 +60,5 @@ jobs: git diff --exit-code snapshots/semver-lock.json - name: Run Forge tests - run: just test + run: just test && just test-multiproof id: test diff --git a/justfile b/justfile index 45fd3ac7..bc2a55ef 100644 --- a/justfile +++ b/justfile @@ -79,6 +79,15 @@ test *ARGS: build-go-ffi test-dev *ARGS: build-go-ffi FOUNDRY_PROFILE=lite forge test {{ARGS}} +# Runs multiproof contract tests. +test-multiproof *ARGS: build-go-ffi + @set -e; \ + echo "Applying patch..."; \ + git apply multiproof_tests/optimism.patch; \ + trap 'echo "Reverting patch..."; git apply -R multiproof_tests/optimism.patch' EXIT; \ + echo "Running tests..."; \ + FOUNDRY_PROFILE=multiproof forge test {{ARGS}} + # Default block number for the forked upgrade path. export sepoliaBlockNumber := "9366100" diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 5f3043e6..6485ab78 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -231,6 +231,10 @@ "initCodeHash": "0x3a82e248129d19764bb975bb79b48a982f077f33bb508480bf8d2ec1c0c9810d", "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, + "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { + "initCodeHash": "0x2d681a39e518379b6d2c013735ae25a9bcf6e939d597bde9e6126375b86fd294", + "sourceCodeHash": "0x7d0711f990d7ae8fdc27bf2da12f6fad21aaeca87f30d53e3e0b772f8d8c0f9f" + }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", "sourceCodeHash": "0xac49a0ecf22b8a7bb3ebef830a2d27b19050f9b08941186e8563d5113cf0ce9c" From 7d94460b97baf5d4871a41efac0b6642e3021da2 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Fri, 27 Feb 2026 23:20:08 -0500 Subject: [PATCH 077/125] fix tests --- test/vendor/Initializable.t.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 1e2425da..00e6c813 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -368,7 +368,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[](17); // 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. @@ -395,6 +395,11 @@ contract Initializer_Test is CommonTest { // Contract is not deployed as part of the standard deployment script. excludes[j++] = "src/revenue-share/BalanceTracker.sol"; + // Exclude Multiproof contracts as they are not part of standard deployment script. + excludes[j++] = "src/multiproof/AggregateVerifier.sol"; + excludes[j++] = "src/multiproof/tee/SystemConfigGlobal.sol"; + excludes[j++] = "src/multiproof/tee/DevSystemConfigGlobal.sol"; + // Get all contract names in the src directory, minus the excluded contracts. string[] memory contractNames = ForgeArtifacts.getContractNames("src/*", excludes); From d416a433d9e5e0ce58771cca561bf5882b299d7d Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Fri, 27 Feb 2026 23:42:18 -0500 Subject: [PATCH 078/125] rm unecessary op commit --- .env | 1 - 1 file changed, 1 deletion(-) diff --git a/.env b/.env index de25d823..00342e1e 100644 --- a/.env +++ b/.env @@ -1,2 +1 @@ BASE_MAINNET_URL=https://mainnet.base.org -OP_COMMIT=d6aa14e96fe5001b5c23c233d0747078d7ea02fb From cb974314b36286c79050427856b6bc950a8bff1f Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Sat, 28 Feb 2026 18:51:33 -0500 Subject: [PATCH 079/125] apply optimism patch for multiproof --- interfaces/dispute/IInitializable.sol | 2 +- src/dispute/DisputeGameFactory.sol | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/interfaces/dispute/IInitializable.sol b/interfaces/dispute/IInitializable.sol index a3bb74b8..bc998e1f 100644 --- a/interfaces/dispute/IInitializable.sol +++ b/interfaces/dispute/IInitializable.sol @@ -2,5 +2,5 @@ pragma solidity ^0.8.0; interface IInitializable { - function initialize() external payable; + function initialize(bytes calldata initData) external payable; } diff --git a/src/dispute/DisputeGameFactory.sol b/src/dispute/DisputeGameFactory.sol index 6129c830..851e898f 100644 --- a/src/dispute/DisputeGameFactory.sol +++ b/src/dispute/DisputeGameFactory.sol @@ -143,11 +143,13 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable /// @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. + /// @param _initData The initialization data for the DisputeGame. /// @return proxy_ The address of the created DisputeGame proxy. function create( GameType _gameType, Claim _rootClaim, - bytes calldata _extraData + bytes calldata _extraData, + bytes calldata _initData ) external payable @@ -165,7 +167,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: @@ -193,13 +197,10 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable // │ [88 + n, 88 + n + m) │ Implementation args (opaque) │ // └──────────────────────┴─────────────────────────────────────┘ proxy_ = IDisputeGame( - address(impl) - .clone( - abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, gameArgs[_gameType]) - ) + address(impl).clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) ); } - proxy_.initialize{ value: msg.value }(); + proxy_.initialize{ value: msg.value }(_initData); // Compute the unique identifier for the dispute game. Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData); From cae85a818bbbfffae1c16af081f2870724299f29 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Sat, 28 Feb 2026 19:10:57 -0500 Subject: [PATCH 080/125] move multiproof tests --- multiproof_tests/optimism.patch | 57 ------------------- patch/optimism.patch | 56 ------------------ src/dispute/zk/OPSuccinctFaultDisputeGame.sol | 2 +- test/dispute/FaultDisputeGame.t.sol | 2 +- test/dispute/SuperFaultDisputeGame.t.sol | 2 +- .../multiproof/AggregateVerifier.t.sol | 0 .../multiproof/BaseTest.t.sol | 0 .../multiproof/Challenge.t.sol | 0 .../multiproof/Nullify.t.sol | 0 .../multiproof/SystemConfigGlobal.t.sol | 0 .../multiproof/TEEVerifier.t.sol | 0 11 files changed, 3 insertions(+), 116 deletions(-) delete mode 100644 multiproof_tests/optimism.patch delete mode 100644 patch/optimism.patch rename {multiproof_tests => test}/multiproof/AggregateVerifier.t.sol (100%) rename {multiproof_tests => test}/multiproof/BaseTest.t.sol (100%) rename {multiproof_tests => test}/multiproof/Challenge.t.sol (100%) rename {multiproof_tests => test}/multiproof/Nullify.t.sol (100%) rename {multiproof_tests => test}/multiproof/SystemConfigGlobal.t.sol (100%) rename {multiproof_tests => test}/multiproof/TEEVerifier.t.sol (100%) diff --git a/multiproof_tests/optimism.patch b/multiproof_tests/optimism.patch deleted file mode 100644 index b75302b5..00000000 --- a/multiproof_tests/optimism.patch +++ /dev/null @@ -1,57 +0,0 @@ -diff --git a/interfaces/dispute/IInitializable.sol b/interfaces/dispute/IInitializable.sol -index a3bb74b8..bc998e1f 100644 ---- a/interfaces/dispute/IInitializable.sol -+++ b/interfaces/dispute/IInitializable.sol -@@ -2,5 +2,5 @@ - pragma solidity ^0.8.0; - - interface IInitializable { -- function initialize() external payable; -+ function initialize(bytes calldata initData) external payable; - } -diff --git a/src/dispute/DisputeGameFactory.sol b/src/dispute/DisputeGameFactory.sol -index 6129c830..851e898f 100644 ---- a/src/dispute/DisputeGameFactory.sol -+++ b/src/dispute/DisputeGameFactory.sol -@@ -143,11 +143,13 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable - /// @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. -+ /// @param _initData The initialization data for the DisputeGame. - /// @return proxy_ The address of the created DisputeGame proxy. - function create( - GameType _gameType, - Claim _rootClaim, -- bytes calldata _extraData -+ bytes calldata _extraData, -+ bytes calldata _initData - ) - external - payable -@@ -165,7 +167,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: -@@ -193,13 +197,10 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable - // │ [88 + n, 88 + n + m) │ Implementation args (opaque) │ - // └──────────────────────┴─────────────────────────────────────┘ - proxy_ = IDisputeGame( -- address(impl) -- .clone( -- abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, gameArgs[_gameType]) -- ) -+ address(impl).clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) - ); - } -- proxy_.initialize{ value: msg.value }(); -+ proxy_.initialize{ value: msg.value }(_initData); - - // Compute the unique identifier for the dispute game. - Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData); diff --git a/patch/optimism.patch b/patch/optimism.patch deleted file mode 100644 index a1823132..00000000 --- a/patch/optimism.patch +++ /dev/null @@ -1,56 +0,0 @@ -diff --git a/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol b/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol -index a3bb74b..bc998e1 100644 ---- a/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol -+++ b/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol -@@ -2,5 +2,5 @@ - pragma solidity ^0.8.0; - - interface IInitializable { -- function initialize() external payable; -+ function initialize(bytes calldata initData) external payable; - } -diff --git a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol -index eb92ca5..abcdef1 100644 ---- a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol -+++ b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol -@@ -143,11 +143,13 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable - /// @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. -+ /// @param _initData The initialization data for the DisputeGame. - /// @return proxy_ The address of the created DisputeGame proxy. - function create( - GameType _gameType, - Claim _rootClaim, -- bytes calldata _extraData -+ bytes calldata _extraData, -+ bytes calldata _initData - ) - external - payable -@@ -165,7 +167,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: -@@ -193,12 +197,10 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable - // │ [88 + n, 88 + n + m) │ Implementation args (opaque) │ - // └──────────────────────┴─────────────────────────────────────┘ - proxy_ = IDisputeGame( -- address(impl).clone( -- abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, gameArgs[_gameType]) -- ) -+ address(impl).clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) - ); - } -- proxy_.initialize{ value: msg.value }(); -+ proxy_.initialize{ value: msg.value }(_initData); - - // Compute the unique identifier for the dispute game. - Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData); diff --git a/src/dispute/zk/OPSuccinctFaultDisputeGame.sol b/src/dispute/zk/OPSuccinctFaultDisputeGame.sol index f7c1e6e7..09bb827b 100644 --- a/src/dispute/zk/OPSuccinctFaultDisputeGame.sol +++ b/src/dispute/zk/OPSuccinctFaultDisputeGame.sol @@ -217,7 +217,7 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { /// @notice Initializes the contract. /// @dev This function may only be called once. - function initialize() external payable virtual { + function initialize(bytes calldata) external payable virtual { // SAFETY: Any revert in this function will bubble up to the DisputeGameFactory and // prevent the game from being created. // diff --git a/test/dispute/FaultDisputeGame.t.sol b/test/dispute/FaultDisputeGame.t.sol index a69f3ab3..5aa593e6 100644 --- a/test/dispute/FaultDisputeGame.t.sol +++ b/test/dispute/FaultDisputeGame.t.sol @@ -491,7 +491,7 @@ contract FaultDisputeGame_Initialize_Test is FaultDisputeGame_TestInit { /// @notice Tests that the game cannot be initialized twice. function test_initialize_onlyOnce_succeeds() public { vm.expectRevert(AlreadyInitialized.selector); - gameProxy.initialize(); + gameProxy.initialize(bytes("")); } /// @notice Tests that initialization reverts when oracle challenge period is too large. diff --git a/test/dispute/SuperFaultDisputeGame.t.sol b/test/dispute/SuperFaultDisputeGame.t.sol index 7f47d1e1..1ff4cf91 100644 --- a/test/dispute/SuperFaultDisputeGame.t.sol +++ b/test/dispute/SuperFaultDisputeGame.t.sol @@ -452,7 +452,7 @@ contract SuperFaultDisputeGame_Initialize_Test is SuperFaultDisputeGame_TestInit /// @notice Tests that the game cannot be initialized twice. function test_initialize_onlyOnce_succeeds() public { vm.expectRevert(AlreadyInitialized.selector); - gameProxy.initialize(); + gameProxy.initialize(bytes("")); } /// @notice Tests that initialization reverts when oracle challenge period is too large. diff --git a/multiproof_tests/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol similarity index 100% rename from multiproof_tests/multiproof/AggregateVerifier.t.sol rename to test/multiproof/AggregateVerifier.t.sol diff --git a/multiproof_tests/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol similarity index 100% rename from multiproof_tests/multiproof/BaseTest.t.sol rename to test/multiproof/BaseTest.t.sol diff --git a/multiproof_tests/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol similarity index 100% rename from multiproof_tests/multiproof/Challenge.t.sol rename to test/multiproof/Challenge.t.sol diff --git a/multiproof_tests/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol similarity index 100% rename from multiproof_tests/multiproof/Nullify.t.sol rename to test/multiproof/Nullify.t.sol diff --git a/multiproof_tests/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol similarity index 100% rename from multiproof_tests/multiproof/SystemConfigGlobal.t.sol rename to test/multiproof/SystemConfigGlobal.t.sol diff --git a/multiproof_tests/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol similarity index 100% rename from multiproof_tests/multiproof/TEEVerifier.t.sol rename to test/multiproof/TEEVerifier.t.sol From cb821124bcf4bf77bb948e2f7fdb94b4ce3cc7c6 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Sat, 28 Feb 2026 19:16:46 -0500 Subject: [PATCH 081/125] move files and delete multiproof test usage and profile --- Makefile | 10 ---------- foundry.toml | 9 --------- justfile | 9 --------- .../MockDevSystemConfigGlobal.sol} | 2 +- test/multiproof/SystemConfigGlobal.t.sol | 2 +- test/multiproof/TEEVerifier.t.sol | 2 +- 6 files changed, 3 insertions(+), 31 deletions(-) rename src/multiproof/{tee/DevSystemConfigGlobal.sol => mocks/MockDevSystemConfigGlobal.sol} (92%) diff --git a/Makefile b/Makefile index 7ebf6b5e..2a3595f3 100644 --- a/Makefile +++ b/Makefile @@ -34,16 +34,6 @@ deps: clean-lib test: forge test --ffi -vvv -.PHONY: test-multiproof -test-multiproof: - @set -e; \ - echo "Applying patch..."; \ - git apply multiproof_tests/optimism.patch; \ - trap 'echo "Reverting patch..."; git apply -R multiproof_tests/optimism.patch' EXIT; \ - echo "Running tests..."; \ - FOUNDRY_PROFILE=multiproof forge test --ffi -vvv - - .PHONY: clean-lib clean-lib: rm -rf lib diff --git a/foundry.toml b/foundry.toml index 78c76c54..bd9343ed 100644 --- a/foundry.toml +++ b/foundry.toml @@ -176,12 +176,3 @@ src = 'test/kontrol/proofs' out = 'kout-proofs' test = 'test/kontrol/proofs' script = 'test/kontrol/proofs' - -################################################################ -# PROFILE: Multiproof # -################################################################ - -[profile.multiproof] -src = 'multiproof_tests' -out = 'multiproof-out' -test = 'multiproof_tests' diff --git a/justfile b/justfile index bc2a55ef..45fd3ac7 100644 --- a/justfile +++ b/justfile @@ -79,15 +79,6 @@ test *ARGS: build-go-ffi test-dev *ARGS: build-go-ffi FOUNDRY_PROFILE=lite forge test {{ARGS}} -# Runs multiproof contract tests. -test-multiproof *ARGS: build-go-ffi - @set -e; \ - echo "Applying patch..."; \ - git apply multiproof_tests/optimism.patch; \ - trap 'echo "Reverting patch..."; git apply -R multiproof_tests/optimism.patch' EXIT; \ - echo "Running tests..."; \ - FOUNDRY_PROFILE=multiproof forge test {{ARGS}} - # Default block number for the forked upgrade path. export sepoliaBlockNumber := "9366100" diff --git a/src/multiproof/tee/DevSystemConfigGlobal.sol b/src/multiproof/mocks/MockDevSystemConfigGlobal.sol similarity index 92% rename from src/multiproof/tee/DevSystemConfigGlobal.sol rename to src/multiproof/mocks/MockDevSystemConfigGlobal.sol index 4ab84a2a..fced8827 100644 --- a/src/multiproof/tee/DevSystemConfigGlobal.sol +++ b/src/multiproof/mocks/MockDevSystemConfigGlobal.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.15; import { ICertManager } from "@nitro-validator/ICertManager.sol"; -import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; /// @title DevSystemConfigGlobal /// @notice Development version of SystemConfigGlobal with bypassed attestation for testing. diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol index 4d6cf303..5f5ee79f 100644 --- a/test/multiproof/SystemConfigGlobal.t.sol +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -8,7 +8,7 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s import {ICertManager} from "@nitro-validator/ICertManager.sol"; -import {DevSystemConfigGlobal} from "src/multiproof/tee/DevSystemConfigGlobal.sol"; +import {DevSystemConfigGlobal} from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; import {MockCertManager} from "src/multiproof/mocks/MockCertManager.sol"; diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 204fabb0..316a0c6b 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -8,7 +8,7 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s import {ICertManager} from "@nitro-validator/ICertManager.sol"; -import {DevSystemConfigGlobal} from "src/multiproof/tee/DevSystemConfigGlobal.sol"; +import {DevSystemConfigGlobal} from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; import {TEEVerifier} from "src/multiproof/tee/TEEVerifier.sol"; From ccf360126157d969b5779d4946f26901d57640bc Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Sat, 28 Feb 2026 19:21:24 -0500 Subject: [PATCH 082/125] remove remappings of nitro and op enclave --- foundry.toml | 4 +--- src/multiproof/mocks/MockCertManager.sol | 2 +- src/multiproof/mocks/MockDevSystemConfigGlobal.sol | 2 +- src/multiproof/tee/SystemConfigGlobal.sol | 10 +++++----- test/multiproof/SystemConfigGlobal.t.sol | 2 +- test/multiproof/TEEVerifier.t.sol | 2 +- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/foundry.toml b/foundry.toml index bd9343ed..6b07a32a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -50,9 +50,7 @@ remappings = [ 'ds-test/=lib/forge-std/lib/ds-test/src', 'safe-contracts/=lib/safe-contracts/contracts', 'kontrol-cheatcodes/=lib/kontrol-cheatcodes/src', - 'interfaces/=interfaces', - '@nitro-validator/=lib/nitro-validator/src/', - '@op-enclave/=lib/op-enclave/contracts/src/' + 'interfaces/=interfaces' ] fs_permissions = [ diff --git a/src/multiproof/mocks/MockCertManager.sol b/src/multiproof/mocks/MockCertManager.sol index 7cf70f01..f7e46c64 100644 --- a/src/multiproof/mocks/MockCertManager.sol +++ b/src/multiproof/mocks/MockCertManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ICertManager } from "@nitro-validator/ICertManager.sol"; +import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; /// @title MockCertManager /// @notice Mock CertManager for testing SystemConfigGlobal. diff --git a/src/multiproof/mocks/MockDevSystemConfigGlobal.sol b/src/multiproof/mocks/MockDevSystemConfigGlobal.sol index fced8827..3f931be6 100644 --- a/src/multiproof/mocks/MockDevSystemConfigGlobal.sol +++ b/src/multiproof/mocks/MockDevSystemConfigGlobal.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ICertManager } from "@nitro-validator/ICertManager.sol"; +import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; diff --git a/src/multiproof/tee/SystemConfigGlobal.sol b/src/multiproof/tee/SystemConfigGlobal.sol index f9695ecb..0570ca3d 100644 --- a/src/multiproof/tee/SystemConfigGlobal.sol +++ b/src/multiproof/tee/SystemConfigGlobal.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { LibCborElement, CborElement, CborDecode } from "@nitro-validator/CborDecode.sol"; -import { ICertManager } from "@nitro-validator/ICertManager.sol"; -import { LibBytes } from "@nitro-validator/LibBytes.sol"; -import { NitroValidator } from "@nitro-validator/NitroValidator.sol"; -import { OwnableManagedUpgradeable } from "@op-enclave/OwnableManagedUpgradeable.sol"; +import { LibCborElement, CborElement, CborDecode } from "lib/nitro-validator/src/CborDecode.sol"; +import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; +import { LibBytes } from "lib/nitro-validator/src/LibBytes.sol"; +import { NitroValidator } from "lib/nitro-validator/src//NitroValidator.sol"; +import { OwnableManagedUpgradeable } from "lib/op-enclave/contracts/src/OwnableManagedUpgradeable.sol"; /// @title SystemConfigGlobal /// @notice Manages TEE signer registration via AWS Nitro attestation. diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol index 5f5ee79f..7f6dff82 100644 --- a/test/multiproof/SystemConfigGlobal.t.sol +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {ICertManager} from "@nitro-validator/ICertManager.sol"; +import {ICertManager} from "lib/nitro-validator/src/ICertManager.sol"; import {DevSystemConfigGlobal} from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 316a0c6b..d30f89d2 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {ICertManager} from "@nitro-validator/ICertManager.sol"; +import {ICertManager} from "lib/nitro-validator/src/ICertManager.sol"; import {DevSystemConfigGlobal} from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; From 1b4343e17830c08a48cf3ba95f564dcfc1e86d2b Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Sat, 28 Feb 2026 19:24:13 -0500 Subject: [PATCH 083/125] undo multiproof changes and solady import change --- .gitignore | 1 - Makefile | 12 ------------ test/libraries/SemverComp.t.sol | 2 +- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index cf6cbd81..73a2e81b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ kout-proofs test/kontrol/logs out lib -multiproof-out # Metrics coverage.out diff --git a/Makefile b/Makefile index 2a3595f3..70534f68 100644 --- a/Makefile +++ b/Makefile @@ -46,15 +46,3 @@ bindings: abigen --abi out/BalanceTracker.sol/BalanceTracker.abi.json --pkg bindings --type BalanceTracker --out bindings/balance_tracker.go abigen --abi out/FeeDisburser.sol/FeeDisburser.abi.json --pkg bindings --type FeeDisburser --out bindings/fee_disburser.go cd bindings && go mod tidy - -.PHONY: checkout-op-commit -checkout-op-commit: - [ -n "$(OP_COMMIT)" ] || (echo "OP_COMMIT must be set in .env" && exit 1) - rm -rf lib/optimism - mkdir -p lib/optimism - cd lib/optimism; \ - git init; \ - git remote add origin https://github.com/ethereum-optimism/optimism.git; \ - git fetch --depth=1 origin $(OP_COMMIT); \ - git reset --hard FETCH_HEAD; \ - git apply ../../patch/optimism.patch; diff --git a/test/libraries/SemverComp.t.sol b/test/libraries/SemverComp.t.sol index eb07ea14..b182af37 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/utils/JSONParserLib.sol"; +import { JSONParserLib } from "solady/src/utils/JSONParserLib.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; /// @title SemverComp_Harness From babc5ad20795a9da020deb8f68a758991e3f125d Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Sun, 1 Mar 2026 23:58:48 -0500 Subject: [PATCH 084/125] support AggregateVerifier in initalizer test --- scripts/deploy/Deploy.s.sol | 1 + scripts/deploy/DeployImplementations.s.sol | 40 +++++++++++++++++++ scripts/multiproof/DeployDevNoNitro.s.sol | 2 +- scripts/multiproof/DeployDevWithNitro.s.sol | 6 +-- src/dispute/DisputeGameFactory.sol | 3 +- test/libraries/SemverComp.t.sol | 2 +- test/multiproof/AggregateVerifier.t.sol | 20 +++++----- test/multiproof/BaseTest.t.sol | 44 ++++++++++++--------- test/multiproof/Challenge.t.sol | 12 +++--- test/multiproof/Nullify.t.sol | 8 ++-- test/multiproof/SystemConfigGlobal.t.sol | 14 +++---- test/multiproof/TEEVerifier.t.sol | 16 ++++---- test/setup/Setup.sol | 3 ++ test/vendor/Initializable.t.sol | 16 +++++--- 14 files changed, 122 insertions(+), 65 deletions(-) diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol index 1b029609..6c413e39 100644 --- a/scripts/deploy/Deploy.s.sol +++ b/scripts/deploy/Deploy.s.sol @@ -300,6 +300,7 @@ 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)); // Get a contract set from the implementation addresses which were just deployed. Types.ContractSet memory impls = ChainAssertions.dioToContractSet(dio); diff --git a/scripts/deploy/DeployImplementations.s.sol b/scripts/deploy/DeployImplementations.s.sol index 7a45a215..bfd01cf6 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 { CertManager } from "lib/nitro-validator/src/CertManager.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; +import { DeployDevWithNitro } from "../multiproof/DeployDevWithNitro.s.sol"; +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; contract DeployImplementations is Script { struct Input { @@ -95,6 +102,7 @@ contract DeployImplementations is Script { IPermissionedDisputeGameV2 permissionedDisputeGameV2Impl; ISuperFaultDisputeGame superFaultDisputeGameImpl; ISuperPermissionedDisputeGame superPermissionedDisputeGameImpl; + IVerifier aggregateVerifierImpl; } bytes32 internal _salt = DeployUtils.DEFAULT_SALT; @@ -128,6 +136,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 +704,37 @@ contract DeployImplementations is Script { _output.opcmStandardValidator = impl; } + function deployAggregateVerifierImpl(Input memory, Output memory _output) private { + DeployDevWithNitro nitro = new DeployDevWithNitro(); + DeployDevWithNitro.DeployConfig memory cfg = nitro.loadConfig(); + + address zkVerifier = address(new MockVerifier()); + + address certManager = address(new CertManager()); + SystemConfigGlobal scgImpl = new SystemConfigGlobal(CertManager(certManager)); + address teeVerifierImpl = address(new TEEVerifier(scgImpl)); + + IVerifier aggregateVerifierImpl = IVerifier( + address( + new AggregateVerifier( + cfg.gameType, + _output.anchorStateRegistryImpl, + _output.delayedWETHImpl, + IVerifier(teeVerifierImpl), + IVerifier(zkVerifier), + cfg.teeImageHash, + bytes32(0), + cfg.configHash, + 8453, + 100, + 10 + ) + ) + ); + vm.label(address(aggregateVerifierImpl), "AggregateVerifierImpl"); + _output.aggregateVerifierImpl = aggregateVerifierImpl; + } + function assertValidInput(Input memory _input) private pure { // Validate V2 game depth parameters are sensible require( diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index a5481f33..36e89f3f 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -52,7 +52,7 @@ pragma solidity 0.8.15; * ══════════════════════════════════════════════════════════════════════════════════ */ -import { CertManager } from "@nitro-validator/CertManager.sol"; +import { CertManager } from "lib/nitro-validator/src/CertManager.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { Script } from "forge-std/Script.sol"; import { stdJson } from "forge-std/StdJson.sol"; diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index a8230ef1..a3d6dca5 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -116,7 +116,7 @@ pragma solidity 0.8.15; * ══════════════════════════════════════════════════════════════════════════════════ */ -import { CertManager } from "@nitro-validator/CertManager.sol"; +import { CertManager } from "lib/nitro-validator/src/CertManager.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { Script } from "forge-std/Script.sol"; import { stdJson } from "forge-std/StdJson.sol"; @@ -172,7 +172,7 @@ contract DeployDevWithNitro is Script { address public aggregateVerifier; function run() public { - DeployConfig memory cfg = _loadConfig(); + DeployConfig memory cfg = loadConfig(); console.log("=== Deploying Dev Infrastructure (WITH NITRO) ==="); console.log("Chain ID:", block.chainid); @@ -195,7 +195,7 @@ contract DeployDevWithNitro is Script { _writeOutput(); } - function _loadConfig() internal view returns (DeployConfig memory cfg) { + function loadConfig() public view returns (DeployConfig memory cfg) { string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia-with-nitro.json")); string memory config = vm.readFile(configPath); diff --git a/src/dispute/DisputeGameFactory.sol b/src/dispute/DisputeGameFactory.sol index 851e898f..7b7a9a61 100644 --- a/src/dispute/DisputeGameFactory.sol +++ b/src/dispute/DisputeGameFactory.sol @@ -197,7 +197,8 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable // │ [88 + n, 88 + n + m) │ Implementation args (opaque) │ // └──────────────────────┴─────────────────────────────────────┘ proxy_ = IDisputeGame( - address(impl).clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) + address(impl) + .clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) ); } proxy_.initialize{ value: msg.value }(_initData); diff --git a/test/libraries/SemverComp.t.sol b/test/libraries/SemverComp.t.sol index b182af37..eb07ea14 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 index 761cb414..2c42ca59 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {BadExtraData} from "src/dispute/lib/Errors.sol"; -import {IAnchorStateRegistry} from "interfaces/dispute/IAnchorStateRegistry.sol"; -import {IDelayedWETH} from "interfaces/dispute/IDelayedWETH.sol"; -import {IDisputeGame} from "interfaces/dispute/IDisputeGame.sol"; -import {IDisputeGameFactory} from "interfaces/dispute/IDisputeGameFactory.sol"; -import {Claim, GameStatus, Hash, Timestamp} from "src/dispute/lib/Types.sol"; +import { BadExtraData } from "src/dispute/lib/Errors.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { Claim, GameStatus, Hash, Timestamp } from "src/dispute/lib/Types.sol"; -import {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; -import {IVerifier} from "interfaces/multiproof/IVerifier.sol"; +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; -import {BaseTest} from "./BaseTest.t.sol"; +import { BaseTest } from "./BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { function testInitializeWithTEEProof() public { @@ -74,7 +74,7 @@ contract AggregateVerifierTest is BaseTest { vm.prank(TEE_PROVER); vm.expectRevert(BadExtraData.selector); - factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData); + factory.create{ value: INIT_BOND }(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData); } function testUpdatingAnchorStateRegistryWithTEEProof() public { diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 02deb1ae..ef2a6c1f 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -1,28 +1,28 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {Test} from "forge-std/Test.sol"; +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"; +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 "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/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 { 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"; +import { MockSystemConfig } from "src/multiproof/mocks/MockSystemConfig.sol"; +import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; contract BaseTest is Test { // Constants @@ -148,7 +148,10 @@ contract BaseTest is Test { uint256 l2BlockNumber, uint32 parentIndex, bytes memory proof - ) internal returns (AggregateVerifier game) { + ) + internal + returns (AggregateVerifier game) + { bytes memory intermediateRoots = abi.encodePacked(_generateIntermediateRootsExceptLast(l2BlockNumber), rootClaim.raw()); bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), uint32(parentIndex), intermediateRoots); @@ -156,7 +159,7 @@ contract BaseTest is Test { vm.deal(creator, INIT_BOND); vm.prank(creator); return AggregateVerifier( - address(factory.create{value: INIT_BOND}(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, proof)) + address(factory.create{ value: INIT_BOND }(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, proof)) ); } @@ -171,7 +174,10 @@ contract BaseTest is Test { /// @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) + function _generateProof( + bytes memory salt, + AggregateVerifier.ProofType proofType + ) internal view returns (bytes memory) diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 4d41278d..561cd308 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {ClaimAlreadyResolved} from "src/dispute/lib/Errors.sol"; -import {IAnchorStateRegistry} from "interfaces/dispute/IAnchorStateRegistry.sol"; -import {IDisputeGame} from "interfaces/dispute/IDisputeGame.sol"; -import {Claim, GameStatus, Hash} from "src/dispute/lib/Types.sol"; +import { 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 { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; -import {BaseTest} from "./BaseTest.t.sol"; +import { BaseTest } from "./BaseTest.t.sol"; contract ChallengeTest is BaseTest { function testChallengeTEEProofWithZKProof() public { diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 1af4aaae..e7a36056 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {GameNotInProgress} from "src/dispute/lib/Errors.sol"; -import {Claim, GameStatus} from "src/dispute/lib/Types.sol"; +import { GameNotInProgress } from "src/dispute/lib/Errors.sol"; +import { Claim, GameStatus } from "src/dispute/lib/Types.sol"; -import {AggregateVerifier} from "src/multiproof/AggregateVerifier.sol"; +import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; -import {BaseTest} from "./BaseTest.t.sol"; +import { BaseTest } from "./BaseTest.t.sol"; contract NullifyTest is BaseTest { function testNullifyWithTEEProof() public { diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol index 7f6dff82..fe3440e0 100644 --- a/test/multiproof/SystemConfigGlobal.t.sol +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {Test} from "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {ICertManager} from "lib/nitro-validator/src/ICertManager.sol"; +import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; -import {DevSystemConfigGlobal} from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; -import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; -import {MockCertManager} from "src/multiproof/mocks/MockCertManager.sol"; +import { MockCertManager } from "src/multiproof/mocks/MockCertManager.sol"; /// @notice Tests for SystemConfigGlobal and DevSystemConfigGlobal contracts. /// @dev IMPORTANT: This test file uses DevSystemConfigGlobal as the implementation because diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index d30f89d2..b9e4dc2f 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import {Test} from "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {ICertManager} from "lib/nitro-validator/src/ICertManager.sol"; +import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; -import {DevSystemConfigGlobal} from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; -import {SystemConfigGlobal} from "src/multiproof/tee/SystemConfigGlobal.sol"; -import {TEEVerifier} from "src/multiproof/tee/TEEVerifier.sol"; +import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; +import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; -import {MockCertManager} from "src/multiproof/mocks/MockCertManager.sol"; +import { MockCertManager } from "src/multiproof/mocks/MockCertManager.sol"; contract TEEVerifierTest is Test { TEEVerifier public verifier; diff --git a/test/setup/Setup.sol b/test/setup/Setup.sol index 3ffc56b0..4d627d3e 100644 --- a/test/setup/Setup.sol +++ b/test/setup/Setup.sol @@ -67,6 +67,7 @@ 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"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -156,6 +157,7 @@ abstract contract Setup is FeatureFlags { IFeeSplitter feeSplitter = IFeeSplitter(payable(Predeploys.FEE_SPLITTER)); IL1Withdrawer l1Withdrawer; ISuperchainRevSharesCalculator superchainRevSharesCalculator; + IVerifier aggregateVerifier; /// @notice Indicates whether a test is running against a forked production network. function isForkTest() public view returns (bool) { @@ -294,6 +296,7 @@ abstract contract Setup is FeatureFlags { superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); superchainProxyAdminOwner = superchainProxyAdmin.owner(); mips = IBigStepper(artifacts.mustGetAddress("MipsSingleton")); + aggregateVerifier = IVerifier(artifacts.mustGetAddress("AggregrateVerifier")); if (deploy.cfg().useAltDA()) { dataAvailabilityChallenge = diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 00e6c813..7cd7a1e8 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -359,6 +359,17 @@ contract Initializer_Test is CommonTest { ) }) ); + + // AggregateVerifier + contracts.push( + InitializeableContract({ + name: "AggregateVerifier", + target: address(aggregateVerifier), + initCalldata: abi.encodeCall( + ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) + ) + }) + ); } /// @notice Tests that: @@ -395,11 +406,6 @@ contract Initializer_Test is CommonTest { // Contract is not deployed as part of the standard deployment script. excludes[j++] = "src/revenue-share/BalanceTracker.sol"; - // Exclude Multiproof contracts as they are not part of standard deployment script. - excludes[j++] = "src/multiproof/AggregateVerifier.sol"; - excludes[j++] = "src/multiproof/tee/SystemConfigGlobal.sol"; - excludes[j++] = "src/multiproof/tee/DevSystemConfigGlobal.sol"; - // Get all contract names in the src directory, minus the excluded contracts. string[] memory contractNames = ForgeArtifacts.getContractNames("src/*", excludes); From 7b90711a4331b48ee7ec9e0a94d3a95e350a9ae2 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 00:14:24 -0500 Subject: [PATCH 085/125] fix import path --- scripts/multiproof/DeployDevNoNitro.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 36e89f3f..081b8ba9 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -66,7 +66,7 @@ import { GameType, Hash } from "src/dispute/lib/Types.sol"; import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; -import { DevSystemConfigGlobal } from "src/multiproof/tee/DevSystemConfigGlobal.sol"; +import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; From 76335ba062b20edf5598ce20eef38dc6e7282f45 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 00:45:34 -0500 Subject: [PATCH 086/125] semver --- snapshots/semver-lock.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 6485ab78..921c8212 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -188,8 +188,8 @@ "sourceCodeHash": "0xdebf2ab3af4d5549c40e9dd9db6b2458af286f323b6891f3b0c4e89f3c8928db" }, "src/dispute/DisputeGameFactory.sol:DisputeGameFactory": { - "initCodeHash": "0x820e876839f94564a9d5d1fa131781c40126aed76fbbd348f49f318ae3e12aae", - "sourceCodeHash": "0x870025c86292471724e2be703b181be8d71a7e1e96ebf181e0a81c005032a5db" + "initCodeHash": "0x5377f55b80de4f18ea270cf73bfdfbf5bae9f5d9065445aaacecbf3256a3f700", + "sourceCodeHash": "0xf2df70bdcdfd489b31077e33dba55174ac4a1bf742fd93e69510528ee2ea5a57" }, "src/dispute/FaultDisputeGame.sol:FaultDisputeGame": { "initCodeHash": "0xe7d3c982532946d196d7efadb9e2576c76b8f9e0d1f885ac36977d6f3fb72a65", @@ -216,8 +216,8 @@ "sourceCodeHash": "0xc0ff6e93b6e2b9111c11e81b5df8948ab71d02b9d2c4dfda982fcb615519f1f7" }, "src/dispute/zk/OPSuccinctFaultDisputeGame.sol:OPSuccinctFaultDisputeGame": { - "initCodeHash": "0xb9d0d9ca4df242f188b2d5be7d692459a12409a67a6504ef44ef589c6ca2c1a9", - "sourceCodeHash": "0x85f80adb845f59e9137d462e219c0cdba27058be77d855075e286aa316735aa0" + "initCodeHash": "0xc034c1adb7149472b7ab449e4beef9b8a6eab2dd4efb1fd025588dbe3376dc83", + "sourceCodeHash": "0x2a1f75dcfbdaca1fc5d868f7c600b48cabe6f7669e5c0389d0d3a20b6288562d" }, "src/legacy/DeployerWhitelist.sol:DeployerWhitelist": { "initCodeHash": "0x2e0ef4c341367eb59cc6c25190c64eff441d3fe130189da91d4d126f6bdbc9b5", @@ -233,7 +233,7 @@ }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { "initCodeHash": "0x2d681a39e518379b6d2c013735ae25a9bcf6e939d597bde9e6126375b86fd294", - "sourceCodeHash": "0x7d0711f990d7ae8fdc27bf2da12f6fad21aaeca87f30d53e3e0b772f8d8c0f9f" + "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", From 1e6412c8ac371ec844c23c55edfd13a5d93645c9 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 01:27:15 -0500 Subject: [PATCH 087/125] semver --- snapshots/semver-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 921c8212..708820e7 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -188,7 +188,7 @@ "sourceCodeHash": "0xdebf2ab3af4d5549c40e9dd9db6b2458af286f323b6891f3b0c4e89f3c8928db" }, "src/dispute/DisputeGameFactory.sol:DisputeGameFactory": { - "initCodeHash": "0x5377f55b80de4f18ea270cf73bfdfbf5bae9f5d9065445aaacecbf3256a3f700", + "initCodeHash": "0xa464c608edf80a5f4277b081be1c302605b05036befb744bbe1dcb42b151ea96", "sourceCodeHash": "0xf2df70bdcdfd489b31077e33dba55174ac4a1bf742fd93e69510528ee2ea5a57" }, "src/dispute/FaultDisputeGame.sol:FaultDisputeGame": { From 5d90e6eb770d0ccfb7e5a2c7a4db66f03d171b73 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 01:27:40 -0500 Subject: [PATCH 088/125] semver --- snapshots/semver-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 708820e7..584111fd 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -232,7 +232,7 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { - "initCodeHash": "0x2d681a39e518379b6d2c013735ae25a9bcf6e939d597bde9e6126375b86fd294", + "initCodeHash": "0x73537cace710afd0dc4ea7c2e25687a29cee13843617f518a3edeb8e79cf324c", "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { From 4fd964b4d2517fb82d45af262a2507c8de7a7293 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 02:54:13 -0500 Subject: [PATCH 089/125] deterministic semverlock --- scripts/autogen/generate-semver-lock/main.go | 3 +++ snapshots/semver-lock.json | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/scripts/autogen/generate-semver-lock/main.go b/scripts/autogen/generate-semver-lock/main.go index 9fb9f210..13af715f 100644 --- a/scripts/autogen/generate-semver-lock/main.go +++ b/scripts/autogen/generate-semver-lock/main.go @@ -81,6 +81,9 @@ func processFile(file string) (*SemverLockResult, []error) { sourceFilePath = path contractName = name contractKey = sourceFilePath + ":" + name + if strings.HasSuffix(file, ".dispute.json") { + contractKey += ":dispute" + } break } diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 584111fd..84fc97ed 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -191,6 +191,10 @@ "initCodeHash": "0xa464c608edf80a5f4277b081be1c302605b05036befb744bbe1dcb42b151ea96", "sourceCodeHash": "0xf2df70bdcdfd489b31077e33dba55174ac4a1bf742fd93e69510528ee2ea5a57" }, + "src/dispute/DisputeGameFactory.sol:DisputeGameFactory:dispute": { + "initCodeHash": "0x5377f55b80de4f18ea270cf73bfdfbf5bae9f5d9065445aaacecbf3256a3f700", + "sourceCodeHash": "0xf2df70bdcdfd489b31077e33dba55174ac4a1bf742fd93e69510528ee2ea5a57" + }, "src/dispute/FaultDisputeGame.sol:FaultDisputeGame": { "initCodeHash": "0xe7d3c982532946d196d7efadb9e2576c76b8f9e0d1f885ac36977d6f3fb72a65", "sourceCodeHash": "0x9c8bca8da051d33272da326d422e980239135def0662fb3476c702b72ac5f77b" @@ -232,6 +236,10 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { + "initCodeHash": "0x2d681a39e518379b6d2c013735ae25a9bcf6e939d597bde9e6126375b86fd294", + "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" + }, + "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { "initCodeHash": "0x73537cace710afd0dc4ea7c2e25687a29cee13843617f518a3edeb8e79cf324c", "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" }, From 928cfe0779952867253da0207007871dc1cdf29a Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 02:58:02 -0500 Subject: [PATCH 090/125] add comment --- scripts/autogen/generate-semver-lock/main.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/autogen/generate-semver-lock/main.go b/scripts/autogen/generate-semver-lock/main.go index 13af715f..44eb9d5c 100644 --- a/scripts/autogen/generate-semver-lock/main.go +++ b/scripts/autogen/generate-semver-lock/main.go @@ -82,6 +82,13 @@ func processFile(file string) (*SemverLockResult, []error) { 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 From ee8c3b9b3053045c4db8d74803c463dc2199e610 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 11:36:28 -0500 Subject: [PATCH 091/125] refactor multiproof patch changes. fix typo --- interfaces/dispute/IDisputeGameFactory.sol | 9 +++ interfaces/dispute/IInitializable.sol | 3 +- src/dispute/DisputeGameFactory.sol | 66 ++++++++++++------- src/dispute/zk/OPSuccinctFaultDisputeGame.sol | 4 +- test/dispute/FaultDisputeGame.t.sol | 2 +- test/dispute/SuperFaultDisputeGame.t.sol | 2 +- test/multiproof/AggregateVerifier.t.sol | 2 +- test/multiproof/BaseTest.t.sol | 6 +- test/multiproof/SystemConfigGlobal.t.sol | 4 +- test/multiproof/TEEVerifier.t.sol | 4 +- test/setup/Setup.sol | 2 +- 11 files changed, 69 insertions(+), 35 deletions(-) diff --git a/interfaces/dispute/IDisputeGameFactory.sol b/interfaces/dispute/IDisputeGameFactory.sol index da99c869..91cc2ce8 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 bc998e1f..c66ec9f8 100644 --- a/interfaces/dispute/IInitializable.sol +++ b/interfaces/dispute/IInitializable.sol @@ -2,5 +2,6 @@ pragma solidity ^0.8.0; interface IInitializable { - function initialize(bytes calldata initData) external payable; + function initialize() external payable; + function initializeWithInitData(bytes calldata initData) external payable; } diff --git a/src/dispute/DisputeGameFactory.sol b/src/dispute/DisputeGameFactory.sol index 7b7a9a61..ba1832a7 100644 --- a/src/dispute/DisputeGameFactory.sol +++ b/src/dispute/DisputeGameFactory.sol @@ -139,22 +139,59 @@ 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. - /// @param _initData The initialization data for the DisputeGame. - /// @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, - bytes calldata _initData + IDisputeGame proxy_ ) - external - payable - returns (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) internal returns (IDisputeGame proxy_) { // Grab the implementation contract for the given `GameType`. IDisputeGame impl = gameImpls[_gameType]; @@ -201,21 +238,6 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable .clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _gameType, _extraData, implArgs)) ); } - proxy_.initialize{ value: msg.value }(_initData); - - // 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 09bb827b..b38a8ffa 100644 --- a/src/dispute/zk/OPSuccinctFaultDisputeGame.sol +++ b/src/dispute/zk/OPSuccinctFaultDisputeGame.sol @@ -217,7 +217,7 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { /// @notice Initializes the contract. /// @dev This function may only be called once. - function initialize(bytes calldata) external payable virtual { + function initialize() external payable virtual { // SAFETY: Any revert in this function will bubble up to the DisputeGameFactory and // prevent the game from being created. // @@ -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/test/dispute/FaultDisputeGame.t.sol b/test/dispute/FaultDisputeGame.t.sol index 5aa593e6..a69f3ab3 100644 --- a/test/dispute/FaultDisputeGame.t.sol +++ b/test/dispute/FaultDisputeGame.t.sol @@ -491,7 +491,7 @@ contract FaultDisputeGame_Initialize_Test is FaultDisputeGame_TestInit { /// @notice Tests that the game cannot be initialized twice. function test_initialize_onlyOnce_succeeds() public { vm.expectRevert(AlreadyInitialized.selector); - gameProxy.initialize(bytes("")); + gameProxy.initialize(); } /// @notice Tests that initialization reverts when oracle challenge period is too large. diff --git a/test/dispute/SuperFaultDisputeGame.t.sol b/test/dispute/SuperFaultDisputeGame.t.sol index 1ff4cf91..7f47d1e1 100644 --- a/test/dispute/SuperFaultDisputeGame.t.sol +++ b/test/dispute/SuperFaultDisputeGame.t.sol @@ -452,7 +452,7 @@ contract SuperFaultDisputeGame_Initialize_Test is SuperFaultDisputeGame_TestInit /// @notice Tests that the game cannot be initialized twice. function test_initialize_onlyOnce_succeeds() public { vm.expectRevert(AlreadyInitialized.selector); - gameProxy.initialize(bytes("")); + gameProxy.initialize(); } /// @notice Tests that initialization reverts when oracle challenge period is too large. diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 2c42ca59..8a666695 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -74,7 +74,7 @@ contract AggregateVerifierTest is BaseTest { vm.prank(TEE_PROVER); vm.expectRevert(BadExtraData.selector); - factory.create{ value: INIT_BOND }(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData); + factory.createWithInitData{ value: INIT_BOND }(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, initData); } function testUpdatingAnchorStateRegistryWithTEEProof() public { diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index ef2a6c1f..dbe4884e 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -15,7 +15,7 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { Claim, GameStatus, GameType, Hash, Proposal, Timestamp } from "src/dispute/lib/Types.sol"; // OpenZeppelin -import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; @@ -82,7 +82,7 @@ contract BaseTest is Test { DisputeGameFactory _factory = new DisputeGameFactory(); // Deploy proxy admin - proxyAdmin = new ProxyAdmin(); + proxyAdmin = new ProxyAdmin(address(this)); // Deploy proxy for anchor state registry TransparentUpgradeableProxy anchorStateRegistryProxy = @@ -159,7 +159,7 @@ contract BaseTest is Test { vm.deal(creator, INIT_BOND); vm.prank(creator); return AggregateVerifier( - address(factory.create{ value: INIT_BOND }(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, proof)) + address(factory.createWithInitData{ value: INIT_BOND }(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, proof)) ); } diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol index fe3440e0..d6b77961 100644 --- a/test/multiproof/SystemConfigGlobal.t.sol +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; @@ -55,7 +55,7 @@ contract SystemConfigGlobalTest is Test { DevSystemConfigGlobal impl = new DevSystemConfigGlobal(ICertManager(address(certManager))); // Deploy proxy admin - proxyAdmin = new ProxyAdmin(); + proxyAdmin = new ProxyAdmin(address(this)); // Deploy proxy TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index b9e4dc2f..af3b91e0 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; @@ -43,7 +43,7 @@ contract TEEVerifierTest is Test { DevSystemConfigGlobal impl = new DevSystemConfigGlobal(ICertManager(address(certManager))); // Deploy proxy admin - proxyAdmin = new ProxyAdmin(); + proxyAdmin = new ProxyAdmin(address(this)); // Deploy proxy TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( diff --git a/test/setup/Setup.sol b/test/setup/Setup.sol index 4d627d3e..cbc8fd9b 100644 --- a/test/setup/Setup.sol +++ b/test/setup/Setup.sol @@ -296,7 +296,7 @@ abstract contract Setup is FeatureFlags { superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); superchainProxyAdminOwner = superchainProxyAdmin.owner(); mips = IBigStepper(artifacts.mustGetAddress("MipsSingleton")); - aggregateVerifier = IVerifier(artifacts.mustGetAddress("AggregrateVerifier")); + aggregateVerifier = IVerifier(artifacts.mustGetAddress("AggregateVerifier")); if (deploy.cfg().useAltDA()) { dataAvailabilityChallenge = From 3c6111b12db291af1ea54a3338398dc56cedb981 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 11:55:15 -0500 Subject: [PATCH 092/125] semver --- snapshots/semver-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 84fc97ed..434bbd76 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -188,12 +188,12 @@ "sourceCodeHash": "0xdebf2ab3af4d5549c40e9dd9db6b2458af286f323b6891f3b0c4e89f3c8928db" }, "src/dispute/DisputeGameFactory.sol:DisputeGameFactory": { - "initCodeHash": "0xa464c608edf80a5f4277b081be1c302605b05036befb744bbe1dcb42b151ea96", - "sourceCodeHash": "0xf2df70bdcdfd489b31077e33dba55174ac4a1bf742fd93e69510528ee2ea5a57" + "initCodeHash": "0xd0e9a179ee30cb43281c385c9674d6d82f5abd1590dc9fe5aedfad12a6e41fdf", + "sourceCodeHash": "0xe13f1f814060ca8ef9ecb291dba5f11acef99ce57c725d3998728185968cfa86" }, "src/dispute/DisputeGameFactory.sol:DisputeGameFactory:dispute": { - "initCodeHash": "0x5377f55b80de4f18ea270cf73bfdfbf5bae9f5d9065445aaacecbf3256a3f700", - "sourceCodeHash": "0xf2df70bdcdfd489b31077e33dba55174ac4a1bf742fd93e69510528ee2ea5a57" + "initCodeHash": "0x727de8dbfd3432d95e19c39ece5ac7aec183dd67a09db769791d0e835e365d88", + "sourceCodeHash": "0xe13f1f814060ca8ef9ecb291dba5f11acef99ce57c725d3998728185968cfa86" }, "src/dispute/FaultDisputeGame.sol:FaultDisputeGame": { "initCodeHash": "0xe7d3c982532946d196d7efadb9e2576c76b8f9e0d1f885ac36977d6f3fb72a65", @@ -220,8 +220,8 @@ "sourceCodeHash": "0xc0ff6e93b6e2b9111c11e81b5df8948ab71d02b9d2c4dfda982fcb615519f1f7" }, "src/dispute/zk/OPSuccinctFaultDisputeGame.sol:OPSuccinctFaultDisputeGame": { - "initCodeHash": "0xc034c1adb7149472b7ab449e4beef9b8a6eab2dd4efb1fd025588dbe3376dc83", - "sourceCodeHash": "0x2a1f75dcfbdaca1fc5d868f7c600b48cabe6f7669e5c0389d0d3a20b6288562d" + "initCodeHash": "0x70d20610e50b4eb713c33ce9b0a64200227f99efad88856f269316c6b789ecd1", + "sourceCodeHash": "0x7fc14ec308e7edd07660e6d8df85d0b9040165125b7234df57aa86d46441dfac" }, "src/legacy/DeployerWhitelist.sol:DeployerWhitelist": { "initCodeHash": "0x2e0ef4c341367eb59cc6c25190c64eff441d3fe130189da91d4d126f6bdbc9b5", From e7abb042fa9081c8b8c4bca1e281a789bf53ee51 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 12:04:19 -0500 Subject: [PATCH 093/125] fmt --- src/dispute/DisputeGameFactory.sol | 20 +++++++++++++++---- src/dispute/zk/OPSuccinctFaultDisputeGame.sol | 2 +- test/multiproof/BaseTest.t.sol | 6 +++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/dispute/DisputeGameFactory.sol b/src/dispute/DisputeGameFactory.sol index ba1832a7..bdc189a5 100644 --- a/src/dispute/DisputeGameFactory.sol +++ b/src/dispute/DisputeGameFactory.sol @@ -143,7 +143,11 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable GameType _gameType, Claim _rootClaim, bytes calldata _extraData - ) external payable returns (IDisputeGame proxy_) { + ) + external + payable + returns (IDisputeGame proxy_) + { proxy_ = _createGameImpl(_gameType, _rootClaim, _extraData); proxy_.initialize{ value: msg.value }(); _finalizeGameCreation(_gameType, _rootClaim, _extraData, proxy_); @@ -154,7 +158,11 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable Claim _rootClaim, bytes calldata _extraData, bytes calldata _initData - ) external payable returns (IDisputeGame proxy_) { + ) + external + payable + returns (IDisputeGame proxy_) + { proxy_ = _createGameImpl(_gameType, _rootClaim, _extraData); proxy_.initializeWithInitData{ value: msg.value }(_initData); _finalizeGameCreation(_gameType, _rootClaim, _extraData, proxy_); @@ -171,7 +179,7 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable bytes calldata _extraData, IDisputeGame proxy_ ) - internal + internal { // Compute the unique identifier for the dispute game. Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData); @@ -191,7 +199,11 @@ contract DisputeGameFactory is ProxyAdminOwnedBase, ReinitializableBase, Ownable function _createGameImpl( GameType _gameType, Claim _rootClaim, - bytes calldata _extraData) internal returns (IDisputeGame proxy_) { + bytes calldata _extraData + ) + internal + returns (IDisputeGame proxy_) + { // Grab the implementation contract for the given `GameType`. IDisputeGame impl = gameImpls[_gameType]; diff --git a/src/dispute/zk/OPSuccinctFaultDisputeGame.sol b/src/dispute/zk/OPSuccinctFaultDisputeGame.sol index b38a8ffa..61579b10 100644 --- a/src/dispute/zk/OPSuccinctFaultDisputeGame.sol +++ b/src/dispute/zk/OPSuccinctFaultDisputeGame.sol @@ -321,7 +321,7 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { GameType.unwrap(ANCHOR_STATE_REGISTRY.respectedGameType()) == GameType.unwrap(GAME_TYPE); } - function initializeWithInitData(bytes calldata) external payable {} + 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_) { diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index dbe4884e..e7172722 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -159,7 +159,11 @@ contract BaseTest is Test { vm.deal(creator, INIT_BOND); vm.prank(creator); return AggregateVerifier( - address(factory.createWithInitData{ value: INIT_BOND }(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, proof)) + address( + factory.createWithInitData{ value: INIT_BOND }( + AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, extraData, proof + ) + ) ); } From 14ed45fb4dfff1b07d271ce3199d7db81bf55a94 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 12:18:56 -0500 Subject: [PATCH 094/125] semver --- snapshots/semver-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 434bbd76..6e37bffa 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -189,11 +189,11 @@ }, "src/dispute/DisputeGameFactory.sol:DisputeGameFactory": { "initCodeHash": "0xd0e9a179ee30cb43281c385c9674d6d82f5abd1590dc9fe5aedfad12a6e41fdf", - "sourceCodeHash": "0xe13f1f814060ca8ef9ecb291dba5f11acef99ce57c725d3998728185968cfa86" + "sourceCodeHash": "0x099527c6ecc13a98b6b9f09b7fcd4b44ffa351665e556df0bd0db344dd749da9" }, "src/dispute/DisputeGameFactory.sol:DisputeGameFactory:dispute": { "initCodeHash": "0x727de8dbfd3432d95e19c39ece5ac7aec183dd67a09db769791d0e835e365d88", - "sourceCodeHash": "0xe13f1f814060ca8ef9ecb291dba5f11acef99ce57c725d3998728185968cfa86" + "sourceCodeHash": "0x099527c6ecc13a98b6b9f09b7fcd4b44ffa351665e556df0bd0db344dd749da9" }, "src/dispute/FaultDisputeGame.sol:FaultDisputeGame": { "initCodeHash": "0xe7d3c982532946d196d7efadb9e2576c76b8f9e0d1f885ac36977d6f3fb72a65", @@ -221,7 +221,7 @@ }, "src/dispute/zk/OPSuccinctFaultDisputeGame.sol:OPSuccinctFaultDisputeGame": { "initCodeHash": "0x70d20610e50b4eb713c33ce9b0a64200227f99efad88856f269316c6b789ecd1", - "sourceCodeHash": "0x7fc14ec308e7edd07660e6d8df85d0b9040165125b7234df57aa86d46441dfac" + "sourceCodeHash": "0xce800b9bd50229b069793b44cf62823135034344a41dcd83e7a24428abe8483a" }, "src/legacy/DeployerWhitelist.sol:DeployerWhitelist": { "initCodeHash": "0x2e0ef4c341367eb59cc6c25190c64eff441d3fe130189da91d4d126f6bdbc9b5", From 9323dfd522672c432ec998eced47a7393c1a1ffc Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 12:52:41 -0500 Subject: [PATCH 095/125] fix tests --- src/multiproof/AggregateVerifier.sol | 2 +- test/vendor/Initializable.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 432fadb5..5d868448 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -286,7 +286,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @param proof The proof. /// @dev This function may only be called once. /// @dev First byte of the proof is the proof type. - function initialize(bytes calldata proof) external payable virtual { + function initializeWithInitData(bytes calldata proof) external payable virtual { // The game must not have already been initialized. if (initialized) revert AlreadyInitialized(); diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 7cd7a1e8..d3b284ec 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -363,7 +363,7 @@ contract Initializer_Test is CommonTest { // AggregateVerifier contracts.push( InitializeableContract({ - name: "AggregateVerifier", + name: "AggregateVerifierImpl", target: address(aggregateVerifier), initCalldata: abi.encodeCall( ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) From 3a65398ccc92fed5043c045fde48e756d317e75e Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 19:19:10 -0500 Subject: [PATCH 096/125] fix tests where bytecodes size was different --- foundry.toml | 14 +++++++++++++- scripts/deploy/Deploy.s.sol | 1 + scripts/deploy/DeployImplementations.s.sol | 17 ++++++++++++----- scripts/libraries/ForgeArtifacts.sol | 4 ++-- test/setup/Setup.sol | 3 +++ test/vendor/Initializable.t.sol | 11 +++++++++++ 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/foundry.toml b/foundry.toml index 6b07a32a..0ceb50d4 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,12 @@ 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 }, ] extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] @@ -154,6 +160,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 }, @@ -163,6 +170,11 @@ 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 }, ] ################################################################ diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol index 6c413e39..ab04b9c3 100644 --- a/scripts/deploy/Deploy.s.sol +++ b/scripts/deploy/Deploy.s.sol @@ -301,6 +301,7 @@ contract Deploy is Deployer { artifacts.save("PreimageOracle", address(dio.preimageOracleSingleton)); artifacts.save("PermissionedDisputeGame", address(dio.permissionedDisputeGameV2Impl)); artifacts.save("AggregateVerifier", address(dio.aggregateVerifierImpl)); + artifacts.save("SystemConfigGlobal", address(dio.systemConfigGlobalImpl)); // Get a contract set from the implementation addresses which were just deployed. Types.ContractSet memory impls = ChainAssertions.dioToContractSet(dio); diff --git a/scripts/deploy/DeployImplementations.s.sol b/scripts/deploy/DeployImplementations.s.sol index bfd01cf6..c4a45f36 100644 --- a/scripts/deploy/DeployImplementations.s.sol +++ b/scripts/deploy/DeployImplementations.s.sol @@ -103,6 +103,7 @@ contract DeployImplementations is Script { ISuperFaultDisputeGame superFaultDisputeGameImpl; ISuperPermissionedDisputeGame superPermissionedDisputeGameImpl; IVerifier aggregateVerifierImpl; + SystemConfigGlobal systemConfigGlobalImpl; } bytes32 internal _salt = DeployUtils.DEFAULT_SALT; @@ -136,6 +137,7 @@ contract DeployImplementations is Script { deployAnchorStateRegistryImpl(_input, output_); deployFaultDisputeGameV2Impl(_input, output_); deployPermissionedDisputeGameV2Impl(_input, output_); + deploySystemConfigGlobalImpl(_input, output_); deployAggregateVerifierImpl(_input, output_); if (DevFeatures.isDevFeatureEnabled(_input.devFeatureBitmap, DevFeatures.OPTIMISM_PORTAL_INTEROP)) { deploySuperFaultDisputeGameImpl(_input, output_); @@ -477,7 +479,7 @@ contract DeployImplementations is Script { function deployDisputeGameFactoryImpl(Output memory _output) private { IDisputeGameFactory impl = IDisputeGameFactory( DeployUtils.createDeterministic({ - _name: "DisputeGameFactory", + _name: "src/dispute/DisputeGameFactory.sol:DisputeGameFactory", _args: DeployUtils.encodeConstructor(abi.encodeCall(IDisputeGameFactory.__constructor__, ())), _salt: _salt }) @@ -704,15 +706,20 @@ contract DeployImplementations is Script { _output.opcmStandardValidator = impl; } + function deploySystemConfigGlobalImpl(Input memory, Output memory _output) private { + address certManager = address(new CertManager()); + SystemConfigGlobal scgImpl = new SystemConfigGlobal(CertManager(certManager)); + + vm.label(address(scgImpl), "SystemConfigGlobalImpl"); + _output.systemConfigGlobalImpl = scgImpl; + } + function deployAggregateVerifierImpl(Input memory, Output memory _output) private { DeployDevWithNitro nitro = new DeployDevWithNitro(); DeployDevWithNitro.DeployConfig memory cfg = nitro.loadConfig(); address zkVerifier = address(new MockVerifier()); - - address certManager = address(new CertManager()); - SystemConfigGlobal scgImpl = new SystemConfigGlobal(CertManager(certManager)); - address teeVerifierImpl = address(new TEEVerifier(scgImpl)); + address teeVerifierImpl = address(new TEEVerifier(_output.systemConfigGlobalImpl)); IVerifier aggregateVerifierImpl = IVerifier( address( diff --git a/scripts/libraries/ForgeArtifacts.sol b/scripts/libraries/ForgeArtifacts.sol index f49faec8..09e4e623 100644 --- a/scripts/libraries/ForgeArtifacts.sol +++ b/scripts/libraries/ForgeArtifacts.sol @@ -152,10 +152,10 @@ 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/test/setup/Setup.sol b/test/setup/Setup.sol index cbc8fd9b..0e9f1fe1 100644 --- a/test/setup/Setup.sol +++ b/test/setup/Setup.sol @@ -68,6 +68,7 @@ import { IFeeSplitter } from "interfaces/L2/IFeeSplitter.sol"; import { IL1Withdrawer } from "interfaces/L2/IL1Withdrawer.sol"; import { ISuperchainRevSharesCalculator } from "interfaces/L2/ISuperchainRevSharesCalculator.sol"; import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -158,6 +159,7 @@ abstract contract Setup is FeatureFlags { IL1Withdrawer l1Withdrawer; ISuperchainRevSharesCalculator superchainRevSharesCalculator; IVerifier aggregateVerifier; + SystemConfigGlobal systemConfigGlobal; /// @notice Indicates whether a test is running against a forked production network. function isForkTest() public view returns (bool) { @@ -297,6 +299,7 @@ abstract contract Setup is FeatureFlags { superchainProxyAdminOwner = superchainProxyAdmin.owner(); mips = IBigStepper(artifacts.mustGetAddress("MipsSingleton")); aggregateVerifier = IVerifier(artifacts.mustGetAddress("AggregateVerifier")); + systemConfigGlobal = SystemConfigGlobal(artifacts.mustGetAddress("SystemConfigGlobal")); if (deploy.cfg().useAltDA()) { dataAvailabilityChallenge = diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index d3b284ec..244c751c 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -365,6 +365,15 @@ contract Initializer_Test is CommonTest { InitializeableContract({ name: "AggregateVerifierImpl", target: address(aggregateVerifier), + initCalldata: abi.encodeWithSignature("initializeWithInitData(bytes)", hex"") + }) + ); + + // SystemConfigGlobal (Multiproof) + contracts.push( + InitializeableContract({ + name: "SystemConfigGlobalImpl", + target: address(systemConfigGlobal), initCalldata: abi.encodeCall( ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) ) @@ -405,6 +414,8 @@ 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"; + // Mock contracts are not deployed as part of the standard deployment script. + excludes[j++] = "src/multiproof/mocks/*"; // Get all contract names in the src directory, minus the excluded contracts. string[] memory contractNames = ForgeArtifacts.getContractNames("src/*", excludes); From b1dc695b9a6c2706a4b29baeb297ceaa4f681853 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 19:37:48 -0500 Subject: [PATCH 097/125] fmt --- scripts/libraries/ForgeArtifacts.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/libraries/ForgeArtifacts.sol b/scripts/libraries/ForgeArtifacts.sol index 09e4e623..a15f799d 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, PermissionedDisputeGame, and AggregateVerifier 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") || LibString.eq(_contractName, "AggregateVerifier")) { + if ( + LibString.eq(_contractName, "FaultDisputeGame") || LibString.eq(_contractName, "PermissionedDisputeGame") + || LibString.eq(_contractName, "AggregateVerifier") + ) { slotName = "initialized"; slotType = "t_bool"; } From 11a0d840b9f64abb7e4001ccdcf4fe0785a7a199 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 19:38:38 -0500 Subject: [PATCH 098/125] semver --- snapshots/semver-lock.json | 44 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 6e37bffa..4eb384ac 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -3,6 +3,10 @@ "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", "sourceCodeHash": "0xdddaf874cd201558e34f0ed78db38a58d798188f974646b0cad8ce9910f99037" }, + "src/L1/DataAvailabilityChallenge.sol:DataAvailabilityChallenge:dispute": { + "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", + "sourceCodeHash": "0xdddaf874cd201558e34f0ed78db38a58d798188f974646b0cad8ce9910f99037" + }, "src/L1/ETHLockbox.sol:ETHLockbox": { "initCodeHash": "0x65db3aa3c2e3221065752f66016fa02b66688a01cc5c3066533b27fe620619c8", "sourceCodeHash": "0x6c9d3e2dee44c234d59ab93b6564536dfd807f1c4a02a82d5393bc53cb15b8b7" @@ -11,6 +15,10 @@ "initCodeHash": "0xe52c51805cfd55967d037173159f18aaf4344e32e5c8ad41f8d5d0025b1d36a8", "sourceCodeHash": "0xe5f2b1915a201c0b8a107f168f5b9bc8aec8e8e95f938082e42ba5b5c8ebbd11" }, + "src/L1/FeesDepositor.sol:FeesDepositor:dispute": { + "initCodeHash": "0xe52c51805cfd55967d037173159f18aaf4344e32e5c8ad41f8d5d0025b1d36a8", + "sourceCodeHash": "0xe5f2b1915a201c0b8a107f168f5b9bc8aec8e8e95f938082e42ba5b5c8ebbd11" + }, "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger": { "initCodeHash": "0x3dc659aafb03bd357f92abfc6794af89ee0ddd5212364551637422bf8d0b00f9", "sourceCodeHash": "0xfd67aae7ef1d684f3fccc036a80123e0ffa13de3e26c910cb7b927059c5a6289" @@ -47,8 +55,12 @@ "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" }, + "src/L1/SuperchainConfig.sol:SuperchainConfig:dispute": { + "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", + "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" + }, "src/L1/SystemConfig.sol:SystemConfig": { - "initCodeHash": "0x184e74fa4993271e35d477155689ceba54c083c71f0f93f2dea0ee6330c61693", + "initCodeHash": "0x3e8e52d96398a6de91d8922769cc5d0bc7acb2a692689ceb70f1de816e8d6b14", "sourceCodeHash": "0x1a24ffe154eddbe0088a4b0848e662bf5eb482fe031ba528f74c51804f6e0395" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { @@ -119,10 +131,18 @@ "initCodeHash": "0xe492fe75e3c0a8a80ede7b50271263c42a2c7616a101861e892dba76f9771e34", "sourceCodeHash": "0xef9cf289b4bf63808b6822fc2c588fe516abbbfb90479553e7d54d43de344e50" }, + "src/L2/LiquidityController.sol:LiquidityController:dispute": { + "initCodeHash": "0xb41fb5ed5d60d9c0fcdc21e222567a48632544b99ef0a298735a56d15e06f327", + "sourceCodeHash": "0xef9cf289b4bf63808b6822fc2c588fe516abbbfb90479553e7d54d43de344e50" + }, "src/L2/NativeAssetLiquidity.sol:NativeAssetLiquidity": { "initCodeHash": "0x04b176e5d484e54173a5644c833117c5fd9f055dce8678be9ec4cf07c0f01f00", "sourceCodeHash": "0x79289174e875ead5a6290df9af1951d6c0ff0dc6809601e1c7a80110ceb8e945" }, + "src/L2/NativeAssetLiquidity.sol:NativeAssetLiquidity:dispute": { + "initCodeHash": "0x04b176e5d484e54173a5644c833117c5fd9f055dce8678be9ec4cf07c0f01f00", + "sourceCodeHash": "0x79289174e875ead5a6290df9af1951d6c0ff0dc6809601e1c7a80110ceb8e945" + }, "src/L2/OperatorFeeVault.sol:OperatorFeeVault": { "initCodeHash": "0x2ebab6af089a714df25888a4dea81dadcb1fb57146be84d2e079041a9396a810", "sourceCodeHash": "0xd6e94bc9df025855916aa4184d0bc739b0fbe786dfd037b99dbb51d0d3e46918" @@ -131,6 +151,10 @@ "initCodeHash": "0x316c6d716358f5b5284cd5457ea9fca4b5ad4a37835d4b4b300413dafbfa2159", "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" }, + "src/L2/OptimismMintableERC721.sol:OptimismMintableERC721:dispute": { + "initCodeHash": "0x363dcb59140ecfbcdff7b5f4e822f0fdc8492fc02b6d9abf3cb822ae75e8dcfb", + "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" + }, "src/L2/OptimismMintableERC721Factory.sol:OptimismMintableERC721Factory": { "initCodeHash": "0xa692a3fc4a71eb3381a59d1ab655bbc02e8b507add7c3f560ee24b001d88ae6e", "sourceCodeHash": "0xb0be3deac32956251adb37d3ca61f619ca4348a1355a41c856a3a95adde0e4ff" @@ -180,18 +204,14 @@ "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": "0xd0e9a179ee30cb43281c385c9674d6d82f5abd1590dc9fe5aedfad12a6e41fdf", - "sourceCodeHash": "0x099527c6ecc13a98b6b9f09b7fcd4b44ffa351665e556df0bd0db344dd749da9" - }, - "src/dispute/DisputeGameFactory.sol:DisputeGameFactory:dispute": { "initCodeHash": "0x727de8dbfd3432d95e19c39ece5ac7aec183dd67a09db769791d0e835e365d88", "sourceCodeHash": "0x099527c6ecc13a98b6b9f09b7fcd4b44ffa351665e556df0bd0db344dd749da9" }, @@ -263,12 +283,20 @@ "initCodeHash": "0x0ad1f0f33517132b06a225b51e6eac48904d4ad691e0045eb70244d811d0d99d", "sourceCodeHash": "0xd6683fe9be4019d34249ada5a4de3e597f1bd9cd473a89f6eff8f749a0b0e978" }, + "src/safe/SaferSafes.sol:SaferSafes:dispute": { + "initCodeHash": "0x6a58b3e2c2567d6f5f936d335e6ae0750fddc367d322a555e685376fad49d7ef", + "sourceCodeHash": "0xd6683fe9be4019d34249ada5a4de3e597f1bd9cd473a89f6eff8f749a0b0e978" + }, "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20": { "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": { From 3953c94a0879ff597b17026d3e8d336784596725 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 20:10:00 -0500 Subject: [PATCH 099/125] semver --- snapshots/semver-lock.json | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 4eb384ac..7a219c88 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -3,10 +3,6 @@ "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", "sourceCodeHash": "0xdddaf874cd201558e34f0ed78db38a58d798188f974646b0cad8ce9910f99037" }, - "src/L1/DataAvailabilityChallenge.sol:DataAvailabilityChallenge:dispute": { - "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", - "sourceCodeHash": "0xdddaf874cd201558e34f0ed78db38a58d798188f974646b0cad8ce9910f99037" - }, "src/L1/ETHLockbox.sol:ETHLockbox": { "initCodeHash": "0x65db3aa3c2e3221065752f66016fa02b66688a01cc5c3066533b27fe620619c8", "sourceCodeHash": "0x6c9d3e2dee44c234d59ab93b6564536dfd807f1c4a02a82d5393bc53cb15b8b7" @@ -15,10 +11,6 @@ "initCodeHash": "0xe52c51805cfd55967d037173159f18aaf4344e32e5c8ad41f8d5d0025b1d36a8", "sourceCodeHash": "0xe5f2b1915a201c0b8a107f168f5b9bc8aec8e8e95f938082e42ba5b5c8ebbd11" }, - "src/L1/FeesDepositor.sol:FeesDepositor:dispute": { - "initCodeHash": "0xe52c51805cfd55967d037173159f18aaf4344e32e5c8ad41f8d5d0025b1d36a8", - "sourceCodeHash": "0xe5f2b1915a201c0b8a107f168f5b9bc8aec8e8e95f938082e42ba5b5c8ebbd11" - }, "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger": { "initCodeHash": "0x3dc659aafb03bd357f92abfc6794af89ee0ddd5212364551637422bf8d0b00f9", "sourceCodeHash": "0xfd67aae7ef1d684f3fccc036a80123e0ffa13de3e26c910cb7b927059c5a6289" @@ -55,10 +47,6 @@ "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" }, - "src/L1/SuperchainConfig.sol:SuperchainConfig:dispute": { - "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", - "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" - }, "src/L1/SystemConfig.sol:SystemConfig": { "initCodeHash": "0x3e8e52d96398a6de91d8922769cc5d0bc7acb2a692689ceb70f1de816e8d6b14", "sourceCodeHash": "0x1a24ffe154eddbe0088a4b0848e662bf5eb482fe031ba528f74c51804f6e0395" @@ -131,18 +119,10 @@ "initCodeHash": "0xe492fe75e3c0a8a80ede7b50271263c42a2c7616a101861e892dba76f9771e34", "sourceCodeHash": "0xef9cf289b4bf63808b6822fc2c588fe516abbbfb90479553e7d54d43de344e50" }, - "src/L2/LiquidityController.sol:LiquidityController:dispute": { - "initCodeHash": "0xb41fb5ed5d60d9c0fcdc21e222567a48632544b99ef0a298735a56d15e06f327", - "sourceCodeHash": "0xef9cf289b4bf63808b6822fc2c588fe516abbbfb90479553e7d54d43de344e50" - }, "src/L2/NativeAssetLiquidity.sol:NativeAssetLiquidity": { "initCodeHash": "0x04b176e5d484e54173a5644c833117c5fd9f055dce8678be9ec4cf07c0f01f00", "sourceCodeHash": "0x79289174e875ead5a6290df9af1951d6c0ff0dc6809601e1c7a80110ceb8e945" }, - "src/L2/NativeAssetLiquidity.sol:NativeAssetLiquidity:dispute": { - "initCodeHash": "0x04b176e5d484e54173a5644c833117c5fd9f055dce8678be9ec4cf07c0f01f00", - "sourceCodeHash": "0x79289174e875ead5a6290df9af1951d6c0ff0dc6809601e1c7a80110ceb8e945" - }, "src/L2/OperatorFeeVault.sol:OperatorFeeVault": { "initCodeHash": "0x2ebab6af089a714df25888a4dea81dadcb1fb57146be84d2e079041a9396a810", "sourceCodeHash": "0xd6e94bc9df025855916aa4184d0bc739b0fbe786dfd037b99dbb51d0d3e46918" @@ -151,10 +131,6 @@ "initCodeHash": "0x316c6d716358f5b5284cd5457ea9fca4b5ad4a37835d4b4b300413dafbfa2159", "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" }, - "src/L2/OptimismMintableERC721.sol:OptimismMintableERC721:dispute": { - "initCodeHash": "0x363dcb59140ecfbcdff7b5f4e822f0fdc8492fc02b6d9abf3cb822ae75e8dcfb", - "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" - }, "src/L2/OptimismMintableERC721Factory.sol:OptimismMintableERC721Factory": { "initCodeHash": "0xa692a3fc4a71eb3381a59d1ab655bbc02e8b507add7c3f560ee24b001d88ae6e", "sourceCodeHash": "0xb0be3deac32956251adb37d3ca61f619ca4348a1355a41c856a3a95adde0e4ff" @@ -259,10 +235,6 @@ "initCodeHash": "0x2d681a39e518379b6d2c013735ae25a9bcf6e939d597bde9e6126375b86fd294", "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" }, - "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { - "initCodeHash": "0x73537cace710afd0dc4ea7c2e25687a29cee13843617f518a3edeb8e79cf324c", - "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" - }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", "sourceCodeHash": "0xac49a0ecf22b8a7bb3ebef830a2d27b19050f9b08941186e8563d5113cf0ce9c" @@ -283,10 +255,6 @@ "initCodeHash": "0x0ad1f0f33517132b06a225b51e6eac48904d4ad691e0045eb70244d811d0d99d", "sourceCodeHash": "0xd6683fe9be4019d34249ada5a4de3e597f1bd9cd473a89f6eff8f749a0b0e978" }, - "src/safe/SaferSafes.sol:SaferSafes:dispute": { - "initCodeHash": "0x6a58b3e2c2567d6f5f936d335e6ae0750fddc367d322a555e685376fad49d7ef", - "sourceCodeHash": "0xd6683fe9be4019d34249ada5a4de3e597f1bd9cd473a89f6eff8f749a0b0e978" - }, "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20": { "initCodeHash": "0x3c85eed0d017dca8eda6396aa842ddc12492587b061e8c756a8d32c4610a9658", "sourceCodeHash": "0x7023665d461f173417d932b55010b8f6c34f2bbaf56cfe4e1b15862c08cbcaac" From 13610a9ce7b853bc5f48bf75925aeb87027fafd2 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 20:44:00 -0500 Subject: [PATCH 100/125] semver --- snapshots/semver-lock.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 7a219c88..4eb384ac 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -3,6 +3,10 @@ "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", "sourceCodeHash": "0xdddaf874cd201558e34f0ed78db38a58d798188f974646b0cad8ce9910f99037" }, + "src/L1/DataAvailabilityChallenge.sol:DataAvailabilityChallenge:dispute": { + "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", + "sourceCodeHash": "0xdddaf874cd201558e34f0ed78db38a58d798188f974646b0cad8ce9910f99037" + }, "src/L1/ETHLockbox.sol:ETHLockbox": { "initCodeHash": "0x65db3aa3c2e3221065752f66016fa02b66688a01cc5c3066533b27fe620619c8", "sourceCodeHash": "0x6c9d3e2dee44c234d59ab93b6564536dfd807f1c4a02a82d5393bc53cb15b8b7" @@ -11,6 +15,10 @@ "initCodeHash": "0xe52c51805cfd55967d037173159f18aaf4344e32e5c8ad41f8d5d0025b1d36a8", "sourceCodeHash": "0xe5f2b1915a201c0b8a107f168f5b9bc8aec8e8e95f938082e42ba5b5c8ebbd11" }, + "src/L1/FeesDepositor.sol:FeesDepositor:dispute": { + "initCodeHash": "0xe52c51805cfd55967d037173159f18aaf4344e32e5c8ad41f8d5d0025b1d36a8", + "sourceCodeHash": "0xe5f2b1915a201c0b8a107f168f5b9bc8aec8e8e95f938082e42ba5b5c8ebbd11" + }, "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger": { "initCodeHash": "0x3dc659aafb03bd357f92abfc6794af89ee0ddd5212364551637422bf8d0b00f9", "sourceCodeHash": "0xfd67aae7ef1d684f3fccc036a80123e0ffa13de3e26c910cb7b927059c5a6289" @@ -47,6 +55,10 @@ "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" }, + "src/L1/SuperchainConfig.sol:SuperchainConfig:dispute": { + "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", + "sourceCodeHash": "0x572b5da38443fa5fc1c1873a733adee1894c6eba94f0e7867f2e81c5eb616a39" + }, "src/L1/SystemConfig.sol:SystemConfig": { "initCodeHash": "0x3e8e52d96398a6de91d8922769cc5d0bc7acb2a692689ceb70f1de816e8d6b14", "sourceCodeHash": "0x1a24ffe154eddbe0088a4b0848e662bf5eb482fe031ba528f74c51804f6e0395" @@ -119,10 +131,18 @@ "initCodeHash": "0xe492fe75e3c0a8a80ede7b50271263c42a2c7616a101861e892dba76f9771e34", "sourceCodeHash": "0xef9cf289b4bf63808b6822fc2c588fe516abbbfb90479553e7d54d43de344e50" }, + "src/L2/LiquidityController.sol:LiquidityController:dispute": { + "initCodeHash": "0xb41fb5ed5d60d9c0fcdc21e222567a48632544b99ef0a298735a56d15e06f327", + "sourceCodeHash": "0xef9cf289b4bf63808b6822fc2c588fe516abbbfb90479553e7d54d43de344e50" + }, "src/L2/NativeAssetLiquidity.sol:NativeAssetLiquidity": { "initCodeHash": "0x04b176e5d484e54173a5644c833117c5fd9f055dce8678be9ec4cf07c0f01f00", "sourceCodeHash": "0x79289174e875ead5a6290df9af1951d6c0ff0dc6809601e1c7a80110ceb8e945" }, + "src/L2/NativeAssetLiquidity.sol:NativeAssetLiquidity:dispute": { + "initCodeHash": "0x04b176e5d484e54173a5644c833117c5fd9f055dce8678be9ec4cf07c0f01f00", + "sourceCodeHash": "0x79289174e875ead5a6290df9af1951d6c0ff0dc6809601e1c7a80110ceb8e945" + }, "src/L2/OperatorFeeVault.sol:OperatorFeeVault": { "initCodeHash": "0x2ebab6af089a714df25888a4dea81dadcb1fb57146be84d2e079041a9396a810", "sourceCodeHash": "0xd6e94bc9df025855916aa4184d0bc739b0fbe786dfd037b99dbb51d0d3e46918" @@ -131,6 +151,10 @@ "initCodeHash": "0x316c6d716358f5b5284cd5457ea9fca4b5ad4a37835d4b4b300413dafbfa2159", "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" }, + "src/L2/OptimismMintableERC721.sol:OptimismMintableERC721:dispute": { + "initCodeHash": "0x363dcb59140ecfbcdff7b5f4e822f0fdc8492fc02b6d9abf3cb822ae75e8dcfb", + "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" + }, "src/L2/OptimismMintableERC721Factory.sol:OptimismMintableERC721Factory": { "initCodeHash": "0xa692a3fc4a71eb3381a59d1ab655bbc02e8b507add7c3f560ee24b001d88ae6e", "sourceCodeHash": "0xb0be3deac32956251adb37d3ca61f619ca4348a1355a41c856a3a95adde0e4ff" @@ -235,6 +259,10 @@ "initCodeHash": "0x2d681a39e518379b6d2c013735ae25a9bcf6e939d597bde9e6126375b86fd294", "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" }, + "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { + "initCodeHash": "0x73537cace710afd0dc4ea7c2e25687a29cee13843617f518a3edeb8e79cf324c", + "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" + }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", "sourceCodeHash": "0xac49a0ecf22b8a7bb3ebef830a2d27b19050f9b08941186e8563d5113cf0ce9c" @@ -255,6 +283,10 @@ "initCodeHash": "0x0ad1f0f33517132b06a225b51e6eac48904d4ad691e0045eb70244d811d0d99d", "sourceCodeHash": "0xd6683fe9be4019d34249ada5a4de3e597f1bd9cd473a89f6eff8f749a0b0e978" }, + "src/safe/SaferSafes.sol:SaferSafes:dispute": { + "initCodeHash": "0x6a58b3e2c2567d6f5f936d335e6ae0750fddc367d322a555e685376fad49d7ef", + "sourceCodeHash": "0xd6683fe9be4019d34249ada5a4de3e597f1bd9cd473a89f6eff8f749a0b0e978" + }, "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20": { "initCodeHash": "0x3c85eed0d017dca8eda6396aa842ddc12492587b061e8c756a8d32c4610a9658", "sourceCodeHash": "0x7023665d461f173417d932b55010b8f6c34f2bbaf56cfe4e1b15862c08cbcaac" From c956defa70b10df264a78a95bea611250545e8b4 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 21:02:42 -0500 Subject: [PATCH 101/125] make aggregateverifies excluded like faultdispute game. fix systemconfiggloabal initialize --- foundry.toml | 2 ++ test/vendor/Initializable.t.sol | 12 ++---------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/foundry.toml b/foundry.toml index 0ceb50d4..3916e6cb 100644 --- a/foundry.toml +++ b/foundry.toml @@ -36,6 +36,7 @@ compilation_restrictions = [ { 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 }, ] extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] @@ -175,6 +176,7 @@ compilation_restrictions = [ { 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 }, ] ################################################################ diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 244c751c..4be0088b 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -360,22 +360,13 @@ contract Initializer_Test is CommonTest { }) ); - // AggregateVerifier - contracts.push( - InitializeableContract({ - name: "AggregateVerifierImpl", - target: address(aggregateVerifier), - initCalldata: abi.encodeWithSignature("initializeWithInitData(bytes)", hex"") - }) - ); - // SystemConfigGlobal (Multiproof) contracts.push( InitializeableContract({ name: "SystemConfigGlobalImpl", target: address(systemConfigGlobal), initCalldata: abi.encodeCall( - ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) + systemConfigGlobal.initialize, (address(0), address(0)) ) }) ); @@ -405,6 +396,7 @@ contract Initializer_Test is CommonTest { excludes[j++] = "src/dispute/PermissionedDisputeGame.sol"; excludes[j++] = "src/dispute/SuperPermissionedDisputeGame.sol"; excludes[j++] = "src/dispute/zk/OPSuccinctFaultDisputeGame.sol"; + excludes[j++] = "src/mutliproof/AggregateVerifier.sol"; // TODO: Eventually remove this exclusion. Same reason as above dispute contracts. excludes[j++] = "src/L1/OPContractsManager.sol"; // TODO: Eventually remove this exclusion. Same reason as above dispute contracts. From a0f0b1483aacd4d468d8b0a1c2ee3a626d3b53c6 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 21:02:58 -0500 Subject: [PATCH 102/125] fmt --- test/vendor/Initializable.t.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 4be0088b..e43ec8c2 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -365,9 +365,7 @@ contract Initializer_Test is CommonTest { InitializeableContract({ name: "SystemConfigGlobalImpl", target: address(systemConfigGlobal), - initCalldata: abi.encodeCall( - systemConfigGlobal.initialize, (address(0), address(0)) - ) + initCalldata: abi.encodeCall(systemConfigGlobal.initialize, (address(0), address(0))) }) ); } From 6b31f9d0c784994daafc02e0f80d9fcc3b6fb2ae Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 21:30:24 -0500 Subject: [PATCH 103/125] fix tests --- foundry.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/foundry.toml b/foundry.toml index 3916e6cb..44da9da9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -37,6 +37,8 @@ compilation_restrictions = [ { 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'] @@ -177,6 +179,8 @@ compilation_restrictions = [ { 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 }, ] ################################################################ From 5cdba94591a4ab2bca4e52db45280b740ecac129 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 21:30:37 -0500 Subject: [PATCH 104/125] semver --- snapshots/semver-lock.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 4eb384ac..61b6e4e8 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -148,15 +148,11 @@ "sourceCodeHash": "0xd6e94bc9df025855916aa4184d0bc739b0fbe786dfd037b99dbb51d0d3e46918" }, "src/L2/OptimismMintableERC721.sol:OptimismMintableERC721": { - "initCodeHash": "0x316c6d716358f5b5284cd5457ea9fca4b5ad4a37835d4b4b300413dafbfa2159", - "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" - }, - "src/L2/OptimismMintableERC721.sol:OptimismMintableERC721:dispute": { "initCodeHash": "0x363dcb59140ecfbcdff7b5f4e822f0fdc8492fc02b6d9abf3cb822ae75e8dcfb", "sourceCodeHash": "0xd93a8d5de6fd89ebf503976511065f0c2414814affdb908f26a867ffdd0f9fbe" }, "src/L2/OptimismMintableERC721Factory.sol:OptimismMintableERC721Factory": { - "initCodeHash": "0xa692a3fc4a71eb3381a59d1ab655bbc02e8b507add7c3f560ee24b001d88ae6e", + "initCodeHash": "0x3f31209e30a79f367b59b66e3ee6f43f5916ec26e6d1c5a08846d636884f020d", "sourceCodeHash": "0xb0be3deac32956251adb37d3ca61f619ca4348a1355a41c856a3a95adde0e4ff" }, "src/L2/OptimismSuperchainERC20.sol:OptimismSuperchainERC20": { From 8e00905c5bf0e287e9b4ae2a910e39759e233d88 Mon Sep 17 00:00:00 2001 From: Joby Thundil Date: Mon, 2 Mar 2026 21:48:12 -0500 Subject: [PATCH 105/125] rm just test-multiproof --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ecbb8d61..b627c147 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,5 +60,5 @@ jobs: git diff --exit-code snapshots/semver-lock.json - name: Run Forge tests - run: just test && just test-multiproof + run: just test id: test From 3e6f2e7abe3a21297014d40b8f375cb952462b7c Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Thu, 5 Mar 2026 02:42:44 +0000 Subject: [PATCH 106/125] Replace onchain Nitro cert verification with Automata ZK verifier (#197) * Replace onchain Nitro cert verification with Automata ZK verifier, add ISemver to multiproof contracts, wire SystemConfigGlobal into standard deploy pipeline * Integrate multiproof config into standard DeployConfig, fix TEEVerifier proof format to match AggregateVerifier's l1head change * Fix fmt * Replace aws-nitro-enclave-attestation submodule with no-git dependency in Makefile * Consolidate deploy configs: migrate dev scripts to standard DeployConfig, add multiproof fields to sepolia.json and hardhat.json, remove separate nitro config files * Regenerate snapshots for updated and new multiproof contracts * Fix Initializable test: guard ETHLockbox entries for non-interop deploys, exclude AggregateVerifier (uses custom bool instead of OZ _initialized) * Add test-multiproof recipe to Justfile for CI * Address PR feedback: extract GameType local var, stricter pubKey check, iterate PCRs, add nitroEnclaveVerifier to Input, revert sepolia.json owner, remove redundant CI recipe * Resolve merge conflicts and regenerate semver-lock snapshots * Regenerate semver-lock with CI profile for correct bytecode hashes * Regenerate semver-lock with all compiler profiles including dispute * Regenerate semver-lock.json to remove stale dispute profile entries * Fix misleading TEEVerifier comment, require nitroEnclaveVerifier in deploy config, and parameterize hardcoded l2ChainID/block intervals in DeployImplementations * Reset semvar versioning in SystemConfigGlobal * Regenerate semver-lock.json for SystemConfigGlobal and TEEVerifier changes * correct SystemConfigGlobal.t.sol testInitialization() test cases to check correct version() number * use a proof threshold and allow ZK proofs after TEE nullification (#199) * use a proof threshold and allow ZK proofs after TEE nullification * pr feedback * update deployment scripts and tests * allow tee nullfiication when a zk proof exists. extend timestamp in this case to allow for zk nullification * Fix stack-too-deep in DeployImplementations and regenerate semver-lock * add multiproofProofThreshold to DeployConfig.s.sol to fix CI failures * Correct semver comment * Regenerate semver-lock following fix --------- Co-authored-by: roger-bai-coinbase --- .gitignore | 3 + Makefile | 3 +- deploy-config/hardhat.json | 12 +- deploy-config/sepolia-no-nitro.json | 9 - deploy-config/sepolia-with-nitro.json | 9 - deploy-config/sepolia.json | 12 +- deployments/11155111-dev-no-nitro.json | 1 + scripts/deploy/Deploy.s.sol | 8 + scripts/deploy/DeployConfig.s.sol | 22 + scripts/deploy/DeployImplementations.s.sol | 58 +- scripts/multiproof/DeployDevNoNitro.s.sol | 130 +- scripts/multiproof/DeployDevWithNitro.s.sol | 196 ++- snapshots/abi/AggregateVerifier.json | 1068 +++++++++++++++++ snapshots/abi/BalanceTracker.json | 194 +++ snapshots/abi/CBMulticall.json | 485 ++++++++ snapshots/abi/DevSystemConfigGlobal.json | 458 +++++++ snapshots/abi/DisputeGameFactory.json | 34 + snapshots/abi/FeeDisburser.json | 182 +++ snapshots/abi/MockSystemConfig.json | 33 + snapshots/abi/MockVerifier.json | 31 + snapshots/abi/OPSuccinctFaultDisputeGame.json | 13 + snapshots/abi/Recovery.json | 138 +++ snapshots/abi/SmartEscrow.json | 977 +++++++++++++++ snapshots/abi/SuperchainConfig.json | 104 +- snapshots/abi/SystemConfigGlobal.json | 440 +++++++ snapshots/abi/TEEVerifier.json | 116 ++ snapshots/semver-lock.json | 40 +- .../storageLayout/AggregateVerifier.json | 93 ++ snapshots/storageLayout/BalanceTracker.json | 44 + snapshots/storageLayout/CBMulticall.json | 1 + .../storageLayout/DevSystemConfigGlobal.json | 65 + snapshots/storageLayout/FeeDisburser.json | 16 + snapshots/storageLayout/MockSystemConfig.json | 9 + snapshots/storageLayout/MockVerifier.json | 1 + snapshots/storageLayout/Recovery.json | 1 + snapshots/storageLayout/SmartEscrow.json | 79 ++ snapshots/storageLayout/SuperchainConfig.json | 23 +- .../storageLayout/SystemConfigGlobal.json | 65 + snapshots/storageLayout/TEEVerifier.json | 1 + src/multiproof/AggregateVerifier.sol | 94 +- src/multiproof/mocks/MockCertManager.sol | 16 - .../mocks/MockDevSystemConfigGlobal.sol | 6 +- src/multiproof/tee/SystemConfigGlobal.sol | 93 +- src/multiproof/tee/TEEVerifier.sol | 23 +- test/multiproof/AggregateVerifier.t.sol | 48 +- test/multiproof/BaseTest.t.sol | 4 +- test/multiproof/Challenge.t.sol | 41 + test/multiproof/Nullify.t.sol | 32 +- test/multiproof/SystemConfigGlobal.t.sol | 17 +- test/multiproof/TEEVerifier.t.sol | 49 +- test/opcm/DeployImplementations.t.sol | 16 + test/opcm/DeployOPChain.t.sol | 8 + test/vendor/Initializable.t.sol | 62 +- 53 files changed, 5181 insertions(+), 502 deletions(-) delete mode 100644 deploy-config/sepolia-no-nitro.json delete mode 100644 deploy-config/sepolia-with-nitro.json create mode 100644 deployments/11155111-dev-no-nitro.json create mode 100644 snapshots/abi/AggregateVerifier.json create mode 100644 snapshots/abi/BalanceTracker.json create mode 100644 snapshots/abi/CBMulticall.json create mode 100644 snapshots/abi/DevSystemConfigGlobal.json create mode 100644 snapshots/abi/FeeDisburser.json create mode 100644 snapshots/abi/MockSystemConfig.json create mode 100644 snapshots/abi/MockVerifier.json create mode 100644 snapshots/abi/Recovery.json create mode 100644 snapshots/abi/SmartEscrow.json create mode 100644 snapshots/abi/SystemConfigGlobal.json create mode 100644 snapshots/abi/TEEVerifier.json create mode 100644 snapshots/storageLayout/AggregateVerifier.json create mode 100644 snapshots/storageLayout/BalanceTracker.json create mode 100644 snapshots/storageLayout/CBMulticall.json create mode 100644 snapshots/storageLayout/DevSystemConfigGlobal.json create mode 100644 snapshots/storageLayout/FeeDisburser.json create mode 100644 snapshots/storageLayout/MockSystemConfig.json create mode 100644 snapshots/storageLayout/MockVerifier.json create mode 100644 snapshots/storageLayout/Recovery.json create mode 100644 snapshots/storageLayout/SmartEscrow.json create mode 100644 snapshots/storageLayout/SystemConfigGlobal.json create mode 100644 snapshots/storageLayout/TEEVerifier.json delete mode 100644 src/multiproof/mocks/MockCertManager.sol diff --git a/.gitignore b/.gitignore index 73a2e81b..afbd7e90 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,8 @@ deploy-config/getting-started.json # IDE /.idea/ +# Foundry +foundry.lock + # OS .DS_Store diff --git a/Makefile b/Makefile index 70534f68..9fdfbdbe 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,8 @@ deps: clean-lib github.com/safe-global/safe-contracts@bf943f80fec5ac647159d26161446ac5d716a294 \ github.com/Vectorized/solady@502cc1ea718e6fa73b380635ee0868b0740595f0 \ github.com/base/nitro-validator@0f006d2075637dd9640e530c4a7065f5c8bb2132 \ - github.com/base/op-enclave@a2d5398f04c3a8e4df929d58ee638ba4a037bfec + github.com/base/op-enclave@a2d5398f04c3a8e4df929d58ee638ba4a037bfec \ + github.com/automata-network/aws-nitro-enclave-attestation@10fe7be8d9840490f5655e4b2a2aba3a95ec88c1 forge install --no-git \ github.com/ethereum-optimism/superchain-registry@84bce73573f130008d84bae6e924163bab589a11 @# openzeppelin-contracts-v5 and solady-v0.0.245 use the same orgs as their diff --git a/deploy-config/hardhat.json b/deploy-config/hardhat.json index 8a0a3d4e..c1c6714e 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-no-nitro.json b/deploy-config/sepolia-no-nitro.json deleted file mode 100644 index a88b1352..00000000 --- a/deploy-config/sepolia-no-nitro.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33", - "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", - "teeProposer": "0xb28E6890Cffa969dA9851c1BF1Ac34B76EbFEe98", - "gameType": "621", - "genesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", - "genesisBlockNumber": "37223829", - "configHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360" -} diff --git a/deploy-config/sepolia-with-nitro.json b/deploy-config/sepolia-with-nitro.json deleted file mode 100644 index ee0ad04e..00000000 --- a/deploy-config/sepolia-with-nitro.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33", - "teeImageHash": "0x692a590aa58825d31ccfa165913f0a915c02ca58607b0f787a149179d2b69ec1", - "teeProposer": "0xb28E6890Cffa969dA9851c1BF1Ac34B76EbFEe98", - "gameType": "621", - "genesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", - "genesisBlockNumber": "37223829", - "configHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360" -} diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index f5d20903..9a85de7a 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 00000000..f98679b2 --- /dev/null +++ b/deployments/11155111-dev-no-nitro.json @@ -0,0 +1 @@ +{"SystemConfigGlobal":"0xf8293c0f3a36A746B559a1a51870339B20F60945","TEEVerifier":"0x82453dA61B397EE366fB2129502de9c216480aB6","DisputeGameFactory":"0xfEa8Cb315F75d838b6c76ae336a9255f81df0D50","AnchorStateRegistry":"0x556BD554854504BE2F2023F6531D25eF6f6Fe77D","DelayedWETH":"0xb1FB7f05711d2270cD658448562A29E8c5C95E9E","AggregateVerifier":"0xeeF18F1640fa79f919799B5D629908909e715f97"} \ No newline at end of file diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol index ab04b9c3..77d2ddd4 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, diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index c1d9694c..08519422 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 c4a45f36..a4676f0d 100644 --- a/scripts/deploy/DeployImplementations.s.sol +++ b/scripts/deploy/DeployImplementations.s.sol @@ -44,12 +44,14 @@ 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 { CertManager } from "lib/nitro-validator/src/CertManager.sol"; +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol"; import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; -import { DeployDevWithNitro } from "../multiproof/DeployDevWithNitro.s.sol"; import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; +import { GameType } from "src/dispute/lib/Types.sol"; contract DeployImplementations is Script { struct Input { @@ -65,6 +67,15 @@ contract DeployImplementations is Script { uint256 faultGameV2SplitDepth; uint256 faultGameV2ClockExtension; uint256 faultGameV2MaxClockDuration; + // Multiproof parameters + bytes32 teeImageHash; + bytes32 multiproofConfigHash; + uint256 multiproofGameType; + address nitroEnclaveVerifier; + uint256 l2ChainID; + uint256 multiproofBlockInterval; + uint256 multiproofIntermediateBlockInterval; + uint256 multiproofProofThreshold; // Outputs from DeploySuperchain.s.sol. ISuperchainConfig superchainConfigProxy; IProtocolVersions protocolVersionsProxy; @@ -137,7 +148,6 @@ contract DeployImplementations is Script { deployAnchorStateRegistryImpl(_input, output_); deployFaultDisputeGameV2Impl(_input, output_); deployPermissionedDisputeGameV2Impl(_input, output_); - deploySystemConfigGlobalImpl(_input, output_); deployAggregateVerifierImpl(_input, output_); if (DevFeatures.isDevFeatureEnabled(_input.devFeatureBitmap, DevFeatures.OPTIMISM_PORTAL_INTEROP)) { deploySuperFaultDisputeGameImpl(_input, output_); @@ -479,7 +489,7 @@ contract DeployImplementations is Script { function deployDisputeGameFactoryImpl(Output memory _output) private { IDisputeGameFactory impl = IDisputeGameFactory( DeployUtils.createDeterministic({ - _name: "src/dispute/DisputeGameFactory.sol:DisputeGameFactory", + _name: "DisputeGameFactory", _args: DeployUtils.encodeConstructor(abi.encodeCall(IDisputeGameFactory.__constructor__, ())), _salt: _salt }) @@ -706,40 +716,36 @@ contract DeployImplementations is Script { _output.opcmStandardValidator = impl; } - function deploySystemConfigGlobalImpl(Input memory, Output memory _output) private { - address certManager = address(new CertManager()); - SystemConfigGlobal scgImpl = new SystemConfigGlobal(CertManager(certManager)); - - vm.label(address(scgImpl), "SystemConfigGlobalImpl"); - _output.systemConfigGlobalImpl = scgImpl; - } - - function deployAggregateVerifierImpl(Input memory, Output memory _output) private { - DeployDevWithNitro nitro = new DeployDevWithNitro(); - DeployDevWithNitro.DeployConfig memory cfg = nitro.loadConfig(); - + function deployAggregateVerifierImpl(Input memory _input, Output memory _output) private { address zkVerifier = address(new MockVerifier()); - address teeVerifierImpl = address(new TEEVerifier(_output.systemConfigGlobalImpl)); - IVerifier aggregateVerifierImpl = IVerifier( + address teeVerifierImpl; + { + SystemConfigGlobal scgImpl = new SystemConfigGlobal(INitroEnclaveVerifier(_input.nitroEnclaveVerifier)); + vm.label(address(scgImpl), "SystemConfigGlobalImpl"); + _output.systemConfigGlobalImpl = scgImpl; + teeVerifierImpl = address(new TEEVerifier(scgImpl)); + } + + _output.aggregateVerifierImpl = IVerifier( address( new AggregateVerifier( - cfg.gameType, + GameType.wrap(uint32(_input.multiproofGameType)), _output.anchorStateRegistryImpl, _output.delayedWETHImpl, IVerifier(teeVerifierImpl), IVerifier(zkVerifier), - cfg.teeImageHash, + _input.teeImageHash, bytes32(0), - cfg.configHash, - 8453, - 100, - 10 + _input.multiproofConfigHash, + _input.l2ChainID, + _input.multiproofBlockInterval, + _input.multiproofIntermediateBlockInterval, + _input.multiproofProofThreshold ) ) ); - vm.label(address(aggregateVerifierImpl), "AggregateVerifierImpl"); - _output.aggregateVerifierImpl = aggregateVerifierImpl; + vm.label(address(_output.aggregateVerifierImpl), "AggregateVerifierImpl"); } function assertValidInput(Input memory _input) private pure { diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 081b8ba9..04f5cc9e 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -43,7 +43,7 @@ pragma solidity 0.8.15; * | SystemConfigGlobal | DevSystemConfigGlobal | SystemConfigGlobal | * | Signer registration | addDevSigner() | registerSigner() | * | Requires Nitro enclave | No | Yes | - * | Validates AWS cert chain | No | Yes | + * | Validates attestation (ZK) | No | Yes | * | PCR0 pre-registration | No | Yes | * | Attestation freshness | N/A | < 60 minutes | * @@ -52,10 +52,11 @@ pragma solidity 0.8.15; * ══════════════════════════════════════════════════════════════════════════════════ */ -import { CertManager } from "lib/nitro-validator/src/CertManager.sol"; +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { Script } from "forge-std/Script.sol"; -import { stdJson } from "forge-std/StdJson.sol"; import { console2 as console } from "forge-std/console2.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; @@ -63,6 +64,10 @@ 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"; @@ -78,29 +83,18 @@ import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; /// @notice Development deployment WITHOUT AWS Nitro attestation validation. /// @dev Uses DevSystemConfigGlobal which allows addDevSigner() to bypass attestation. contract DeployDevNoNitro is Script { - using stdJson for string; - /// @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; - /// @notice Config struct to reduce stack variables. - struct DeployConfig { - address owner; - bytes32 teeImageHash; - address teeProposer; - GameType gameType; - uint256 gameTypeRaw; - bytes32 genesisOutputRoot; - uint256 genesisBlockNumber; - bytes32 configHash; - } + DeployConfig public constant cfg = + DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig")))))); // Deployed addresses - address public certManager; address public systemConfigGlobalProxy; address public teeVerifier; address public disputeGameFactory; @@ -108,61 +102,44 @@ contract DeployDevNoNitro is Script { address public mockDelayedWETH; address public aggregateVerifier; + function setUp() public { + DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" }); + cfg.read(Config.deployConfigPath()); + } + function run() public { - DeployConfig memory cfg = _loadConfig(); + GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); console.log("=== Deploying Dev Infrastructure (NO NITRO) ==="); console.log("Chain ID:", block.chainid); - console.log("Owner:", cfg.owner); - console.log("TEE Proposer:", cfg.teeProposer); - console.log("Game Type:", cfg.gameTypeRaw); + console.log("Owner:", cfg.finalSystemOwner()); + console.log("TEE Proposer:", cfg.teeProposer()); + console.log("Game Type:", cfg.multiproofGameType()); console.log(""); console.log("NOTE: Using DevSystemConfigGlobal - NO attestation required."); vm.startBroadcast(); - _deployTEEContracts(cfg.owner); - _registerProposer(cfg.teeProposer); - _deployInfrastructure(cfg); - _deployAggregateVerifier(cfg); + _deployTEEContracts(cfg.finalSystemOwner()); + _registerProposer(cfg.teeProposer()); + _deployInfrastructure(gameType); + _deployAggregateVerifier(gameType); vm.stopBroadcast(); - _printSummary(cfg.teeImageHash, cfg.gameTypeRaw, cfg.configHash); + _printSummary(); _writeOutput(); } - function _loadConfig() internal view returns (DeployConfig memory cfg) { - string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia-no-nitro.json")); - string memory config = vm.readFile(configPath); - - cfg.owner = config.readAddress(".finalSystemOwner"); - cfg.teeImageHash = config.readBytes32(".teeImageHash"); - cfg.teeProposer = config.readAddressOr(".teeProposer", cfg.owner); - cfg.gameTypeRaw = config.readUintOr(".gameType", 621); - cfg.gameType = GameType.wrap(uint32(cfg.gameTypeRaw)); - cfg.genesisOutputRoot = config.readBytes32Or(".genesisOutputRoot", bytes32(uint256(1))); - cfg.genesisBlockNumber = config.readUintOr(".genesisBlockNumber", 0); - cfg.configHash = config.readBytes32Or(".configHash", bytes32(0)); - } - function _deployTEEContracts(address owner) internal { - // 1. CertManager (not used in dev mode, but deployed for interface compatibility) - certManager = address(new CertManager()); - console.log("CertManager:", certManager); - - // 2. DevSystemConfigGlobal - allows addDevSigner() to bypass attestation - address scgImpl = address(new DevSystemConfigGlobal(CertManager(certManager))); + address scgImpl = address(new DevSystemConfigGlobal(INitroEnclaveVerifier(address(0)))); systemConfigGlobalProxy = address( new TransparentUpgradeableProxy( - scgImpl, - address(0xdead), // Non-upgradeable for testing - abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) + scgImpl, address(0xdead), abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) ) ); console.log("DevSystemConfigGlobal:", systemConfigGlobalProxy); - // 3. TEEVerifier teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); console.log("TEEVerifier:", teeVerifier); } @@ -172,65 +149,64 @@ contract DeployDevNoNitro is Script { console.log("Registered TEE proposer:", teeProposer); } - function _deployInfrastructure(DeployConfig memory cfg) internal { - // 4. REAL DisputeGameFactory (behind proxy) + function _deployInfrastructure(GameType gameType) internal { address factoryImpl = address(new DisputeGameFactory()); - MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.owner); + 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.owner); + DisputeGameFactory(address(proxy)).initialize(cfg.finalSystemOwner()); disputeGameFactory = address(proxy); console.log("DisputeGameFactory:", disputeGameFactory); - // 5. Mock AnchorStateRegistry MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); mockAnchorRegistry = address(asr); - asr.initialize(disputeGameFactory, Hash.wrap(cfg.genesisOutputRoot), cfg.genesisBlockNumber, cfg.gameType); + asr.initialize( + disputeGameFactory, + Hash.wrap(cfg.multiproofGenesisOutputRoot()), + cfg.multiproofGenesisBlockNumber(), + gameType + ); console.log("AnchorStateRegistry (mock):", mockAnchorRegistry); } - function _deployAggregateVerifier(DeployConfig memory cfg) internal { - // 6. Mock ZK Verifier + function _deployAggregateVerifier(GameType gameType) internal { address zkVerifier = address(new MockVerifier()); console.log("MockVerifier (ZK):", zkVerifier); - // 6.5. Mock DelayedWETH for bond handling mockDelayedWETH = address(new MockDelayedWETH()); console.log("MockDelayedWETH:", mockDelayedWETH); - // 7. AggregateVerifier aggregateVerifier = address( new AggregateVerifier( - cfg.gameType, + gameType, IAnchorStateRegistry(mockAnchorRegistry), IDelayedWETH(payable(mockDelayedWETH)), IVerifier(teeVerifier), IVerifier(zkVerifier), - cfg.teeImageHash, - bytes32(0), // zkImageHash (unused) - cfg.configHash, - 8453, // l2ChainId (Base mainnet) + cfg.teeImageHash(), + bytes32(0), + cfg.multiproofConfigHash(), + 8453, BLOCK_INTERVAL, - INTERMEDIATE_BLOCK_INTERVAL + INTERMEDIATE_BLOCK_INTERVAL, + PROOF_THRESHOLD ) ); console.log("AggregateVerifier:", aggregateVerifier); - // 8. Register AggregateVerifier with the factory - DisputeGameFactory(disputeGameFactory).setImplementation(cfg.gameType, IDisputeGame(aggregateVerifier)); - DisputeGameFactory(disputeGameFactory).setInitBond(cfg.gameType, INIT_BOND); + DisputeGameFactory(disputeGameFactory).setImplementation(gameType, IDisputeGame(aggregateVerifier)); + DisputeGameFactory(disputeGameFactory).setInitBond(gameType, INIT_BOND); console.log("Registered AggregateVerifier with factory"); } - function _printSummary(bytes32 teeImageHash, uint256 gameType, bytes32 configHash) internal view { + function _printSummary() internal view { console.log("\n========================================"); console.log(" DEV DEPLOYMENT COMPLETE (NO NITRO)"); console.log("========================================"); console.log("\nTEE Contracts:"); - console.log(" CertManager:", certManager); console.log(" DevSystemConfigGlobal:", systemConfigGlobalProxy); console.log(" TEEVerifier:", teeVerifier); console.log("\nInfrastructure:"); @@ -239,14 +215,14 @@ contract DeployDevNoNitro is Script { console.log(" DelayedWETH (mock):", mockDelayedWETH); console.log("\nGame:"); console.log(" AggregateVerifier:", aggregateVerifier); - console.log(" Game Type:", gameType); - console.log(" TEE Image Hash:", vm.toString(teeImageHash)); - console.log(" Config Hash:", vm.toString(configHash)); + console.log(" Game Type:", cfg.multiproofGameType()); + console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash())); + console.log(" Config Hash:", vm.toString(cfg.multiproofConfigHash())); console.log("========================================"); console.log("\n>>> NEXT STEP - Register dev signer (NO ATTESTATION NEEDED) <<<"); console.log("\ncast send", systemConfigGlobalProxy); console.log(' "addDevSigner(address,bytes32)" '); - console.log(" ", vm.toString(teeImageHash)); + console.log(" ", vm.toString(cfg.teeImageHash())); console.log(" --private-key --rpc-url "); console.log("\n========================================\n"); } @@ -254,9 +230,7 @@ contract DeployDevNoNitro is Script { function _writeOutput() internal { string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-dev-no-nitro.json"); string memory output = string.concat( - '{"CertManager":"', - vm.toString(certManager), - '","SystemConfigGlobal":"', + '{"SystemConfigGlobal":"', vm.toString(systemConfigGlobalProxy), '","TEEVerifier":"', vm.toString(teeVerifier), diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index a3d6dca5..875674ae 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -10,8 +10,9 @@ pragma solidity 0.8.15; * ══════════════════════════════════════════════════════════════════════════════════ * * This script deploys infrastructure using the REAL SystemConfigGlobal, which - * REQUIRES valid AWS Nitro attestation for signer registration. You cannot use - * addDevSigner() - you must go through the full registerSigner() flow. + * 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 @@ -43,49 +44,18 @@ pragma solidity 0.8.15; * Note: The teeImageHash in deploy-config is keccak256(PCR0_RAW), not the raw bytes. * * ───────────────────────────────────────────────────────────────────────────────── - * STEP 2: Get fresh attestation from the enclave + * STEP 2: Generate ZK proof of the attestation and register the signer * ───────────────────────────────────────────────────────────────────────────────── * - * Query the Nitro enclave for a signed attestation document. This contains: - * - The enclave's public key (from which the signer address is derived) - * - PCR0 (image hash) - * - Timestamp - * - AWS certificate chain signature - * - * curl -s -X POST $ENCLAVE_URL \ - * -H "Content-Type: application/json" \ - * -d '{"jsonrpc":"2.0","method":"enclave_signerAttestation","id":1}' \ - * | jq -r '.result' - * - * IMPORTANT: The attestation is only valid for 60 minutes! You must complete - * Step 3 within that window. - * - * ───────────────────────────────────────────────────────────────────────────────── - * STEP 3: Register the signer using the attestation - OWNER OR MANAGER - * ───────────────────────────────────────────────────────────────────────────────── - * - * Option A: Use the register-signer tool from base/op-enclave - * - * go install github.com/base/op-enclave/tools/register-signer@latest - * - * register-signer \ - * -attestation $ATTESTATION_HEX \ - * -deployment deployments/-dev-with-nitro.json \ - * -rpc $RPC_URL \ - * -private-key $OWNER_OR_MANAGER_KEY - * - * Option B: Call the contract directly (requires parsing attestation into TBS + sig) - * - * # The attestation is a COSE Sign1 structure. You need to split it into: - * # - attestationTbs: The "to-be-signed" payload - * # - signature: The ECDSA signature over the payload - * # - * # See: https://github.com/base/op-enclave/tree/main/tools/register-signer + * Generate a Risc0 ZK proof of the Nitro attestation offchain, then call: * * cast send $SYSTEM_CONFIG_GLOBAL \ - * "registerSigner(bytes,bytes)" $ATTESTATION_TBS $SIGNATURE \ + * "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 * ───────────────────────────────────────────────────────────────────────────────── @@ -107,7 +77,7 @@ pragma solidity 0.8.15; * | SystemConfigGlobal | DevSystemConfigGlobal | SystemConfigGlobal | * | Signer registration | addDevSigner() | registerSigner() | * | Requires Nitro enclave | No | Yes | - * | Validates AWS cert chain | No | Yes | + * | Validates attestation (ZK) | No | Yes | * | PCR0 pre-registration | No | Yes | * | Attestation freshness | N/A | < 60 minutes | * @@ -116,10 +86,11 @@ pragma solidity 0.8.15; * ══════════════════════════════════════════════════════════════════════════════════ */ -import { CertManager } from "lib/nitro-validator/src/CertManager.sol"; +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { Script } from "forge-std/Script.sol"; -import { stdJson } from "forge-std/StdJson.sol"; import { console2 as console } from "forge-std/console2.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; @@ -127,6 +98,10 @@ 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"; @@ -141,29 +116,18 @@ import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; /// @notice Development deployment WITH AWS Nitro attestation validation. /// @dev Uses real SystemConfigGlobal which requires registerSigner() with valid attestation. contract DeployDevWithNitro is Script { - using stdJson for string; - /// @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; - /// @notice Config struct to reduce stack variables. - struct DeployConfig { - address owner; - bytes32 teeImageHash; - address teeProposer; - GameType gameType; - uint256 gameTypeRaw; - bytes32 genesisOutputRoot; - uint256 genesisBlockNumber; - bytes32 configHash; - } + DeployConfig public constant cfg = + DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig")))))); // Deployed addresses - address public certManager; address public systemConfigGlobalProxy; address public teeVerifier; address public disputeGameFactory; @@ -171,61 +135,46 @@ contract DeployDevWithNitro is Script { address public mockDelayedWETH; address public aggregateVerifier; + function setUp() public { + DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" }); + cfg.read(Config.deployConfigPath()); + } + function run() public { - DeployConfig memory cfg = loadConfig(); + GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); console.log("=== Deploying Dev Infrastructure (WITH NITRO) ==="); console.log("Chain ID:", block.chainid); - console.log("Owner:", cfg.owner); - console.log("TEE Proposer:", cfg.teeProposer); - console.log("Game Type:", cfg.gameTypeRaw); + console.log("Owner:", cfg.finalSystemOwner()); + console.log("TEE Proposer:", cfg.teeProposer()); + console.log("Game Type:", cfg.multiproofGameType()); console.log(""); - console.log("NOTE: Using REAL SystemConfigGlobal - attestation REQUIRED."); + console.log("NOTE: Using REAL SystemConfigGlobal - ZK attestation proof REQUIRED."); + console.log("NitroEnclaveVerifier:", cfg.nitroEnclaveVerifier()); vm.startBroadcast(); - _deployTEEContracts(cfg.owner); - _registerProposer(cfg.teeProposer); - _deployInfrastructure(cfg); - _deployAggregateVerifier(cfg); + _deployTEEContracts(cfg.finalSystemOwner(), cfg.nitroEnclaveVerifier()); + _registerProposer(cfg.teeProposer()); + _deployInfrastructure(gameType); + _deployAggregateVerifier(gameType); vm.stopBroadcast(); - _printSummary(cfg); + _printSummary(); _writeOutput(); } - function loadConfig() public view returns (DeployConfig memory cfg) { - string memory configPath = vm.envOr("DEPLOY_CONFIG_PATH", string("deploy-config/sepolia-with-nitro.json")); - string memory config = vm.readFile(configPath); - - cfg.owner = config.readAddress(".finalSystemOwner"); - cfg.teeImageHash = config.readBytes32(".teeImageHash"); - cfg.teeProposer = config.readAddressOr(".teeProposer", cfg.owner); - cfg.gameTypeRaw = config.readUintOr(".gameType", 621); - cfg.gameType = GameType.wrap(uint32(cfg.gameTypeRaw)); - cfg.genesisOutputRoot = config.readBytes32Or(".genesisOutputRoot", bytes32(uint256(1))); - cfg.genesisBlockNumber = config.readUintOr(".genesisBlockNumber", 0); - cfg.configHash = config.readBytes32Or(".configHash", bytes32(0)); - } - - function _deployTEEContracts(address owner) internal { - // 1. CertManager - validates AWS Nitro certificate chains - certManager = address(new CertManager()); - console.log("CertManager:", certManager); - - // 2. SystemConfigGlobal (REAL version) - requires attestation for signer registration - address scgImpl = address(new SystemConfigGlobal(CertManager(certManager))); + function _deployTEEContracts(address owner, address _nitroEnclaveVerifier) internal { + address scgImpl = address(new SystemConfigGlobal(INitroEnclaveVerifier(_nitroEnclaveVerifier))); + console.log("NitroEnclaveVerifier (external):", _nitroEnclaveVerifier); systemConfigGlobalProxy = address( new TransparentUpgradeableProxy( - scgImpl, - address(0xdead), // Non-upgradeable for testing - abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) + scgImpl, address(0xdead), abi.encodeCall(SystemConfigGlobal.initialize, (owner, owner)) ) ); console.log("SystemConfigGlobal:", systemConfigGlobalProxy); - // 3. TEEVerifier teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); console.log("TEEVerifier:", teeVerifier); } @@ -235,65 +184,65 @@ contract DeployDevWithNitro is Script { console.log("Registered TEE proposer:", teeProposer); } - function _deployInfrastructure(DeployConfig memory cfg) internal { - // 4. REAL DisputeGameFactory (behind proxy) + function _deployInfrastructure(GameType gameType) internal { address factoryImpl = address(new DisputeGameFactory()); - MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.owner); + 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.owner); + DisputeGameFactory(address(proxy)).initialize(cfg.finalSystemOwner()); disputeGameFactory = address(proxy); console.log("DisputeGameFactory:", disputeGameFactory); - // 5. Mock AnchorStateRegistry MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); mockAnchorRegistry = address(asr); - asr.initialize(disputeGameFactory, Hash.wrap(cfg.genesisOutputRoot), cfg.genesisBlockNumber, cfg.gameType); + asr.initialize( + disputeGameFactory, + Hash.wrap(cfg.multiproofGenesisOutputRoot()), + cfg.multiproofGenesisBlockNumber(), + gameType + ); console.log("AnchorStateRegistry (mock):", mockAnchorRegistry); } - function _deployAggregateVerifier(DeployConfig memory cfg) internal { - // 6. Mock ZK Verifier + function _deployAggregateVerifier(GameType gameType) internal { address zkVerifier = address(new MockVerifier()); console.log("MockVerifier (ZK):", zkVerifier); - // 6.5. Mock DelayedWETH for bond handling mockDelayedWETH = address(new MockDelayedWETH()); console.log("MockDelayedWETH:", mockDelayedWETH); - // 7. AggregateVerifier aggregateVerifier = address( new AggregateVerifier( - cfg.gameType, + gameType, IAnchorStateRegistry(mockAnchorRegistry), IDelayedWETH(payable(mockDelayedWETH)), IVerifier(teeVerifier), IVerifier(zkVerifier), - cfg.teeImageHash, - bytes32(0), // zkImageHash (unused) - cfg.configHash, - 8453, // l2ChainId (Base mainnet) + cfg.teeImageHash(), + bytes32(0), + cfg.multiproofConfigHash(), + 8453, BLOCK_INTERVAL, - INTERMEDIATE_BLOCK_INTERVAL + INTERMEDIATE_BLOCK_INTERVAL, + PROOF_THRESHOLD ) ); console.log("AggregateVerifier:", aggregateVerifier); - // 8. Register AggregateVerifier with the factory - DisputeGameFactory(disputeGameFactory).setImplementation(cfg.gameType, IDisputeGame(aggregateVerifier)); - DisputeGameFactory(disputeGameFactory).setInitBond(cfg.gameType, INIT_BOND); + DisputeGameFactory(disputeGameFactory).setImplementation(gameType, IDisputeGame(aggregateVerifier)); + DisputeGameFactory(disputeGameFactory).setInitBond(gameType, INIT_BOND); console.log("Registered AggregateVerifier with factory"); } - function _printSummary(DeployConfig memory cfg) internal view { + function _printSummary() internal view { console.log("\n========================================"); console.log(" DEV DEPLOYMENT COMPLETE (WITH NITRO)"); console.log("========================================"); console.log("\nTEE Contracts:"); - console.log(" CertManager:", certManager); + console.log(" NitroEnclaveVerifier (external):", cfg.nitroEnclaveVerifier()); console.log(" SystemConfigGlobal:", systemConfigGlobalProxy); console.log(" TEEVerifier:", teeVerifier); console.log("\nInfrastructure:"); @@ -302,21 +251,20 @@ contract DeployDevWithNitro is Script { console.log(" DelayedWETH (mock):", mockDelayedWETH); console.log("\nGame:"); console.log(" AggregateVerifier:", aggregateVerifier); - console.log(" Game Type:", cfg.gameTypeRaw); - console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash)); - console.log(" Config Hash:", vm.toString(cfg.configHash)); + 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 (ATTESTATION REQUIRED) <<<"); + console.log("\n>>> NEXT STEPS (ZK ATTESTATION PROOF REQUIRED) <<<"); console.log("\n1. Register the PCR0 (raw 48-byte enclave image hash):"); console.log(" cast send", systemConfigGlobalProxy); console.log(' "registerPCR0(bytes)" '); console.log(" --private-key --rpc-url "); - console.log("\n2. Get attestation from enclave (valid for 60 min):"); - console.log(' curl -X POST -H "Content-Type: application/json"'); - console.log(" -d '{\"jsonrpc\":\"2.0\",\"method\":\"enclave_signerAttestation\",\"id\":1}'"); - console.log("\n3. Register signer with attestation:"); - console.log(" go install github.com/base/op-enclave/tools/register-signer@latest"); - console.log(" register-signer -attestation -rpc -private-key "); + console.log("\n2. Generate a Risc0 ZK proof of the Nitro attestation offchain."); + console.log("\n3. Register signer with ZK proof:"); + console.log(" cast send", systemConfigGlobalProxy); + console.log(' "registerSigner(bytes,bytes)" '); + console.log(" --private-key --rpc-url "); console.log("\nSee the comments at the top of this file for full details."); console.log("========================================\n"); } @@ -324,9 +272,7 @@ contract DeployDevWithNitro is Script { function _writeOutput() internal { string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-dev-with-nitro.json"); string memory output = string.concat( - '{"CertManager":"', - vm.toString(certManager), - '","SystemConfigGlobal":"', + '{"SystemConfigGlobal":"', vm.toString(systemConfigGlobalProxy), '","TEEVerifier":"', vm.toString(teeVerifier), diff --git a/snapshots/abi/AggregateVerifier.json b/snapshots/abi/AggregateVerifier.json new file mode 100644 index 00000000..2a142bdd --- /dev/null +++ b/snapshots/abi/AggregateVerifier.json @@ -0,0 +1,1068 @@ +[ + { + "inputs": [ + { + "internalType": "GameType", + "name": "gameType_", + "type": "uint32" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistry_", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + }, + { + "internalType": "contract IVerifier", + "name": "teeVerifier", + "type": "address" + }, + { + "internalType": "contract IVerifier", + "name": "zkVerifier", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "teeImageHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "zkImageHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "configHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "intermediateBlockInterval", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BLOCKHASH_WINDOW", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BLOCK_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONFIG_HASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DELAYED_WETH", + "outputs": [ + { + "internalType": "contract IDelayedWETH", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DISPUTE_GAME_FACTORY", + "outputs": [ + { + "internalType": "contract IDisputeGameFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EIP2935_CONTRACT", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EIP2935_WINDOW", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "FAST_FINALIZATION_DELAY", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INTERMEDIATE_BLOCK_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "L2_CHAIN_ID", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SLOW_FINALIZATION_DELAY", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TEE_IMAGE_HASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TEE_VERIFIER", + "outputs": [ + { + "internalType": "contract IVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ZK_IMAGE_HASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ZK_VERIFIER", + "outputs": [ + { + "internalType": "contract IVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "anchorStateRegistry", + "outputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bondAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bondClaimed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bondRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bondUnlocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gameIndex", + "type": "uint256" + } + ], + "name": "challenge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimCredit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "closeGame", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "counteredByGameAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "createdAt", + "outputs": [ + { + "internalType": "Timestamp", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "expectedResolution", + "outputs": [ + { + "internalType": "Timestamp", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "extraData", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gameCreator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "gameData", + "outputs": [ + { + "internalType": "GameType", + "name": "", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gameOver", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gameType", + "outputs": [ + { + "internalType": "GameType", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "name": "initializeWithInitData", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "intermediateOutputRoot", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "intermediateOutputRoots", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "intermediateOutputRootsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1Head", + "outputs": [ + { + "internalType": "Hash", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "l2SequenceNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "intermediateRootIndex", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "intermediateRootToProve", + "type": "bytes32" + } + ], + "name": "nullify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "parentIndex", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "resolve", + "outputs": [ + { + "internalType": "enum GameStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resolvedAt", + "outputs": [ + { + "internalType": "Timestamp", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rootClaim", + "outputs": [ + { + "internalType": "Claim", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "startingBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingOutputRoot", + "outputs": [ + { + "internalType": "Hash", + "name": "root", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "l2SequenceNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingRootHash", + "outputs": [ + { + "internalType": "Hash", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "enum GameStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "teeProver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "verifyProposalProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "wasRespectedGameTypeWhenCreated", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "zkProver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "challenger", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IDisputeGame", + "name": "game", + "type": "address" + } + ], + "name": "Challenged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "CreditClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nullifier", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "intermediateRootIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "intermediateRoot", + "type": "bytes32" + } + ], + "name": "Nullified", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "prover", + "type": "address" + }, + { + "indexed": true, + "internalType": "enum AggregateVerifier.ProofType", + "name": "proofType", + "type": "uint8" + } + ], + "name": "Proved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "enum GameStatus", + "name": "status", + "type": "uint8" + } + ], + "name": "Resolved", + "type": "event" + }, + { + "inputs": [], + "name": "AlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum AggregateVerifier.ProofType", + "name": "proofType", + "type": "uint8" + } + ], + "name": "AlreadyProven", + "type": "error" + }, + { + "inputs": [], + "name": "BondRecipientEmpty", + "type": "error" + }, + { + "inputs": [], + "name": "BondTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "ClaimAlreadyResolved", + "type": "error" + }, + { + "inputs": [], + "name": "CounteredByGameNotResolved", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotFinalized", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotInProgress", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotOver", + "type": "error" + }, + { + "inputs": [], + "name": "GameNotResolved", + "type": "error" + }, + { + "inputs": [], + "name": "GameOver", + "type": "error" + }, + { + "inputs": [], + "name": "GamePaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "intermediateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "claim", + "type": "bytes32" + } + ], + "name": "IntermediateRootMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "IntermediateRootSameAsProposed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "intermediateBlockInterval", + "type": "uint256" + } + ], + "name": "InvalidBlockInterval", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCounteredByGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIntermediateRootIndex", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidParentGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProof", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProofType", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "claimed", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "actual", + "type": "bytes32" + } + ], + "name": "L1OriginHashMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "l1OriginNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentBlock", + "type": "uint256" + } + ], + "name": "L1OriginInFuture", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "l1OriginNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentBlock", + "type": "uint256" + } + ], + "name": "L1OriginTooOld", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum AggregateVerifier.ProofType", + "name": "proofType", + "type": "uint8" + } + ], + "name": "MissingProof", + "type": "error" + }, + { + "inputs": [], + "name": "NoCreditToClaim", + "type": "error" + }, + { + "inputs": [], + "name": "NoProofProvided", + "type": "error" + }, + { + "inputs": [], + "name": "ParentGameNotResolved", + "type": "error" + }, + { + "inputs": [], + "name": "Reentrancy", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expectedBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actualBlockNumber", + "type": "uint256" + } + ], + "name": "UnexpectedBlockNumber", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/BalanceTracker.json b/snapshots/abi/BalanceTracker.json new file mode 100644 index 00000000..136ef890 --- /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 00000000..9ac8ef7f --- /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/DevSystemConfigGlobal.json b/snapshots/abi/DevSystemConfigGlobal.json new file mode 100644 index 00000000..a082b6f7 --- /dev/null +++ b/snapshots/abi/DevSystemConfigGlobal.json @@ -0,0 +1,458 @@ +[ + { + "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": [ + { + "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 016224be..54f34da5 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 00000000..4dfa787a --- /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 00000000..b36dfe19 --- /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 00000000..d36cb2b2 --- /dev/null +++ b/snapshots/abi/MockVerifier.json @@ -0,0 +1,31 @@ +[ + { + "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": "pure", + "type": "function" + } +] \ No newline at end of file diff --git a/snapshots/abi/OPSuccinctFaultDisputeGame.json b/snapshots/abi/OPSuccinctFaultDisputeGame.json index 9e60e003..6eb3a95f 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 00000000..32eda021 --- /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 00000000..06bdc528 --- /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 67e88650..cfc27101 100644 --- a/snapshots/abi/SuperchainConfig.json +++ b/snapshots/abi/SuperchainConfig.json @@ -1,9 +1,46 @@ [ { - "inputs": [], + "inputs": [ + { + "internalType": "address", + "name": "_guardian", + "type": "address" + }, + { + "internalType": "address", + "name": "_incidentResponder", + "type": "address" + } + ], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "GUARDIAN", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INCIDENT_RESPONDER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -51,28 +88,15 @@ }, { "inputs": [], - "name": "initVersion", + "name": "incidentResponder", "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", - "name": "_guardian", + "name": "", "type": "address" } ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -223,38 +247,6 @@ "stateMutability": "view", "type": "function" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "enum SuperchainConfig.UpdateType", - "name": "updateType", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "ConfigUpdate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -265,7 +257,7 @@ "type": "address" } ], - "name": "Paused", + "name": "PauseExtended", "type": "event" }, { @@ -278,7 +270,7 @@ "type": "address" } ], - "name": "PauseExtended", + "name": "Paused", "type": "event" }, { @@ -324,11 +316,6 @@ "name": "ProxyAdminOwnedBase_ProxyAdminNotFound", "type": "error" }, - { - "inputs": [], - "name": "ReinitializableBase_ZeroInitVersion", - "type": "error" - }, { "inputs": [ { @@ -355,5 +342,10 @@ "inputs": [], "name": "SuperchainConfig_OnlyGuardian", "type": "error" + }, + { + "inputs": [], + "name": "SuperchainConfig_OnlyGuardianOrIncidentResponder", + "type": "error" } ] \ No newline at end of file diff --git a/snapshots/abi/SystemConfigGlobal.json b/snapshots/abi/SystemConfigGlobal.json new file mode 100644 index 00000000..e2e1a3db --- /dev/null +++ b/snapshots/abi/SystemConfigGlobal.json @@ -0,0 +1,440 @@ +[ + { + "inputs": [ + { + "internalType": "contract INitroEnclaveVerifier", + "name": "nitroVerifier", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "MAX_AGE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NITRO_VERIFIER", + "outputs": [ + { + "internalType": "contract INitroEnclaveVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "pcr0", + "type": "bytes" + } + ], + "name": "deregisterPCR0", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "deregisterSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "initialManager", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isValidProposer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "isValidSigner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "manager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "pcr0", + "type": "bytes" + } + ], + "name": "registerPCR0", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "output", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "registerSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceManagement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "name": "setProposer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "signerPCR0", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newManager", + "type": "address" + } + ], + "name": "transferManagement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "validPCR0s", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousManager", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newManager", + "type": "address" + } + ], + "name": "ManagementTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "pcr0Hash", + "type": "bytes32" + } + ], + "name": "PCR0Deregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "pcr0Hash", + "type": "bytes32" + } + ], + "name": "PCR0Registered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "name": "ProposerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "SignerDeregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "pcr0", + "type": "bytes32" + } + ], + "name": "SignerRegistered", + "type": "event" + }, + { + "inputs": [], + "name": "AttestationTooOld", + "type": "error" + }, + { + "inputs": [], + "name": "AttestationVerificationFailed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPCR0", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPublicKey", + "type": "error" + }, + { + "inputs": [], + "name": "PCR0NotFound", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/TEEVerifier.json b/snapshots/abi/TEEVerifier.json new file mode 100644 index 00000000..b1bccadb --- /dev/null +++ b/snapshots/abi/TEEVerifier.json @@ -0,0 +1,116 @@ +[ + { + "inputs": [ + { + "internalType": "contract SystemConfigGlobal", + "name": "systemConfigGlobal", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "SYSTEM_CONFIG_GLOBAL", + "outputs": [ + { + "internalType": "contract SystemConfigGlobal", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "imageId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "journal", + "type": "bytes32" + } + ], + "name": "verify", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "signerPCR0", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "claimedImageId", + "type": "bytes32" + } + ], + "name": "ImageIdMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProofFormat", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposer", + "type": "address" + } + ], + "name": "InvalidProposer", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "InvalidSigner", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 61b6e4e8..08bc12a0 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -3,10 +3,6 @@ "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", "sourceCodeHash": "0xdddaf874cd201558e34f0ed78db38a58d798188f974646b0cad8ce9910f99037" }, - "src/L1/DataAvailabilityChallenge.sol:DataAvailabilityChallenge:dispute": { - "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", - "sourceCodeHash": "0xdddaf874cd201558e34f0ed78db38a58d798188f974646b0cad8ce9910f99037" - }, "src/L1/ETHLockbox.sol:ETHLockbox": { "initCodeHash": "0x65db3aa3c2e3221065752f66016fa02b66688a01cc5c3066533b27fe620619c8", "sourceCodeHash": "0x6c9d3e2dee44c234d59ab93b6564536dfd807f1c4a02a82d5393bc53cb15b8b7" @@ -131,18 +127,10 @@ "initCodeHash": "0xe492fe75e3c0a8a80ede7b50271263c42a2c7616a101861e892dba76f9771e34", "sourceCodeHash": "0xef9cf289b4bf63808b6822fc2c588fe516abbbfb90479553e7d54d43de344e50" }, - "src/L2/LiquidityController.sol:LiquidityController:dispute": { - "initCodeHash": "0xb41fb5ed5d60d9c0fcdc21e222567a48632544b99ef0a298735a56d15e06f327", - "sourceCodeHash": "0xef9cf289b4bf63808b6822fc2c588fe516abbbfb90479553e7d54d43de344e50" - }, "src/L2/NativeAssetLiquidity.sol:NativeAssetLiquidity": { "initCodeHash": "0x04b176e5d484e54173a5644c833117c5fd9f055dce8678be9ec4cf07c0f01f00", "sourceCodeHash": "0x79289174e875ead5a6290df9af1951d6c0ff0dc6809601e1c7a80110ceb8e945" }, - "src/L2/NativeAssetLiquidity.sol:NativeAssetLiquidity:dispute": { - "initCodeHash": "0x04b176e5d484e54173a5644c833117c5fd9f055dce8678be9ec4cf07c0f01f00", - "sourceCodeHash": "0x79289174e875ead5a6290df9af1951d6c0ff0dc6809601e1c7a80110ceb8e945" - }, "src/L2/OperatorFeeVault.sol:OperatorFeeVault": { "initCodeHash": "0x2ebab6af089a714df25888a4dea81dadcb1fb57146be84d2e079041a9396a810", "sourceCodeHash": "0xd6e94bc9df025855916aa4184d0bc739b0fbe786dfd037b99dbb51d0d3e46918" @@ -251,13 +239,29 @@ "initCodeHash": "0x3a82e248129d19764bb975bb79b48a982f077f33bb508480bf8d2ec1c0c9810d", "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, + "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { + "initCodeHash": "0x6e30a9816642f1ea2887f16223868fcd04ac80c3a42a4583dfc611d8331f8674", + "sourceCodeHash": "0xb9786dc79b4b494d81905235f7bda044c5b1a98ad82416829f5f6948be1175cc" + }, + "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { + "initCodeHash": "0xc59c62a455533735aa9337d2db88b255713bd4dde20d3345265ec2fafe62af70", + "sourceCodeHash": "0xb9786dc79b4b494d81905235f7bda044c5b1a98ad82416829f5f6948be1175cc" + }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { - "initCodeHash": "0x2d681a39e518379b6d2c013735ae25a9bcf6e939d597bde9e6126375b86fd294", - "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" + "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", + "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { - "initCodeHash": "0x73537cace710afd0dc4ea7c2e25687a29cee13843617f518a3edeb8e79cf324c", - "sourceCodeHash": "0xf1ec5f9b79fa641ab6ad4c6a2ff6e8fb313df953f773a7c8923e911fa465ffb3" + "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", + "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" + }, + "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { + "initCodeHash": "0x78317d9088a26523d938ce0d88ed0134abc60292c0cdb3f8a6a9530638fd9e9a", + "sourceCodeHash": "0x08e6d340b025c5a6c5997ef6fb69e6c14444f9e473b2c5337d6c10b6c89c0b4f" + }, + "src/multiproof/tee/TEEVerifier.sol:TEEVerifier:dispute": { + "initCodeHash": "0x78317d9088a26523d938ce0d88ed0134abc60292c0cdb3f8a6a9530638fd9e9a", + "sourceCodeHash": "0x08e6d340b025c5a6c5997ef6fb69e6c14444f9e473b2c5337d6c10b6c89c0b4f" }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", @@ -279,10 +283,6 @@ "initCodeHash": "0x0ad1f0f33517132b06a225b51e6eac48904d4ad691e0045eb70244d811d0d99d", "sourceCodeHash": "0xd6683fe9be4019d34249ada5a4de3e597f1bd9cd473a89f6eff8f749a0b0e978" }, - "src/safe/SaferSafes.sol:SaferSafes:dispute": { - "initCodeHash": "0x6a58b3e2c2567d6f5f936d335e6ae0750fddc367d322a555e685376fad49d7ef", - "sourceCodeHash": "0xd6683fe9be4019d34249ada5a4de3e597f1bd9cd473a89f6eff8f749a0b0e978" - }, "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20": { "initCodeHash": "0x3c85eed0d017dca8eda6396aa842ddc12492587b061e8c756a8d32c4610a9658", "sourceCodeHash": "0x7023665d461f173417d932b55010b8f6c34f2bbaf56cfe4e1b15862c08cbcaac" diff --git a/snapshots/storageLayout/AggregateVerifier.json b/snapshots/storageLayout/AggregateVerifier.json new file mode 100644 index 00000000..1045c417 --- /dev/null +++ b/snapshots/storageLayout/AggregateVerifier.json @@ -0,0 +1,93 @@ +[ + { + "bytes": "8", + "label": "createdAt", + "offset": 0, + "slot": "0", + "type": "Timestamp" + }, + { + "bytes": "8", + "label": "resolvedAt", + "offset": 8, + "slot": "0", + "type": "Timestamp" + }, + { + "bytes": "1", + "label": "status", + "offset": 16, + "slot": "0", + "type": "enum GameStatus" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 17, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1", + "label": "wasRespectedGameTypeWhenCreated", + "offset": 18, + "slot": "0", + "type": "bool" + }, + { + "bytes": "64", + "label": "startingOutputRoot", + "offset": 0, + "slot": "1", + "type": "struct Proposal" + }, + { + "bytes": "20", + "label": "bondRecipient", + "offset": 0, + "slot": "3", + "type": "address" + }, + { + "bytes": "1", + "label": "bondUnlocked", + "offset": 20, + "slot": "3", + "type": "bool" + }, + { + "bytes": "1", + "label": "bondClaimed", + "offset": 21, + "slot": "3", + "type": "bool" + }, + { + "bytes": "32", + "label": "bondAmount", + "offset": 0, + "slot": "4", + "type": "uint256" + }, + { + "bytes": "20", + "label": "counteredByGameAddress", + "offset": 0, + "slot": "5", + "type": "address" + }, + { + "bytes": "32", + "label": "proofTypeToProver", + "offset": 0, + "slot": "6", + "type": "mapping(enum AggregateVerifier.ProofType => address)" + }, + { + "bytes": "8", + "label": "expectedResolution", + "offset": 0, + "slot": "7", + "type": "Timestamp" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/BalanceTracker.json b/snapshots/storageLayout/BalanceTracker.json new file mode 100644 index 00000000..2fb7cb0a --- /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 00000000..0637a088 --- /dev/null +++ b/snapshots/storageLayout/CBMulticall.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/snapshots/storageLayout/DevSystemConfigGlobal.json b/snapshots/storageLayout/DevSystemConfigGlobal.json new file mode 100644 index 00000000..2bd347c5 --- /dev/null +++ b/snapshots/storageLayout/DevSystemConfigGlobal.json @@ -0,0 +1,65 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "address" + }, + { + "bytes": "20", + "label": "_manager", + "offset": 0, + "slot": "52", + "type": "address" + }, + { + "bytes": "1536", + "label": "__gap", + "offset": 0, + "slot": "53", + "type": "uint256[48]" + }, + { + "bytes": "32", + "label": "validPCR0s", + "offset": 0, + "slot": "101", + "type": "mapping(bytes32 => bool)" + }, + { + "bytes": "32", + "label": "signerPCR0", + "offset": 0, + "slot": "102", + "type": "mapping(address => bytes32)" + }, + { + "bytes": "32", + "label": "isValidProposer", + "offset": 0, + "slot": "103", + "type": "mapping(address => bool)" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/FeeDisburser.json b/snapshots/storageLayout/FeeDisburser.json new file mode 100644 index 00000000..53ffb02a --- /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 00000000..8ef57702 --- /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 00000000..0637a088 --- /dev/null +++ b/snapshots/storageLayout/MockVerifier.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/snapshots/storageLayout/Recovery.json b/snapshots/storageLayout/Recovery.json new file mode 100644 index 00000000..0637a088 --- /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 00000000..1b1333a1 --- /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 b0aae64a..57708ae7 100644 --- a/snapshots/storageLayout/SuperchainConfig.json +++ b/snapshots/storageLayout/SuperchainConfig.json @@ -1,30 +1,9 @@ [ - { - "bytes": "1", - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "uint8" - }, - { - "bytes": "1", - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "bool" - }, - { - "bytes": "20", - "label": "guardian", - "offset": 2, - "slot": "0", - "type": "address" - }, { "bytes": "32", "label": "pauseTimestamps", "offset": 0, - "slot": "1", + "slot": "0", "type": "mapping(address => uint256)" } ] \ No newline at end of file diff --git a/snapshots/storageLayout/SystemConfigGlobal.json b/snapshots/storageLayout/SystemConfigGlobal.json new file mode 100644 index 00000000..2bd347c5 --- /dev/null +++ b/snapshots/storageLayout/SystemConfigGlobal.json @@ -0,0 +1,65 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "address" + }, + { + "bytes": "20", + "label": "_manager", + "offset": 0, + "slot": "52", + "type": "address" + }, + { + "bytes": "1536", + "label": "__gap", + "offset": 0, + "slot": "53", + "type": "uint256[48]" + }, + { + "bytes": "32", + "label": "validPCR0s", + "offset": 0, + "slot": "101", + "type": "mapping(bytes32 => bool)" + }, + { + "bytes": "32", + "label": "signerPCR0", + "offset": 0, + "slot": "102", + "type": "mapping(address => bytes32)" + }, + { + "bytes": "32", + "label": "isValidProposer", + "offset": 0, + "slot": "103", + "type": "mapping(address => bool)" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/TEEVerifier.json b/snapshots/storageLayout/TEEVerifier.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/snapshots/storageLayout/TEEVerifier.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 5d868448..21d255d4 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -24,8 +24,9 @@ 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 { +contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { //////////////////////////////////////////////////////////////// // Enums // //////////////////////////////////////////////////////////////// @@ -56,6 +57,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice The maximum number of blocks that EIP-2935 can look back (~8192). uint256 public constant EIP2935_WINDOW = 8191; + /// @notice For when the game no longer accepts proofs and prevents resolution. + int8 internal constant NEGATIVE_PROOF_COUNT = type(int8).min; + //////////////////////////////////////////////////////////////// // Immutables // //////////////////////////////////////////////////////////////// @@ -100,6 +104,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @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 // //////////////////////////////////////////////////////////////// @@ -143,6 +150,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice The timestamp of the game's expected resolution. Timestamp public expectedResolution; + /// @notice The number of proofs provided. + /// @dev Can be negative if a ZK proof is nullified. + int8 public proofCount; + //////////////////////////////////////////////////////////////// // Events // //////////////////////////////////////////////////////////////// @@ -238,6 +249,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @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. @@ -248,6 +265,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @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_, @@ -259,12 +278,17 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bytes32 configHash, uint256 l2ChainId, uint256 blockInterval, - uint256 intermediateBlockInterval + 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_; @@ -278,6 +302,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { L2_CHAIN_ID = l2ChainId; BLOCK_INTERVAL = blockInterval; INTERMEDIATE_BLOCK_INTERVAL = intermediateBlockInterval; + PROOF_THRESHOLD = proofThreshold; INITIALIZE_CALLDATA_SIZE = 0x7E + 0x20 * intermediateOutputRootsCount(); } @@ -436,6 +461,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard { status = GameStatus.DEFENDER_WINS; } + // casting to 'int256' is safe because 1 <= PROOF_THRESHOLD <= 2 + // forge-lint: disable-next-line(unsafe-typecast) + if (proofCount < int256(PROOF_THRESHOLD)) revert NotEnoughProofs(); + // Bond is refunded as no challenge was made or parent is invalid. bondRecipient = gameCreator(); // Mark the game as resolved. @@ -464,6 +493,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard { if (proofTypeToProver[ProofType.TEE] == address(0)) revert MissingProof(ProofType.TEE); if (proofTypeToProver[ProofType.ZK] != address(0)) revert AlreadyProven(ProofType.ZK); + // Prevents challenging after TEE nullification. + if (proofCount != 1) revert NotEnoughProofs(); + (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); // The game must be a valid game used to challenge. @@ -480,6 +512,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard { // Set the game as challenged. status = GameStatus.CHALLENGER_WINS; + // Prevent resolution if any proof was somehow able to be provided later. + proofCount = NEGATIVE_PROOF_COUNT; + + // Update the expected resolution. + _updateExpectedResolution(); + // Set the bond recipient. // Bond cannot be claimed until the game used to challenge resolves as DEFENDER_WINS. bondRecipient = challengingGame.zkProver(); @@ -508,6 +546,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { if (proposedIntermediateRoot == intermediateRootToProve) revert IntermediateRootSameAsProposed(); ProofType proofType = ProofType(uint8(proofBytes[0])); + if (proofTypeToProver[proofType] == address(0)) revert MissingProof(proofType); bytes32 startingRoot = intermediateRootIndex == 0 @@ -529,9 +568,30 @@ contract AggregateVerifier is Clone, ReentrancyGuard { abi.encodePacked(intermediateRootToProve) ); - // Set the game as challenged so that child games can't resolve. - status = GameStatus.CHALLENGER_WINS; - // Refund the bond. This can override a challenge. + if (proofType == ProofType.ZK) { + // Since a ZK proof vetoes a TEE proof, we make the proof count negative for ZK nullifications. + // This ensures that the game cannot resolve if a TEE proof can somehow be provided later. + proofCount = NEGATIVE_PROOF_COUNT; + + // Set the game as challenged so that child games can't resolve. + status = GameStatus.CHALLENGER_WINS; + } else if (proofType == ProofType.TEE) { + // The status is not updated here to still allow a ZK proof to be provided later. + proofCount -= 1; + + // Increase the expected resolution by the SLOW_FINALIZATION_DELAY. + // This gives us enough time to nullify a ZK proof if it was already provided. + // Otherwise the below _updateExpectedResolution() makes the expected resolution + // the maximum timestamp. + expectedResolution = Timestamp.wrap(uint64(block.timestamp + SLOW_FINALIZATION_DELAY)); + } + + // If there are no proofs, the expected resolution will be set to type(uint64).max. + // It's not possible to go from FAST_FINALIZATION_DELAY to SLOW_FINALIZATION_DELAY + // as we can only do a ZK nullification in this case, causing proofCount to be negative. + _updateExpectedResolution(); + + // Refund the bond as either a ZK proof was nullified or a ZK proof has to be provided later. bondRecipient = gameCreator(); emit Nullified(msg.sender, intermediateRootIndex, intermediateRootToProve); @@ -708,12 +768,18 @@ contract AggregateVerifier is Clone, ReentrancyGuard { /// @notice Updates the expected resolution timestamp. function _updateExpectedResolution() internal { - bool hasTee = proofTypeToProver[ProofType.TEE] != address(0); - bool hasZk = proofTypeToProver[ProofType.ZK] != address(0); + uint64 delay; - if (!hasTee && !hasZk) revert NoProofProvided(); + if (proofCount >= 2) { + delay = FAST_FINALIZATION_DELAY; + } else if (proofCount >= 1) { + delay = SLOW_FINALIZATION_DELAY; + } else { + // If there are no proofs, don't allow the game to resolve. + expectedResolution = Timestamp.wrap(type(uint64).max); + return; + } - uint64 delay = (hasTee && hasZk) ? FAST_FINALIZATION_DELAY : SLOW_FINALIZATION_DELAY; uint64 newResolution = uint64(block.timestamp) + delay; expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, expectedResolution.raw()))); } @@ -726,6 +792,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard { bondRecipient = gameCreator(); } + proofCount += 1; + _updateExpectedResolution(); } @@ -773,7 +841,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard { } /// @notice Verifies a TEE proof for the current game. - /// @param proofBytes The proof: prover(20) + signature (65). + /// @param proofBytes The proof: signature(65). function _verifyTeeProof( bytes calldata proofBytes, address prover, @@ -914,4 +982,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard { revert L1OriginHashMismatch(l1OriginHash, actualHash); } } + + /// @notice Semantic version. + /// @custom:semver 0.1.0 + function version() public pure virtual returns (string memory) { + return "0.1.0"; + } } diff --git a/src/multiproof/mocks/MockCertManager.sol b/src/multiproof/mocks/MockCertManager.sol deleted file mode 100644 index f7e46c64..00000000 --- a/src/multiproof/mocks/MockCertManager.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; - -/// @title MockCertManager -/// @notice Mock CertManager for testing SystemConfigGlobal. -contract MockCertManager is ICertManager { - function verifyCACert(bytes memory, bytes32) external pure returns (bytes32) { - return bytes32(0); - } - - function verifyClientCert(bytes memory, bytes32) external pure returns (VerifiedCert memory) { - return VerifiedCert({ ca: false, notAfter: 0, maxPathLen: 0, subjectHash: bytes32(0), pubKey: "" }); - } -} diff --git a/src/multiproof/mocks/MockDevSystemConfigGlobal.sol b/src/multiproof/mocks/MockDevSystemConfigGlobal.sol index 3f931be6..dfd61fa5 100644 --- a/src/multiproof/mocks/MockDevSystemConfigGlobal.sol +++ b/src/multiproof/mocks/MockDevSystemConfigGlobal.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; @@ -10,7 +12,7 @@ import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; /// @dev This contract adds addDevSigner() which bypasses AWS Nitro attestation verification. /// DO NOT deploy this contract to production networks. contract DevSystemConfigGlobal is SystemConfigGlobal { - constructor(ICertManager certManager) SystemConfigGlobal(certManager) { } + constructor(INitroEnclaveVerifier nitroVerifier) SystemConfigGlobal(nitroVerifier) { } /// @notice Registers a signer for testing (bypasses attestation verification). /// @dev Only callable by owner. For development/testing use only. diff --git a/src/multiproof/tee/SystemConfigGlobal.sol b/src/multiproof/tee/SystemConfigGlobal.sol index 0570ca3d..3cce9895 100644 --- a/src/multiproof/tee/SystemConfigGlobal.sol +++ b/src/multiproof/tee/SystemConfigGlobal.sol @@ -1,22 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { LibCborElement, CborElement, CborDecode } from "lib/nitro-validator/src/CborDecode.sol"; -import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; -import { LibBytes } from "lib/nitro-validator/src/LibBytes.sol"; -import { NitroValidator } from "lib/nitro-validator/src//NitroValidator.sol"; +import { + INitroEnclaveVerifier, + ZkCoProcessorType, + VerifierJournal, + VerificationResult, + Pcr, + Bytes48 +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; import { OwnableManagedUpgradeable } from "lib/op-enclave/contracts/src/OwnableManagedUpgradeable.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title SystemConfigGlobal -/// @notice Manages TEE signer registration via AWS Nitro attestation. -/// @dev Signers are registered by providing a valid AWS Nitro attestation document. +/// @notice Manages TEE signer registration via ZK-verified AWS Nitro attestation. +/// @dev Signers are registered by providing a ZK proof of a valid AWS Nitro attestation document, +/// verified through an external NitroEnclaveVerifier contract (Risc0). /// Each signer is associated with the PCR0 (enclave image hash) from their attestation, /// which allows TEEVerifier to validate that a signer was registered with a specific image. -contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { - using LibBytes for bytes; - using CborDecode for bytes; - using LibCborElement for CborElement; - +contract SystemConfigGlobal is OwnableManagedUpgradeable, ISemver { /// @notice Maximum age of an attestation document (60 minutes), in seconds. uint256 public constant MAX_AGE = 60 minutes; @@ -25,13 +27,15 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { /// 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. - /// This replaces the old validSigners(address => bool) mapping to enable imageId validation. mapping(address => bytes32) public signerPCR0; /// @notice Mapping of whether an address is a valid proposer. @@ -58,9 +62,17 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { /// @notice Thrown when the attestation document is too old. error AttestationTooOld(); - constructor(ICertManager certManager) NitroValidator(certManager) { - // Always disable the implementation contract by setting dead addresses. - // Proxies will call initialize() to set the real owner/manager. + /// @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) }); } @@ -88,27 +100,33 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { emit PCR0Deregistered(pcr0Hash); } - /// @notice Registers a signer using an AWS Nitro attestation document. - /// @dev The attestation must: - /// 1. Be signed by a valid AWS Nitro certificate chain - /// 2. Contain a PCR0 that has been pre-registered via registerPCR0 - /// 3. Be less than MAX_AGE old - /// @param attestationTbs The TBS (to-be-signed) portion of the attestation document. - /// @param signature The signature over the attestation. - function registerSigner(bytes calldata attestationTbs, bytes calldata signature) external onlyOwnerOrManager { - Ptrs memory ptrs = validateAttestation(attestationTbs, signature); - bytes32 pcr0Hash = attestationTbs.keccak(ptrs.pcrs[0]); + /// @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(); - // Convert attestation timestamp from milliseconds to seconds before comparing - if (ptrs.timestamp / MS_PER_SECOND + MAX_AGE <= block.timestamp) revert AttestationTooOld(); - // The publicKey is encoded in the form specified in section 4.3.6 of ANSI X9.62, - // which is a 0x04 byte followed by the x and y coordinates of the public key. - // We ignore the first byte when hashing. - bytes32 publicKeyHash = attestationTbs.keccak(ptrs.publicKey.start() + 1, ptrs.publicKey.length() - 1); + // 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))); - // Store the PCR0 hash for this signer (enables imageId validation) signerPCR0[enclaveAddress] = pcr0Hash; emit SignerRegistered(enclaveAddress, pcr0Hash); } @@ -141,4 +159,15 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, NitroValidator { function version() public pure virtual returns (string memory) { return "0.1.0"; } + + /// @dev Finds PCR0 (index 0) in the PCR array and returns its keccak256 hash. + function _extractPCR0Hash(Pcr[] memory pcrs) internal pure returns (bytes32) { + for (uint256 i = 0; i < pcrs.length; i++) { + if (pcrs[i].index == 0) { + Bytes48 memory value = pcrs[i].value; + return keccak256(abi.encodePacked(value.first, value.second)); + } + } + revert PCR0NotFound(); + } } diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index c3785776..5bbf4e90 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.15; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; @@ -12,11 +13,9 @@ import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; /// @dev This contract is designed to be used as the TEE_VERIFIER in the AggregateVerifier. /// It verifies that proofs are signed by enclave addresses registered in SystemConfigGlobal /// via AWS Nitro attestation, and that the signer's PCR0 matches the claimed imageId. -/// Additionally, it verifies that the L1 origin block referenced in the proof actually exists -/// by checking against blockhash() or the EIP-2935 history contract. -/// The contract is intentionally stateless - all state related to output proposals is -/// managed by the calling contract (e.g., AggregateVerifier). -contract TEEVerifier is IVerifier { +/// The contract is intentionally stateless - all state related to output proposals and +/// L1 origin verification is managed by the calling contract (e.g., AggregateVerifier). +contract TEEVerifier is IVerifier, ISemver { /// @notice The SystemConfigGlobal contract that manages valid TEE signers. /// @dev Signers are registered via AWS Nitro attestation in SystemConfigGlobal. SystemConfigGlobal public immutable SYSTEM_CONFIG_GLOBAL; @@ -43,15 +42,15 @@ contract TEEVerifier is IVerifier { } /// @notice Verifies a TEE proof for a state transition. - /// @param proofBytes The proof: proposer (20) + l1OriginHash (32) + l1OriginNumber (32) + signature (65) = 149 - /// bytes. @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. + /// @param proofBytes The proof: proposer(20) + signature(65) = 85 bytes. + /// @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. /// @param journal The keccak256 hash of the proof's public inputs. /// @return valid Whether the proof is valid. function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view override returns (bool) { - if (proofBytes.length < 149) revert InvalidProofFormat(); + if (proofBytes.length < 85) revert InvalidProofFormat(); address proposer = address(bytes20(proofBytes[0:20])); - bytes calldata signature = proofBytes[84:149]; + 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) @@ -81,4 +80,10 @@ contract TEEVerifier is IVerifier { 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/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index 8a666695..d7d00dff 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -37,6 +37,7 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.bondRecipient(), address(0)); assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); + assertEq(game.proofCount(), 1); } function testInitializeWithZKProof() public { @@ -62,6 +63,7 @@ contract AggregateVerifierTest is BaseTest { 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 { @@ -149,6 +151,7 @@ contract AggregateVerifierTest is BaseTest { _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, teeProof); _provideProof(game, ZK_PROVER, zkProof); + assertEq(game.proofCount(), 2); // Unlock bond uint256 balanceBefore = game.gameCreator().balance; @@ -326,7 +329,8 @@ contract AggregateVerifierTest is BaseTest { CONFIG_HASH, L2_CHAIN_ID, 0, - INTERMEDIATE_BLOCK_INTERVAL + INTERMEDIATE_BLOCK_INTERVAL, + PROOF_THRESHOLD ); // Case 2: INTERMEDIATE_BLOCK_INTERVAL is 0 @@ -342,7 +346,8 @@ contract AggregateVerifierTest is BaseTest { CONFIG_HASH, L2_CHAIN_ID, BLOCK_INTERVAL, - 0 + 0, + PROOF_THRESHOLD ); // Case 3: BLOCK_INTERVAL is not divisible by INTERMEDIATE_BLOCK_INTERVAL @@ -358,7 +363,44 @@ contract AggregateVerifierTest is BaseTest { CONFIG_HASH, L2_CHAIN_ID, 3, - 2 + 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 index e7172722..69cd7dd7 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -37,6 +37,7 @@ contract BaseTest is Test { 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; @@ -131,7 +132,8 @@ contract BaseTest is Test { CONFIG_HASH, L2_CHAIN_ID, BLOCK_INTERVAL, - INTERMEDIATE_BLOCK_INTERVAL + INTERMEDIATE_BLOCK_INTERVAL, + PROOF_THRESHOLD ); // Set the implementation for the aggregate verifier diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 561cd308..3d0ef2f6 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -38,6 +38,8 @@ contract ChallengeTest is BaseTest { assertEq(game1.bondRecipient(), ZK_PROVER); address counteredBy = game1.counteredByGameAddress(); assertEq(counteredBy, address(game2)); + assertEq(game1.proofCount(), -128); + assertEq(game1.expectedResolution().raw(), type(uint64).max); // Retrieve bond after challenge vm.warp(block.timestamp + 7 days); @@ -190,4 +192,43 @@ contract ChallengeTest is BaseTest { vm.expectRevert(AggregateVerifier.InvalidGame.selector); game.challenge(gameIndex); } + + function testChallengeFailsAfterTEENullification() public { + currentL2BlockNumber += BLOCK_INTERVAL; + + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); + + AggregateVerifier game = + _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); + + game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + // challenge game + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(AggregateVerifier.NotEnoughProofs.selector); + game.challenge(gameIndex); + } + + function testChallengeFailsAfterZKNullification() public { + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); + + AggregateVerifier game = + _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1); + + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); + + game.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + + // challenge game + uint256 gameIndex = factory.gameCount() - 1; + vm.expectRevert(ClaimAlreadyResolved.selector); + game.challenge(gameIndex); + } } diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index e7a36056..9ee51c06 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -23,8 +23,10 @@ contract NullifyTest is BaseTest { game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); - assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); assertEq(game.bondRecipient(), TEE_PROVER); + assertEq(game.proofCount(), 0); + assertEq(game.expectedResolution().raw(), type(uint64).max); uint256 balanceBefore = game.gameCreator().balance; game.claimCredit(); @@ -50,6 +52,8 @@ contract NullifyTest is BaseTest { assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(game1.bondRecipient(), ZK_PROVER); + assertEq(game1.proofCount(), -128); + assertEq(game1.expectedResolution().raw(), type(uint64).max); uint256 balanceBefore = game1.gameCreator().balance; game1.claimCredit(); @@ -59,22 +63,28 @@ contract NullifyTest is BaseTest { assertEq(delayedWETH.balanceOf(address(game1)), 0); } - function testTEENullifyFailsIfNoTEEProof() public { + function testNullifyWithTEEProofWhenTEEAndZKProofsAreProvided() public { currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); - AggregateVerifier game1 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof); + 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 teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); + game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); - vm.expectRevert( - abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) - ); - game1.nullify(teeProof, 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 { diff --git a/test/multiproof/SystemConfigGlobal.t.sol b/test/multiproof/SystemConfigGlobal.t.sol index d6b77961..10636cbf 100644 --- a/test/multiproof/SystemConfigGlobal.t.sol +++ b/test/multiproof/SystemConfigGlobal.t.sol @@ -6,24 +6,23 @@ import { Test } from "forge-std/Test.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; -import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; -import { MockCertManager } from "src/multiproof/mocks/MockCertManager.sol"; - /// @notice Tests for SystemConfigGlobal and DevSystemConfigGlobal contracts. /// @dev IMPORTANT: This test file uses DevSystemConfigGlobal as the implementation because -/// registering signers on the production SystemConfigGlobal requires valid AWS Nitro attestation -/// documents, which cannot be generated in a test environment. DevSystemConfigGlobal extends +/// registering signers on the production SystemConfigGlobal requires a ZK proof of a valid +/// AWS Nitro attestation, which cannot be generated in a test environment. DevSystemConfigGlobal extends /// SystemConfigGlobal with an `addDevSigner` function that bypasses attestation verification, /// allowing us to test all signer-related functionality. All tests for base SystemConfigGlobal /// functionality (PCR0 management, ownership, proposer, etc.) are equally valid since /// DevSystemConfigGlobal inherits from SystemConfigGlobal without modifying those functions. contract SystemConfigGlobalTest is Test { DevSystemConfigGlobal public systemConfigGlobal; - MockCertManager public certManager; ProxyAdmin public proxyAdmin; address public owner; @@ -48,11 +47,9 @@ contract SystemConfigGlobalTest is Test { pcr0Hash = keccak256(TEST_PCR0); - // Deploy mock cert manager - certManager = new MockCertManager(); - // Deploy implementation (using DevSystemConfigGlobal for test flexibility) - DevSystemConfigGlobal impl = new DevSystemConfigGlobal(ICertManager(address(certManager))); + // NitroEnclaveVerifier is not needed since tests use addDevSigner(), so pass address(0). + DevSystemConfigGlobal impl = new DevSystemConfigGlobal(INitroEnclaveVerifier(address(0))); // Deploy proxy admin proxyAdmin = new ProxyAdmin(address(this)); diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index af3b91e0..3a1095d9 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -6,18 +6,17 @@ import { Test } from "forge-std/Test.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; -import { ICertManager } from "lib/nitro-validator/src/ICertManager.sol"; +import { + INitroEnclaveVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; -import { MockCertManager } from "src/multiproof/mocks/MockCertManager.sol"; - contract TEEVerifierTest is Test { TEEVerifier public verifier; DevSystemConfigGlobal public systemConfigGlobal; - MockCertManager public certManager; ProxyAdmin public proxyAdmin; // Test signer - we'll derive address from private key @@ -36,11 +35,8 @@ contract TEEVerifierTest is Test { // Derive signer address from private key signerAddress = vm.addr(SIGNER_PRIVATE_KEY); - // Deploy mock cert manager - certManager = new MockCertManager(); - - // Deploy implementation - DevSystemConfigGlobal impl = new DevSystemConfigGlobal(ICertManager(address(certManager))); + // Deploy implementation (NitroEnclaveVerifier not needed for dev signer tests) + DevSystemConfigGlobal impl = new DevSystemConfigGlobal(INitroEnclaveVerifier(address(0))); // Deploy proxy admin proxyAdmin = new ProxyAdmin(address(this)); @@ -66,16 +62,12 @@ contract TEEVerifierTest is Test { // Create a journal hash bytes32 journal = keccak256("test-journal"); - // Get current block info for L1 origin - uint256 l1OriginNumber = block.number - 1; - bytes32 l1OriginHash = blockhash(l1OriginNumber); - // 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) + l1OriginHash (32) + l1OriginNumber (32) + signature (65) - bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, signature); + // 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); @@ -85,14 +77,11 @@ contract TEEVerifierTest is Test { function testVerifyFailsWithInvalidSignature() public { bytes32 journal = keccak256("test-journal"); - uint256 l1OriginNumber = block.number - 1; - bytes32 l1OriginHash = blockhash(l1OriginNumber); - // 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, l1OriginHash, l1OriginNumber, invalidSignature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, invalidSignature); vm.expectRevert(TEEVerifier.InvalidSignature.selector); verifier.verify(proofBytes, IMAGE_ID, journal); @@ -102,16 +91,12 @@ contract TEEVerifierTest is Test { // Create a journal hash bytes32 journal = keccak256("test-journal"); - // Get current block info for L1 origin - uint256 l1OriginNumber = block.number - 1; - bytes32 l1OriginHash = blockhash(l1OriginNumber); - // 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) + l1OriginHash (32) + l1OriginNumber (32) + signature (65) - bytes memory proofBytes = abi.encodePacked(address(0), l1OriginHash, l1OriginNumber, signature); + // 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); @@ -124,13 +109,10 @@ contract TEEVerifierTest is Test { bytes32 journal = keccak256("test-journal"); - uint256 l1OriginNumber = block.number - 1; - bytes32 l1OriginHash = blockhash(l1OriginNumber); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(unregisteredKey, journal); bytes memory signature = abi.encodePacked(r, s, v); - bytes memory proofBytes = abi.encodePacked(PROPOSER, l1OriginHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, signature); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidSigner.selector, unregisteredSigner)); verifier.verify(proofBytes, IMAGE_ID, journal); @@ -139,13 +121,10 @@ contract TEEVerifierTest is Test { function testVerifyFailsWithImageIdMismatch() public { bytes32 journal = keccak256("test-journal"); - uint256 l1OriginNumber = block.number - 1; - bytes32 l1OriginHash = blockhash(l1OriginNumber); - (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, l1OriginHash, l1OriginNumber, signature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, signature); // Use a different imageId that doesn't match the registered PCR0 bytes32 wrongImageId = keccak256("wrong-image-id"); @@ -157,8 +136,8 @@ contract TEEVerifierTest is Test { function testVerifyFailsWithInvalidProofFormat() public { bytes32 journal = keccak256("test-journal"); - // Proof too short (less than 129 bytes) - bytes memory shortProof = new bytes(100); + // Proof too short (less than 85 bytes) + bytes memory shortProof = new bytes(50); vm.expectRevert(TEEVerifier.InvalidProofFormat.selector); verifier.verify(shortProof, IMAGE_ID, journal); diff --git a/test/opcm/DeployImplementations.t.sol b/test/opcm/DeployImplementations.t.sol index 1a4f4162..73315b82 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 c5f94582..b8520079 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/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index e43ec8c2..306eb262 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -22,6 +22,7 @@ import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol" import { ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; +import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; /// @title Initializer_Test /// @dev Ensures that the `initialize()` function on contracts cannot be called more than @@ -338,34 +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 - contracts.push( - InitializeableContract({ - name: "ETHLockboxProxy", - target: address(ethLockbox), - initCalldata: abi.encodeCall( - ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) - ) - }) - ); + // 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. - // SystemConfigGlobal (Multiproof) + // SystemConfigGlobalImpl contracts.push( InitializeableContract({ name: "SystemConfigGlobalImpl", target: address(systemConfigGlobal), - initCalldata: abi.encodeCall(systemConfigGlobal.initialize, (address(0), address(0))) + initCalldata: abi.encodeCall(SystemConfigGlobal.initialize, (address(0), address(0))) }) ); } @@ -377,7 +384,7 @@ contract Initializer_Test is CommonTest { function test_cannotReinitialize_succeeds() public { // Collect exclusions. uint256 j; - string[] memory excludes = new string[](17); + 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,7 +401,6 @@ contract Initializer_Test is CommonTest { excludes[j++] = "src/dispute/PermissionedDisputeGame.sol"; excludes[j++] = "src/dispute/SuperPermissionedDisputeGame.sol"; excludes[j++] = "src/dispute/zk/OPSuccinctFaultDisputeGame.sol"; - excludes[j++] = "src/mutliproof/AggregateVerifier.sol"; // TODO: Eventually remove this exclusion. Same reason as above dispute contracts. excludes[j++] = "src/L1/OPContractsManager.sol"; // TODO: Eventually remove this exclusion. Same reason as above dispute contracts. @@ -404,8 +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"; - // Mock contracts are not deployed as part of the standard deployment script. + // 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); From cd2c74c1703249eb3e86844fd36c3e469b2db87d Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 12:17:41 -0500 Subject: [PATCH 107/125] Simplify AggregateVerifier and make it more modular for further proof systems --- interfaces/multiproof/IVerifier.sol | 13 ++ src/multiproof/AggregateVerifier.sol | 232 +++++++++++++++------------ src/multiproof/tee/TEEVerifier.sol | 15 ++ 3 files changed, 159 insertions(+), 101 deletions(-) diff --git a/interfaces/multiproof/IVerifier.sol b/interfaces/multiproof/IVerifier.sol index d6a6b874..25c2c1f1 100644 --- a/interfaces/multiproof/IVerifier.sol +++ b/interfaces/multiproof/IVerifier.sol @@ -2,5 +2,18 @@ pragma solidity 0.8.15; interface IVerifier { + /// @notice When the prover is nullified to prevent further proof verification. + error SoundnessIssue(); + + /// @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/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 21d255d4..d3f9d1d4 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -56,10 +56,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The maximum number of blocks that EIP-2935 can look back (~8192). uint256 public constant EIP2935_WINDOW = 8191; - - /// @notice For when the game no longer accepts proofs and prevents resolution. - int8 internal constant NEGATIVE_PROOF_COUNT = type(int8).min; - //////////////////////////////////////////////////////////////// // Immutables // //////////////////////////////////////////////////////////////// @@ -141,17 +137,19 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The amount of the bond. uint256 public bondAmount; - /// @notice The address of the game that countered this game. - address public counteredByGameAddress; + /// @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. - /// @dev Can be negative if a ZK proof is nullified. int8 public proofCount; //////////////////////////////////////////////////////////////// @@ -164,8 +162,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. /// @param challenger The address of the challenger. - /// @param game The game used to challenge this proposal. - event Challenged(address indexed challenger, IDisputeGame game); + /// @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. @@ -405,9 +403,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { intermediateOutputRoots() ); - _updateProvingData(proofType, gameCreator()); + _proofVerifiedUpdate(proofType, gameCreator()); - emit Proved(gameCreator(), proofType); + // Set the bond recipient to the creator. It can change if challenged successfully. + bondRecipient = gameCreator(); // Deposit the bond. bondAmount = msg.value; @@ -438,9 +437,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { l2SequenceNumber(), intermediateOutputRoots() ); - _updateProvingData(proofType, msg.sender); - - emit Proved(msg.sender, proofType); + _proofVerifiedUpdate(proofType, msg.sender); } /// @notice Resolves the game after a proof has been provided and enough time has passed. @@ -452,21 +449,27 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // 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(); - status = GameStatus.DEFENDER_WINS; + // 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; } // casting to 'int256' is safe because 1 <= PROOF_THRESHOLD <= 2 // forge-lint: disable-next-line(unsafe-typecast) if (proofCount < int256(PROOF_THRESHOLD)) revert NotEnoughProofs(); - // Bond is refunded as no challenge was made or parent is invalid. - bondRecipient = gameCreator(); + // 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); @@ -475,10 +478,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } /// @notice Challenges the TEE proof with a ZK proof. - /// @param gameIndex The index of the game used to challenge. - /// @dev The game used to challenge must have a ZK proof for the same - /// block number but a different root claim as the current game. - function challenge(uint256 gameIndex) external { + /// @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(); @@ -493,37 +496,42 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { if (proofTypeToProver[ProofType.TEE] == address(0)) revert MissingProof(ProofType.TEE); if (proofTypeToProver[ProofType.ZK] != address(0)) revert AlreadyProven(ProofType.ZK); - // Prevents challenging after TEE nullification. - if (proofCount != 1) revert NotEnoughProofs(); - - (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); + _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); - // The game must be a valid game used to challenge. - if (!_isValidChallengingGame(game)) revert InvalidGame(); - - AggregateVerifier challengingGame = AggregateVerifier(address(game)); + // Can only challenge with a ZK proof. + ProofType proofType = ProofType(uint8(proofBytes[0])); + if (proofType != ProofType.ZK) revert InvalidProofType(); - // The ZK prover must not be empty. - if (challengingGame.zkProver() == address(0)) revert MissingProof(ProofType.ZK); + (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); - // Update the counteredBy address. - counteredByGameAddress = address(challengingGame); + _verifyProof( + proofBytes[1:], + proofType, + msg.sender, + l1Head().raw(), + startingRoot, + startingL2SequenceNumber, + intermediateRootToProve, + endingL2SequenceNumber, + abi.encodePacked(intermediateRootToProve) + ); - // Set the game as challenged. - status = GameStatus.CHALLENGER_WINS; + // This allows a ZK nullification to be performed. + proofTypeToProver[proofType] = msg.sender; - // Prevent resolution if any proof was somehow able to be provided later. - proofCount = NEGATIVE_PROOF_COUNT; + // 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; - // Update the expected resolution. - _updateExpectedResolution(); + // We purposely increase the resolution to allow for a ZK nullification. + expectedResolution = Timestamp.wrap(uint64(block.timestamp + SLOW_FINALIZATION_DELAY)); - // Set the bond recipient. - // Bond cannot be claimed until the game used to challenge resolves as DEFENDER_WINS. - bondRecipient = challengingGame.zkProver(); + // Store which intermediate root was countered. + counteredByIntermediateRootIndexPlusOne = intermediateRootIndex + 1; // Emit the challenged event. - emit Challenged(challengingGame.zkProver(), game); + emit Challenged(msg.sender, intermediateRootIndex); } /// @notice Nullifies the game if a soundness issue is found. @@ -538,24 +546,22 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { ) external { - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - if (intermediateRootIndex >= intermediateOutputRootsCount()) revert InvalidIntermediateRootIndex(); + // Can only nullify if the game is still in progress. + if (status != GameStatus.IN_PROGRESS) revert GameAlreadyResolved(); - bytes32 proposedIntermediateRoot = intermediateOutputRoot(intermediateRootIndex); - if (proposedIntermediateRoot == intermediateRootToProve) revert IntermediateRootSameAsProposed(); + _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); ProofType proofType = ProofType(uint8(proofBytes[0])); - if (proofTypeToProver[proofType] == address(0)) revert MissingProof(proofType); - bytes32 startingRoot = intermediateRootIndex == 0 - ? startingOutputRoot.root.raw() - : intermediateOutputRoot(intermediateRootIndex - 1); - uint256 startingL2SequenceNumber = - startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL; - uint256 endingL2SequenceNumber = startingL2SequenceNumber + INTERMEDIATE_BLOCK_INTERVAL; + // 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 (proofType != ProofType.ZK) revert InvalidProofType(); + } + (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); + _verifyProof( proofBytes[1:], proofType, @@ -568,33 +574,19 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { abi.encodePacked(intermediateRootToProve) ); - if (proofType == ProofType.ZK) { - // Since a ZK proof vetoes a TEE proof, we make the proof count negative for ZK nullifications. - // This ensures that the game cannot resolve if a TEE proof can somehow be provided later. - proofCount = NEGATIVE_PROOF_COUNT; - - // Set the game as challenged so that child games can't resolve. - status = GameStatus.CHALLENGER_WINS; - } else if (proofType == ProofType.TEE) { - // The status is not updated here to still allow a ZK proof to be provided later. - proofCount -= 1; - - // Increase the expected resolution by the SLOW_FINALIZATION_DELAY. - // This gives us enough time to nullify a ZK proof if it was already provided. - // Otherwise the below _updateExpectedResolution() makes the expected resolution - // the maximum timestamp. - expectedResolution = Timestamp.wrap(uint64(block.timestamp + SLOW_FINALIZATION_DELAY)); - } + _proofRefutedUpdate(proofType); - // If there are no proofs, the expected resolution will be set to type(uint64).max. - // It's not possible to go from FAST_FINALIZATION_DELAY to SLOW_FINALIZATION_DELAY - // as we can only do a ZK nullification in this case, causing proofCount to be negative. - _updateExpectedResolution(); - - // Refund the bond as either a ZK proof was nullified or a ZK proof has to be provided later. - bondRecipient = gameCreator(); + // If the ZK proof was nullified, delete the countered intermediate root index. + if (proofType == ProofType.ZK) delete counteredByIntermediateRootIndexPlusOne; emit Nullified(msg.sender, intermediateRootIndex, intermediateRootToProve); + + // Nullify the verifier to prevent further proof verification. + if (proofType == ProofType.ZK) { + 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 @@ -606,17 +598,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // The bond recipient must not be empty. if (bondRecipient == address(0)) revert BondRecipientEmpty(); - // If this game was challenged, the countered by game must be valid or else the bond is refunded. - if (counteredByGameAddress != address(0)) { - GameStatus counteredByGameStatus = IDisputeGame(counteredByGameAddress).status(); - if (counteredByGameStatus == GameStatus.IN_PROGRESS) { - revert CounteredByGameNotResolved(); - } - // If the countered by game is invalid or not resolved, the bond is refunded. - if (!_isValidChallengingGame(IDisputeGame(counteredByGameAddress))) { - bondRecipient = gameCreator(); - } - } + // The game must be over. + if (!gameOver()) revert GameNotOver(); if (!bondUnlocked) { DELAYED_WETH.unlock(bondRecipient, bondAmount); @@ -766,13 +749,22 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { return _getArgUint32(0x74); } - /// @notice Updates the expected resolution timestamp. - function _updateExpectedResolution() internal { + 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) { + } else if (proofCount == 1) { delay = SLOW_FINALIZATION_DELAY; } else { // If there are no proofs, don't allow the game to resolve. @@ -780,21 +772,36 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { return; } + // Only allow decreases to the expected resolution. uint64 newResolution = uint64(block.timestamp) + delay; expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, expectedResolution.raw()))); } - function _updateProvingData(ProofType proofType, address prover) internal { - proofTypeToProver[proofType] = prover; + /// @dev Should only occur if challenged or nullified. + function _proofRefutedUpdate(ProofType proofType) internal { + delete proofTypeToProver[proofType]; + proofCount -= 1; - // Bond can be reclaimed after a ZK proof is provided. - if (proofType == ProofType.ZK) { - bondRecipient = gameCreator(); - } + _increaseExpectedResolution(); + } - proofCount += 1; + 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; + } - _updateExpectedResolution(); + // 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( @@ -983,6 +990,29 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } } + /// @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) { diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 5bbf4e90..f7c7c4b6 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -5,6 +5,7 @@ import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; @@ -20,6 +21,10 @@ contract TEEVerifier is IVerifier, ISemver { /// @dev Signers are registered via AWS Nitro attestation in SystemConfigGlobal. SystemConfigGlobal public immutable SYSTEM_CONFIG_GLOBAL; + /// @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 recovered signer is not a valid registered signer. error InvalidSigner(address signer); @@ -41,12 +46,22 @@ contract TEEVerifier is IVerifier, ISemver { SYSTEM_CONFIG_GLOBAL = systemConfigGlobal; } + /// @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; + } + /// @notice Verifies a TEE proof for a state transition. /// @param proofBytes The proof: proposer(20) + signature(65) = 85 bytes. /// @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. /// @param journal The keccak256 hash of the proof's public inputs. /// @return valid Whether the proof is valid. function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view override returns (bool) { + if (nullified) revert SoundnessIssue(); + if (proofBytes.length < 85) revert InvalidProofFormat(); address proposer = address(bytes20(proofBytes[0:20])); From c9a3748d99322fdeecab4a113b0a664c76f60e8f Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 12:44:31 -0500 Subject: [PATCH 108/125] allow bond refund after 14 days --- src/multiproof/AggregateVerifier.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index d3f9d1d4..ae086ece 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -598,8 +598,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // The bond recipient must not be empty. if (bondRecipient == address(0)) revert BondRecipientEmpty(); - // The game must be over. - if (!gameOver()) revert GameNotOver(); + // The game must be over 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 (!gameOver()) revert GameNotOver(); + } else { + if (block.timestamp < createdAt.raw() + 14 days) revert GameNotOver(); + } if (!bondUnlocked) { DELAYED_WETH.unlock(bondRecipient, bondAmount); From 1f81c50c993d322df8865daf82c656e90180e669 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 15:26:57 -0500 Subject: [PATCH 109/125] fix build and tests --- interfaces/multiproof/IVerifier.sol | 2 - scripts/deploy/DeployImplementations.s.sol | 4 +- scripts/multiproof/DeployDevNoNitro.s.sol | 6 +- scripts/multiproof/DeployDevWithNitro.s.sol | 6 +- src/multiproof/AggregateVerifier.sol | 9 +- src/multiproof/Verifier.sol | 48 +++++++ src/multiproof/mocks/MockVerifier.sol | 9 +- src/multiproof/tee/TEEVerifier.sol | 23 +--- test/multiproof/AggregateVerifier.t.sol | 26 ++-- test/multiproof/BaseTest.t.sol | 4 +- test/multiproof/Challenge.t.sol | 139 ++++++++------------ test/multiproof/Nullify.t.sol | 35 +++-- test/multiproof/TEEVerifier.t.sol | 6 +- 13 files changed, 166 insertions(+), 151 deletions(-) create mode 100644 src/multiproof/Verifier.sol diff --git a/interfaces/multiproof/IVerifier.sol b/interfaces/multiproof/IVerifier.sol index 25c2c1f1..e1ae29a5 100644 --- a/interfaces/multiproof/IVerifier.sol +++ b/interfaces/multiproof/IVerifier.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.15; interface IVerifier { - /// @notice When the prover is nullified to prevent further proof verification. - error SoundnessIssue(); /// @notice Verifies a proof. /// @param proofBytes The proof. diff --git a/scripts/deploy/DeployImplementations.s.sol b/scripts/deploy/DeployImplementations.s.sol index a4676f0d..6ba8570a 100644 --- a/scripts/deploy/DeployImplementations.s.sol +++ b/scripts/deploy/DeployImplementations.s.sol @@ -717,14 +717,14 @@ contract DeployImplementations is Script { } function deployAggregateVerifierImpl(Input memory _input, Output memory _output) private { - address zkVerifier = address(new MockVerifier()); + address zkVerifier = address(new MockVerifier(_output.anchorStateRegistryImpl)); address teeVerifierImpl; { SystemConfigGlobal scgImpl = new SystemConfigGlobal(INitroEnclaveVerifier(_input.nitroEnclaveVerifier)); vm.label(address(scgImpl), "SystemConfigGlobalImpl"); _output.systemConfigGlobalImpl = scgImpl; - teeVerifierImpl = address(new TEEVerifier(scgImpl)); + teeVerifierImpl = address(new TEEVerifier(scgImpl, _output.anchorStateRegistryImpl)); } _output.aggregateVerifierImpl = IVerifier( diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 04f5cc9e..d02f9ce1 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -120,9 +120,9 @@ contract DeployDevNoNitro is Script { vm.startBroadcast(); - _deployTEEContracts(cfg.finalSystemOwner()); _registerProposer(cfg.teeProposer()); _deployInfrastructure(gameType); + _deployTEEContracts(cfg.finalSystemOwner()); _deployAggregateVerifier(gameType); vm.stopBroadcast(); @@ -140,7 +140,7 @@ contract DeployDevNoNitro is Script { ); console.log("DevSystemConfigGlobal:", systemConfigGlobalProxy); - teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); + teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy), IAnchorStateRegistry(mockAnchorRegistry))); console.log("TEEVerifier:", teeVerifier); } @@ -173,7 +173,7 @@ contract DeployDevNoNitro is Script { } function _deployAggregateVerifier(GameType gameType) internal { - address zkVerifier = address(new MockVerifier()); + address zkVerifier = address(new MockVerifier(IAnchorStateRegistry(mockAnchorRegistry))); console.log("MockVerifier (ZK):", zkVerifier); mockDelayedWETH = address(new MockDelayedWETH()); diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index 875674ae..67fce3b9 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -154,9 +154,9 @@ contract DeployDevWithNitro is Script { vm.startBroadcast(); - _deployTEEContracts(cfg.finalSystemOwner(), cfg.nitroEnclaveVerifier()); _registerProposer(cfg.teeProposer()); _deployInfrastructure(gameType); + _deployTEEContracts(cfg.finalSystemOwner(), cfg.nitroEnclaveVerifier()); _deployAggregateVerifier(gameType); vm.stopBroadcast(); @@ -175,7 +175,7 @@ contract DeployDevWithNitro is Script { ); console.log("SystemConfigGlobal:", systemConfigGlobalProxy); - teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy))); + teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy), IAnchorStateRegistry(mockAnchorRegistry))); console.log("TEEVerifier:", teeVerifier); } @@ -208,7 +208,7 @@ contract DeployDevWithNitro is Script { } function _deployAggregateVerifier(GameType gameType) internal { - address zkVerifier = address(new MockVerifier()); + address zkVerifier = address(new MockVerifier(IAnchorStateRegistry(mockAnchorRegistry))); console.log("MockVerifier (ZK):", zkVerifier); mockDelayedWETH = address(new MockDelayedWETH()); diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index ae086ece..0a34be2f 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -547,7 +547,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { external { // Can only nullify if the game is still in progress. - if (status != GameStatus.IN_PROGRESS) revert GameAlreadyResolved(); + if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); @@ -595,14 +595,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // The bond must not have been claimed yet. if (bondClaimed) revert NoCreditToClaim(); - // The bond recipient must not be empty. - if (bondRecipient == address(0)) revert BondRecipientEmpty(); - - // The game must be over or 14 days have passed since creation. + // 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 (!gameOver()) revert GameNotOver(); + if (resolvedAt.raw() == 0) revert GameNotResolved(); } else { if (block.timestamp < createdAt.raw() + 14 days) revert GameNotOver(); } diff --git a/src/multiproof/Verifier.sol b/src/multiproof/Verifier.sol new file mode 100644 index 00000000..6f37974c --- /dev/null +++ b/src/multiproof/Verifier.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: UNLICENSED +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; + } + + /// @notice Verifies a proof. + /// @param proofBytes The proof. + /// @param imageId The image ID. + /// @param journal The journal. + /// @return valid Whether the proof is valid. + /// @dev This function must be overridden by the concrete verifier implementation. + /// Ensure the modifier stays. + function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view virtual override notNullified returns (bool) {} +} \ No newline at end of file diff --git a/src/multiproof/mocks/MockVerifier.sol b/src/multiproof/mocks/MockVerifier.sol index fff83d15..b0527454 100644 --- a/src/multiproof/mocks/MockVerifier.sol +++ b/src/multiproof/mocks/MockVerifier.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; +import { Verifier } from "../Verifier.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -contract MockVerifier is IVerifier { - function verify(bytes calldata, bytes32, bytes32) external pure returns (bool) { +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/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index f7c7c4b6..d97ae6e3 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -3,11 +3,11 @@ pragma solidity 0.8.15; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; -import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; +import { Verifier } from "../Verifier.sol"; /// @title TEEVerifier /// @notice Stateless TEE proof verifier that validates signatures against registered signers. @@ -16,15 +16,11 @@ import { SystemConfigGlobal } from "./SystemConfigGlobal.sol"; /// via AWS Nitro attestation, and that the signer's PCR0 matches the claimed imageId. /// The contract is intentionally stateless - all state related to output proposals and /// L1 origin verification is managed by the calling contract (e.g., AggregateVerifier). -contract TEEVerifier is IVerifier, ISemver { +contract TEEVerifier is Verifier, ISemver { /// @notice The SystemConfigGlobal contract that manages valid TEE signers. /// @dev Signers are registered via AWS Nitro attestation in SystemConfigGlobal. SystemConfigGlobal public immutable SYSTEM_CONFIG_GLOBAL; - /// @notice 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 recovered signer is not a valid registered signer. error InvalidSigner(address signer); @@ -42,25 +38,16 @@ contract TEEVerifier is IVerifier, ISemver { /// @notice Constructs the TEEVerifier contract. /// @param systemConfigGlobal The SystemConfigGlobal contract address. - constructor(SystemConfigGlobal systemConfigGlobal) { + constructor(SystemConfigGlobal systemConfigGlobal, IAnchorStateRegistry anchorStateRegistry) Verifier(anchorStateRegistry) { SYSTEM_CONFIG_GLOBAL = systemConfigGlobal; } - /// @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; - } - /// @notice Verifies a TEE proof for a state transition. /// @param proofBytes The proof: proposer(20) + signature(65) = 85 bytes. /// @param imageId The claimed TEE image hash (PCR0). Must match the signer's registered PCR0. /// @param journal The keccak256 hash of the proof's public inputs. /// @return valid Whether the proof is valid. - function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view override returns (bool) { - if (nullified) revert SoundnessIssue(); + function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view override notNullified returns (bool) { if (proofBytes.length < 85) revert InvalidProofFormat(); diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index d7d00dff..a60f081e 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import { BadExtraData } from "src/dispute/lib/Errors.sol"; +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"; @@ -34,7 +34,7 @@ contract AggregateVerifierTest is BaseTest { assertEq( game.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max, game.intermediateOutputRoots()) ); - assertEq(game.bondRecipient(), address(0)); + assertEq(game.bondRecipient(), TEE_PROVER); assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); assertEq(game.proofCount(), 1); @@ -87,8 +87,8 @@ contract AggregateVerifierTest is BaseTest { AggregateVerifier game = _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); - // Cannot claim bond before resolving - vm.expectRevert(AggregateVerifier.BondRecipientEmpty.selector); + // Cannot claim bond before game is over + vm.expectRevert(GameNotResolved.selector); game.claimCredit(); // Resolve after 7 days @@ -120,6 +120,11 @@ contract AggregateVerifierTest is BaseTest { 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(); @@ -128,11 +133,6 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); assertEq(delayedWETH.balanceOf(address(game)), 0); - // Resolve after another 7 days - vm.warp(block.timestamp + 7 days); - game.resolve(); - assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); - // Update AnchorStateRegistry vm.warp(block.timestamp + 1); game.closeGame(); @@ -153,11 +153,7 @@ contract AggregateVerifierTest is BaseTest { _provideProof(game, ZK_PROVER, zkProof); assertEq(game.proofCount(), 2); - // Unlock bond - uint256 balanceBefore = game.gameCreator().balance; - game.claimCredit(); - - // Resolve after 1 day + // 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)); @@ -170,6 +166,8 @@ contract AggregateVerifierTest is BaseTest { 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); diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol index 69cd7dd7..c4781146 100644 --- a/test/multiproof/BaseTest.t.sol +++ b/test/multiproof/BaseTest.t.sol @@ -101,8 +101,8 @@ contract BaseTest is Test { delayedWETH = DelayedWETH(payable(address(delayedWETHProxy))); // Deploy the verifiers - teeVerifier = new MockVerifier(); - zkVerifier = new MockVerifier(); + teeVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); + zkVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); } function _initializeProxies() internal { diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index 3d0ef2f6..aefe5a4d 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -7,6 +7,7 @@ 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"; @@ -14,44 +15,37 @@ contract ChallengeTest is BaseTest { function testChallengeTEEProofWithZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; - // Create first game with TEE proof + // Create game with TEE proof Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - AggregateVerifier game1 = + AggregateVerifier game = _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); - // Create second game with different root claim and ZK proof + // Challenge game with ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); + vm.prank(ZK_PROVER); + game.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); - // Get game index from factory - uint256 gameIndex = factory.gameCount() - 1; + 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); - // Challenge game1 with game2 - game1.challenge(gameIndex); + // Resolve after SLOW_FINALIZATION_DELAY + vm.warp(block.timestamp + 7 days); + game.resolve(); - assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); - assertEq(game1.bondRecipient(), ZK_PROVER); - address counteredBy = game1.counteredByGameAddress(); - assertEq(counteredBy, address(game2)); - assertEq(game1.proofCount(), -128); - assertEq(game1.expectedResolution().raw(), type(uint64).max); + assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(game.bondRecipient(), ZK_PROVER); - // Retrieve bond after challenge - vm.warp(block.timestamp + 7 days); - game2.resolve(); - assertEq(uint8(game2.status()), uint8(GameStatus.DEFENDER_WINS)); - assertEq(ZK_PROVER.balance, 0); - assertEq(delayedWETH.balanceOf(address(game1)), INIT_BOND); - game1.claimCredit(); + uint256 balanceBefore = ZK_PROVER.balance; + game.claimCredit(); vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game1.claimCredit(); - assertEq(ZK_PROVER.balance, INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game1)), 0); + game.claimCredit(); + assertEq(ZK_PROVER.balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); } function testChallengeFailsIfNoTEEProof() public { @@ -64,45 +58,16 @@ contract ChallengeTest is BaseTest { AggregateVerifier game1 = _createAggregateVerifierGame(ZK_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, zkProof1); - // Create second game with different root claim and ZK proof - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + // Challenge game with ZK proof bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof2); - - uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) ); - game1.challenge(gameIndex); - } - - function testChallengeFailsIfDifferentParentIndex() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); - bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game1 = - _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof); - - // Create game2 with game1 as parent - uint256 game1Index = factory.gameCount() - 1; - uint256 nextBlockNumber = currentL2BlockNumber + BLOCK_INTERVAL; - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(nextBlockNumber, "zk"))); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - - // forge-lint: disable-next-line(unsafe-typecast) - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, nextBlockNumber, uint32(game1Index), zkProof); - - uint256 gameIndex = factory.gameCount() - 1; - - vm.expectRevert(AggregateVerifier.InvalidGame.selector); - game1.challenge(gameIndex); + game1.challenge(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); } - function testChallengeFailsIfChallengingGameHasNoZKProof() public { + function testChallengeFailsIfNotZKProof() public { currentL2BlockNumber += BLOCK_INTERVAL; Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); @@ -114,12 +79,8 @@ contract ChallengeTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, teeProof2); - - uint256 gameIndex = factory.gameCount() - 1; - - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.ZK)); - game1.challenge(gameIndex); + vm.expectRevert(AggregateVerifier.InvalidProofType.selector); + game1.challenge(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } function testChallengeFailsIfGameAlreadyResolved() public { @@ -139,11 +100,8 @@ contract ChallengeTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); - - uint256 challengeIndex1 = factory.gameCount() - 1; vm.expectRevert(ClaimAlreadyResolved.selector); - game1.challenge(challengeIndex1); + game1.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } function testChallengeFailsIfParentGameStatusIsChallenged() public { @@ -160,7 +118,7 @@ contract ChallengeTest is BaseTest { currentL2BlockNumber += BLOCK_INTERVAL; // create child game - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); bytes memory childProof = _generateProof("child-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier childGame = @@ -170,10 +128,12 @@ contract ChallengeTest is BaseTest { // blacklist parent game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); - // challenge child game - uint256 childGameIndex = factory.gameCount() - 1; + // 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(childGameIndex); + childGame.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim3.raw()); } function testChallengeFailsIfGameItselfIsBlacklisted() public { @@ -188,9 +148,11 @@ contract ChallengeTest is BaseTest { anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); // challenge game - uint256 gameIndex = factory.gameCount() - 1; + 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(gameIndex); + game.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } function testChallengeFailsAfterTEENullification() public { @@ -207,28 +169,37 @@ contract ChallengeTest is BaseTest { game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); - // challenge game - uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(AggregateVerifier.NotEnoughProofs.selector); - game.challenge(gameIndex); + // 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, "zk1"))); + 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, zkProof1); + _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 - uint256 gameIndex = factory.gameCount() - 1; - vm.expectRevert(ClaimAlreadyResolved.selector); - game.challenge(gameIndex); + // 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/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 9ee51c06..7b8d2b5c 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -import { GameNotInProgress } from "src/dispute/lib/Errors.sol"; +import { ClaimAlreadyResolved } from "src/dispute/lib/Errors.sol"; import { Claim, GameStatus } from "src/dispute/lib/Types.sol"; import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol"; @@ -28,6 +28,9 @@ contract NullifyTest is BaseTest { 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); @@ -50,11 +53,14 @@ contract NullifyTest is BaseTest { game1.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); - assertEq(uint8(game1.status()), uint8(GameStatus.CHALLENGER_WINS)); + assertEq(uint8(game1.status()), uint8(GameStatus.IN_PROGRESS)); assertEq(game1.bondRecipient(), ZK_PROVER); - assertEq(game1.proofCount(), -128); + 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); @@ -120,7 +126,7 @@ contract NullifyTest is BaseTest { Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - vm.expectRevert(GameNotInProgress.selector); + vm.expectRevert(ClaimAlreadyResolved.selector); game1.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); } @@ -133,23 +139,26 @@ contract NullifyTest is BaseTest { AggregateVerifier game1 = _createAggregateVerifierGame(TEE_PROVER, rootClaim1, currentL2BlockNumber, type(uint32).max, teeProof1); - // Challenge game1 + // Challenge game1 with ZK proof Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - AggregateVerifier game2 = - _createAggregateVerifierGame(ZK_PROVER, rootClaim2, currentL2BlockNumber, type(uint32).max, zkProof); - - uint256 challengeIndex = factory.gameCount() - 1; - game1.challenge(challengeIndex); - assertEq(game1.bondRecipient(), ZK_PROVER); + vm.prank(ZK_PROVER); + game1.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); // Nullify can override challenge - game2.nullify(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); + Claim rootClaim3 = 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, rootClaim3.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(); - assertEq(game1.bondRecipient(), TEE_PROVER); vm.warp(block.timestamp + DELAYED_WETH_DELAY); game1.claimCredit(); assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 3a1095d9..a16ce0d4 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -9,7 +9,9 @@ import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { INitroEnclaveVerifier } from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { MockAnchorStateRegistry } from "scripts/multiproof/mocks/MockAnchorStateRegistry.sol"; import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; @@ -18,6 +20,7 @@ contract TEEVerifierTest is Test { TEEVerifier public verifier; DevSystemConfigGlobal public systemConfigGlobal; ProxyAdmin public proxyAdmin; + MockAnchorStateRegistry public anchorStateRegistry; // Test signer - we'll derive address from private key uint256 internal constant SIGNER_PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; @@ -55,7 +58,8 @@ contract TEEVerifierTest is Test { systemConfigGlobal.setProposer(PROPOSER, true); // Deploy TEEVerifier - verifier = new TEEVerifier(SystemConfigGlobal(address(systemConfigGlobal))); + anchorStateRegistry = new MockAnchorStateRegistry(); + verifier = new TEEVerifier(SystemConfigGlobal(address(systemConfigGlobal)), IAnchorStateRegistry(address(anchorStateRegistry))); } function testVerifyValidSignature() public view { From b4e62b907bbd9eaa1793aab33fdeffeac2f8916e Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 15:27:25 -0500 Subject: [PATCH 110/125] forge fmt --- src/multiproof/AggregateVerifier.sol | 44 ++++++++++++++++++--------- src/multiproof/Verifier.sol | 20 ++++++++++-- src/multiproof/mocks/MockVerifier.sol | 2 +- src/multiproof/tee/TEEVerifier.sol | 20 ++++++++++-- test/multiproof/TEEVerifier.t.sol | 4 ++- 5 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 0a34be2f..6a4ae3ca 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -138,8 +138,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { 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. + /// @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. @@ -481,7 +481,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @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 { + 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(); @@ -502,7 +508,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { ProofType proofType = ProofType(uint8(proofBytes[0])); if (proofType != ProofType.ZK) revert InvalidProofType(); - (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); + (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = + _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); _verifyProof( proofBytes[1:], @@ -556,12 +563,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // 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 (intermediateRootIndex != counteredByIntermediateRootIndexPlusOne - 1) { + revert InvalidIntermediateRootIndex(); + } if (proofType != ProofType.ZK) revert InvalidProofType(); } - (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); - + (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = + _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); + _verifyProof( proofBytes[1:], proofType, @@ -596,7 +606,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { 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 + // 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(); @@ -993,20 +1003,26 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } } - /// @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. + /// @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(); + 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. + /// @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) { + function _getStartingIntermediateRootAndL2SequenceNumbers(uint256 intermediateRootIndex) + internal + view + returns (bytes32, uint256, uint256) + { bytes32 startingRoot = intermediateRootIndex == 0 ? startingOutputRoot.root.raw() : intermediateOutputRoot(intermediateRootIndex - 1); diff --git a/src/multiproof/Verifier.sol b/src/multiproof/Verifier.sol index 6f37974c..ed9419d2 100644 --- a/src/multiproof/Verifier.sol +++ b/src/multiproof/Verifier.sol @@ -33,7 +33,10 @@ abstract contract Verifier is IVerifier { /// @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(); + if ( + !ANCHOR_STATE_REGISTRY.isGameProper(IDisputeGame(msg.sender)) + || !ANCHOR_STATE_REGISTRY.isGameRespected(IDisputeGame(msg.sender)) + ) revert NotProperGame(); nullified = true; } @@ -44,5 +47,16 @@ abstract contract Verifier is IVerifier { /// @return valid Whether the proof is valid. /// @dev This function must be overridden by the concrete verifier implementation. /// Ensure the modifier stays. - function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view virtual override notNullified returns (bool) {} -} \ No newline at end of file + function verify( + bytes calldata proofBytes, + bytes32 imageId, + bytes32 journal + ) + external + view + virtual + override + notNullified + returns (bool) + { } +} diff --git a/src/multiproof/mocks/MockVerifier.sol b/src/multiproof/mocks/MockVerifier.sol index b0527454..c9093337 100644 --- a/src/multiproof/mocks/MockVerifier.sol +++ b/src/multiproof/mocks/MockVerifier.sol @@ -5,7 +5,7 @@ import { Verifier } from "../Verifier.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; contract MockVerifier is Verifier { - constructor(IAnchorStateRegistry anchorStateRegistry) Verifier(anchorStateRegistry) {} + 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/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index d97ae6e3..ea35faa4 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -38,7 +38,12 @@ contract TEEVerifier is Verifier, ISemver { /// @notice Constructs the TEEVerifier contract. /// @param systemConfigGlobal The SystemConfigGlobal contract address. - constructor(SystemConfigGlobal systemConfigGlobal, IAnchorStateRegistry anchorStateRegistry) Verifier(anchorStateRegistry) { + constructor( + SystemConfigGlobal systemConfigGlobal, + IAnchorStateRegistry anchorStateRegistry + ) + Verifier(anchorStateRegistry) + { SYSTEM_CONFIG_GLOBAL = systemConfigGlobal; } @@ -47,8 +52,17 @@ contract TEEVerifier is Verifier, ISemver { /// @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) { - + 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])); diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index a16ce0d4..3b19a0c5 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -59,7 +59,9 @@ contract TEEVerifierTest is Test { // Deploy TEEVerifier anchorStateRegistry = new MockAnchorStateRegistry(); - verifier = new TEEVerifier(SystemConfigGlobal(address(systemConfigGlobal)), IAnchorStateRegistry(address(anchorStateRegistry))); + verifier = new TEEVerifier( + SystemConfigGlobal(address(systemConfigGlobal)), IAnchorStateRegistry(address(anchorStateRegistry)) + ); } function testVerifyValidSignature() public view { From 85e463b932fb9556701f45d09f45f7f80ddeca15 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 15:56:36 -0500 Subject: [PATCH 111/125] forge fmt --- scripts/multiproof/DeployDevNoNitro.s.sol | 4 +++- scripts/multiproof/DeployDevWithNitro.s.sol | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index d02f9ce1..2507f851 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -140,7 +140,9 @@ contract DeployDevNoNitro is Script { ); console.log("DevSystemConfigGlobal:", systemConfigGlobalProxy); - teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy), IAnchorStateRegistry(mockAnchorRegistry))); + teeVerifier = address( + new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy), IAnchorStateRegistry(mockAnchorRegistry)) + ); console.log("TEEVerifier:", teeVerifier); } diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index 67fce3b9..ce54547f 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -175,7 +175,9 @@ contract DeployDevWithNitro is Script { ); console.log("SystemConfigGlobal:", systemConfigGlobalProxy); - teeVerifier = address(new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy), IAnchorStateRegistry(mockAnchorRegistry))); + teeVerifier = address( + new TEEVerifier(SystemConfigGlobal(systemConfigGlobalProxy), IAnchorStateRegistry(mockAnchorRegistry)) + ); console.log("TEEVerifier:", teeVerifier); } From cd07d7cfdcea80f3f0166ee58a1ff0b187fda68a Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 16:13:34 -0500 Subject: [PATCH 112/125] pr feedback --- src/multiproof/Verifier.sol | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/multiproof/Verifier.sol b/src/multiproof/Verifier.sol index ed9419d2..a9e1e4c3 100644 --- a/src/multiproof/Verifier.sol +++ b/src/multiproof/Verifier.sol @@ -39,24 +39,4 @@ abstract contract Verifier is IVerifier { ) revert NotProperGame(); nullified = true; } - - /// @notice Verifies a proof. - /// @param proofBytes The proof. - /// @param imageId The image ID. - /// @param journal The journal. - /// @return valid Whether the proof is valid. - /// @dev This function must be overridden by the concrete verifier implementation. - /// Ensure the modifier stays. - function verify( - bytes calldata proofBytes, - bytes32 imageId, - bytes32 journal - ) - external - view - virtual - override - notNullified - returns (bool) - { } } From 1f670f5937d1bf86d5949012cb043104d2be936e Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 16:28:52 -0500 Subject: [PATCH 113/125] semver lock --- snapshots/semver-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 08bc12a0..c391b592 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,12 +240,12 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x6e30a9816642f1ea2887f16223868fcd04ac80c3a42a4583dfc611d8331f8674", - "sourceCodeHash": "0xb9786dc79b4b494d81905235f7bda044c5b1a98ad82416829f5f6948be1175cc" + "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", + "sourceCodeHash": "0xa871d050ae46ea637a9c2ead9412a016d43d421fa0b946acc856a47f8f64f0f7" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { - "initCodeHash": "0xc59c62a455533735aa9337d2db88b255713bd4dde20d3345265ec2fafe62af70", - "sourceCodeHash": "0xb9786dc79b4b494d81905235f7bda044c5b1a98ad82416829f5f6948be1175cc" + "initCodeHash": "0x86ab8bc345cf9775a5a1b3734c8e4231d77c1e2c8e953cb1e4d9a358d19f6b96", + "sourceCodeHash": "0xa871d050ae46ea637a9c2ead9412a016d43d421fa0b946acc856a47f8f64f0f7" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", @@ -256,12 +256,12 @@ "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" }, "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { - "initCodeHash": "0x78317d9088a26523d938ce0d88ed0134abc60292c0cdb3f8a6a9530638fd9e9a", - "sourceCodeHash": "0x08e6d340b025c5a6c5997ef6fb69e6c14444f9e473b2c5337d6c10b6c89c0b4f" + "initCodeHash": "0x090330a5c64206b523fd5d9e199269268131035c62ff9eb518247a9024c4b703", + "sourceCodeHash": "0x87b0ad3f68294d64b584e54cc719cc3be0624cbab2940a0a341c502dcc69b4fc" }, "src/multiproof/tee/TEEVerifier.sol:TEEVerifier:dispute": { - "initCodeHash": "0x78317d9088a26523d938ce0d88ed0134abc60292c0cdb3f8a6a9530638fd9e9a", - "sourceCodeHash": "0x08e6d340b025c5a6c5997ef6fb69e6c14444f9e473b2c5337d6c10b6c89c0b4f" + "initCodeHash": "0x090330a5c64206b523fd5d9e199269268131035c62ff9eb518247a9024c4b703", + "sourceCodeHash": "0x87b0ad3f68294d64b584e54cc719cc3be0624cbab2940a0a341c502dcc69b4fc" }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { "initCodeHash": "0x1278027e3756e2989e80c0a7b513e221a5fe0d3dbd9ded108375a29b2c1f3d57", From 250534caf7f0fd19618601b9b1af88252c6e3483 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 17:01:50 -0500 Subject: [PATCH 114/125] Clean up AggregateVerifier --- snapshots/semver-lock.json | 8 ++++---- src/multiproof/AggregateVerifier.sol | 28 ---------------------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index c391b592..5873ef44 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,15 +240,15 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", - "sourceCodeHash": "0xa871d050ae46ea637a9c2ead9412a016d43d421fa0b946acc856a47f8f64f0f7" + "initCodeHash": "0x5b7bc4e863bb1c9340859eca396bc76b69d4fc0eadeb95c6d201367da8a65f9c", + "sourceCodeHash": "0x394b571921e5e17e224d0dca2637ef3dd02fec875ea605414c5939ebdd712904" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { "initCodeHash": "0x86ab8bc345cf9775a5a1b3734c8e4231d77c1e2c8e953cb1e4d9a358d19f6b96", - "sourceCodeHash": "0xa871d050ae46ea637a9c2ead9412a016d43d421fa0b946acc856a47f8f64f0f7" + "sourceCodeHash": "0x394b571921e5e17e224d0dca2637ef3dd02fec875ea605414c5939ebdd712904" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { - "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", + "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 6a4ae3ca..7ae984e0 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -217,18 +217,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice When an invalid proof type is provided. error InvalidProofType(); - /// @notice When no proof was provided. - error NoProofProvided(); - - /// @notice When the countered by game is invalid. - error InvalidCounteredByGame(); - - /// @notice When the countered by game is not resolved. - error CounteredByGameNotResolved(); - - /// @notice When the bond recipient is empty. - error BondRecipientEmpty(); - /// @notice When the intermediate root index is invalid. error InvalidIntermediateRootIndex(); @@ -950,22 +938,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { && !ANCHOR_STATE_REGISTRY.isGameRetired(game) && (game.status() != GameStatus.CHALLENGER_WINS); } - /// @notice Checks if the game is a valid game used to challenge or nullify. - /// @param game The game to check. - function _isValidChallengingGame(IDisputeGame game) internal view returns (bool) { - return - // The game type must be the same. - game.gameType().raw() == GAME_TYPE.raw() && - // The parent game must be the same. - AggregateVerifier(address(game)).parentIndex() == parentIndex() && - // The block number must be the same. - game.l2SequenceNumber() == l2SequenceNumber() && - // The root claim must be different. - game.rootClaim().raw() != rootClaim().raw() && - // The game must be valid. - _isValidGame(game); - } - /// @notice Verifies that the claimed L1 origin hash matches the actual blockhash. /// @param l1OriginHash The L1 block hash claimed in the proof. /// @param l1OriginNumber The L1 block number claimed in the proof. From 2d40f60ede2a36ecb7bd568f5a36ac5757140c98 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 17:07:31 -0500 Subject: [PATCH 115/125] Remove challenge after nullification --- snapshots/semver-lock.json | 8 ++++---- src/multiproof/AggregateVerifier.sol | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 5873ef44..dff9a54d 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,12 +240,12 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x5b7bc4e863bb1c9340859eca396bc76b69d4fc0eadeb95c6d201367da8a65f9c", - "sourceCodeHash": "0x394b571921e5e17e224d0dca2637ef3dd02fec875ea605414c5939ebdd712904" + "initCodeHash": "0x1e7244e2f137ecc7178226a728e43768c0f46cfb4646e35122135cb100298a5b", + "sourceCodeHash": "0xc0e2502c677ab48cebaaa0079c2fd5f6391f75b7b09ee2499077b3d14d043dd9" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { - "initCodeHash": "0x86ab8bc345cf9775a5a1b3734c8e4231d77c1e2c8e953cb1e4d9a358d19f6b96", - "sourceCodeHash": "0x394b571921e5e17e224d0dca2637ef3dd02fec875ea605414c5939ebdd712904" + "initCodeHash": "0xb85fcef72dd02c68abc4df29188a9ba4c6a0a7d79d84a2dfff3ca8b1766867f5", + "sourceCodeHash": "0xc0e2502c677ab48cebaaa0079c2fd5f6391f75b7b09ee2499077b3d14d043dd9" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 7ae984e0..b7a86f27 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -486,8 +486,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { if (_getParentGameStatus() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); // The TEE prover must not be empty. - // You should nullify the game if a ZK proof has already been provided. if (proofTypeToProver[ProofType.TEE] == address(0)) revert MissingProof(ProofType.TEE); + // 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); _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); @@ -582,6 +583,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // Nullify the verifier to prevent further proof verification. if (proofType == ProofType.ZK) { IVerifier(ZK_VERIFIER).nullify(); + // Delete the challenged intermediate root if one existed. + delete counteredByIntermediateRootIndexPlusOne; } else if (proofType == ProofType.TEE) { IVerifier(TEE_VERIFIER).nullify(); } From 51f36b2d6ce63229e9a5fa20db42b48c9928d1ea Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 17:23:00 -0500 Subject: [PATCH 116/125] semver lock --- snapshots/semver-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index dff9a54d..d248f044 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,7 +240,7 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x1e7244e2f137ecc7178226a728e43768c0f46cfb4646e35122135cb100298a5b", + "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", "sourceCodeHash": "0xc0e2502c677ab48cebaaa0079c2fd5f6391f75b7b09ee2499077b3d14d043dd9" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { From 34fb1c09c5a98ecf5b8b88683d611845a059f86f Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 17:43:52 -0500 Subject: [PATCH 117/125] Clean up --- snapshots/semver-lock.json | 8 ++++---- src/multiproof/AggregateVerifier.sol | 16 ++++------------ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index d248f044..74118ef1 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -241,14 +241,14 @@ }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", - "sourceCodeHash": "0xc0e2502c677ab48cebaaa0079c2fd5f6391f75b7b09ee2499077b3d14d043dd9" + "sourceCodeHash": "0xdfe97218b4fcfad4a8b198e574506088b2bfd34147b80d8145878f7120f88bc6" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { - "initCodeHash": "0xb85fcef72dd02c68abc4df29188a9ba4c6a0a7d79d84a2dfff3ca8b1766867f5", - "sourceCodeHash": "0xc0e2502c677ab48cebaaa0079c2fd5f6391f75b7b09ee2499077b3d14d043dd9" + "initCodeHash": "0x35e0845d63dd53d9ac2703e686c03971bd7c536db2a2917b9d2abe16744adf5e", + "sourceCodeHash": "0xdfe97218b4fcfad4a8b198e574506088b2bfd34147b80d8145878f7120f88bc6" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { - "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", + "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index b7a86f27..19c81bd0 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -150,7 +150,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { Timestamp public expectedResolution; /// @notice The number of proofs provided. - int8 public proofCount; + uint8 public proofCount; //////////////////////////////////////////////////////////////// // Events // @@ -450,9 +450,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { status = isChallenged ? GameStatus.CHALLENGER_WINS : GameStatus.DEFENDER_WINS; } - // casting to 'int256' is safe because 1 <= PROOF_THRESHOLD <= 2 - // forge-lint: disable-next-line(unsafe-typecast) - if (proofCount < int256(PROOF_THRESHOLD)) revert NotEnoughProofs(); + if (proofCount < PROOF_THRESHOLD) revert NotEnoughProofs(); // Default bond recipient is the creator. We only change if successfully challenged. if (isChallenged) { @@ -575,16 +573,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { _proofRefutedUpdate(proofType); - // If the ZK proof was nullified, delete the countered intermediate root index. - if (proofType == ProofType.ZK) delete counteredByIntermediateRootIndexPlusOne; - emit Nullified(msg.sender, intermediateRootIndex, intermediateRootToProve); // Nullify the verifier to prevent further proof verification. if (proofType == ProofType.ZK) { - IVerifier(ZK_VERIFIER).nullify(); // Delete the challenged intermediate root if one existed. delete counteredByIntermediateRootIndexPlusOne; + + IVerifier(ZK_VERIFIER).nullify(); } else if (proofType == ProofType.TEE) { IVerifier(TEE_VERIFIER).nullify(); } @@ -612,10 +608,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } bondClaimed = true; - // This can fail if this game was challenged and the countered by game is - // blacklisted/retired after it resolved to DEFENDER_WINS. - // The centralized functions in DELAYED_WETH will handle this as it's a already - // a very centralized action to blacklist/retire a valid challenging game. DELAYED_WETH.withdraw(bondRecipient, bondAmount); // Transfer the credit to the bond recipient. From 4009c978137288cd9fc6067527219ffa6f2a62f0 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 18:17:55 -0500 Subject: [PATCH 118/125] semver lock --- snapshots/semver-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 74118ef1..86480510 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,7 +240,7 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", + "initCodeHash": "0x977b0a7135a272c3e9f4a61a88cc6862fab35f967e5c26dc54e23924b28eb6ed", "sourceCodeHash": "0xdfe97218b4fcfad4a8b198e574506088b2bfd34147b80d8145878f7120f88bc6" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { @@ -248,7 +248,7 @@ "sourceCodeHash": "0xdfe97218b4fcfad4a8b198e574506088b2bfd34147b80d8145878f7120f88bc6" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { - "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", + "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { From 86fa133e3610ae90ab5206302065821e287c9b84 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 5 Mar 2026 19:20:10 -0500 Subject: [PATCH 119/125] fix: nullifying zk challenge has to use original intermediate root --- snapshots/semver-lock.json | 10 +++++----- src/multiproof/AggregateVerifier.sol | 5 +++-- test/multiproof/Nullify.t.sol | 3 +-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 86480510..5b3e9850 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,15 +240,15 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x977b0a7135a272c3e9f4a61a88cc6862fab35f967e5c26dc54e23924b28eb6ed", - "sourceCodeHash": "0xdfe97218b4fcfad4a8b198e574506088b2bfd34147b80d8145878f7120f88bc6" + "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", + "sourceCodeHash": "0x480582750c1e118198ca1077dfe25d910785377c3e29c340c72a3e23e83a476f" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { - "initCodeHash": "0x35e0845d63dd53d9ac2703e686c03971bd7c536db2a2917b9d2abe16744adf5e", - "sourceCodeHash": "0xdfe97218b4fcfad4a8b198e574506088b2bfd34147b80d8145878f7120f88bc6" + "initCodeHash": "0x019b91d97231d2b4d236208d4f0dafa4b0e33b3cbcc8061319ec09ebdb8b8888", + "sourceCodeHash": "0x480582750c1e118198ca1077dfe25d910785377c3e29c340c72a3e23e83a476f" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { - "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", + "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 19c81bd0..d716feed 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -543,8 +543,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // Can only nullify if the game is still in progress. if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); - _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); - ProofType proofType = ProofType(uint8(proofBytes[0])); if (proofTypeToProver[proofType] == address(0)) revert MissingProof(proofType); @@ -553,7 +551,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { 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) = diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 7b8d2b5c..4674fc38 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -147,9 +147,8 @@ contract NullifyTest is BaseTest { game1.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); // Nullify can override challenge - Claim rootClaim3 = 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, rootClaim3.raw()); + game1.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); assertEq(game1.bondRecipient(), TEE_PROVER); From bd460e34d934267d545c2f067d9d85d57604b189 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 6 Mar 2026 09:09:34 -0500 Subject: [PATCH 120/125] forge fmt --- snapshots/semver-lock.json | 8 ++++---- src/multiproof/AggregateVerifier.sol | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 5b3e9850..485d8d44 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,15 +240,15 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", - "sourceCodeHash": "0x480582750c1e118198ca1077dfe25d910785377c3e29c340c72a3e23e83a476f" + "initCodeHash": "0xc458e7a57cb0f2291de0f609d94589bf242fcc7a7b2ab089983a9ef5d6e48354", + "sourceCodeHash": "0xd39f61ba9886b04a5adc0350739cf23bb7fb7a76bed04afcb380398247295c57" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { "initCodeHash": "0x019b91d97231d2b4d236208d4f0dafa4b0e33b3cbcc8061319ec09ebdb8b8888", - "sourceCodeHash": "0x480582750c1e118198ca1077dfe25d910785377c3e29c340c72a3e23e83a476f" + "sourceCodeHash": "0xd39f61ba9886b04a5adc0350739cf23bb7fb7a76bed04afcb380398247295c57" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { - "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", + "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index d716feed..67de9614 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -551,7 +551,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { if (intermediateRootIndex != counteredByIntermediateRootIndexPlusOne - 1) { revert InvalidIntermediateRootIndex(); } - if (intermediateRootToProve != intermediateOutputRoot(intermediateRootIndex)) revert IntermediateRootMismatch(intermediateRootToProve, intermediateOutputRoot(intermediateRootIndex)); + if (intermediateRootToProve != intermediateOutputRoot(intermediateRootIndex)) { + revert IntermediateRootMismatch(intermediateRootToProve, intermediateOutputRoot(intermediateRootIndex)); + } if (proofType != ProofType.ZK) revert InvalidProofType(); } else { _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); From 8c6c89c850583804e6047e1ebc0ed7adcc2a4e7e Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 6 Mar 2026 15:27:01 -0500 Subject: [PATCH 121/125] fix: use starting anchor root when there is no parent game --- src/multiproof/AggregateVerifier.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 67de9614..94af4655 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -349,8 +349,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { l2SequenceNumber: parentGame.l2SequenceNumber(), root: Hash.wrap(parentGame.rootClaim().raw()) }); } else { - // When there is no parent game, the starting output root is the anchor state for the game type. - (startingOutputRoot.root, startingOutputRoot.l2SequenceNumber) = ANCHOR_STATE_REGISTRY.getAnchorRoot(); + // 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. From 71f4f0ca801b28cda218f09b5d31398d81b28038 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 6 Mar 2026 15:53:39 -0500 Subject: [PATCH 122/125] merge from main --- interfaces/multiproof/IVerifier.sol | 4 - scripts/deploy/DeployImplementations.s.sol | 8 - scripts/multiproof/DeployDevNoNitro.s.sol | 4 - snapshots/semver-lock.json | 10 +- src/multiproof/AggregateVerifier.sol | 237 --------------------- src/multiproof/mocks/MockVerifier.sol | 7 - test/multiproof/AggregateVerifier.t.sol | 35 --- test/multiproof/TEEVerifier.t.sol | 11 - 8 files changed, 5 insertions(+), 311 deletions(-) diff --git a/interfaces/multiproof/IVerifier.sol b/interfaces/multiproof/IVerifier.sol index 14c50d2d..e1ae29a5 100644 --- a/interfaces/multiproof/IVerifier.sol +++ b/interfaces/multiproof/IVerifier.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.15; interface IVerifier { -<<<<<<< simplify-and-modularize-aggregate-verifier /// @notice Verifies a proof. /// @param proofBytes The proof. @@ -15,7 +14,4 @@ interface IVerifier { /// @dev Should only occur if a soundness issue is found. /// @dev Should only be callable by a proper dispute game. function nullify() external; -======= - function verify(bytes calldata proofBytes, bytes32 imageId, bytes32 journal) external view returns (bool); ->>>>>>> main } diff --git a/scripts/deploy/DeployImplementations.s.sol b/scripts/deploy/DeployImplementations.s.sol index 044bd513..6ba8570a 100644 --- a/scripts/deploy/DeployImplementations.s.sol +++ b/scripts/deploy/DeployImplementations.s.sol @@ -717,22 +717,14 @@ contract DeployImplementations is Script { } function deployAggregateVerifierImpl(Input memory _input, Output memory _output) private { -<<<<<<< simplify-and-modularize-aggregate-verifier address zkVerifier = address(new MockVerifier(_output.anchorStateRegistryImpl)); -======= - address zkVerifier = address(new MockVerifier()); ->>>>>>> main address teeVerifierImpl; { SystemConfigGlobal scgImpl = new SystemConfigGlobal(INitroEnclaveVerifier(_input.nitroEnclaveVerifier)); vm.label(address(scgImpl), "SystemConfigGlobalImpl"); _output.systemConfigGlobalImpl = scgImpl; -<<<<<<< simplify-and-modularize-aggregate-verifier teeVerifierImpl = address(new TEEVerifier(scgImpl, _output.anchorStateRegistryImpl)); -======= - teeVerifierImpl = address(new TEEVerifier(scgImpl)); ->>>>>>> main } _output.aggregateVerifierImpl = IVerifier( diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 13effadd..2507f851 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -175,11 +175,7 @@ contract DeployDevNoNitro is Script { } function _deployAggregateVerifier(GameType gameType) internal { -<<<<<<< simplify-and-modularize-aggregate-verifier address zkVerifier = address(new MockVerifier(IAnchorStateRegistry(mockAnchorRegistry))); -======= - address zkVerifier = address(new MockVerifier()); ->>>>>>> main console.log("MockVerifier (ZK):", zkVerifier); mockDelayedWETH = address(new MockDelayedWETH()); diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 485d8d44..83574d92 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,15 +240,15 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0xc458e7a57cb0f2291de0f609d94589bf242fcc7a7b2ab089983a9ef5d6e48354", - "sourceCodeHash": "0xd39f61ba9886b04a5adc0350739cf23bb7fb7a76bed04afcb380398247295c57" + "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", + "sourceCodeHash": "0x4fb3fe897e702eb2d47cfaf67fa5b9651112580ed47028a5f5b3b810866635af" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { - "initCodeHash": "0x019b91d97231d2b4d236208d4f0dafa4b0e33b3cbcc8061319ec09ebdb8b8888", - "sourceCodeHash": "0xd39f61ba9886b04a5adc0350739cf23bb7fb7a76bed04afcb380398247295c57" + "initCodeHash": "0x317136b944b743a70c447d78e362edc3e17cda44526ee97d90e66584ba64b21c", + "sourceCodeHash": "0x4fb3fe897e702eb2d47cfaf67fa5b9651112580ed47028a5f5b3b810866635af" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { - "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", + "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", "sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 9308792b..94af4655 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -56,13 +56,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The maximum number of blocks that EIP-2935 can look back (~8192). uint256 public constant EIP2935_WINDOW = 8191; -<<<<<<< simplify-and-modularize-aggregate-verifier -======= - - /// @notice For when the game no longer accepts proofs and prevents resolution. - int8 internal constant NEGATIVE_PROOF_COUNT = type(int8).min; - ->>>>>>> main //////////////////////////////////////////////////////////////// // Immutables // //////////////////////////////////////////////////////////////// @@ -144,7 +137,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The amount of the bond. uint256 public bondAmount; -<<<<<<< simplify-and-modularize-aggregate-verifier /// @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. @@ -152,24 +144,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @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. -======= - /// @notice The address of the game that countered this game. - address public counteredByGameAddress; - - /// @notice The address that provided a proof of the given type. ->>>>>>> main mapping(ProofType => address) internal proofTypeToProver; /// @notice The timestamp of the game's expected resolution. Timestamp public expectedResolution; /// @notice The number of proofs provided. -<<<<<<< simplify-and-modularize-aggregate-verifier uint8 public proofCount; -======= - /// @dev Can be negative if a ZK proof is nullified. - int8 public proofCount; ->>>>>>> main //////////////////////////////////////////////////////////////// // Events // @@ -181,13 +162,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice Emitted when a proposal with a TEE proof is challenged with a ZK proof. /// @param challenger The address of the challenger. -<<<<<<< simplify-and-modularize-aggregate-verifier /// @param intermediateRootIndex The index of the intermediate root that was countered. event Challenged(address indexed challenger, uint256 intermediateRootIndex); -======= - /// @param game The game used to challenge this proposal. - event Challenged(address indexed challenger, IDisputeGame game); ->>>>>>> main /// @notice Emitted when the game is proved. /// @param prover The address of the prover. @@ -241,21 +217,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice When an invalid proof type is provided. error InvalidProofType(); -<<<<<<< simplify-and-modularize-aggregate-verifier -======= - /// @notice When no proof was provided. - error NoProofProvided(); - - /// @notice When the countered by game is invalid. - error InvalidCounteredByGame(); - - /// @notice When the countered by game is not resolved. - error CounteredByGameNotResolved(); - - /// @notice When the bond recipient is empty. - error BondRecipientEmpty(); - ->>>>>>> main /// @notice When the intermediate root index is invalid. error InvalidIntermediateRootIndex(); @@ -388,13 +349,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { l2SequenceNumber: parentGame.l2SequenceNumber(), root: Hash.wrap(parentGame.rootClaim().raw()) }); } else { -<<<<<<< simplify-and-modularize-aggregate-verifier // When there is no parent game, the starting output root is the starting root in the AnchorStateRegistry. startingOutputRoot = ANCHOR_STATE_REGISTRY.getStartingAnchorRoot(); -======= - // When there is no parent game, the starting output root is the anchor state for the game type. - (startingOutputRoot.root, startingOutputRoot.l2SequenceNumber) = ANCHOR_STATE_REGISTRY.getAnchorRoot(); ->>>>>>> main } // The block number must be BLOCK_INTERVAL blocks after the starting block number. @@ -435,16 +391,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { intermediateOutputRoots() ); -<<<<<<< simplify-and-modularize-aggregate-verifier _proofVerifiedUpdate(proofType, gameCreator()); // Set the bond recipient to the creator. It can change if challenged successfully. bondRecipient = gameCreator(); -======= - _updateProvingData(proofType, gameCreator()); - - emit Proved(gameCreator(), proofType); ->>>>>>> main // Deposit the bond. bondAmount = msg.value; @@ -475,13 +425,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { l2SequenceNumber(), intermediateOutputRoots() ); -<<<<<<< simplify-and-modularize-aggregate-verifier _proofVerifiedUpdate(proofType, msg.sender); -======= - _updateProvingData(proofType, msg.sender); - - emit Proved(msg.sender, proofType); ->>>>>>> main } /// @notice Resolves the game after a proof has been provided and enough time has passed. @@ -493,18 +437,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // The parent game must have resolved. if (parentGameStatus == GameStatus.IN_PROGRESS) revert ParentGameNotResolved(); -<<<<<<< simplify-and-modularize-aggregate-verifier bool isChallenged = counteredByIntermediateRootIndexPlusOne > 0; -======= ->>>>>>> main // 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(); -<<<<<<< simplify-and-modularize-aggregate-verifier // 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; @@ -516,17 +456,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { if (isChallenged) { bondRecipient = proofTypeToProver[ProofType.ZK]; } -======= - status = GameStatus.DEFENDER_WINS; - } - - // casting to 'int256' is safe because 1 <= PROOF_THRESHOLD <= 2 - // forge-lint: disable-next-line(unsafe-typecast) - if (proofCount < int256(PROOF_THRESHOLD)) revert NotEnoughProofs(); - - // Bond is refunded as no challenge was made or parent is invalid. - bondRecipient = gameCreator(); ->>>>>>> main // Mark the game as resolved. resolvedAt = Timestamp.wrap(uint64(block.timestamp)); emit Resolved(status); @@ -535,7 +464,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } /// @notice Challenges the TEE proof with a ZK proof. -<<<<<<< simplify-and-modularize-aggregate-verifier /// @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. @@ -546,12 +474,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { ) external { -======= - /// @param gameIndex The index of the game used to challenge. - /// @dev The game used to challenge must have a ZK proof for the same - /// block number but a different root claim as the current game. - function challenge(uint256 gameIndex) external { ->>>>>>> main // Can only challenge a game that has not been challenged or resolved yet. if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); @@ -562,7 +484,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { if (_getParentGameStatus() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); // The TEE prover must not be empty. -<<<<<<< simplify-and-modularize-aggregate-verifier 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. @@ -605,43 +526,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // Emit the challenged event. emit Challenged(msg.sender, intermediateRootIndex); -======= - // You should nullify the game if a ZK proof has already been provided. - if (proofTypeToProver[ProofType.TEE] == address(0)) revert MissingProof(ProofType.TEE); - if (proofTypeToProver[ProofType.ZK] != address(0)) revert AlreadyProven(ProofType.ZK); - - // Prevents challenging after TEE nullification. - if (proofCount != 1) revert NotEnoughProofs(); - - (,, IDisputeGame game) = DISPUTE_GAME_FACTORY.gameAtIndex(gameIndex); - - // The game must be a valid game used to challenge. - if (!_isValidChallengingGame(game)) revert InvalidGame(); - - AggregateVerifier challengingGame = AggregateVerifier(address(game)); - - // The ZK prover must not be empty. - if (challengingGame.zkProver() == address(0)) revert MissingProof(ProofType.ZK); - - // Update the counteredBy address. - counteredByGameAddress = address(challengingGame); - - // Set the game as challenged. - status = GameStatus.CHALLENGER_WINS; - - // Prevent resolution if any proof was somehow able to be provided later. - proofCount = NEGATIVE_PROOF_COUNT; - - // Update the expected resolution. - _updateExpectedResolution(); - - // Set the bond recipient. - // Bond cannot be claimed until the game used to challenge resolves as DEFENDER_WINS. - bondRecipient = challengingGame.zkProver(); - - // Emit the challenged event. - emit Challenged(challengingGame.zkProver(), game); ->>>>>>> main } /// @notice Nullifies the game if a soundness issue is found. @@ -656,7 +540,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { ) external { -<<<<<<< simplify-and-modularize-aggregate-verifier // Can only nullify if the game is still in progress. if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); @@ -678,25 +561,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { (bytes32 startingRoot, uint256 startingL2SequenceNumber, uint256 endingL2SequenceNumber) = _getStartingIntermediateRootAndL2SequenceNumbers(intermediateRootIndex); -======= - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - if (intermediateRootIndex >= intermediateOutputRootsCount()) revert InvalidIntermediateRootIndex(); - - bytes32 proposedIntermediateRoot = intermediateOutputRoot(intermediateRootIndex); - if (proposedIntermediateRoot == intermediateRootToProve) revert IntermediateRootSameAsProposed(); - - ProofType proofType = ProofType(uint8(proofBytes[0])); - - if (proofTypeToProver[proofType] == address(0)) revert MissingProof(proofType); - - bytes32 startingRoot = intermediateRootIndex == 0 - ? startingOutputRoot.root.raw() - : intermediateOutputRoot(intermediateRootIndex - 1); - uint256 startingL2SequenceNumber = - startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL; - uint256 endingL2SequenceNumber = startingL2SequenceNumber + INTERMEDIATE_BLOCK_INTERVAL; ->>>>>>> main _verifyProof( proofBytes[1:], @@ -710,7 +574,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { abi.encodePacked(intermediateRootToProve) ); -<<<<<<< simplify-and-modularize-aggregate-verifier _proofRefutedUpdate(proofType); emit Nullified(msg.sender, intermediateRootIndex, intermediateRootToProve); @@ -724,35 +587,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } else if (proofType == ProofType.TEE) { IVerifier(TEE_VERIFIER).nullify(); } -======= - if (proofType == ProofType.ZK) { - // Since a ZK proof vetoes a TEE proof, we make the proof count negative for ZK nullifications. - // This ensures that the game cannot resolve if a TEE proof can somehow be provided later. - proofCount = NEGATIVE_PROOF_COUNT; - - // Set the game as challenged so that child games can't resolve. - status = GameStatus.CHALLENGER_WINS; - } else if (proofType == ProofType.TEE) { - // The status is not updated here to still allow a ZK proof to be provided later. - proofCount -= 1; - - // Increase the expected resolution by the SLOW_FINALIZATION_DELAY. - // This gives us enough time to nullify a ZK proof if it was already provided. - // Otherwise the below _updateExpectedResolution() makes the expected resolution - // the maximum timestamp. - expectedResolution = Timestamp.wrap(uint64(block.timestamp + SLOW_FINALIZATION_DELAY)); - } - - // If there are no proofs, the expected resolution will be set to type(uint64).max. - // It's not possible to go from FAST_FINALIZATION_DELAY to SLOW_FINALIZATION_DELAY - // as we can only do a ZK nullification in this case, causing proofCount to be negative. - _updateExpectedResolution(); - - // Refund the bond as either a ZK proof was nullified or a ZK proof has to be provided later. - bondRecipient = gameCreator(); - - emit Nullified(msg.sender, intermediateRootIndex, intermediateRootToProve); ->>>>>>> main } /// @notice Claim the credit belonging to the bond recipient. Reverts if the game isn't @@ -761,7 +595,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // The bond must not have been claimed yet. if (bondClaimed) revert NoCreditToClaim(); -<<<<<<< simplify-and-modularize-aggregate-verifier // 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. @@ -769,21 +602,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { if (resolvedAt.raw() == 0) revert GameNotResolved(); } else { if (block.timestamp < createdAt.raw() + 14 days) revert GameNotOver(); -======= - // The bond recipient must not be empty. - if (bondRecipient == address(0)) revert BondRecipientEmpty(); - - // If this game was challenged, the countered by game must be valid or else the bond is refunded. - if (counteredByGameAddress != address(0)) { - GameStatus counteredByGameStatus = IDisputeGame(counteredByGameAddress).status(); - if (counteredByGameStatus == GameStatus.IN_PROGRESS) { - revert CounteredByGameNotResolved(); - } - // If the countered by game is invalid or not resolved, the bond is refunded. - if (!_isValidChallengingGame(IDisputeGame(counteredByGameAddress))) { - bondRecipient = gameCreator(); - } ->>>>>>> main } if (!bondUnlocked) { @@ -793,13 +611,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } bondClaimed = true; -<<<<<<< simplify-and-modularize-aggregate-verifier -======= - // This can fail if this game was challenged and the countered by game is - // blacklisted/retired after it resolved to DEFENDER_WINS. - // The centralized functions in DELAYED_WETH will handle this as it's a already - // a very centralized action to blacklist/retire a valid challenging game. ->>>>>>> main DELAYED_WETH.withdraw(bondRecipient, bondAmount); // Transfer the credit to the bond recipient. @@ -937,7 +748,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { return _getArgUint32(0x74); } -<<<<<<< simplify-and-modularize-aggregate-verifier function _proofVerifiedUpdate(ProofType proofType, address prover) internal { proofTypeToProver[proofType] = prover; proofCount += 1; @@ -949,19 +759,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice Decreases the expected resolution timestamp. function _decreaseExpectedResolution() internal { -======= - /// @notice Updates the expected resolution timestamp. - function _updateExpectedResolution() internal { ->>>>>>> main uint64 delay; if (proofCount >= 2) { delay = FAST_FINALIZATION_DELAY; -<<<<<<< simplify-and-modularize-aggregate-verifier } else if (proofCount == 1) { -======= - } else if (proofCount >= 1) { ->>>>>>> main delay = SLOW_FINALIZATION_DELAY; } else { // If there are no proofs, don't allow the game to resolve. @@ -969,15 +771,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { return; } -<<<<<<< simplify-and-modularize-aggregate-verifier // Only allow decreases to the expected resolution. -======= ->>>>>>> main uint64 newResolution = uint64(block.timestamp) + delay; expectedResolution = Timestamp.wrap(uint64(FixedPointMathLib.min(newResolution, expectedResolution.raw()))); } -<<<<<<< simplify-and-modularize-aggregate-verifier /// @dev Should only occur if challenged or nullified. function _proofRefutedUpdate(ProofType proofType) internal { delete proofTypeToProver[proofType]; @@ -1003,19 +801,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // 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 _updateProvingData(ProofType proofType, address prover) internal { - proofTypeToProver[proofType] = prover; - - // Bond can be reclaimed after a ZK proof is provided. - if (proofType == ProofType.ZK) { - bondRecipient = gameCreator(); - } - - proofCount += 1; - - _updateExpectedResolution(); ->>>>>>> main } function _verifyProof( @@ -1151,25 +936,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { && !ANCHOR_STATE_REGISTRY.isGameRetired(game) && (game.status() != GameStatus.CHALLENGER_WINS); } -<<<<<<< simplify-and-modularize-aggregate-verifier -======= - /// @notice Checks if the game is a valid game used to challenge or nullify. - /// @param game The game to check. - function _isValidChallengingGame(IDisputeGame game) internal view returns (bool) { - return - // The game type must be the same. - game.gameType().raw() == GAME_TYPE.raw() && - // The parent game must be the same. - AggregateVerifier(address(game)).parentIndex() == parentIndex() && - // The block number must be the same. - game.l2SequenceNumber() == l2SequenceNumber() && - // The root claim must be different. - game.rootClaim().raw() != rootClaim().raw() && - // The game must be valid. - _isValidGame(game); - } - ->>>>>>> main /// @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. @@ -1207,7 +973,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } } -<<<<<<< simplify-and-modularize-aggregate-verifier /// @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. @@ -1237,8 +1002,6 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { return (startingRoot, startingL2SequenceNumber, endingL2SequenceNumber); } -======= ->>>>>>> main /// @notice Semantic version. /// @custom:semver 0.1.0 function version() public pure virtual returns (string memory) { diff --git a/src/multiproof/mocks/MockVerifier.sol b/src/multiproof/mocks/MockVerifier.sol index 8af3e0c1..c9093337 100644 --- a/src/multiproof/mocks/MockVerifier.sol +++ b/src/multiproof/mocks/MockVerifier.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -<<<<<<< simplify-and-modularize-aggregate-verifier import { Verifier } from "../Verifier.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; @@ -9,12 +8,6 @@ contract MockVerifier is Verifier { constructor(IAnchorStateRegistry anchorStateRegistry) Verifier(anchorStateRegistry) { } function verify(bytes calldata, bytes32, bytes32) external view override notNullified returns (bool) { -======= -import { IVerifier } from "interfaces/multiproof/IVerifier.sol"; - -contract MockVerifier is IVerifier { - function verify(bytes calldata, bytes32, bytes32) external pure returns (bool) { ->>>>>>> main return true; } } diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index f56fb633..a60f081e 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -1,11 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.15; -<<<<<<< simplify-and-modularize-aggregate-verifier import { BadExtraData, GameNotResolved } from "src/dispute/lib/Errors.sol"; -======= -import { BadExtraData } from "src/dispute/lib/Errors.sol"; ->>>>>>> main import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; @@ -38,11 +34,7 @@ contract AggregateVerifierTest is BaseTest { assertEq( game.extraData(), abi.encodePacked(currentL2BlockNumber, type(uint32).max, game.intermediateOutputRoots()) ); -<<<<<<< simplify-and-modularize-aggregate-verifier assertEq(game.bondRecipient(), TEE_PROVER); -======= - assertEq(game.bondRecipient(), address(0)); ->>>>>>> main assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); assertEq(game.proofCount(), 1); @@ -95,13 +87,8 @@ contract AggregateVerifierTest is BaseTest { AggregateVerifier game = _createAggregateVerifierGame(TEE_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); -<<<<<<< simplify-and-modularize-aggregate-verifier // Cannot claim bond before game is over vm.expectRevert(GameNotResolved.selector); -======= - // Cannot claim bond before resolving - vm.expectRevert(AggregateVerifier.BondRecipientEmpty.selector); ->>>>>>> main game.claimCredit(); // Resolve after 7 days @@ -133,14 +120,11 @@ contract AggregateVerifierTest is BaseTest { AggregateVerifier game = _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, type(uint32).max, proof); -<<<<<<< simplify-and-modularize-aggregate-verifier // Resolve after 7 days vm.warp(block.timestamp + 7 days); game.resolve(); assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); -======= ->>>>>>> main // Unlock and reclaim bond after delay uint256 balanceBefore = game.gameCreator().balance; game.claimCredit(); @@ -149,14 +133,6 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); assertEq(delayedWETH.balanceOf(address(game)), 0); -<<<<<<< simplify-and-modularize-aggregate-verifier -======= - // Resolve after another 7 days - vm.warp(block.timestamp + 7 days); - game.resolve(); - assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); - ->>>>>>> main // Update AnchorStateRegistry vm.warp(block.timestamp + 1); game.closeGame(); @@ -177,15 +153,7 @@ contract AggregateVerifierTest is BaseTest { _provideProof(game, ZK_PROVER, zkProof); assertEq(game.proofCount(), 2); -<<<<<<< simplify-and-modularize-aggregate-verifier // Resolve after 1 day (FAST_FINALIZATION_DELAY with 2 proofs) -======= - // Unlock bond - uint256 balanceBefore = game.gameCreator().balance; - game.claimCredit(); - - // Resolve after 1 day ->>>>>>> main vm.warp(block.timestamp + 1 days); game.resolve(); assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); @@ -198,11 +166,8 @@ contract AggregateVerifierTest is BaseTest { assertEq(l2SequenceNumber, currentL2BlockNumber); // Unlock and reclaim bond after delay -<<<<<<< simplify-and-modularize-aggregate-verifier uint256 balanceBefore = game.gameCreator().balance; game.claimCredit(); -======= ->>>>>>> main vm.warp(block.timestamp + DELAYED_WETH_DELAY); game.claimCredit(); assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index f7a355b3..3b19a0c5 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -9,13 +9,9 @@ import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { INitroEnclaveVerifier } from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; -<<<<<<< simplify-and-modularize-aggregate-verifier import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { MockAnchorStateRegistry } from "scripts/multiproof/mocks/MockAnchorStateRegistry.sol"; -======= - ->>>>>>> main import { DevSystemConfigGlobal } from "src/multiproof/mocks/MockDevSystemConfigGlobal.sol"; import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol"; import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol"; @@ -24,10 +20,7 @@ contract TEEVerifierTest is Test { TEEVerifier public verifier; DevSystemConfigGlobal public systemConfigGlobal; ProxyAdmin public proxyAdmin; -<<<<<<< simplify-and-modularize-aggregate-verifier MockAnchorStateRegistry public anchorStateRegistry; -======= ->>>>>>> main // Test signer - we'll derive address from private key uint256 internal constant SIGNER_PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; @@ -65,14 +58,10 @@ contract TEEVerifierTest is Test { systemConfigGlobal.setProposer(PROPOSER, true); // Deploy TEEVerifier -<<<<<<< simplify-and-modularize-aggregate-verifier anchorStateRegistry = new MockAnchorStateRegistry(); verifier = new TEEVerifier( SystemConfigGlobal(address(systemConfigGlobal)), IAnchorStateRegistry(address(anchorStateRegistry)) ); -======= - verifier = new TEEVerifier(SystemConfigGlobal(address(systemConfigGlobal))); ->>>>>>> main } function testVerifyValidSignature() public view { From f94c6a33df57cdd336957f23c04cff6fd3130bed Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 6 Mar 2026 16:16:32 -0500 Subject: [PATCH 123/125] forge clean and rebuild --- scripts/L2Genesis.s.sol | 12 ++--- scripts/deploy/Deploy.s.sol | 6 +-- snapshots/semver-lock.json | 4 +- src/L2/SuperchainETHBridge.sol | 8 +-- test/L1/OptimismPortal2.t.sol | 94 +++++++++++++++++----------------- 5 files changed, 62 insertions(+), 62 deletions(-) diff --git a/scripts/L2Genesis.s.sol b/scripts/L2Genesis.s.sol index 0b26f2cc..0031122d 100644 --- a/scripts/L2Genesis.s.sol +++ b/scripts/L2Genesis.s.sol @@ -553,10 +553,10 @@ contract L2Genesis is Script { ILiquidityController(Predeploys.LIQUIDITY_CONTROLLER) .initialize({ - _owner: _input.liquidityControllerOwner, - _gasPayingTokenName: _input.gasPayingTokenName, - _gasPayingTokenSymbol: _input.gasPayingTokenSymbol - }); + _owner: _input.liquidityControllerOwner, + _gasPayingTokenName: _input.gasPayingTokenName, + _gasPayingTokenSymbol: _input.gasPayingTokenSymbol + }); } /// @notice This predeploy is following the safety invariant #1. @@ -730,8 +730,8 @@ contract L2Genesis is Script { // Initialize the predeploy IFeeVault(payable(_vaultAddr)) .initialize({ - _recipient: recipient, _minWithdrawalAmount: minWithdrawalAmount, _withdrawalNetwork: network - }); + _recipient: recipient, _minWithdrawalAmount: minWithdrawalAmount, _withdrawalNetwork: network + }); } /// @notice Funds the default dev accounts with ether diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol index 77d2ddd4..dc47bfa3 100644 --- a/scripts/deploy/Deploy.s.sol +++ b/scripts/deploy/Deploy.s.sol @@ -393,9 +393,9 @@ contract Deploy is Deployer { vm.broadcast(address(deployOutput.opChainProxyAdmin)); IProxy(payable(delayedWETHPermissionlessGameProxy)) .upgradeToAndCall({ - _implementation: delayedWETHImpl, - _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) - }); + _implementation: delayedWETHImpl, + _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) + }); } //////////////////////////////////////////////////////////////// diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 83574d92..18762cc9 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -165,7 +165,7 @@ }, "src/L2/SuperchainETHBridge.sol:SuperchainETHBridge": { "initCodeHash": "0xa43665ad0f2b4f092ff04b12e38f24aa8d2cb34ae7a06fc037970743547bdf98", - "sourceCodeHash": "0xd45e857e7c182cc9f78f331b3bcc49ec15405ed9a75eabc4dceaefea94fc8677" + "sourceCodeHash": "0xb1f34902ef64b7cf9d658cca6f6fdac2c366fc941527bcb16d74864944893f24" }, "src/L2/SuperchainRevSharesCalculator.sol:SuperchainRevSharesCalculator": { "initCodeHash": "0xdfff95660d2d470e198054bb1717a30a45a806d2eaa3720fb43acaa3356c9a3e", @@ -240,7 +240,7 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0xa8fdf5cb9a7966da0aab10eb6ab59103ee401c6dbc9c02b5b18f7b92581cb9a0", + "initCodeHash": "0x24c7c851cb37c11121fcbfc20b2474d9f15122c020ac5b9894e371cf2f669c5b", "sourceCodeHash": "0x4fb3fe897e702eb2d47cfaf67fa5b9651112580ed47028a5f5b3b810866635af" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { diff --git a/src/L2/SuperchainETHBridge.sol b/src/L2/SuperchainETHBridge.sol index ee2d0238..bbfbd1bc 100644 --- a/src/L2/SuperchainETHBridge.sol +++ b/src/L2/SuperchainETHBridge.sol @@ -50,10 +50,10 @@ contract SuperchainETHBridge is ISemver { msgHash_ = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) .sendMessage({ - _destination: _chainId, - _target: address(this), - _message: abi.encodeCall(this.relayETH, (msg.sender, _to, msg.value)) - }); + _destination: _chainId, + _target: address(this), + _message: abi.encodeCall(this.relayETH, (msg.sender, _to, msg.value)) + }); emit SendETH(msg.sender, _to, msg.value, _chainId); } diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index fdbf4c36..fbd5206f 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -1172,11 +1172,11 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_WrongProofMethod.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1196,13 +1196,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_WrongProofMethod.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1225,13 +1225,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_InvalidSuperRootProof.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1261,13 +1261,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_InvalidOutputRootIndex.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: outputRootWithChainIdArr.length, // out of bounds - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: outputRootWithChainIdArr.length, // out of bounds + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1299,13 +1299,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_InvalidOutputRootChainId.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1337,13 +1337,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_InvalidOutputRootProof.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` succeeds when all parameters are valid. @@ -1370,13 +1370,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test // Should succeed. IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` succeeds. From 2e49e4cdd6d63ca03b751dc1d5aba55c57949fe8 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 6 Mar 2026 16:41:27 -0500 Subject: [PATCH 124/125] fix forge fmt --- scripts/L2Genesis.s.sol | 12 ++--- scripts/deploy/Deploy.s.sol | 6 +-- snapshots/semver-lock.json | 2 +- src/L2/SuperchainETHBridge.sol | 8 +-- test/L1/OptimismPortal2.t.sol | 94 +++++++++++++++++----------------- 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/scripts/L2Genesis.s.sol b/scripts/L2Genesis.s.sol index 0031122d..0b26f2cc 100644 --- a/scripts/L2Genesis.s.sol +++ b/scripts/L2Genesis.s.sol @@ -553,10 +553,10 @@ contract L2Genesis is Script { ILiquidityController(Predeploys.LIQUIDITY_CONTROLLER) .initialize({ - _owner: _input.liquidityControllerOwner, - _gasPayingTokenName: _input.gasPayingTokenName, - _gasPayingTokenSymbol: _input.gasPayingTokenSymbol - }); + _owner: _input.liquidityControllerOwner, + _gasPayingTokenName: _input.gasPayingTokenName, + _gasPayingTokenSymbol: _input.gasPayingTokenSymbol + }); } /// @notice This predeploy is following the safety invariant #1. @@ -730,8 +730,8 @@ contract L2Genesis is Script { // Initialize the predeploy IFeeVault(payable(_vaultAddr)) .initialize({ - _recipient: recipient, _minWithdrawalAmount: minWithdrawalAmount, _withdrawalNetwork: network - }); + _recipient: recipient, _minWithdrawalAmount: minWithdrawalAmount, _withdrawalNetwork: network + }); } /// @notice Funds the default dev accounts with ether diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol index dc47bfa3..77d2ddd4 100644 --- a/scripts/deploy/Deploy.s.sol +++ b/scripts/deploy/Deploy.s.sol @@ -393,9 +393,9 @@ contract Deploy is Deployer { vm.broadcast(address(deployOutput.opChainProxyAdmin)); IProxy(payable(delayedWETHPermissionlessGameProxy)) .upgradeToAndCall({ - _implementation: delayedWETHImpl, - _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) - }); + _implementation: delayedWETHImpl, + _data: abi.encodeCall(IDelayedWETH.initialize, (deployOutput.systemConfigProxy)) + }); } //////////////////////////////////////////////////////////////// diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 18762cc9..c57d90e7 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -165,7 +165,7 @@ }, "src/L2/SuperchainETHBridge.sol:SuperchainETHBridge": { "initCodeHash": "0xa43665ad0f2b4f092ff04b12e38f24aa8d2cb34ae7a06fc037970743547bdf98", - "sourceCodeHash": "0xb1f34902ef64b7cf9d658cca6f6fdac2c366fc941527bcb16d74864944893f24" + "sourceCodeHash": "0xd45e857e7c182cc9f78f331b3bcc49ec15405ed9a75eabc4dceaefea94fc8677" }, "src/L2/SuperchainRevSharesCalculator.sol:SuperchainRevSharesCalculator": { "initCodeHash": "0xdfff95660d2d470e198054bb1717a30a45a806d2eaa3720fb43acaa3356c9a3e", diff --git a/src/L2/SuperchainETHBridge.sol b/src/L2/SuperchainETHBridge.sol index bbfbd1bc..ee2d0238 100644 --- a/src/L2/SuperchainETHBridge.sol +++ b/src/L2/SuperchainETHBridge.sol @@ -50,10 +50,10 @@ contract SuperchainETHBridge is ISemver { msgHash_ = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) .sendMessage({ - _destination: _chainId, - _target: address(this), - _message: abi.encodeCall(this.relayETH, (msg.sender, _to, msg.value)) - }); + _destination: _chainId, + _target: address(this), + _message: abi.encodeCall(this.relayETH, (msg.sender, _to, msg.value)) + }); emit SendETH(msg.sender, _to, msg.value, _chainId); } diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index fbd5206f..fdbf4c36 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -1172,11 +1172,11 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_WrongProofMethod.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1196,13 +1196,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_WrongProofMethod.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1225,13 +1225,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_InvalidSuperRootProof.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1261,13 +1261,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_InvalidOutputRootIndex.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: outputRootWithChainIdArr.length, // out of bounds - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: outputRootWithChainIdArr.length, // out of bounds + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1299,13 +1299,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_InvalidOutputRootChainId.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version @@ -1337,13 +1337,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.expectRevert(IOptimismPortalInterop.OptimismPortal_InvalidOutputRootProof.selector); IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` succeeds when all parameters are valid. @@ -1370,13 +1370,13 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test // Should succeed. IOptimismPortalInterop(payable(optimismPortal2)) .proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameProxy: game, - _outputRootIndex: 0, - _superRootProof: superRootProof, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); } /// @notice Tests that `proveWithdrawalTransaction` succeeds. From 7ae44406c20aeab4014624f9d3f37629f29d8c87 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Tue, 10 Mar 2026 09:39:21 -0400 Subject: [PATCH 125/125] pr feedback --- snapshots/semver-lock.json | 8 ++++---- src/multiproof/AggregateVerifier.sol | 14 ++++++++++---- src/multiproof/Verifier.sol | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index c57d90e7..aa08cb81 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,12 +240,12 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x24c7c851cb37c11121fcbfc20b2474d9f15122c020ac5b9894e371cf2f669c5b", - "sourceCodeHash": "0x4fb3fe897e702eb2d47cfaf67fa5b9651112580ed47028a5f5b3b810866635af" + "initCodeHash": "0xc3866b1d4515c9d7b0ac6679b182d836f79371402d9e649e301b24cf8ae8fade", + "sourceCodeHash": "0x3a079ea52a26c8c38fb0cb3e9a9ff6ec9648cf83786b65c0fc1161e949b8f7e0" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { - "initCodeHash": "0x317136b944b743a70c447d78e362edc3e17cda44526ee97d90e66584ba64b21c", - "sourceCodeHash": "0x4fb3fe897e702eb2d47cfaf67fa5b9651112580ed47028a5f5b3b810866635af" + "initCodeHash": "0xe28eaeecda21594f6db23bb70127daa2b7b71debe38ce65b598f28d78d2561eb", + "sourceCodeHash": "0x3a079ea52a26c8c38fb0cb3e9a9ff6ec9648cf83786b65c0fc1161e949b8f7e0" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { "initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411", diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 94af4655..72499a1c 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -489,12 +489,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // This also prevents another challenge while the current challenge is in progress. if (proofTypeToProver[ProofType.ZK] != address(0)) revert AlreadyProven(ProofType.ZK); - _checkIntermediateRoot(intermediateRootIndex, intermediateRootToProve); - // 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); @@ -779,7 +779,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @dev Should only occur if challenged or nullified. function _proofRefutedUpdate(ProofType proofType) internal { delete proofTypeToProver[proofType]; - proofCount -= 1; + + // Should not be possible, but just in case. + if (proofCount == 0) revert NotEnoughProofs(); + unchecked { + proofCount -= 1; + } _increaseExpectedResolution(); } @@ -974,7 +979,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } /// @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. + /// 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(); diff --git a/src/multiproof/Verifier.sol b/src/multiproof/Verifier.sol index a9e1e4c3..a792f11d 100644 --- a/src/multiproof/Verifier.sol +++ b/src/multiproof/Verifier.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.15; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";