diff --git a/hardhat-test/ZkEvmVerifierV2.spec.ts b/hardhat-test/ZkEvmVerifierV2.spec.ts index f493fdf5..c6df8d30 100644 --- a/hardhat-test/ZkEvmVerifierV2.spec.ts +++ b/hardhat-test/ZkEvmVerifierV2.spec.ts @@ -95,11 +95,18 @@ describe("ZkEvmVerifierV2", async () => { const chainProxy = await TransparentUpgradeableProxy.deploy(empty.getAddress(), admin.getAddress(), "0x"); const ScrollChainMockBlob = await ethers.getContractFactory("ScrollChainMockBlob", deployer); - const chainImpl = await ScrollChainMockBlob.deploy(layer2ChainId, deployer.address, verifier.getAddress()); + const chainImpl = await ScrollChainMockBlob.deploy( + layer2ChainId, + deployer.address, + verifier.getAddress(), + verifier.getAddress(), + 0 + ); await admin.upgrade(chainProxy.getAddress(), chainImpl.getAddress()); chain = await ethers.getContractAt("ScrollChainMockBlob", await chainProxy.getAddress(), deployer); await chain.initialize(deployer.address, deployer.address, 100); + await chain.initializeV2(Number(BigInt(hexlify(publicInputs.subarray(8, 12))))); await chain.addProver(deployer.address); }); @@ -129,7 +136,7 @@ describe("ZkEvmVerifierV2", async () => { const withdrawRoot = hexlify(publicInputs.subarray(140, 172)); await chain.setOverrideBatchHashCheck(true); - await chain.setLastFinalizedBatchIndex(lastFinalizedBatchIndex); + await chain.setLastZkpVerifiedBatchIndex(lastFinalizedBatchIndex); await chain.setFinalizedStateRoots(lastFinalizedBatchIndex, prevStateRoot); await chain.setCommittedBatches(lastFinalizedBatchIndex, prevBatchHash); await chain.setCommittedBatches(batchIndex, batchHash); diff --git a/scripts/foundry/DeployL1BridgeContracts.s.sol b/scripts/foundry/DeployL1BridgeContracts.s.sol index 827db491..67fe8d01 100644 --- a/scripts/foundry/DeployL1BridgeContracts.s.sol +++ b/scripts/foundry/DeployL1BridgeContracts.s.sol @@ -109,7 +109,13 @@ contract DeployL1BridgeContracts is Script { } function deployScrollChain() internal { - ScrollChain impl = new ScrollChain(CHAIN_ID_L2, L1_MESSAGE_QUEUE_PROXY_ADDR, address(rollupVerifier)); + ScrollChain impl = new ScrollChain( + CHAIN_ID_L2, + L1_MESSAGE_QUEUE_PROXY_ADDR, + address(rollupVerifier), + address(0), + 0 + ); logAddress("L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR", address(impl)); } diff --git a/src/L1/rollup/IScrollChain.sol b/src/L1/rollup/IScrollChain.sol index 637b0067..95f48da1 100644 --- a/src/L1/rollup/IScrollChain.sol +++ b/src/L1/rollup/IScrollChain.sol @@ -19,6 +19,30 @@ interface IScrollChain { /// @param batchHash The hash of the batch event RevertBatch(uint256 indexed batchIndex, bytes32 indexed batchHash); + /// @notice Emitted when a batch is verified by zk proof + /// @param batchIndex The index of the batch. + /// @param batchHash The hash of the batch + /// @param stateRoot The state root on layer 2 after this batch. + /// @param withdrawRoot The merkle root on layer2 after this batch. + event VerifyBatchWithZkp( + uint256 indexed batchIndex, + bytes32 indexed batchHash, + bytes32 stateRoot, + bytes32 withdrawRoot + ); + + /// @notice Emitted when a batch is verified by tee proof + /// @param batchIndex The index of the batch. + /// @param batchHash The hash of the batch + /// @param stateRoot The state root on layer 2 after this batch. + /// @param withdrawRoot The merkle root on layer2 after this batch. + event VerifyBatchWithTee( + uint256 indexed batchIndex, + bytes32 indexed batchHash, + bytes32 stateRoot, + bytes32 withdrawRoot + ); + /// @notice Emitted when a batch is finalized. /// @param batchIndex The index of the batch. /// @param batchHash The hash of the batch @@ -26,6 +50,18 @@ interface IScrollChain { /// @param withdrawRoot The merkle root on layer2 after this batch. event FinalizeBatch(uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot); + /// @notice Emitted when state between zk proof and tee proof mismatch + /// @param batchIndex The index of the batch. + /// @param stateRoot The state root from tee proof. + /// @param withdrawRoot The correct withdraw root from tee proof. + event StateMismatch(uint256 indexed batchIndex, bytes32 stateRoot, bytes32 withdrawRoot); + + /// @notice Emitted when mismatched state is resolved. + /// @param batchIndex The index of the batch. + /// @param stateRoot The correct state root. + /// @param withdrawRoot The correct withdraw root. + event ResolveState(uint256 indexed batchIndex, bytes32 stateRoot, bytes32 withdrawRoot); + /// @notice Emitted when owner updates the status of sequencer. /// @param account The address of account updated. /// @param status The status of the account updated. @@ -41,6 +77,22 @@ interface IScrollChain { /// @param newMaxNumTxInChunk The new value of `maxNumTxInChunk`. event UpdateMaxNumTxInChunk(uint256 oldMaxNumTxInChunk, uint256 newMaxNumTxInChunk); + /// @notice Emitted when bundle size initialized. + /// @param size The size of bundle (i.e. number of batches in bundle). + /// @param index The start batch index for this size. + event InitializeBundleSize(uint256 size, uint256 index); + + /// @notice Emitted when bundle size updated. + /// @param index The array index of bundle size array. + /// @param size The size of bundle (i.e. number of batches in bundle). + /// @param batchIndex The start batch index for this size. + event ChangeBundleSize(uint256 index, uint256 size, uint256 batchIndex); + + /// @notice Emitted when enable new proof types. + /// @param oldMask The previous enabled proof types. + /// @param newMask The current enabled proof types. + event EnableProofTypes(uint256 oldMask, uint256 newMask); + /************************* * Public View Functions * *************************/ @@ -48,18 +100,36 @@ interface IScrollChain { /// @return The latest finalized batch index. function lastFinalizedBatchIndex() external view returns (uint256); + /// @return The latest verified batch index by zkp proof. + function lastZkpVerifiedBatchIndex() external view returns (uint256); + + /// @return The latest verified batch index by tee proof. + function lastTeeVerifiedBatchIndex() external view returns (uint256); + + /// @notice Return the batch hash for a given batch. + /// /// @param batchIndex The index of the batch. /// @return The batch hash of a committed batch. function committedBatches(uint256 batchIndex) external view returns (bytes32); + /// @notice Return the finalized state root for a given batch. + /// + /// @dev Users should call `isBatchFinalized(batchIndex)` before call this function. + /// /// @param batchIndex The index of the batch. /// @return The state root of a committed batch. function finalizedStateRoots(uint256 batchIndex) external view returns (bytes32); + /// @notice Return the finalized withdraw root for a given batch. + /// + /// @dev Users should call `isBatchFinalized(batchIndex)` before call this function. + /// /// @param batchIndex The index of the batch. /// @return The message root of a committed batch. function withdrawRoots(uint256 batchIndex) external view returns (bytes32); + /// @notice Return whether a batch is finalized. + /// /// @param batchIndex The index of the batch. /// @return Whether the batch is finalized by batch index. function isBatchFinalized(uint256 batchIndex) external view returns (bool); @@ -107,6 +177,7 @@ interface IScrollChain { /// @param lastBatchHeader The header of last batch to revert, see the encoding in comments of `commitBatch`. function revertBatch(bytes calldata firstBatchHeader, bytes calldata lastBatchHeader) external; + /* This function will never be used since we already upgrade to Darwin. We comment out the codes for reference. /// @notice Finalize a committed batch (with blob) on layer 1. /// /// @dev Memory layout of `blobDataProof`: @@ -128,9 +199,10 @@ interface IScrollChain { bytes calldata blobDataProof, bytes calldata aggrProof ) external; + */ /// @notice Finalize a list of committed batches (i.e. bundle) on layer 1. - /// @param batchHeader The header of last batch in current bundle, see the encoding in comments of `commitBatch. + /// @param batchHeader The header of last batch in current bundle, see the encoding in comments of `commitBatch`. /// @param postStateRoot The state root after current bundle. /// @param withdrawRoot The withdraw trie root after current batch. /// @param aggrProof The aggregation proof for current bundle. @@ -140,4 +212,16 @@ interface IScrollChain { bytes32 withdrawRoot, bytes calldata aggrProof ) external; + + /// @notice Finalize a list of committed batches (i.e. bundle) on layer 1 with TEE proof. + /// @param batchHeader The header of last batch in current bundle, see the encoding in comments of `commitBatch`. + /// @param postStateRoot The state root after current bundle. + /// @param withdrawRoot The withdraw trie root after current batch. + /// @param teeProof The tee proof for current bundle. + function finalizeBundleWithTeeProof( + bytes calldata batchHeader, + bytes32 postStateRoot, + bytes32 withdrawRoot, + bytes calldata teeProof + ) external; } diff --git a/src/L1/rollup/MultipleVersionRollupVerifier.sol b/src/L1/rollup/MultipleVersionRollupVerifier.sol index 9b0ac120..09cf06e1 100644 --- a/src/L1/rollup/MultipleVersionRollupVerifier.sol +++ b/src/L1/rollup/MultipleVersionRollupVerifier.sol @@ -76,10 +76,7 @@ contract MultipleVersionRollupVerifier is IRollupVerifier, Ownable { return legacyVerifiers[_version].length; } - /// @notice Compute the verifier should be used for specific batch. - /// @param _version The version of verifier to query. - /// @param _batchIndex The batch index to query. - /// @return The address of verifier. + /// @inheritdoc IRollupVerifier function getVerifier(uint256 _version, uint256 _batchIndex) public view returns (address) { // Normally, we will use the latest verifier. Verifier memory _verifier = latestVerifier[_version]; diff --git a/src/L1/rollup/ScrollChain.sol b/src/L1/rollup/ScrollChain.sol index 0cf493dd..39cacc1a 100644 --- a/src/L1/rollup/ScrollChain.sol +++ b/src/L1/rollup/ScrollChain.sol @@ -13,6 +13,7 @@ import {BatchHeaderV3Codec} from "../../libraries/codec/BatchHeaderV3Codec.sol"; import {ChunkCodecV0} from "../../libraries/codec/ChunkCodecV0.sol"; import {ChunkCodecV1} from "../../libraries/codec/ChunkCodecV1.sol"; import {IRollupVerifier} from "../../libraries/verifier/IRollupVerifier.sol"; +import {ISGXVerifier} from "../../sgx-verifier/ISGXVerifier.sol"; // solhint-disable no-inline-assembly // solhint-disable reason-string @@ -93,6 +94,9 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @dev Thrown when reverting a finalized batch. error ErrorRevertFinalizedBatch(); + /// @dev Thrown when reverting an unresolved state. + error ErrorRevertUnresolvedState(); + /// @dev Thrown when the given state root is zero. error ErrorStateRootIsZero(); @@ -105,6 +109,27 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @dev Thrown when the given address is `address(0)`. error ErrorZeroAddress(); + /// @dev Thrown when no unresolved state exists. + error ErrorNoUnresolvedState(); + + /// @dev Thrown when the finalization is paused. + error ErrorFinalizationPaused(); + + /// @dev Thrown when the batch index mismatch. + error ErrorBatchIndexMismatch(); + + /// @dev Thrown when bundle size doesn't match. + error ErrorBundleSizeMismatch(); + + /// @dev Thrown when update size for finalized batch. + error ErrorUseFinalizedBatch(); + + /// @dev Thrown when batch index delta is not multiple of previous `BundleSizeStruct.bundleSize`. + error ErrorBatchIndexDeltaNotMultipleOfBundleSize(); + + /// @dev Thrown when the proof type mask is greater than 3. + error ErrorInvalidProofTypeMask(); + /************* * Constants * *************/ @@ -123,8 +148,46 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @notice The address of L1MessageQueue contract. address public immutable messageQueue; - /// @notice The address of RollupVerifier. - address public immutable verifier; + /// @notice The address of `MultipleVersionRollupVerifier` for zk proof. + address public immutable zkpVerifier; + + /// @notice The address of `MultipleVersionRollupVerifier` for tee proof. + address public immutable teeVerifier; + + /// @notice The duration to delay proof when we only has one proof type. + /// @dev This is enabled after Euclid upgrade. + uint256 public immutable emergencyFinalizationDelay; + + /********* + * Enums * + *********/ + + enum ProofType { + ZkProof, + TeeProof + } + + /*********** + * Structs * + ***********/ + + /// @param proofType The type of proof for the state roots. + /// @param batchIndex The index of mismatched batch. + /// @param stateRoot The mismatched state root. + /// @param withdrawRoot The mismatched withdraw root. + struct UnresolvedState { + ProofType proofType; + uint248 batchIndex; + bytes32 stateRoot; + bytes32 withdrawRoot; + } + + /// @param bundleSize The number of batches in each bundle in current setting. + /// @param batchIndex The start batch index for current setting. + struct BundleSizeStruct { + uint128 bundleSize; + uint128 batchIndex; + } /************* * Variables * @@ -146,7 +209,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { mapping(address => bool) public isProver; /// @inheritdoc IScrollChain - uint256 public override lastFinalizedBatchIndex; + uint256 public override lastZkpVerifiedBatchIndex; /// @inheritdoc IScrollChain mapping(uint256 => bytes32) public override committedBatches; @@ -157,6 +220,31 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @inheritdoc IScrollChain mapping(uint256 => bytes32) public override withdrawRoots; + /// @notice Mapping from batch index to batch committed timestamp. + /// @dev This is enabled after Euclid upgrade. + mapping(uint256 => uint256) public batchCommittedTimestamp; + + /// @inheritdoc IScrollChain + /// @dev This is enabled after Euclid upgrade. + uint256 public override lastTeeVerifiedBatchIndex; + + /// @notice The mask for enabled proof types. + /// @dev This is enabled after Euclid upgrade. + uint256 public enabledProofTypeMask; + + /// @notice The state for mismatched batch. + /// @dev This is enabled after Euclid upgrade. + UnresolvedState public unresolvedState; + + /// @notice The state for bundle size. + /// @dev This is enabled after Euclid upgrade. + /// @dev Assume the list is `[(s[1], b[1]), (s[2], b[2]), ..., (s[n], b[n])]`, where `s[i]` is the bundle size + /// and `b[i]` is the start batch index. Then for each `i > 1`, we should have `b[i] > b[i - 1]` and + /// `(b[i] - b[i - 1]) % s[i - 1] = 0`. + /// If a bundle has end batch range `[x, y]`, we need to find last `i` such that `y > b[i]`. And the following + /// should be satisfied: `(y - b[i]) % s[i] = 0` and `y - x + 1 = s[i]`. + BundleSizeStruct[] public bundleSize; + /********************** * Function Modifiers * **********************/ @@ -172,6 +260,14 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { _; } + modifier whenFinalizeNotPaused(ProofType proofType) { + // check we have unresolved state. + if (unresolvedState.batchIndex > 0) revert ErrorFinalizationPaused(); + // check whether security council paused this proof type. + if (((enabledProofTypeMask >> uint256(proofType)) & 1) == 0) revert ErrorFinalizationPaused(); + _; + } + /*************** * Constructor * ***************/ @@ -180,13 +276,16 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// /// @param _chainId The chain id of L2. /// @param _messageQueue The address of `L1MessageQueue` contract. - /// @param _verifier The address of zkevm verifier contract. + /// @param _zkpVerifier The address of zkevm verifier contract. + /// @param _teeVerifier The address of tee verifier contract. constructor( uint64 _chainId, address _messageQueue, - address _verifier + address _zkpVerifier, + address _teeVerifier, + uint256 _emergencyFinalizationDelay ) { - if (_messageQueue == address(0) || _verifier == address(0)) { + if (_messageQueue == address(0) || _zkpVerifier == address(0) || _teeVerifier == address(0)) { revert ErrorZeroAddress(); } @@ -194,7 +293,9 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { layer2ChainId = _chainId; messageQueue = _messageQueue; - verifier = _verifier; + zkpVerifier = _zkpVerifier; + teeVerifier = _teeVerifier; + emergencyFinalizationDelay = _emergencyFinalizationDelay; } /// @notice Initialize the storage of ScrollChain. @@ -218,13 +319,75 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { emit UpdateMaxNumTxInChunk(0, _maxNumTxInChunk); } + function initializeV2(uint128 _bundleSize) external reinitializer(2) { + // initialize tee proof state + uint256 cachedIndex = lastZkpVerifiedBatchIndex; + lastTeeVerifiedBatchIndex = cachedIndex; + emit VerifyBatchWithTee( + cachedIndex, + committedBatches[cachedIndex], + finalizedStateRoots[cachedIndex], + withdrawRoots[cachedIndex] + ); + + // initialize the first element in the array + bundleSize.push(BundleSizeStruct(_bundleSize, uint128(cachedIndex))); + emit InitializeBundleSize(_bundleSize, cachedIndex); + + // initialize proof type mask + _enableProofTypes(3); + } + /************************* * Public View Functions * *************************/ + /// @inheritdoc IScrollChain + function lastFinalizedBatchIndex() public view returns (uint256) { + uint256 cachedLastTeeVerifiedBatchIndex = lastTeeVerifiedBatchIndex; + uint256 cachedLastZkpVerifiedBatchIndex = lastZkpVerifiedBatchIndex; + uint256 mask = enabledProofTypeMask; + // the value of mask is 1, 2, or 3 + if (mask == 1) return cachedLastZkpVerifiedBatchIndex; + else if (mask == 2) return cachedLastTeeVerifiedBatchIndex; + else { + return + cachedLastTeeVerifiedBatchIndex < cachedLastZkpVerifiedBatchIndex + ? cachedLastTeeVerifiedBatchIndex + : cachedLastZkpVerifiedBatchIndex; + } + } + /// @inheritdoc IScrollChain function isBatchFinalized(uint256 _batchIndex) external view override returns (bool) { - return _batchIndex <= lastFinalizedBatchIndex; + uint256 mask = enabledProofTypeMask; + if (mask == 1 || mask == 2) { + // add delay when we only have one proof enable + return + _batchIndex <= lastFinalizedBatchIndex() && + block.timestamp >= batchCommittedTimestamp[_batchIndex] + emergencyFinalizationDelay; + } else { + return _batchIndex <= lastFinalizedBatchIndex(); + } + } + + /// @dev Get bundle size with given end batch index. + /// @param batchIndex The end batch index of given bundle. + /// @return size The size of the given bundle. + function getBundleSizeGivenEndBatchIndex(uint256 batchIndex) public view returns (uint256) { + uint256 index = bundleSize.length; + // Usually the last item in the array is what we want, loop here won't cause much gas. + while (index > 0) { + unchecked { + index -= 1; + } + BundleSizeStruct memory s = bundleSize[index]; + if (batchIndex > s.batchIndex) { + return s.bundleSize; + } + } + // It means the batch index is before Euclid upgrade, just return zero. + return 0; } /***************************** @@ -419,7 +582,9 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { if (committedBatches[_lastBatchIndex + 1] != bytes32(0)) revert ErrorRevertNotStartFromEnd(); // check finalization - if (_firstBatchIndex <= lastFinalizedBatchIndex) revert ErrorRevertFinalizedBatch(); + if (_firstBatchIndex <= lastFinalizedBatchIndex()) revert ErrorRevertFinalizedBatch(); + // check unresolved state + if (_firstBatchIndex <= unresolvedState.batchIndex) revert ErrorRevertUnresolvedState(); // actual revert for (uint256 _batchIndex = _lastBatchIndex; _batchIndex >= _firstBatchIndex; --_batchIndex) { @@ -438,7 +603,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } - /* This function will never be used since we already upgrade to 4844. We comment out the codes for reference. + /* This function will never be used since we already upgrade to Bernoulli. We comment out the codes for reference. /// @inheritdoc IScrollChain function finalizeBatchWithProof( bytes calldata _batchHeader, @@ -476,6 +641,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } */ + /* This function will never be used since we already upgrade to Darwin. We comment out the codes for reference. /// @inheritdoc IScrollChain /// @dev Memory layout of `_blobDataProof`: /// ```text @@ -485,7 +651,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// ``` function finalizeBatchWithProof4844( bytes calldata _batchHeader, - bytes32, /*_prevStateRoot*/ + bytes32 _prevStateRoot, bytes32 _postStateRoot, bytes32 _withdrawRoot, bytes calldata _blobDataProof, @@ -532,6 +698,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { _afterFinalizeBatch(_totalL1MessagesPoppedOverall, _batchIndex, _batchHash, _postStateRoot, _withdrawRoot); } + */ /// @inheritdoc IScrollChain function finalizeBundleWithProof( @@ -539,53 +706,124 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { bytes32 _postStateRoot, bytes32 _withdrawRoot, bytes calldata _aggrProof - ) external override OnlyProver whenNotPaused { - if (_postStateRoot == bytes32(0)) revert ErrorStateRootIsZero(); + ) external override OnlyProver whenNotPaused whenFinalizeNotPaused(ProofType.ZkProof) { + // verify bundle logic + (uint256 _batchIndex, bytes32 _batchHash, uint256 _totalL1MessagesPoppedOverall) = _verifyBundle( + ProofType.ZkProof, + _batchHeader, + _postStateRoot, + _withdrawRoot, + _aggrProof + ); - // compute pending batch hash and verify - ( - uint256 batchPtr, - bytes32 _batchHash, - uint256 _batchIndex, - uint256 _totalL1MessagesPoppedOverall - ) = _loadBatchHeader(_batchHeader); + // verify successfully, record state and emit event first + lastZkpVerifiedBatchIndex = _batchIndex; + emit VerifyBatchWithZkp(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); - // retrieve finalized state root and batch hash from storage to construct the public input - uint256 _finalizedBatchIndex = lastFinalizedBatchIndex; - if (_batchIndex <= _finalizedBatchIndex) revert ErrorBatchIsAlreadyVerified(); + // post actions after bundle verification + _afterVerifyBundle( + ProofType.ZkProof, + _batchIndex, + _batchHash, + _postStateRoot, + _withdrawRoot, + _totalL1MessagesPoppedOverall + ); + } - bytes memory _publicInput = abi.encodePacked( - layer2ChainId, - uint32(_batchIndex - _finalizedBatchIndex), // numBatches - finalizedStateRoots[_finalizedBatchIndex], // _prevStateRoot - committedBatches[_finalizedBatchIndex], // _prevBatchHash + /// @inheritdoc IScrollChain + function finalizeBundleWithTeeProof( + bytes calldata _batchHeader, + bytes32 _postStateRoot, + bytes32 _withdrawRoot, + bytes calldata _teeProof + ) external override whenNotPaused whenFinalizeNotPaused(ProofType.TeeProof) { + // verify bundle logic + (uint256 _batchIndex, bytes32 _batchHash, uint256 _totalL1MessagesPoppedOverall) = _verifyBundle( + ProofType.TeeProof, + _batchHeader, _postStateRoot, + _withdrawRoot, + _teeProof + ); + + // verify successfully, record state and emit event first + lastTeeVerifiedBatchIndex = _batchIndex; + emit VerifyBatchWithTee(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); + + // post actions after bundle verification + _afterVerifyBundle( + ProofType.TeeProof, + _batchIndex, _batchHash, - _withdrawRoot + _postStateRoot, + _withdrawRoot, + _totalL1MessagesPoppedOverall ); + } - // load version from batch header, it is always the first byte. - uint256 batchVersion = BatchHeaderV0Codec.getVersion(batchPtr); + /************************ + * Restricted Functions * + ************************/ - // verify bundle, choose the correct verifier based on the last batch - // our off-chain service will make sure all unfinalized batches have the same batch version. - IRollupVerifier(verifier).verifyBundleProof(batchVersion, _batchIndex, _aggrProof, _publicInput); + /// @notice Resolve mismatched state. + /// + /// @dev This should only be called by Security Council. + /// + /// @param useUnresolvedState Whether we want to use the state root from unresolved one. + function resolveStateMismatch(bytes calldata _batchHeader, bool useUnresolvedState) external onlyOwner { + UnresolvedState memory state = unresolvedState; + if (state.batchIndex == 0) { + revert ErrorNoUnresolvedState(); + } + (, , uint256 _batchIndex, uint256 _totalL1MessagesPoppedOverall) = _loadBatchHeader(_batchHeader); + if (_batchIndex != state.batchIndex) revert ErrorBatchIndexMismatch(); + + if (useUnresolvedState) { + finalizedStateRoots[state.batchIndex] = state.stateRoot; + withdrawRoots[state.batchIndex] = state.withdrawRoot; + if (state.proofType == ProofType.ZkProof) { + // reset tee verified batch index, tee prover need to reprove everything after this batch + lastTeeVerifiedBatchIndex = state.batchIndex; + // disable tee proof + enabledProofTypeMask ^= 2; + } else { + // reset zkp verified batch index, zk prover need to reprove everything after this batch + lastZkpVerifiedBatchIndex = state.batchIndex; + // disable zkp proof + enabledProofTypeMask ^= 1; + } + } else { + if (state.proofType == ProofType.TeeProof) { + // reset tee verified batch index, tee prover need to reprove everything after this batch + lastTeeVerifiedBatchIndex = state.batchIndex; + // disable tee proof + enabledProofTypeMask ^= 2; + } else { + // reset zkp verified batch index, zk prover need to reprove everything after this batch + lastZkpVerifiedBatchIndex = state.batchIndex; + // disable zkp proof + enabledProofTypeMask ^= 1; + } + } - // store in state - // @note we do not store intermediate finalized roots - lastFinalizedBatchIndex = _batchIndex; - finalizedStateRoots[_batchIndex] = _postStateRoot; - withdrawRoots[_batchIndex] = _withdrawRoot; + // emit resolve event + emit ResolveState(state.batchIndex, finalizedStateRoots[state.batchIndex], withdrawRoots[state.batchIndex]); // Pop finalized and non-skipped message from L1MessageQueue. _finalizePoppedL1Messages(_totalL1MessagesPoppedOverall); - emit FinalizeBatch(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); - } + // emit finalize event + emit FinalizeBatch( + state.batchIndex, + committedBatches[state.batchIndex], + finalizedStateRoots[state.batchIndex], + withdrawRoots[state.batchIndex] + ); - /************************ - * Restricted Functions * - ************************/ + // clear state + delete unresolvedState; + } /// @notice Add an account to the sequencer list. /// @param _account The address of account to add. @@ -645,10 +883,68 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } + /// @notice Update the bundle size + /// @param size The new bundle size. + /// @param batchIndex The start batch index for new bundle size. + function updateBundleSize(uint128 size, uint128 batchIndex) external onlyOwner { + uint256 cachedLastTeeVerifiedBatchIndex = lastTeeVerifiedBatchIndex; + uint256 cachedLastZkpVerifiedBatchIndex = lastZkpVerifiedBatchIndex; + if (batchIndex <= cachedLastTeeVerifiedBatchIndex) revert ErrorUseFinalizedBatch(); + if (batchIndex <= cachedLastZkpVerifiedBatchIndex) revert ErrorUseFinalizedBatch(); + + uint256 index = bundleSize.length - 1; + BundleSizeStruct memory last = bundleSize[index]; + if (last.batchIndex > cachedLastTeeVerifiedBatchIndex && last.batchIndex > cachedLastZkpVerifiedBatchIndex) { + // last is future batch index, we override the last one + BundleSizeStruct memory prev = bundleSize[index - 1]; + // since prev.batchIndex <= max(lastTeeVerifiedBatchIndex, lastZkpVerifiedBatchIndex) + // we always have batchIndex > prev.batchIndex + if ((batchIndex - prev.batchIndex) % prev.bundleSize != 0) { + revert ErrorBatchIndexDeltaNotMultipleOfBundleSize(); + } + last.bundleSize = size; + last.batchIndex = batchIndex; + bundleSize[index] = last; + } else { + // last is past batch index, we append a new one + // since batchIndex > max(lastTeeVerifiedBatchIndex, lastZkpVerifiedBatchIndex) and + // last.batchIndex <= max(lastTeeVerifiedBatchIndex, lastZkpVerifiedBatchIndex) + // we always have batchIndex > last.batchIndex + if ((batchIndex - last.batchIndex) % last.bundleSize != 0) { + revert ErrorBatchIndexDeltaNotMultipleOfBundleSize(); + } + + index += 1; + last.bundleSize = size; + last.batchIndex = batchIndex; + bundleSize.push(last); + } + + emit ChangeBundleSize(index, size, batchIndex); + } + + /// @notice Enable proof types. + /// @param mask The mask for new enabled proof types. + function enableProofTypes(uint256 mask) external onlyOwner { + _enableProofTypes(mask); + } + /********************** * Internal Functions * **********************/ + /// @dev Internal function to enable proof types. + /// @param mask The mask for new enabled proof types. + function _enableProofTypes(uint256 mask) internal { + if (mask > 3) revert ErrorInvalidProofTypeMask(); + + uint256 oldMask = enabledProofTypeMask; + uint256 newMask = oldMask | mask; + enabledProofTypeMask = newMask; + + emit EnableProofTypes(oldMask, newMask); + } + /// @dev Internal function to do common checks before actual batch committing. /// @param _parentBatchHeader The parent batch header in calldata. /// @param _chunks The list of chunks in memory. @@ -678,24 +974,143 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @param _batchHash The hash of current batch. function _afterCommitBatch(uint256 _batchIndex, bytes32 _batchHash) private { committedBatches[_batchIndex] = _batchHash; + batchCommittedTimestamp[_batchIndex] = block.timestamp; emit CommitBatch(_batchIndex, _batchHash); } + /// @dev Internal function to verify bundle. + /// @param _proofType The proof type (zk proof or tee proof). + /// @param _batchHeader The batch header bytes in calldata. + /// @param _postStateRoot The state root after this bundle. + /// @param _withdrawRoot The withdraw root after this bundle. + /// @param _bundleProof The proof for bundle. + function _verifyBundle( + ProofType _proofType, + bytes calldata _batchHeader, + bytes32 _postStateRoot, + bytes32 _withdrawRoot, + bytes calldata _bundleProof + ) + internal + returns ( + uint256 _batchIndex, + bytes32 _batchHash, + uint256 _totalL1MessagesPoppedOverall + ) + { + uint256 _lastVerifiedBatchIndex = _proofType == ProofType.ZkProof + ? lastZkpVerifiedBatchIndex + : lastTeeVerifiedBatchIndex; + uint256 batchPtr; + // compute pending batch hash and verify + (batchPtr, _batchHash, _batchIndex, _totalL1MessagesPoppedOverall) = _loadBatchHeader(_batchHeader); + + // check bundle size + uint256 numBatches = getBundleSizeGivenEndBatchIndex(_batchIndex); + if (_batchIndex != _lastVerifiedBatchIndex + numBatches) revert ErrorBundleSizeMismatch(); + + // construct the public input + bytes memory _publicInput = abi.encodePacked( + layer2ChainId, + uint32(numBatches), // numBatches + finalizedStateRoots[_lastVerifiedBatchIndex], // _prevStateRoot + committedBatches[_lastVerifiedBatchIndex], // _prevBatchHash + _postStateRoot, + _batchHash, + _withdrawRoot + ); + + // load version from batch header, it is always the first byte. + uint256 batchVersion = BatchHeaderV0Codec.getVersion(batchPtr); + + // verify bundle, choose the correct verifier based on the last batch + // our off-chain service will make sure all unfinalized batches have the same batch version. + IRollupVerifier(_proofType == ProofType.ZkProof ? zkpVerifier : teeVerifier).verifyBundleProof( + batchVersion, + _batchIndex, + _bundleProof, + _publicInput + ); + + // random select next prover for tee proof + if (_proofType == ProofType.TeeProof) { + ISGXVerifier(IRollupVerifier(teeVerifier).getVerifier(batchVersion, _batchIndex)).randomSelectNextProver(); + } + } + + /// @dev Internal function to do actions after bundle verification, including state recording and + /// state match checking. + /// @param _proofType The proof type (zk proof or tee proof). + /// @param _batchIndex The last batch index of this bundle. + /// @param _batchHash The hash of the batch. + /// @param _postStateRoot The state root after this bundle. + /// @param _withdrawRoot The withdraw root after this bundle. + /// @param _totalL1MessagesPoppedOverall The total number l1 messages popped after this bundle. + function _afterVerifyBundle( + ProofType _proofType, + uint256 _batchIndex, + bytes32 _batchHash, + bytes32 _postStateRoot, + bytes32 _withdrawRoot, + uint256 _totalL1MessagesPoppedOverall + ) internal { + bool counterpartProofEnabled; + { + uint256 mask = enabledProofTypeMask; + mask ^= 1 << uint256(uint8(_proofType)); + counterpartProofEnabled = mask > 0; + } + bool overrideStateRoot = false; + bool finalizeBundle = false; + if (counterpartProofEnabled) { + uint256 counterpartVerifiedBatchIndex = _proofType == ProofType.ZkProof + ? lastTeeVerifiedBatchIndex + : lastZkpVerifiedBatchIndex; + if (_batchIndex <= counterpartVerifiedBatchIndex) { + // The current proof is behind counterpart proof, we compare state roots here + if (_postStateRoot != finalizedStateRoots[_batchIndex] || withdrawRoots[_batchIndex] != _withdrawRoot) { + unresolvedState.proofType = _proofType; + unresolvedState.batchIndex = uint248(_batchIndex); + unresolvedState.stateRoot = _postStateRoot; + unresolvedState.withdrawRoot = _withdrawRoot; + emit StateMismatch(_batchIndex, _postStateRoot, _withdrawRoot); + } else { + finalizeBundle = true; + } + } else { + overrideStateRoot = true; + } + } else { + overrideStateRoot = true; + finalizeBundle = true; + } + // override state root, when + // 1. we only has this type proof enabled; or + // 2. current proof ahead counterpart proof. + if (overrideStateRoot) { + finalizedStateRoots[_batchIndex] = _postStateRoot; + withdrawRoots[_batchIndex] = _withdrawRoot; + } + // finalize bundle, when + // 1. we only has this type proof enabled; or + // 2. current proof behind counterpart proof and state root matches. + if (finalizeBundle) { + _finalizePoppedL1Messages(_totalL1MessagesPoppedOverall); + emit FinalizeBatch(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); + } + } + + /* This function will never be used since we already upgrade to Darwin. We comment out the codes for reference. /// @dev Internal function to do common checks before actual batch finalization. /// @param _batchHeader The current batch header in calldata. /// @param _postStateRoot The state root after current batch. /// @return batchPtr The start memory offset of current batch in memory. /// @return _batchHash The hash of current batch. /// @return _batchIndex The index of current batch. - function _beforeFinalizeBatch(bytes calldata _batchHeader, bytes32 _postStateRoot) - internal - view - returns ( - uint256 batchPtr, - bytes32 _batchHash, - uint256 _batchIndex - ) - { + function _beforeFinalizeBatch( + bytes calldata _batchHeader, + bytes32 _postStateRoot + ) internal view returns (uint256 batchPtr, bytes32 _batchHash, uint256 _batchIndex) { if (_postStateRoot == bytes32(0)) revert ErrorStateRootIsZero(); // compute batch hash and verify @@ -704,7 +1119,9 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { // avoid duplicated verification if (finalizedStateRoots[_batchIndex] != bytes32(0)) revert ErrorBatchIsAlreadyVerified(); } + */ + /* This function will never be used since we already upgrade to Darwin. We comment out the codes for reference. /// @dev Internal function to do common checks after actual batch finalization. /// @param _totalL1MessagesPoppedOverall The total number of L1 messages popped after current batch. /// @param _batchIndex The index of current batch. @@ -733,6 +1150,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { emit FinalizeBatch(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); } + */ /// @dev Internal function to check blob versioned hash. /// @param _blobVersionedHash The blob versioned hash to check. @@ -788,6 +1206,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { if (_secondBlob != bytes32(0)) revert ErrorFoundMultipleBlobs(); } + /* This function will never be used since we already upgrade to Bernoulli. We comment out the codes for reference. /// @dev Internal function to commit chunks with version 0 /// @param _totalL1MessagesPoppedOverall The number of L1 messages popped before the list of chunks. /// @param _chunks The list of chunks to commit. @@ -833,6 +1252,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { _batchDataHash := keccak256(sub(batchDataHashPtr, dataLen), dataLen) } } + */ /// @dev Internal function to commit chunks with version 1 /// @param _totalL1MessagesPoppedOverall The number of L1 messages popped before the list of chunks. diff --git a/src/libraries/codec/BatchHeaderV3Codec.sol b/src/libraries/codec/BatchHeaderV3Codec.sol index c07014b0..b8f44dc4 100644 --- a/src/libraries/codec/BatchHeaderV3Codec.sol +++ b/src/libraries/codec/BatchHeaderV3Codec.sol @@ -14,7 +14,7 @@ pragma solidity ^0.8.24; /// * dataHash 32 bytes32 25 The data hash of the batch /// * blobVersionedHash 32 bytes32 57 The versioned hash of the blob with this batch’s data /// * parentBatchHash 32 bytes32 89 The parent batch hash -/// * lastBlockTimestamp 8 uint64 121 A bitmap to indicate which L1 messages are skipped in the batch +/// * lastBlockTimestamp 8 uint64 121 The timestamp of the last block in this batch /// * blobDataProof 64 bytes64 129 The blob data proof: z (32), y (32) /// ``` /// The codes for `version`, `batchIndex`, `l1MessagePopped`, `totalL1MessagePopped`, `dataHash` and `computeBatchHash` diff --git a/src/libraries/verifier/IRollupVerifier.sol b/src/libraries/verifier/IRollupVerifier.sol index b5f77526..de5168d7 100644 --- a/src/libraries/verifier/IRollupVerifier.sol +++ b/src/libraries/verifier/IRollupVerifier.sol @@ -5,6 +5,12 @@ pragma solidity ^0.8.24; /// @title IRollupVerifier /// @notice The interface for rollup verifier. interface IRollupVerifier { + /// @notice Compute the verifier should be used for specific batch. + /// @param _version The version of verifier to query. + /// @param _batchIndex The batch index to query. + /// @return The address of verifier. + function getVerifier(uint256 _version, uint256 _batchIndex) external view returns (address); + /// @notice Verify aggregate zk proof. /// @param batchIndex The batch index to verify. /// @param aggrProof The aggregated proof. diff --git a/src/mocks/ScrollChainMockBlob.sol b/src/mocks/ScrollChainMockBlob.sol index 74aa8fee..e92e6641 100644 --- a/src/mocks/ScrollChainMockBlob.sol +++ b/src/mocks/ScrollChainMockBlob.sol @@ -23,8 +23,10 @@ contract ScrollChainMockBlob is ScrollChain { constructor( uint64 _chainId, address _messageQueue, - address _verifier - ) ScrollChain(_chainId, _messageQueue, _verifier) {} + address _verifier, + address _sgxVerifier, + uint256 _delay + ) ScrollChain(_chainId, _messageQueue, _verifier, _sgxVerifier, _delay) {} /********************** * Internal Functions * @@ -34,8 +36,12 @@ contract ScrollChainMockBlob is ScrollChain { blobVersionedHash = _blobVersionedHash; } - function setLastFinalizedBatchIndex(uint256 index) external { - lastFinalizedBatchIndex = index; + function setLastZkpVerifiedBatchIndex(uint256 index) external { + lastZkpVerifiedBatchIndex = index; + } + + function setLastTeeVerifiedBatchIndex(uint256 index) external { + lastTeeVerifiedBatchIndex = index; } function setFinalizedStateRoots(uint256 index, bytes32 value) external { @@ -50,6 +56,14 @@ contract ScrollChainMockBlob is ScrollChain { overrideBatchHashCheck = status; } + function setEnabledProofTypeMask(uint256 mask) external { + enabledProofTypeMask = mask; + } + + function setBatchCommittedTimestamp(uint256 index, uint256 timestamp) external { + batchCommittedTimestamp[index] = timestamp; + } + function _getBlobVersionedHash() internal virtual override returns (bytes32 _blobVersionedHash) { _blobVersionedHash = blobVersionedHash; } diff --git a/src/mocks/ScrollChainMockFinalize.sol b/src/mocks/ScrollChainMockFinalize.sol index e4ff3758..11f0735b 100644 --- a/src/mocks/ScrollChainMockFinalize.sol +++ b/src/mocks/ScrollChainMockFinalize.sol @@ -21,16 +21,17 @@ contract ScrollChainMockFinalize is ScrollChain { uint64 _chainId, address _messageQueue, address _verifier - ) ScrollChain(_chainId, _messageQueue, _verifier) {} + ) ScrollChain(_chainId, _messageQueue, _verifier, address(0), 0) {} /***************************** * Public Mutating Functions * *****************************/ + /* This function will never be used since we already upgrade to Darwin. We comment out the codes for reference. /// @notice Finalize 4844 batch without proof, See the comments of {ScrollChain-finalizeBatchWithProof4844}. function finalizeBatch4844( bytes calldata _batchHeader, - bytes32, /*_prevStateRoot*/ + bytes32 _prevStateRoot, bytes32 _postStateRoot, bytes32 _withdrawRoot, bytes calldata _blobDataProof @@ -54,30 +55,50 @@ contract ScrollChainMockFinalize is ScrollChain { _afterFinalizeBatch(_totalL1MessagesPoppedOverall, _batchIndex, _batchHash, _postStateRoot, _withdrawRoot); } + */ /// @notice Finalize bundle without proof, See the comments of {ScrollChain-finalizeBundleWithProof}. function finalizeBundle( bytes calldata _batchHeader, bytes32 _postStateRoot, bytes32 _withdrawRoot - ) external OnlyProver whenNotPaused { + ) external OnlyProver { if (_postStateRoot == bytes32(0)) revert ErrorStateRootIsZero(); // compute pending batch hash and verify - (, bytes32 _batchHash, uint256 _batchIndex, uint256 _totalL1MessagesPoppedOverall) = _loadBatchHeader( - _batchHeader + (, bytes32 _batchHash, uint256 _batchIndex, ) = _loadBatchHeader(_batchHeader); + + // retrieve finalized state root and batch hash from storage to construct the public input + uint256 _finalizedBatchIndex = lastZkpVerifiedBatchIndex; + if (_batchIndex <= _finalizedBatchIndex) revert ErrorBatchIsAlreadyVerified(); + + /* @note skip verifier in mock + bytes memory _publicInput = abi.encodePacked( + layer2ChainId, + uint32(_batchIndex - _finalizedBatchIndex), // numBatches + finalizedStateRoots[_finalizedBatchIndex], // _prevStateRoot + committedBatches[_finalizedBatchIndex], // _prevBatchHash + _postStateRoot, + _batchHash, + _withdrawRoot ); - if (_batchIndex <= lastFinalizedBatchIndex) revert ErrorBatchIsAlreadyVerified(); + + // load version from batch header, it is always the first byte. + uint256 batchVersion = BatchHeaderV0Codec.getVersion(batchPtr); + // verify bundle, choose the correct verifier based on the last batch + // our off-chain service will make sure all unfinalized batches have the same batch version. + IRollupVerifier(verifier).verifyBundleProof(batchVersion, _batchIndex, _aggrProof, _publicInput); + */ // store in state // @note we do not store intermediate finalized roots - lastFinalizedBatchIndex = _batchIndex; + lastZkpVerifiedBatchIndex = _batchIndex; finalizedStateRoots[_batchIndex] = _postStateRoot; withdrawRoots[_batchIndex] = _withdrawRoot; - // Pop finalized and non-skipped message from L1MessageQueue. - _finalizePoppedL1Messages(_totalL1MessagesPoppedOverall); + // @note we will pop finalized and non-skipped message in `finalizeBundleWithTeeProof` + // _finalizePoppedL1Messages(_totalL1MessagesPoppedOverall); - emit FinalizeBatch(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); + emit VerifyBatchWithZkp(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); } } diff --git a/src/sgx-verifier/AttestationVerifier.sol b/src/sgx-verifier/AttestationVerifier.sol new file mode 100644 index 00000000..67fc06ed --- /dev/null +++ b/src/sgx-verifier/AttestationVerifier.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +import {IAttestationVerifier} from "./IAttestationVerifier.sol"; +import {IDcapAttestation} from "./IDcapAttestation.sol"; + +import {BytesUtils} from "./utils/BytesUtils.sol"; + +/// @dev This contract is modified from https://github.com/automata-network/scroll-prover/blob/demo/contracts/src/core/AttestationVerifier.sol +contract AttestationVerifier is Ownable, IAttestationVerifier { + using BytesUtils for bytes; + using EnumerableSet for EnumerableSet.Bytes32Set; + + /********** + * Errors * + **********/ + + /// @dev Thrown when the given attestation report is invalid. + error ErrorInvalidReport(); + + /// @dev Thrown when the user data from the attestation report mismatch. + error ErrorReportDataMismatch(); + + /// @dev Thrown when the MrSigner from the attestation report is invalid. + error ErrorInvalidMrSigner(); + + /// @dev Thrown when the MrEnclave from the attestation report is invalid. + error ErrorInvalidMrEnclave(); + + /*********************** + * Immutable Variables * + ***********************/ + + /// @notice The address of automata's DCAP Attestation contract. + IDcapAttestation public immutable attestationVerifier; + + /********************* + * Storage Variables * + *********************/ + + /// @dev The list of trusted enclaves. + EnumerableSet.Bytes32Set private trustedUserMrEnclave; + + /// @dev The list of trusted signers. + EnumerableSet.Bytes32Set private trustedUserMrSigner; + + /*************** + * Constructor * + ***************/ + + constructor(address _attestationVerifierAddr) { + attestationVerifier = IDcapAttestation(_attestationVerifierAddr); + } + + /************************* + * Public View Functions * + *************************/ + + /// @inheritdoc IAttestationVerifier + function isTrustedMrSigner(bytes32 mrSigner) public view returns (bool) { + return trustedUserMrSigner.contains(mrSigner); + } + + /// @inheritdoc IAttestationVerifier + function isTrustedMrEnclave(bytes32 mrEnclave) public view returns (bool) { + return trustedUserMrEnclave.contains(mrEnclave); + } + + /// @inheritdoc IAttestationVerifier + function getTrustedMrSigners() external view returns (bytes32[] memory signers) { + uint256 length = trustedUserMrSigner.length(); + signers = new bytes32[](length); + for (uint256 i = 0; i < length; i++) { + signers[i] = trustedUserMrSigner.at(i); + } + } + + /// @inheritdoc IAttestationVerifier + function getTrustedMrEnclaves() external view returns (bytes32[] memory enclaves) { + uint256 length = trustedUserMrEnclave.length(); + enclaves = new bytes32[](length); + for (uint256 i = 0; i < length; i++) { + enclaves[i] = trustedUserMrEnclave.at(i); + } + } + + /// @inheritdoc IAttestationVerifier + function verifyAttestation(bytes calldata _report, bytes32 _userData) external view { + (bool success, bytes memory output) = attestationVerifier.verifyAndAttestOnChain(_report); + if (!success) { + revert ErrorInvalidReport(); + } + + (bytes32 reportUserData, bytes32 mrEnclave, bytes32 mrSigner) = extractEnclaveReport(output); + if (reportUserData != _userData) { + revert ErrorReportDataMismatch(); + } + // check local enclave report + if (!isTrustedMrEnclave(mrEnclave)) { + revert ErrorInvalidMrEnclave(); + } + if (!isTrustedMrSigner(mrSigner)) { + revert ErrorInvalidMrSigner(); + } + } + + /************************ + * Restricted Functions * + ************************/ + + /// @notice Update the status of a signer. + /// @param _mrSigner The signer to update. + /// @param _status The new status to update. If it is `true`, the signer is trusted. + function updateMrSigner(bytes32 _mrSigner, bool _status) external onlyOwner { + // @note No need to check the previous status, offline owner will make sure this tx is meaningful. + if (_status) { + trustedUserMrSigner.add(_mrSigner); + } else { + trustedUserMrSigner.remove(_mrSigner); + } + + emit UpdateMrSigner(_mrSigner, _status); + } + + /// @notice Update the status of an enclave. + /// @param _mrEnclave The enclave to update. + /// @param _status The new status to update. If it is `true`, the enclave is trusted. + function updateMrEnclave(bytes32 _mrEnclave, bool _status) external onlyOwner { + // @note No need to check the previous status, offline owner will make sure this tx is meaningful. + if (_status) { + trustedUserMrEnclave.add(_mrEnclave); + } else { + trustedUserMrEnclave.remove(_mrEnclave); + } + + emit UpdateMrEnclave(_mrEnclave, _status); + } + + /********************** + * Internal Functions * + **********************/ + + /// @dev Internal function to extract `reportUserData`, `mrEnclave` and `mrSigner` from report. + function extractEnclaveReport(bytes memory rawEnclaveReport) + internal + pure + returns ( + bytes32 reportUserData, + bytes32 mrEnclave, + bytes32 mrSigner + ) + { + // @note the actual length is 384, but we have extra 13 bytes at the beginning. + if (rawEnclaveReport.length != 397) { + revert ErrorInvalidReport(); + } + + // @note The actual offsets are `64+32`, `128+32` and `320+32`, but we have extra 13 bytes at the beginning. + // The offsets used here become `77+32`, `141+32` and `333+32`. + assembly { + mrEnclave := mload(add(rawEnclaveReport, 109)) + mrSigner := mload(add(rawEnclaveReport, 173)) + reportUserData := mload(add(rawEnclaveReport, 397)) + } + } + + /* for reference + struct EnclaveReport { + bytes16 cpuSvn; + bytes4 miscSelect; + bytes28 reserved1; + bytes16 attributes; + bytes32 mrEnclave; + bytes32 reserved2; + bytes32 mrSigner; + bytes reserved3; // 96 bytes + uint16 isvProdId; + uint16 isvSvn; + bytes reserved4; // 60 bytes + bytes reportData; // 64 bytes - For QEReports, this contains the hash of the concatenation of attestation key and QEAuthData + } + + function parseEnclaveReport( + bytes memory rawEnclaveReport + ) internal pure returns (EnclaveReport memory enclaveReport) { + if (rawEnclaveReport.length != 384) { + revert ErrorInvalidReport(); + } + + enclaveReport.cpuSvn = bytes16(rawEnclaveReport.substring(0, 16)); + enclaveReport.miscSelect = bytes4(rawEnclaveReport.substring(16, 4)); + enclaveReport.reserved1 = bytes28(rawEnclaveReport.substring(20, 28)); + enclaveReport.attributes = bytes16(rawEnclaveReport.substring(48, 16)); + enclaveReport.mrEnclave = bytes32(rawEnclaveReport.substring(64, 32)); + enclaveReport.reserved2 = bytes32(rawEnclaveReport.substring(96, 32)); + enclaveReport.mrSigner = bytes32(rawEnclaveReport.substring(128, 32)); + enclaveReport.reserved3 = rawEnclaveReport.substring(160, 96); + enclaveReport.isvProdId = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(256, 2))); + enclaveReport.isvSvn = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(258, 2))); + enclaveReport.reserved4 = rawEnclaveReport.substring(260, 60); + enclaveReport.reportData = rawEnclaveReport.substring(320, 64); + } + */ +} diff --git a/src/sgx-verifier/IAttestationVerifier.sol b/src/sgx-verifier/IAttestationVerifier.sol new file mode 100644 index 00000000..66fb2495 --- /dev/null +++ b/src/sgx-verifier/IAttestationVerifier.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +interface IAttestationVerifier { + /********** + * Events * + **********/ + + /// @notice Emitted when the `mrSigner` status is updated. + /// @param mrSigner The value of mrSigner. + /// @param status The new status of the signer. + event UpdateMrSigner(bytes32 indexed mrSigner, bool status); + + /// @notice Emitted when the `mrEnclave` status is updated. + /// @param mrEnclave The value of mrEnclave. + /// @param status The new status of the enclave. + event UpdateMrEnclave(bytes32 indexed mrEnclave, bool status); + + /************************* + * Public View Functions * + *************************/ + + /// @notice Return whether the signer is trusted. + /// @param mrSigner The value of signer to check. + function isTrustedMrSigner(bytes32 mrSigner) external view returns (bool); + + /// @notice Return whether the enclave is trusted. + /// @param mrEnclave The value of enclave to check. + function isTrustedMrEnclave(bytes32 mrEnclave) external view returns (bool); + + /// @notice Return the list of trusted signers. + function getTrustedMrSigners() external view returns (bytes32[] memory signers); + + /// @notice Return the list of trusted enclaves. + function getTrustedMrEnclaves() external view returns (bytes32[] memory enclaves); + + /// @notice Verify attestation report. + /// + /// @dev This function should revert when the attestation report is invalid. + /// + /// @param report The attestation report generated by enclave. + /// @param userData The user data attached with the attestation report. + function verifyAttestation(bytes calldata report, bytes32 userData) external view; +} diff --git a/src/sgx-verifier/IDcapAttestation.sol b/src/sgx-verifier/IDcapAttestation.sol new file mode 100644 index 00000000..f9d417fa --- /dev/null +++ b/src/sgx-verifier/IDcapAttestation.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +/** + * @title Interface standard that implement attestation contracts whose verification logic can be implemented + * both on-chain and with Risc0 ZK proofs + * @notice The interface simply provides two verification methods for a given attestation input. + * The user can either pay a possibly hefty gas cost to fully verify an attestation fully on-chain + * OR + * Provides ZK proofs from executing an off-chain program where the verification of such attestation is conducted. + * @dev should also implement Risc0 Guest Program to use this interface. + * See https://dev.risczero.com/api/blockchain-integration/bonsai-on-eth to learn more + * + * @dev copy from https://github.com/automata-network/automata-dcap-attestation/blob/main/contracts/interfaces/IAttestation.sol + */ +interface IDcapAttestation { + /** + * @notice full on-chain verification for an attestation + * @dev must further specify the structure of inputs/outputs, to be serialized and passed to this method + * @param input - serialized raw input as defined by the project + * @return success - whether the quote has been successfully verified or not + * @return output - the output upon completion of verification. The output data may require post-processing by the consumer. + * For verification failures, the output is simply a UTF-8 encoded string, describing the reason for failure. + * @dev can directly type cast the failed output as a string + */ + function verifyAndAttestOnChain(bytes calldata input) external view returns (bool success, bytes memory output); + + /** + * @param journal - The output of the Guest program, this includes: + * - VerifiedOutput struct + * - TcbInfo hash + * - QEID hash + * - RootCA hash + * - TCB Signing CA hash + * - Root CRL hash + * - Platform CRL hash + * - Processor CRL hash + * @param seal - The encoded cryptographic proof (i.e. SNARK). + */ + function verifyAndAttestWithZKProof(bytes calldata journal, bytes calldata seal) + external + view + returns (bool success, bytes memory output); +} diff --git a/src/sgx-verifier/ISGXVerifier.sol b/src/sgx-verifier/ISGXVerifier.sol new file mode 100644 index 00000000..7ead3af7 --- /dev/null +++ b/src/sgx-verifier/ISGXVerifier.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {IZkEvmVerifierV2} from "../libraries/verifier/IZkEvmVerifier.sol"; + +interface ISGXVerifier is IZkEvmVerifierV2 { + /********** + * Events * + **********/ + + /// @notice Emitted when a prover submit a valid attestation report. + event ProverRegistered(address indexed prover, uint256 validUntil); + + /// @notice Emitted when a prover is selected to prove for next bundle. + event ProverSelected(address indexed prover, uint256 validUntil); + + /*********** + * Structs * + ***********/ + + /// @dev The struct for report data. + /// @param addr The address of the prover. + /// @param referenceBlockNumber The reference block number when the prover generated the report. + /// @param referenceBlockHash The reference block hash when the prover generated the report. + struct ReportData { + address addr; + uint256 referenceBlockNumber; + bytes32 referenceBlockHash; + } + + /***************************** + * Public Mutating Functions * + *****************************/ + + /// @notice Random select a prover. + function randomSelectNextProver() external returns (address); +} diff --git a/src/sgx-verifier/SGXVerifier.sol b/src/sgx-verifier/SGXVerifier.sol new file mode 100644 index 00000000..36442ed6 --- /dev/null +++ b/src/sgx-verifier/SGXVerifier.sol @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +import {IAttestationVerifier} from "./IAttestationVerifier.sol"; +import {ISGXVerifier, IZkEvmVerifierV2} from "./ISGXVerifier.sol"; + +/// @dev This contract is modified from https://github.com/automata-network/scroll-prover/blob/demo/contracts/src/core/ProverRegistry.sol +contract SGXVerifier is AccessControlEnumerable, EIP712, ISGXVerifier { + /********** + * Errors * + **********/ + + /// @dev Thrown when the given block number is invalid. + error ErrorInvalidBlockNumber(); + + /// @dev Thrown when the given block number is outdated. + error ErrorBlockNumberOutOfDate(); + + /// @dev Thrown when the given block hash mismatch. + error ErrorBlockHashMismatch(); + + /// @dev Thrown when the given attestation report is used before. + error ErrorReportUsed(); + + /// @dev Thrown when the prover's attestation report expired. + error ErrorProverOutOfDate(); + + /// @dev Thrown when the prover is not selected one. + error ErrorNotSelectedProver(); + + /************* + * Constants * + *************/ + + /// @dev The role for prover registration. + bytes32 public PROVER_REGISTER_ROLE = keccak256("PROVER_REGISTER_ROLE"); + + /// @dev The role for next prover selection. + bytes32 public PROVER_SELECTION_ROLE = keccak256("PROVER_SELECTION_ROLE"); + + /// @dev type hash for struct `ProveBundleSignatureData`. + bytes32 private constant _BUNDLE_PAYLOAD_TYPEHASH = + keccak256( + "ProveBundleSignatureData(uint64 layer2ChainId,uint32 numBatches,bytes32 prevStateRoot,bytes32 prevBatchHash,bytes32 postStateRoot,bytes32 batchHash,bytes32 postWithdrawRoot)" + ); + + /*********************** + * Immutable Variables * + ***********************/ + + /// @notice The address of `AttestationVerifier` contract. + address public immutable attestationVerifier; + + /// @notice The number of seconds of the attestation validity. + uint256 public immutable attestValiditySeconds; + + /// @notice The maximum number of blocks allowed for the attestation report. + uint256 public immutable maxBlockNumberDiff; + + /// @notice The delay for prover to submit a proof. + uint256 public immutable proofSubmissionDelay; + + /*********** + * Structs * + ***********/ + + /// @param prover The address of prover. + /// @param expireTime The timestamp when this prover expired. + struct NextProver { + address prover; + uint64 expireTime; + } + + /********************* + * Storage Variables * + *********************/ + + /// @notice The list of attested reports, mapping from report hash to attested status. + mapping(bytes32 => bool) public attestedReports; + + /// @notice Mapping from attested prover address to expired timestamp. + mapping(address => uint256) public attestedProverExpireTime; + + /// @notice The struct of next selected prover. + NextProver public nextProver; + + /// @notice The queue for attested provers. + mapping(uint256 => address) public proverQueue; + + /// @notice The queue head for `proverQueue`. + uint256 public proverQueueHead; + + /// @notice The queue tail for `proverQueue`. + uint256 public proverQueueTail; + + /*************** + * Constructor * + ***************/ + + constructor( + address _attestationVerifier, + uint256 _attestValiditySeconds, + uint256 _maxBlockNumberDiff, + uint256 _proofSubmissionDelay + ) EIP712("SGXVerifier", "1") { + attestationVerifier = _attestationVerifier; + attestValiditySeconds = _attestValiditySeconds; + maxBlockNumberDiff = _maxBlockNumberDiff; + proofSubmissionDelay = _proofSubmissionDelay; + + _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); + } + + /************************* + * Public View Functions * + *************************/ + + /// @notice Return the EIP712 hash for `ProveBundleSignatureData` + /// @dev The struct is below, also see https://github.com/scroll-tech/sgx-prover/blob/main/crates/rpc/src/types.rs + /// ```text + /// struct ProveBundleSignatureData { + /// layer2ChainId uint64; + /// numBatches uint32; + /// prevStateRoot bytes32; + /// prevBatchHash bytes32; + /// postStateRoot bytes32; + /// batchHash bytes32; + /// postWithdrawRoot bytes32; + /// } + /// ``` + function getProveBundleSignatureDataHash( + uint64 layer2ChainId, + uint32 numBatches, + bytes32 prevStateRoot, + bytes32 prevBatchHash, + bytes32 postStateRoot, + bytes32 batchHash, + bytes32 postWithdrawRoot + ) public view returns (bytes32) { + bytes32 structHash = keccak256( + abi.encode( + _BUNDLE_PAYLOAD_TYPEHASH, + layer2ChainId, + numBatches, + prevStateRoot, + prevBatchHash, + postStateRoot, + batchHash, + postWithdrawRoot + ) + ); + return _hashTypedDataV4(structHash); + } + + /// @inheritdoc IZkEvmVerifierV2 + /// + /// @dev Encoding for `publicInput` + /// ```text + /// | layer2ChainId | numBatches | prevStateRoot | prevBatchHash | postStateRoot | batchHash | postWithdrawRoot | + /// | 8 bytes | 4 bytes | 32 bytes | 32 bytes | 32 bytes | 32 bytes | 32 bytes | + /// ``` + /// + /// This function will revert when the proof is invalid. + function verify(bytes calldata bundleProof, bytes calldata publicInput) external view override { + uint64 layer2ChainId; + uint32 numBatches; + bytes32 prevStateRoot; + bytes32 prevBatchHash; + bytes32 postStateRoot; + bytes32 batchHash; + bytes32 postWithdrawRoot; + assembly { + layer2ChainId := shr(192, calldataload(publicInput.offset)) + numBatches := shr(224, calldataload(add(publicInput.offset, 0x08))) + prevStateRoot := calldataload(add(publicInput.offset, 0xc)) + prevBatchHash := calldataload(add(publicInput.offset, 0x2c)) + postStateRoot := calldataload(add(publicInput.offset, 0x4c)) + batchHash := calldataload(add(publicInput.offset, 0x6c)) + postWithdrawRoot := calldataload(add(publicInput.offset, 0x8c)) + } + + bytes32 hash = getProveBundleSignatureDataHash( + layer2ChainId, + numBatches, + prevStateRoot, + prevBatchHash, + postStateRoot, + batchHash, + postWithdrawRoot + ); + address prover = ECDSA.recover(hash, bundleProof); + + NextProver memory selectedProver = nextProver; + // Allow any prover when selected prover doesn't submit proof within `expireTime`. + if (selectedProver.prover != prover && selectedProver.expireTime > block.timestamp) { + revert ErrorNotSelectedProver(); + } + + // prover expired + if (attestedProverExpireTime[prover] < block.timestamp) { + revert ErrorProverOutOfDate(); + } + } + + /***************************** + * Public Mutating Functions * + *****************************/ + + /// @inheritdoc ISGXVerifier + function randomSelectNextProver() external onlyRole(PROVER_SELECTION_ROLE) returns (address) { + uint256 cachedQueueHead = proverQueueHead; + uint256 cachedQueueTail = proverQueueTail; + bytes32 digest = keccak256(abi.encode(block.timestamp, block.prevrandao, blockhash(block.number - 1))); + // The expected number of randoms is `O(log(proverQueueTail - proverQueueHead))`. + while (cachedQueueHead < cachedQueueTail) { + uint256 size = cachedQueueTail - cachedQueueHead; + uint256 index = (uint256(digest) % size) + cachedQueueHead; + address proverAddr = proverQueue[index]; + uint256 validUntil = attestedProverExpireTime[proverAddr]; + if (validUntil > block.timestamp + proofSubmissionDelay) { + _selectProver(proverAddr, block.timestamp + proofSubmissionDelay); + break; + } else { + cachedQueueHead = index + 1; + } + + digest = keccak256(abi.encode(digest)); + } + proverQueueHead = cachedQueueHead; + if (cachedQueueHead == cachedQueueTail) return address(0); + else return nextProver.prover; + } + + /************************ + * Restricted Functions * + ************************/ + + /// @notice register prover instance with quote + /// @param _report The generated attestation report by the prover. + /// @param _data The custom data attached with the report. + function register(bytes calldata _report, ReportData calldata _data) external { + // @note We disable whitelist when `address(0)` is whitelisted. + if (!hasRole(PROVER_REGISTER_ROLE, address(0))) { + _checkRole(PROVER_REGISTER_ROLE, _msgSender()); + } + + // check reference block number is valid + _checkBlockNumber(_data.referenceBlockNumber, _data.referenceBlockHash); + bytes32 dataHash = keccak256(abi.encode(_data)); + + // verify the report + IAttestationVerifier(attestationVerifier).verifyAttestation(_report, dataHash); + + bytes32 reportHash = keccak256(_report); + if (attestedReports[reportHash]) { + revert ErrorReportUsed(); + } + attestedReports[reportHash] = true; + + // This won't exceed `type(uint64).max`. + uint256 validUntil = block.timestamp + attestValiditySeconds; + attestedProverExpireTime[_data.addr] = validUntil; + + uint256 cachedQueueTail = proverQueueTail; + proverQueue[cachedQueueTail] = _data.addr; + proverQueueTail = cachedQueueTail + 1; + + // initialize first selected prover when queue size is 1 + if (cachedQueueTail == proverQueueHead) { + _selectProver(_data.addr, block.timestamp + proofSubmissionDelay); + } + + emit ProverRegistered(_data.addr, validUntil); + } + + /********************** + * Internal Functions * + **********************/ + + /// Due to the inherent unpredictability of blockHash, it mitigates the risk of mass-generation + /// of attestation reports in a short time frame, preventing their delayed and gradual exploitation. + /// This function will make sure the attestation report generated in recent ${maxBlockNumberDiff} blocks + function _checkBlockNumber(uint256 blockNumber, bytes32 blockHash) private view { + if (blockNumber >= block.number) { + revert ErrorInvalidBlockNumber(); + } + if (block.number - blockNumber >= maxBlockNumberDiff) { + revert ErrorBlockNumberOutOfDate(); + } + if (blockhash(blockNumber) != blockHash) { + revert ErrorBlockHashMismatch(); + } + } + + /// @dev Internal function to select one prover with expire time. + function _selectProver(address proverAddr, uint256 expireAt) private { + nextProver = NextProver(proverAddr, uint64(expireAt)); + + emit ProverSelected(proverAddr, expireAt); + } +} diff --git a/src/sgx-verifier/utils/BytesUtils.sol b/src/sgx-verifier/utils/BytesUtils.sol new file mode 100644 index 00000000..d9d5614a --- /dev/null +++ b/src/sgx-verifier/utils/BytesUtils.sol @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: BSD 2-Clause License +pragma solidity ^0.8.24; + +// Inspired by ensdomains/dnssec-oracle - BSD-2-Clause license +// https://github.com/ensdomains/dnssec-oracle/blob/master/contracts/BytesUtils.sol + +/// @dev copy from: https://github.com/automata-network/automata-dcap-attestation/blob/main/contracts/utils/BytesUtils.sol +library BytesUtils { + /* + * @dev Returns the keccak-256 hash of a byte range. + * @param self The byte string to hash. + * @param offset The position to start hashing at. + * @param len The number of bytes to hash. + * @return The hash of the byte range. + */ + function keccak( + bytes memory self, + uint256 offset, + uint256 len + ) internal pure returns (bytes32 ret) { + require(offset + len <= self.length); + assembly { + ret := keccak256(add(add(self, 32), offset), len) + } + } + + /* + * @dev Returns a positive number if `other` comes lexicographically after + * `self`, a negative number if it comes before, or zero if the + * contents of the two bytes are equal. + * @param self The first bytes to compare. + * @param other The second bytes to compare. + * @return The result of the comparison. + */ + function compare(bytes memory self, bytes memory other) internal pure returns (int256) { + return compare(self, 0, self.length, other, 0, other.length); + } + + /* + * @dev Returns a positive number if `other` comes lexicographically after + * `self`, a negative number if it comes before, or zero if the + * contents of the two bytes are equal. Comparison is done per-rune, + * on unicode codepoints. + * @param self The first bytes to compare. + * @param offset The offset of self. + * @param len The length of self. + * @param other The second bytes to compare. + * @param otheroffset The offset of the other string. + * @param otherlen The length of the other string. + * @return The result of the comparison. + */ + function compare( + bytes memory self, + uint256 offset, + uint256 len, + bytes memory other, + uint256 otheroffset, + uint256 otherlen + ) internal pure returns (int256) { + uint256 shortest = len; + if (otherlen < len) { + shortest = otherlen; + } + + uint256 selfptr; + uint256 otherptr; + + assembly { + selfptr := add(self, add(offset, 32)) + otherptr := add(other, add(otheroffset, 32)) + } + for (uint256 idx = 0; idx < shortest; idx += 32) { + uint256 a; + uint256 b; + assembly { + a := mload(selfptr) + b := mload(otherptr) + } + if (a != b) { + // Mask out irrelevant bytes and check again + uint256 mask; + if (shortest > 32) { + mask = type(uint256).max; // aka 0xffffff.... + } else { + mask = ~(2**(8 * (32 - shortest + idx)) - 1); + } + uint256 diff = (a & mask) - (b & mask); + if (diff != 0) { + return int256(diff); + } + } + selfptr += 32; + otherptr += 32; + } + + return int256(len) - int256(otherlen); + } + + /* + * @dev Returns true if the two byte ranges are equal. + * @param self The first byte range to compare. + * @param offset The offset into the first byte range. + * @param other The second byte range to compare. + * @param otherOffset The offset into the second byte range. + * @param len The number of bytes to compare + * @return True if the byte ranges are equal, false otherwise. + */ + function equals( + bytes memory self, + uint256 offset, + bytes memory other, + uint256 otherOffset, + uint256 len + ) internal pure returns (bool) { + return keccak(self, offset, len) == keccak(other, otherOffset, len); + } + + /* + * @dev Returns true if the two byte ranges are equal with offsets. + * @param self The first byte range to compare. + * @param offset The offset into the first byte range. + * @param other The second byte range to compare. + * @param otherOffset The offset into the second byte range. + * @return True if the byte ranges are equal, false otherwise. + */ + function equals( + bytes memory self, + uint256 offset, + bytes memory other, + uint256 otherOffset + ) internal pure returns (bool) { + return keccak(self, offset, self.length - offset) == keccak(other, otherOffset, other.length - otherOffset); + } + + /* + * @dev Compares a range of 'self' to all of 'other' and returns True iff + * they are equal. + * @param self The first byte range to compare. + * @param offset The offset into the first byte range. + * @param other The second byte range to compare. + * @return True if the byte ranges are equal, false otherwise. + */ + function equals( + bytes memory self, + uint256 offset, + bytes memory other + ) internal pure returns (bool) { + return self.length >= offset + other.length && equals(self, offset, other, 0, other.length); + } + + /* + * @dev Returns true if the two byte ranges are equal. + * @param self The first byte range to compare. + * @param other The second byte range to compare. + * @return True if the byte ranges are equal, false otherwise. + */ + function equals(bytes memory self, bytes memory other) internal pure returns (bool) { + return self.length == other.length && equals(self, 0, other, 0, self.length); + } + + /* + * @dev Returns the 8-bit number at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes + * @return The specified 8 bits of the string, interpreted as an integer. + */ + function readUint8(bytes memory self, uint256 idx) internal pure returns (uint8 ret) { + return uint8(self[idx]); + } + + /* + * @dev Returns the 16-bit number at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes + * @return The specified 16 bits of the string, interpreted as an integer. + */ + function readUint16(bytes memory self, uint256 idx) internal pure returns (uint16 ret) { + require(idx + 2 <= self.length); + assembly { + ret := and(mload(add(add(self, 2), idx)), 0xFFFF) + } + } + + /* + * @dev Returns the 32-bit number at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes + * @return The specified 32 bits of the string, interpreted as an integer. + */ + function readUint32(bytes memory self, uint256 idx) internal pure returns (uint32 ret) { + require(idx + 4 <= self.length); + assembly { + ret := and(mload(add(add(self, 4), idx)), 0xFFFFFFFF) + } + } + + /* + * @dev Returns the 32 byte value at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes + * @return The specified 32 bytes of the string. + */ + function readBytes32(bytes memory self, uint256 idx) internal pure returns (bytes32 ret) { + require(idx + 32 <= self.length); + assembly { + ret := mload(add(add(self, 32), idx)) + } + } + + /* + * @dev Returns the 32 byte value at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes + * @return The specified 32 bytes of the string. + */ + function readBytes20(bytes memory self, uint256 idx) internal pure returns (bytes20 ret) { + require(idx + 20 <= self.length); + assembly { + ret := and( + mload(add(add(self, 32), idx)), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000 + ) + } + } + + /* + * @dev Returns the n byte value at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes. + * @param len The number of bytes. + * @return The specified 32 bytes of the string. + */ + function readBytesN( + bytes memory self, + uint256 idx, + uint256 len + ) internal pure returns (bytes32 ret) { + require(len <= 32); + require(idx + len <= self.length); + assembly { + let mask := not(sub(exp(256, sub(32, len)), 1)) + ret := and(mload(add(add(self, 32), idx)), mask) + } + } + + function memcpy( + uint256 dest, + uint256 src, + uint256 len + ) private pure { + // Copy word-length chunks while possible + for (; len >= 32; len -= 32) { + assembly { + mstore(dest, mload(src)) + } + dest += 32; + src += 32; + } + + // Copy remaining bytes + uint256 mask; + if (len == 0) { + mask = type(uint256).max; // Set to maximum value of uint256 + } else { + mask = 256**(32 - len) - 1; + } + + assembly { + let srcpart := and(mload(src), not(mask)) + let destpart := and(mload(dest), mask) + mstore(dest, or(destpart, srcpart)) + } + } + + /* + * @dev Copies a substring into a new byte string. + * @param self The byte string to copy from. + * @param offset The offset to start copying at. + * @param len The number of bytes to copy. + */ + function substring( + bytes memory self, + uint256 offset, + uint256 len + ) internal pure returns (bytes memory) { + require(offset + len <= self.length); + + bytes memory ret = new bytes(len); + uint256 dest; + uint256 src; + + assembly { + dest := add(ret, 32) + src := add(add(self, 32), offset) + } + memcpy(dest, src, len); + + return ret; + } + + // Maps characters from 0x30 to 0x7A to their base32 values. + // 0xFF represents invalid characters in that range. + bytes constant base32HexTable = + hex"00010203040506070809FFFFFFFFFFFFFF0A0B0C0D0E0F101112131415161718191A1B1C1D1E1FFFFFFFFFFFFFFFFFFFFF0A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"; + + /** + * @dev Decodes unpadded base32 data of up to one word in length. + * @param self The data to decode. + * @param off Offset into the string to start at. + * @param len Number of characters to decode. + * @return The decoded data, left aligned. + */ + function base32HexDecodeWord( + bytes memory self, + uint256 off, + uint256 len + ) internal pure returns (bytes32) { + require(len <= 52); + + uint256 ret = 0; + uint8 decoded; + for (uint256 i = 0; i < len; i++) { + bytes1 char = self[off + i]; + require(char >= 0x30 && char <= 0x7A); + decoded = uint8(base32HexTable[uint256(uint8(char)) - 0x30]); + require(decoded <= 0x20); + if (i == len - 1) { + break; + } + ret = (ret << 5) | decoded; + } + + uint256 bitlen = len * 5; + if (len % 8 == 0) { + // Multiple of 8 characters, no padding + ret = (ret << 5) | decoded; + } else if (len % 8 == 2) { + // Two extra characters - 1 byte + ret = (ret << 3) | (decoded >> 2); + bitlen -= 2; + } else if (len % 8 == 4) { + // Four extra characters - 2 bytes + ret = (ret << 1) | (decoded >> 4); + bitlen -= 4; + } else if (len % 8 == 5) { + // Five extra characters - 3 bytes + ret = (ret << 4) | (decoded >> 1); + bitlen -= 1; + } else if (len % 8 == 7) { + // Seven extra characters - 4 bytes + ret = (ret << 2) | (decoded >> 3); + bitlen -= 3; + } else { + revert(); + } + + return bytes32(ret << (256 - bitlen)); + } + + function compareBytes(bytes memory a, bytes memory b) internal pure returns (bool) { + if (a.length != b.length) { + return false; + } + for (uint256 i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } +} diff --git a/src/test/L1GatewayTestBase.t.sol b/src/test/L1GatewayTestBase.t.sol index c5889350..e1c1263a 100644 --- a/src/test/L1GatewayTestBase.t.sol +++ b/src/test/L1GatewayTestBase.t.sol @@ -64,7 +64,8 @@ abstract contract L1GatewayTestBase is ScrollTestBase { EnforcedTxGateway internal enforcedTxGateway; ScrollChainMockBlob internal rollup; - MockRollupVerifier internal verifier; + MockRollupVerifier internal zkpVerifier; + MockRollupVerifier internal teeVerifier; address internal feeVault; Whitelist private whitelist; @@ -91,7 +92,8 @@ abstract contract L1GatewayTestBase is ScrollTestBase { enforcedTxGateway = EnforcedTxGateway(_deployProxy(address(new EnforcedTxGateway()))); gasOracle = L2GasPriceOracle(_deployProxy(address(new L2GasPriceOracle()))); whitelist = new Whitelist(address(this)); - verifier = new MockRollupVerifier(); + zkpVerifier = new MockRollupVerifier(); + teeVerifier = new MockRollupVerifier(); // deploy proxy and contracts in L2 l2Messenger = L2ScrollMessenger(payable(_deployProxy(address(0)))); @@ -124,9 +126,10 @@ abstract contract L1GatewayTestBase is ScrollTestBase { // Upgrade the ScrollChain implementation and initialize admin.upgrade( ITransparentUpgradeableProxy(address(rollup)), - address(new ScrollChainMockBlob(1233, address(messageQueue), address(verifier))) + address(new ScrollChainMockBlob(1233, address(messageQueue), address(zkpVerifier), address(teeVerifier), 0)) ); rollup.initialize(address(messageQueue), address(0), 44); + rollup.initializeV2(1); // Setup whitelist address[] memory _accounts = new address[](1); @@ -176,14 +179,10 @@ abstract contract L1GatewayTestBase is ScrollTestBase { } hevm.startPrank(address(0)); - rollup.finalizeBatchWithProof4844( - batchHeader1, - bytes32(uint256(1)), - bytes32(uint256(2)), - messageHash, - blobDataProof, - new bytes(0) - ); + rollup.finalizeBundleWithProof(batchHeader1, bytes32(uint256(2)), messageHash, new bytes(0)); + rollup.finalizeBundleWithTeeProof(batchHeader1, bytes32(uint256(2)), messageHash, new bytes(0)); hevm.stopPrank(); + + rollup.lastFinalizedBatchIndex(); } } diff --git a/src/test/MultipleVersionRollupVerifier.t.sol b/src/test/MultipleVersionRollupVerifier.t.sol index 878c3e5c..fe7ee2b2 100644 --- a/src/test/MultipleVersionRollupVerifier.t.sol +++ b/src/test/MultipleVersionRollupVerifier.t.sol @@ -7,7 +7,6 @@ import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; import {L1MessageQueue} from "../L1/rollup/L1MessageQueue.sol"; import {MultipleVersionRollupVerifier} from "../L1/rollup/MultipleVersionRollupVerifier.sol"; -import {MockScrollChain} from "./mocks/MockScrollChain.sol"; import {MockZkEvmVerifier} from "./mocks/MockZkEvmVerifier.sol"; contract MultipleVersionRollupVerifierTest is DSTestPlus { diff --git a/src/test/ScrollChain.t.sol b/src/test/ScrollChain.t.sol index a5eb8612..1c7cd01b 100644 --- a/src/test/ScrollChain.t.sol +++ b/src/test/ScrollChain.t.sol @@ -30,22 +30,44 @@ contract ScrollChainTest is DSTestPlus { event UpdateMaxNumTxInChunk(uint256 oldMaxNumTxInChunk, uint256 newMaxNumTxInChunk); event CommitBatch(uint256 indexed batchIndex, bytes32 indexed batchHash); - event FinalizeBatch(uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot); event RevertBatch(uint256 indexed batchIndex, bytes32 indexed batchHash); + event VerifyBatchWithZkp( + uint256 indexed batchIndex, + bytes32 indexed batchHash, + bytes32 stateRoot, + bytes32 withdrawRoot + ); + event VerifyBatchWithTee( + uint256 indexed batchIndex, + bytes32 indexed batchHash, + bytes32 stateRoot, + bytes32 withdrawRoot + ); + event FinalizeBatch(uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot); + event StateMismatch(uint256 indexed batchIndex, bytes32 stateRoot, bytes32 withdrawRoot); + event ResolveState(uint256 indexed batchIndex, bytes32 stateRoot, bytes32 withdrawRoot); + event ChangeBundleSize(uint256 index, uint256 size, uint256 batchIndex); + + // from L1MessageQueue + event DequeueTransaction(uint256 startIndex, uint256 count, uint256 skippedBitmap); + event ResetDequeuedTransaction(uint256 startIndex); + event FinalizedDequeuedTransaction(uint256 finalizedIndex); ProxyAdmin internal admin; EmptyContract private placeholder; ScrollChain private rollup; L1MessageQueue internal messageQueue; - MockRollupVerifier internal verifier; + MockRollupVerifier internal zkpVerifier; + MockRollupVerifier internal teeVerifier; function setUp() public { placeholder = new EmptyContract(); admin = new ProxyAdmin(); messageQueue = L1MessageQueue(_deployProxy(address(0))); rollup = ScrollChain(_deployProxy(address(0))); - verifier = new MockRollupVerifier(); + zkpVerifier = new MockRollupVerifier(); + teeVerifier = new MockRollupVerifier(); // Upgrade the L1MessageQueue implementation and initialize admin.upgrade( @@ -56,9 +78,10 @@ contract ScrollChainTest is DSTestPlus { // Upgrade the ScrollChain implementation and initialize admin.upgrade( ITransparentUpgradeableProxy(address(rollup)), - address(new ScrollChain(233, address(messageQueue), address(verifier))) + address(new ScrollChain(233, address(messageQueue), address(zkpVerifier), address(teeVerifier), 0)) ); - rollup.initialize(address(messageQueue), address(verifier), 100); + rollup.initialize(address(messageQueue), address(zkpVerifier), 100); + rollup.initializeV2(1); } function testInitialized() external { @@ -189,7 +212,9 @@ contract ScrollChainTest is DSTestPlus { ScrollChainMockBlob impl = new ScrollChainMockBlob( rollup.layer2ChainId(), rollup.messageQueue(), - rollup.verifier() + rollup.zkpVerifier(), + rollup.teeVerifier(), + 0 ); admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); // this is keccak(""); @@ -232,30 +257,124 @@ contract ScrollChainTest is DSTestPlus { hevm.stopPrank(); } - function testFinalizeBatchWithProof4844() external { - // caller not prover, revert - hevm.expectRevert(ScrollChain.ErrorCallerIsNotProver.selector); - rollup.finalizeBatchWithProof4844(new bytes(0), bytes32(0), bytes32(0), bytes32(0), new bytes(0), new bytes(0)); - - rollup.addProver(address(0)); - rollup.addSequencer(address(0)); - + function testCommitBatchV3() external { bytes memory batchHeader0 = new bytes(89); - // import genesis batch + // import 10 L1 messages + for (uint256 i = 0; i < 10; i++) { + messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); + } + // import genesis batch first assembly { mstore(add(batchHeader0, add(0x20, 25)), 1) } rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1))); + assertEq(rollup.committedBatches(0), keccak256(batchHeader0)); + + // caller not sequencer, revert + hevm.expectRevert(ScrollChain.ErrorCallerIsNotSequencer.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); + rollup.addSequencer(address(0)); + + // revert when ErrorIncorrectBatchVersion + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorIncorrectBatchVersion.selector); + rollup.commitBatchWithBlobProof(2, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); + hevm.stopPrank(); + + // revert when ErrorBatchIsEmpty + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorBatchIsEmpty.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); + hevm.stopPrank(); + + // revert when ErrorBatchHeaderV3LengthMismatch + bytes memory header = new bytes(192); + assembly { + mstore8(add(header, 0x20), 3) // version + } + hevm.startPrank(address(0)); + hevm.expectRevert(BatchHeaderV3Codec.ErrorBatchHeaderV3LengthMismatch.selector); + rollup.commitBatchWithBlobProof(3, header, new bytes[](1), new bytes(0), new bytes(0)); + hevm.stopPrank(); + + // revert when ErrorIncorrectBatchHash + assembly { + mstore(add(batchHeader0, add(0x20, 25)), 2) // change data hash for batch0 + } + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, new bytes[](1), new bytes(0), new bytes(0)); + hevm.stopPrank(); + assembly { + mstore(add(batchHeader0, add(0x20, 25)), 1) // change back + } bytes[] memory chunks = new bytes[](1); bytes memory chunk0; + // no block in chunk, revert + chunk0 = new bytes(1); + chunks[0] = chunk0; + hevm.startPrank(address(0)); + hevm.expectRevert(ChunkCodecV1.ErrorNoBlockInChunkV1.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), new bytes(0)); + hevm.stopPrank(); + + // invalid chunk length, revert + chunk0 = new bytes(1); + chunk0[0] = bytes1(uint8(1)); // one block in this chunk + chunks[0] = chunk0; + hevm.startPrank(address(0)); + hevm.expectRevert(ChunkCodecV1.ErrorIncorrectChunkLengthV1.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), new bytes(0)); + hevm.stopPrank(); + + // cannot skip last L1 message, revert + chunk0 = new bytes(1 + 60); + bytes memory bitmap = new bytes(32); + chunk0[0] = bytes1(uint8(1)); // one block in this chunk + chunk0[58] = bytes1(uint8(1)); // numTransactions = 1 + chunk0[60] = bytes1(uint8(1)); // numL1Messages = 1 + bitmap[31] = bytes1(uint8(1)); + chunks[0] = chunk0; + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorLastL1MessageSkipped.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, bitmap, new bytes(0)); + hevm.stopPrank(); + + // num txs less than num L1 msgs, revert + chunk0 = new bytes(1 + 60); + bitmap = new bytes(32); + chunk0[0] = bytes1(uint8(1)); // one block in this chunk + chunk0[58] = bytes1(uint8(1)); // numTransactions = 1 + chunk0[60] = bytes1(uint8(3)); // numL1Messages = 3 + bitmap[31] = bytes1(uint8(3)); + chunks[0] = chunk0; + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorNumTxsLessThanNumL1Msgs.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, bitmap, new bytes(0)); + hevm.stopPrank(); + + // revert when ErrorNoBlobFound + // revert when ErrorNoBlobFound + chunk0 = new bytes(1 + 60); + chunk0[0] = bytes1(uint8(1)); // one block in this chunk + chunks[0] = chunk0; + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorNoBlobFound.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), new bytes(0)); + hevm.stopPrank(); + + // @note we cannot check `ErrorFoundMultipleBlobs` here + // upgrade to ScrollChainMockBlob ScrollChainMockBlob impl = new ScrollChainMockBlob( rollup.layer2ChainId(), rollup.messageQueue(), - rollup.verifier() + rollup.zkpVerifier(), + rollup.teeVerifier(), + 0 ); admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); // from https://etherscan.io/blob/0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757?bid=740652 @@ -264,935 +383,609 @@ contract ScrollChainTest is DSTestPlus { memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; ScrollChainMockBlob(address(rollup)).setBlobVersionedHash(blobVersionedHash); + chunk0 = new bytes(1 + 60); + chunk0[0] = bytes1(uint8(1)); // one block in this chunk + chunks[0] = chunk0; + // revert when ErrorCallPointEvaluationPrecompileFailed + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorCallPointEvaluationPrecompileFailed.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), new bytes(0)); + hevm.stopPrank(); + bytes32 batchHash0 = rollup.committedBatches(0); - bytes memory batchHeader1 = new bytes(121); + bytes memory batchHeader1 = new bytes(193); assembly { - mstore8(add(batchHeader1, 0x20), 1) // version + mstore8(add(batchHeader1, 0x20), 3) // version mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex mstore(add(batchHeader1, add(0x20, 9)), 0) // l1MessagePopped mstore(add(batchHeader1, add(0x20, 17)), 0) // totalL1MessagePopped mstore(add(batchHeader1, add(0x20, 25)), 0x246394445f4fe64ed5598554d55d1682d6fb3fe04bf58eb54ef81d1189fafb51) // dataHash mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash + mstore(add(batchHeader1, add(0x20, 121)), 0) // lastBlockTimestamp + mcopy(add(batchHeader1, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof } - // batch hash is 0xf7d9af8c2c8e1a84f1fa4b6af9425f85c50a61b24cdd28101a5f6d781906a5b9 + // hash is ed32768c5f910a11edaf1c1ec0c0da847def9d24e0a24567c3c3d284061cf935 - // commit one batch - chunk0 = new bytes(1 + 60); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunks[0] = chunk0; + // succeed hevm.startPrank(address(0)); - rollup.commitBatch(1, batchHeader0, chunks, new bytes(0)); + assertEq(rollup.committedBatches(1), bytes32(0)); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), blobDataProof); hevm.stopPrank(); assertEq(rollup.committedBatches(1), keccak256(batchHeader1)); - // incorrect batch hash, revert - batchHeader1[1] = bytes1(uint8(1)); // change random byte + // revert when ErrorBatchIsAlreadyCommitted hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); - rollup.finalizeBatchWithProof4844( - batchHeader1, - bytes32(uint256(1)), - bytes32(uint256(2)), - bytes32(0), - new bytes(0), - new bytes(0) - ); + hevm.expectRevert(ScrollChain.ErrorBatchIsAlreadyCommitted.selector); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), blobDataProof); hevm.stopPrank(); - batchHeader1[1] = bytes1(uint8(0)); // change back + } - // batch header length too small, revert - bytes memory header = new bytes(120); + function testFinalizeBundleWithOnlyZkProof() external { + hevm.warp(100000); + + // revert ErrorCallerIsNotProver + hevm.expectRevert(ScrollChain.ErrorCallerIsNotProver.selector); + rollup.finalizeBundleWithProof(new bytes(0), bytes32(0), bytes32(0), new bytes(0)); + + bytes[] memory headers = _prepareFinalizeBundle(); + + // revert when ErrorBatchHeaderV3LengthMismatch + bytes memory header = new bytes(192); assembly { - mstore8(add(header, 0x20), 1) // version + mstore8(add(header, 0x20), 3) // version } hevm.startPrank(address(0)); - hevm.expectRevert(BatchHeaderV1Codec.ErrorBatchHeaderV1LengthTooSmall.selector); - rollup.finalizeBatchWithProof4844( - header, - bytes32(uint256(1)), - bytes32(uint256(2)), - bytes32(0), - new bytes(0), - new bytes(0) - ); + hevm.expectRevert(BatchHeaderV3Codec.ErrorBatchHeaderV3LengthMismatch.selector); + rollup.finalizeBundleWithProof(header, bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); hevm.stopPrank(); - // wrong bitmap length, revert - header = new bytes(122); - assembly { - mstore8(add(header, 0x20), 1) // version - } + // revert ErrorIncorrectBatchHash + headers[1][1] = bytes1(uint8(1)); // change random byte hevm.startPrank(address(0)); - hevm.expectRevert(BatchHeaderV1Codec.ErrorIncorrectBitmapLengthV1.selector); - rollup.finalizeBatchWithProof4844( - header, - bytes32(uint256(1)), - bytes32(uint256(2)), - bytes32(0), - new bytes(0), - new bytes(0) - ); + hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); + rollup.finalizeBundleWithProof(headers[1], bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); hevm.stopPrank(); + headers[1][1] = bytes1(uint8(0)); // change back - // verify success - assertBoolEq(rollup.isBatchFinalized(1), false); + // revert ErrorBundleSizeMismatch hevm.startPrank(address(0)); - rollup.finalizeBatchWithProof4844( - batchHeader1, - bytes32(uint256(1)), - bytes32(uint256(2)), - bytes32(uint256(3)), - blobDataProof, - new bytes(0) - ); + hevm.expectRevert(ScrollChain.ErrorBundleSizeMismatch.selector); + rollup.finalizeBundleWithProof(headers[2], bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(1), true); - assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(2))); - assertEq(rollup.withdrawRoots(1), bytes32(uint256(3))); + + // only enable zk proof + ScrollChainMockBlob(address(rollup)).setEnabledProofTypeMask(1); + + // revert ErrorFinalizationPaused + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorFinalizationPaused.selector); + rollup.finalizeBundleWithTeeProof(headers[1], bytes32(uint256(1001)), bytes32(uint256(2001)), new bytes(0)); + hevm.stopPrank(); + + // prove batch 1, bundle size = 1 + hevm.startPrank(address(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(1); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + rollup.finalizeBundleWithProof(headers[1], bytes32(uint256(1001)), bytes32(uint256(2001)), new bytes(0)); + hevm.stopPrank(); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 0); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 1); assertEq(rollup.lastFinalizedBatchIndex(), 1); + assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(1001))); + assertEq(rollup.withdrawRoots(1), bytes32(uint256(2001))); + ScrollChainMockBlob(address(rollup)).setBatchCommittedTimestamp(1, block.timestamp - 99); + assertBoolEq(rollup.isBatchFinalized(1), false); + ScrollChainMockBlob(address(rollup)).setBatchCommittedTimestamp(1, block.timestamp - 100); + assertBoolEq(rollup.isBatchFinalized(1), true); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 2); - // batch already verified, revert + // change bundle size to 3, starting with batch index 3 + rollup.updateBundleSize(3, 2); + + // prove batch 2, bundle size = 1 hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsAlreadyVerified.selector); - rollup.finalizeBatchWithProof4844( - batchHeader1, - bytes32(uint256(1)), - bytes32(uint256(2)), - bytes32(uint256(3)), - blobDataProof, - new bytes(0) - ); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(3); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + rollup.finalizeBundleWithProof(headers[2], bytes32(uint256(1002)), bytes32(uint256(2002)), new bytes(0)); + hevm.stopPrank(); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 0); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 2); + assertEq(rollup.lastFinalizedBatchIndex(), 2); + assertEq(rollup.finalizedStateRoots(2), bytes32(uint256(1002))); + assertEq(rollup.withdrawRoots(2), bytes32(uint256(2002))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 4); + + // change bundle size to 5, starting with batch index 6 + rollup.updateBundleSize(5, 5); + + // prove batch 3~5, bundle size = 3 + hevm.startPrank(address(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(9); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + rollup.finalizeBundleWithProof(headers[5], bytes32(uint256(1005)), bytes32(uint256(2005)), new bytes(0)); + hevm.stopPrank(); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 0); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 5); + assertEq(rollup.lastFinalizedBatchIndex(), 5); + assertEq(rollup.finalizedStateRoots(5), bytes32(uint256(1005))); + assertEq(rollup.withdrawRoots(5), bytes32(uint256(2005))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 10); + + // prove batch 6~10, bundle size = 5 + hevm.startPrank(address(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(10, keccak256(headers[10]), bytes32(uint256(1010)), bytes32(uint256(2010))); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(10, keccak256(headers[10]), bytes32(uint256(1010)), bytes32(uint256(2010))); + rollup.finalizeBundleWithProof(headers[10], bytes32(uint256(1010)), bytes32(uint256(2010)), new bytes(0)); hevm.stopPrank(); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 0); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 10); + assertEq(rollup.lastFinalizedBatchIndex(), 10); + assertEq(rollup.finalizedStateRoots(10), bytes32(uint256(1010))); + assertEq(rollup.withdrawRoots(10), bytes32(uint256(2010))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 10); } - function testCommitAndFinalizeWithL1MessagesV1() external { - rollup.addSequencer(address(0)); - rollup.addProver(address(0)); + function testFinalizeBundleWithOnlyTeeProof() external { + hevm.warp(100000); - // import 300 L1 messages - for (uint256 i = 0; i < 300; i++) { - messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); - } + bytes[] memory headers = _prepareFinalizeBundle(); - // import genesis batch first - bytes memory batchHeader0 = new bytes(89); + // revert when ErrorBatchHeaderV3LengthMismatch + bytes memory header = new bytes(192); assembly { - mstore(add(batchHeader0, add(0x20, 25)), 1) + mstore8(add(header, 0x20), 3) // version } - rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1))); - bytes32 batchHash0 = rollup.committedBatches(0); + hevm.startPrank(address(0)); + hevm.expectRevert(BatchHeaderV3Codec.ErrorBatchHeaderV3LengthMismatch.selector); + rollup.finalizeBundleWithTeeProof(header, bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); + hevm.stopPrank(); - // upgrade to ScrollChainMockBlob - ScrollChainMockBlob impl = new ScrollChainMockBlob( - rollup.layer2ChainId(), - rollup.messageQueue(), - rollup.verifier() - ); - admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); - // from https://etherscan.io/blob/0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757?bid=740652 - bytes32 blobVersionedHash = 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757; - bytes - memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; - ScrollChainMockBlob(address(rollup)).setBlobVersionedHash(blobVersionedHash); + // revert ErrorIncorrectBatchHash + headers[1][1] = bytes1(uint8(1)); // change random byte + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); + rollup.finalizeBundleWithTeeProof(headers[1], bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); + hevm.stopPrank(); + headers[1][1] = bytes1(uint8(0)); // change back - bytes memory bitmap; - bytes[] memory chunks; - bytes memory chunk0; - bytes memory chunk1; + // revert ErrorBundleSizeMismatch + hevm.startPrank(address(0)); + hevm.expectRevert(ScrollChain.ErrorBundleSizeMismatch.selector); + rollup.finalizeBundleWithTeeProof(headers[2], bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); + hevm.stopPrank(); - // commit batch1, one chunk with one block, 1 tx, 1 L1 message, no skip - // => payload for data hash of chunk0 - // 0000000000000000 - // 0000000000000000 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 0001 - // a2277fd30bbbe74323309023b56035b376d7768ad237ae4fc46ead7dc9591ae1 - // => data hash for chunk0 - // 9ef1e5694bdb014a1eea42be756a8f63bfd8781d6332e9ef3b5126d90c62f110 - // => data hash for all chunks - // d9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3 - // => payload for batch header - // 01 - // 0000000000000001 - // 0000000000000001 - // 0000000000000001 - // d9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3 - // 013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757 - // 119b828c2a2798d2c957228ebeaff7e10bb099ae0d4e224f3eeb779ff61cba61 - // 0000000000000000000000000000000000000000000000000000000000000000 - // => hash for batch header - // 66b68a5092940d88a8c6f203d2071303557c024275d8ceaa2e12662bc61c8d8f - bytes memory batchHeader1 = new bytes(121 + 32); - assembly { - mstore8(add(batchHeader1, 0x20), 1) // version - mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex = 1 - mstore(add(batchHeader1, add(0x20, 9)), shl(192, 1)) // l1MessagePopped = 1 - mstore(add(batchHeader1, add(0x20, 17)), shl(192, 1)) // totalL1MessagePopped = 1 - mstore(add(batchHeader1, add(0x20, 25)), 0xd9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3) // dataHash - mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash - mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - mstore(add(batchHeader1, add(0x20, 121)), 0) // bitmap0 - } - chunk0 = new bytes(1 + 60); - assembly { - mstore(add(chunk0, 0x20), shl(248, 1)) // numBlocks = 1 - mstore(add(chunk0, add(0x21, 56)), shl(240, 1)) // numTransactions = 1 - mstore(add(chunk0, add(0x21, 58)), shl(240, 1)) // numL1Messages = 1 - } - chunks = new bytes[](1); - chunks[0] = chunk0; - bitmap = new bytes(32); + // only enable zk proof + ScrollChainMockBlob(address(rollup)).setEnabledProofTypeMask(2); + + // revert ErrorFinalizationPaused hevm.startPrank(address(0)); - hevm.expectEmit(true, true, false, true); - emit CommitBatch(1, keccak256(batchHeader1)); - rollup.commitBatch(1, batchHeader0, chunks, bitmap); + hevm.expectRevert(ScrollChain.ErrorFinalizationPaused.selector); + rollup.finalizeBundleWithProof(headers[1], bytes32(uint256(1001)), bytes32(uint256(2001)), new bytes(0)); hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(1), false); - bytes32 batchHash1 = rollup.committedBatches(1); - assertEq(batchHash1, keccak256(batchHeader1)); - // finalize batch1 + // prove batch 1, bundle size = 1 hevm.startPrank(address(0)); - hevm.expectEmit(true, true, false, true); - emit FinalizeBatch(1, batchHash1, bytes32(uint256(2)), bytes32(uint256(3))); - rollup.finalizeBatchWithProof4844( - batchHeader1, - bytes32(uint256(1)), - bytes32(uint256(2)), - bytes32(uint256(3)), - blobDataProof, - new bytes(0) - ); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(1); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + rollup.finalizeBundleWithTeeProof(headers[1], bytes32(uint256(1001)), bytes32(uint256(2001)), new bytes(0)); hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(1), true); - assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(2))); - assertEq(rollup.withdrawRoots(1), bytes32(uint256(3))); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 0); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 1); assertEq(rollup.lastFinalizedBatchIndex(), 1); - assertBoolEq(messageQueue.isMessageSkipped(0), false); - assertEq(messageQueue.pendingQueueIndex(), 1); + assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(1001))); + assertEq(rollup.withdrawRoots(1), bytes32(uint256(2001))); + ScrollChainMockBlob(address(rollup)).setBatchCommittedTimestamp(1, block.timestamp - 99); + assertBoolEq(rollup.isBatchFinalized(1), false); + ScrollChainMockBlob(address(rollup)).setBatchCommittedTimestamp(1, block.timestamp - 100); + assertBoolEq(rollup.isBatchFinalized(1), true); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 2); - // commit batch2 with two chunks, correctly - // 1. chunk0 has one block, 3 tx, no L1 messages - // => payload for chunk0 - // 0000000000000000 - // 0000000000000000 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 0003 - // ... (some tx hashes) - // => data hash for chunk0 - // c4e0d99a191bfcb1ba2edd2964a0f0a56c929b1ecdf149ba3ae4f045d6e6ef8b - // 2. chunk1 has three blocks - // 2.1 block0 has 5 tx, 3 L1 messages, no skips - // 2.2 block1 has 10 tx, 5 L1 messages, even is skipped, last is not skipped - // 2.2 block1 has 300 tx, 256 L1 messages, odd position is skipped, last is not skipped - // => payload for chunk1 - // 0000000000000000 - // 0000000000000000 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 0005 - // 0000000000000000 - // 0000000000000000 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 000a - // 0000000000000000 - // 0000000000000000 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 012c - // => data hash for chunk2 - // a84759a83bba5f73e3a748d138ae7b6c5a31a8a5273aeb0e578807bf1ef6ed4e - // => data hash for all chunks - // dae89323bf398ca9f6f8e83b1b0d603334be063fa3920015b6aa9df77a0ccbcd - // => payload for batch header - // 01 - // 0000000000000002 - // 0000000000000108 - // 0000000000000109 - // dae89323bf398ca9f6f8e83b1b0d603334be063fa3920015b6aa9df77a0ccbcd - // 013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757 - // 66b68a5092940d88a8c6f203d2071303557c024275d8ceaa2e12662bc61c8d8f - // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa28000000000000000000000000000000000000000000000000000000000000002a - // => hash for batch header - // b9dff5d21381176a73b20a9294eb2703c803113f9559e358708c659fa1cf62eb - bytes memory batchHeader2 = new bytes(121 + 32 + 32); - assembly { - mstore8(add(batchHeader2, 0x20), 1) // version - mstore(add(batchHeader2, add(0x20, 1)), shl(192, 2)) // batchIndex = 2 - mstore(add(batchHeader2, add(0x20, 9)), shl(192, 264)) // l1MessagePopped = 264 - mstore(add(batchHeader2, add(0x20, 17)), shl(192, 265)) // totalL1MessagePopped = 265 - mstore(add(batchHeader2, add(0x20, 25)), 0xdae89323bf398ca9f6f8e83b1b0d603334be063fa3920015b6aa9df77a0ccbcd) // dataHash - mstore(add(batchHeader2, add(0x20, 57)), blobVersionedHash) // blobVersionedHash - mstore(add(batchHeader2, add(0x20, 89)), batchHash1) // parentBatchHash - mstore( - add(batchHeader2, add(0x20, 121)), - 77194726158210796949047323339125271902179989777093709359638389338608753093160 - ) // bitmap0 - mstore(add(batchHeader2, add(0x20, 153)), 42) // bitmap1 - } - chunk0 = new bytes(1 + 60); - assembly { - mstore(add(chunk0, 0x20), shl(248, 1)) // numBlocks = 1 - mstore(add(chunk0, add(0x21, 56)), shl(240, 3)) // numTransactions = 3 - mstore(add(chunk0, add(0x21, 58)), shl(240, 0)) // numL1Messages = 0 - } - chunk1 = new bytes(1 + 60 * 3); - assembly { - mstore(add(chunk1, 0x20), shl(248, 3)) // numBlocks = 3 - mstore(add(chunk1, add(33, 56)), shl(240, 5)) // block0.numTransactions = 5 - mstore(add(chunk1, add(33, 58)), shl(240, 3)) // block0.numL1Messages = 3 - mstore(add(chunk1, add(93, 56)), shl(240, 10)) // block1.numTransactions = 10 - mstore(add(chunk1, add(93, 58)), shl(240, 5)) // block1.numL1Messages = 5 - mstore(add(chunk1, add(153, 56)), shl(240, 300)) // block1.numTransactions = 300 - mstore(add(chunk1, add(153, 58)), shl(240, 256)) // block1.numL1Messages = 256 - } - chunks = new bytes[](2); - chunks[0] = chunk0; - chunks[1] = chunk1; - bitmap = new bytes(64); - assembly { - mstore( - add(bitmap, add(0x20, 0)), - 77194726158210796949047323339125271902179989777093709359638389338608753093160 - ) // bitmap0 - mstore(add(bitmap, add(0x20, 32)), 42) // bitmap1 - } + // change bundle size to 3, starting with batch index 3 + rollup.updateBundleSize(3, 2); - // too many txs in one chunk, revert - rollup.updateMaxNumTxInChunk(2); // 3 - 1 - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorTooManyTxsInOneChunk.selector); - rollup.commitBatch(1, batchHeader1, chunks, bitmap); // first chunk with too many txs - hevm.stopPrank(); - rollup.updateMaxNumTxInChunk(185); // 5+10+300 - 2 - 127 + // prove batch 2, bundle size = 1 hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorTooManyTxsInOneChunk.selector); - rollup.commitBatch(1, batchHeader1, chunks, bitmap); // second chunk with too many txs + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(3); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + rollup.finalizeBundleWithTeeProof(headers[2], bytes32(uint256(1002)), bytes32(uint256(2002)), new bytes(0)); hevm.stopPrank(); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 0); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 2); + assertEq(rollup.lastFinalizedBatchIndex(), 2); + assertEq(rollup.finalizedStateRoots(2), bytes32(uint256(1002))); + assertEq(rollup.withdrawRoots(2), bytes32(uint256(2002))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 4); - rollup.updateMaxNumTxInChunk(186); + // change bundle size to 5, starting with batch index 6 + rollup.updateBundleSize(5, 5); + + // prove batch 3~5, bundle size = 3 hevm.startPrank(address(0)); - hevm.expectEmit(true, true, false, true); - emit CommitBatch(2, keccak256(batchHeader2)); - rollup.commitBatch(1, batchHeader1, chunks, bitmap); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(9); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + rollup.finalizeBundleWithTeeProof(headers[5], bytes32(uint256(1005)), bytes32(uint256(2005)), new bytes(0)); hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(2), false); - bytes32 batchHash2 = rollup.committedBatches(2); - assertEq(batchHash2, keccak256(batchHeader2)); - - // verify committed batch correctly + assertEq(rollup.lastZkpVerifiedBatchIndex(), 0); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 5); + assertEq(rollup.lastFinalizedBatchIndex(), 5); + assertEq(rollup.finalizedStateRoots(5), bytes32(uint256(1005))); + assertEq(rollup.withdrawRoots(5), bytes32(uint256(2005))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 10); + + // prove batch 6~10, bundle size = 5 hevm.startPrank(address(0)); - hevm.expectEmit(true, true, false, true); - emit FinalizeBatch(2, batchHash2, bytes32(uint256(4)), bytes32(uint256(5))); - rollup.finalizeBatchWithProof4844( - batchHeader2, - bytes32(uint256(2)), - bytes32(uint256(4)), - bytes32(uint256(5)), - blobDataProof, - new bytes(0) - ); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(10, keccak256(headers[10]), bytes32(uint256(1010)), bytes32(uint256(2010))); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(10, keccak256(headers[10]), bytes32(uint256(1010)), bytes32(uint256(2010))); + rollup.finalizeBundleWithTeeProof(headers[10], bytes32(uint256(1010)), bytes32(uint256(2010)), new bytes(0)); hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(2), true); - assertEq(rollup.finalizedStateRoots(2), bytes32(uint256(4))); - assertEq(rollup.withdrawRoots(2), bytes32(uint256(5))); - assertEq(rollup.lastFinalizedBatchIndex(), 2); - assertEq(messageQueue.pendingQueueIndex(), 265); - // 1 ~ 4, zero - for (uint256 i = 1; i < 4; i++) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } - // 4 ~ 9, even is nonzero, odd is zero - for (uint256 i = 4; i < 9; i++) { - if (i % 2 == 1 || i == 8) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } else { - assertBoolEq(messageQueue.isMessageSkipped(i), true); - } - } - // 9 ~ 265, even is nonzero, odd is zero - for (uint256 i = 9; i < 265; i++) { - if (i % 2 == 1 || i == 264) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } else { - assertBoolEq(messageQueue.isMessageSkipped(i), true); - } - } + assertEq(rollup.lastZkpVerifiedBatchIndex(), 0); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 10); + assertEq(rollup.lastFinalizedBatchIndex(), 10); + assertEq(rollup.finalizedStateRoots(10), bytes32(uint256(1010))); + assertEq(rollup.withdrawRoots(10), bytes32(uint256(2010))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 10); } - function testCommitBatchV3() external { - bytes memory batchHeader0 = new bytes(89); - - // import 10 L1 messages - for (uint256 i = 0; i < 10; i++) { - messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); - } - // import genesis batch first - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 1) - } - rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1))); - assertEq(rollup.committedBatches(0), keccak256(batchHeader0)); - - // caller not sequencer, revert - hevm.expectRevert(ScrollChain.ErrorCallerIsNotSequencer.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); - rollup.addSequencer(address(0)); + function testFinalizeBundleWithBothProof() external { + bytes[] memory headers = _prepareFinalizeBundle(); - // revert when ErrorIncorrectBatchVersion + // verify batch 1 with tee, bundle size = 1 hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchVersion.selector); - rollup.commitBatchWithBlobProof(2, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + rollup.finalizeBundleWithTeeProof(headers[1], bytes32(uint256(1001)), bytes32(uint256(2001)), new bytes(0)); hevm.stopPrank(); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 0); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 1); + assertEq(rollup.lastFinalizedBatchIndex(), 0); + assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(1001))); + assertEq(rollup.withdrawRoots(1), bytes32(uint256(2001))); + assertBoolEq(rollup.isBatchFinalized(1), false); - // revert when ErrorBatchIsEmpty + // verify batch 1 with zkp, bundle size = 1 hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsEmpty.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(1); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + rollup.finalizeBundleWithProof(headers[1], bytes32(uint256(1001)), bytes32(uint256(2001)), new bytes(0)); hevm.stopPrank(); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 1); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 1); + assertEq(rollup.lastFinalizedBatchIndex(), 1); + assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(1001))); + assertEq(rollup.withdrawRoots(1), bytes32(uint256(2001))); + assertBoolEq(rollup.isBatchFinalized(1), true); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 2); - // revert when ErrorBatchHeaderV3LengthMismatch - bytes memory header = new bytes(192); - assembly { - mstore8(add(header, 0x20), 3) // version - } + // change bundle size to 3, starting with batch index 3 + rollup.updateBundleSize(3, 2); + + // verify batch 2 with zkp, bundle size = 1 hevm.startPrank(address(0)); - hevm.expectRevert(BatchHeaderV3Codec.ErrorBatchHeaderV3LengthMismatch.selector); - rollup.commitBatchWithBlobProof(3, header, new bytes[](1), new bytes(0), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + rollup.finalizeBundleWithProof(headers[2], bytes32(uint256(1002)), bytes32(uint256(2002)), new bytes(0)); hevm.stopPrank(); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 2); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 1); + assertEq(rollup.lastFinalizedBatchIndex(), 1); + assertEq(rollup.finalizedStateRoots(2), bytes32(uint256(1002))); + assertEq(rollup.withdrawRoots(2), bytes32(uint256(2002))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 2); - // revert when ErrorIncorrectBatchHash - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 2) // change data hash for batch0 - } + // verify batch 2 with tee, bundle size = 1 hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, new bytes[](1), new bytes(0), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(3); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + rollup.finalizeBundleWithTeeProof(headers[2], bytes32(uint256(1002)), bytes32(uint256(2002)), new bytes(0)); hevm.stopPrank(); - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 1) // change back - } + assertEq(rollup.lastZkpVerifiedBatchIndex(), 2); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 2); + assertEq(rollup.lastFinalizedBatchIndex(), 2); + assertEq(rollup.finalizedStateRoots(2), bytes32(uint256(1002))); + assertEq(rollup.withdrawRoots(2), bytes32(uint256(2002))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 4); - bytes[] memory chunks = new bytes[](1); - bytes memory chunk0; + // change bundle size to 5, starting with batch index 6 + rollup.updateBundleSize(5, 5); - // no block in chunk, revert - chunk0 = new bytes(1); - chunks[0] = chunk0; + // prove batch 3~5 with tee and then zkp, bundle size = 3 hevm.startPrank(address(0)); - hevm.expectRevert(ChunkCodecV1.ErrorNoBlockInChunkV1.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + rollup.finalizeBundleWithTeeProof(headers[5], bytes32(uint256(1005)), bytes32(uint256(2005)), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(9); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + rollup.finalizeBundleWithProof(headers[5], bytes32(uint256(1005)), bytes32(uint256(2005)), new bytes(0)); hevm.stopPrank(); - - // invalid chunk length, revert - chunk0 = new bytes(1); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunks[0] = chunk0; + assertEq(rollup.lastZkpVerifiedBatchIndex(), 5); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 5); + assertEq(rollup.lastFinalizedBatchIndex(), 5); + assertEq(rollup.finalizedStateRoots(5), bytes32(uint256(1005))); + assertEq(rollup.withdrawRoots(5), bytes32(uint256(2005))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 10); + + // prove batch 6~10 with zkp and then tee, bundle size = 5 hevm.startPrank(address(0)); - hevm.expectRevert(ChunkCodecV1.ErrorIncorrectChunkLengthV1.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(10, keccak256(headers[10]), bytes32(uint256(1010)), bytes32(uint256(2010))); + rollup.finalizeBundleWithProof(headers[10], bytes32(uint256(1010)), bytes32(uint256(2010)), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(10, keccak256(headers[10]), bytes32(uint256(1010)), bytes32(uint256(2010))); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(10, keccak256(headers[10]), bytes32(uint256(1010)), bytes32(uint256(2010))); + rollup.finalizeBundleWithTeeProof(headers[10], bytes32(uint256(1010)), bytes32(uint256(2010)), new bytes(0)); hevm.stopPrank(); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 10); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 10); + assertEq(rollup.lastFinalizedBatchIndex(), 10); + assertEq(rollup.finalizedStateRoots(10), bytes32(uint256(1010))); + assertEq(rollup.withdrawRoots(10), bytes32(uint256(2010))); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 10); + } - // cannot skip last L1 message, revert - chunk0 = new bytes(1 + 60); - bytes memory bitmap = new bytes(32); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunk0[58] = bytes1(uint8(1)); // numTransactions = 1 - chunk0[60] = bytes1(uint8(1)); // numL1Messages = 1 - bitmap[31] = bytes1(uint8(1)); - chunks[0] = chunk0; + function testFinalizeBundleWithBothProofMismatch() external { + // 11 batches, including genesis + bytes[] memory headers = _prepareFinalizeBundle(); + + // revert ErrorNoUnresolvedState + hevm.expectRevert(ScrollChain.ErrorNoUnresolvedState.selector); + rollup.resolveStateMismatch(headers[1], false); + + (ScrollChain.ProofType urProofType, uint248 urBatchIndex, bytes32 urStateRoot, bytes32 urWithdrawRoot) = rollup + .unresolvedState(); + assertEq(uint8(urProofType), 0); + assertEq(urBatchIndex, 0); + assertEq(urStateRoot, bytes32(0)); + assertEq(urWithdrawRoot, bytes32(0)); + + // batch 1, have unresolved state, both tee and zkp paused hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorLastL1MessageSkipped.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, bitmap, new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + rollup.finalizeBundleWithTeeProof(headers[1], bytes32(uint256(1001)), bytes32(uint256(2001)), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit StateMismatch(1, bytes32(uint256(3001)), bytes32(uint256(4001))); + rollup.finalizeBundleWithProof(headers[1], bytes32(uint256(3001)), bytes32(uint256(4001)), new bytes(0)); hevm.stopPrank(); + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 0); + assertEq(urBatchIndex, 1); + assertEq(urStateRoot, bytes32(uint256(3001))); + assertEq(urWithdrawRoot, bytes32(uint256(4001))); - // num txs less than num L1 msgs, revert - chunk0 = new bytes(1 + 60); - bitmap = new bytes(32); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunk0[58] = bytes1(uint8(1)); // numTransactions = 1 - chunk0[60] = bytes1(uint8(3)); // numL1Messages = 3 - bitmap[31] = bytes1(uint8(3)); - chunks[0] = chunk0; + // revert ErrorFinalizationPaused hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorNumTxsLessThanNumL1Msgs.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, bitmap, new bytes(0)); + hevm.expectRevert(ScrollChain.ErrorFinalizationPaused.selector); + rollup.finalizeBundleWithTeeProof(headers[1], bytes32(uint256(1001)), bytes32(uint256(2001)), new bytes(0)); + hevm.expectRevert(ScrollChain.ErrorFinalizationPaused.selector); + rollup.finalizeBundleWithProof(headers[1], bytes32(uint256(3001)), bytes32(uint256(4001)), new bytes(0)); hevm.stopPrank(); - // revert when ErrorNoBlobFound - // revert when ErrorNoBlobFound - chunk0 = new bytes(1 + 60); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunks[0] = chunk0; - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorNoBlobFound.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), new bytes(0)); - hevm.stopPrank(); - - // @note we cannot check `ErrorFoundMultipleBlobs` here - - // upgrade to ScrollChainMockBlob - ScrollChainMockBlob impl = new ScrollChainMockBlob( - rollup.layer2ChainId(), - rollup.messageQueue(), - rollup.verifier() - ); - admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); - // from https://etherscan.io/blob/0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757?bid=740652 - bytes32 blobVersionedHash = 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757; - bytes - memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; - ScrollChainMockBlob(address(rollup)).setBlobVersionedHash(blobVersionedHash); - - chunk0 = new bytes(1 + 60); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunks[0] = chunk0; - // revert when ErrorCallPointEvaluationPrecompileFailed - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorCallPointEvaluationPrecompileFailed.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), new bytes(0)); - hevm.stopPrank(); - - bytes32 batchHash0 = rollup.committedBatches(0); - bytes memory batchHeader1 = new bytes(193); - assembly { - mstore8(add(batchHeader1, 0x20), 3) // version - mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex - mstore(add(batchHeader1, add(0x20, 9)), 0) // l1MessagePopped - mstore(add(batchHeader1, add(0x20, 17)), 0) // totalL1MessagePopped - mstore(add(batchHeader1, add(0x20, 25)), 0x246394445f4fe64ed5598554d55d1682d6fb3fe04bf58eb54ef81d1189fafb51) // dataHash - mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash - mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - mstore(add(batchHeader1, add(0x20, 121)), 0) // lastBlockTimestamp - mcopy(add(batchHeader1, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof - } - // hash is ed32768c5f910a11edaf1c1ec0c0da847def9d24e0a24567c3c3d284061cf935 - - // succeed - hevm.startPrank(address(0)); - assertEq(rollup.committedBatches(1), bytes32(0)); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), blobDataProof); - hevm.stopPrank(); - assertEq(rollup.committedBatches(1), keccak256(batchHeader1)); - - // revert when ErrorBatchIsAlreadyCommitted - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsAlreadyCommitted.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), blobDataProof); - hevm.stopPrank(); - } - - function testFinalizeBundleWithProof() external { - // caller not prover, revert - hevm.expectRevert(ScrollChain.ErrorCallerIsNotProver.selector); - rollup.finalizeBundleWithProof(new bytes(0), bytes32(0), bytes32(0), new bytes(0)); - - rollup.addProver(address(0)); - rollup.addSequencer(address(0)); - - // import genesis batch - bytes memory batchHeader0 = new bytes(89); - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 1) - } - rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1))); - - // upgrade to ScrollChainMockBlob - ScrollChainMockBlob impl = new ScrollChainMockBlob( - rollup.layer2ChainId(), - rollup.messageQueue(), - rollup.verifier() - ); - admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); - // from https://etherscan.io/blob/0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757?bid=740652 - bytes32 blobVersionedHash = 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757; - bytes - memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; - ScrollChainMockBlob(address(rollup)).setBlobVersionedHash(blobVersionedHash); - - bytes[] memory chunks = new bytes[](1); - bytes memory chunk0; - - bytes32 batchHash0 = rollup.committedBatches(0); - bytes memory batchHeader1 = new bytes(193); - assembly { - mstore8(add(batchHeader1, 0x20), 3) // version - mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex - mstore(add(batchHeader1, add(0x20, 9)), 0) // l1MessagePopped - mstore(add(batchHeader1, add(0x20, 17)), 0) // totalL1MessagePopped - mstore(add(batchHeader1, add(0x20, 25)), 0x246394445f4fe64ed5598554d55d1682d6fb3fe04bf58eb54ef81d1189fafb51) // dataHash - mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash - mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - mstore(add(batchHeader1, add(0x20, 121)), 0) // lastBlockTimestamp - mcopy(add(batchHeader1, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof - } - // hash is ed32768c5f910a11edaf1c1ec0c0da847def9d24e0a24567c3c3d284061cf935 - - // commit one batch - chunk0 = new bytes(1 + 60); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunks[0] = chunk0; - hevm.startPrank(address(0)); - assertEq(rollup.committedBatches(1), bytes32(0)); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, new bytes(0), blobDataProof); - hevm.stopPrank(); - assertEq(rollup.committedBatches(1), keccak256(batchHeader1)); - - // revert when ErrorStateRootIsZero - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorStateRootIsZero.selector); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(0), bytes32(0), new bytes(0)); - hevm.stopPrank(); + // revert ErrorBatchIndexMismatch + hevm.expectRevert(ScrollChain.ErrorBatchIndexMismatch.selector); + rollup.resolveStateMismatch(headers[2], false); - // revert when ErrorBatchHeaderV3LengthMismatch - bytes memory header = new bytes(192); - assembly { - mstore8(add(header, 0x20), 3) // version - } - hevm.startPrank(address(0)); - hevm.expectRevert(BatchHeaderV3Codec.ErrorBatchHeaderV3LengthMismatch.selector); - rollup.finalizeBundleWithProof(header, bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); - hevm.stopPrank(); - - // revert when ErrorIncorrectBatchHash - batchHeader1[1] = bytes1(uint8(1)); // change random byte - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); - hevm.stopPrank(); - batchHeader1[1] = bytes1(uint8(0)); // change back - - // verify success - assertBoolEq(rollup.isBatchFinalized(1), false); - hevm.startPrank(address(0)); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(uint256(2)), bytes32(uint256(3)), new bytes(0)); - hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(1), true); - assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(2))); - assertEq(rollup.withdrawRoots(1), bytes32(uint256(3))); + // resolve mismatch and enable both proof again + hevm.expectEmit(true, false, false, true); + emit ResolveState(1, bytes32(uint256(1001)), bytes32(uint256(2001))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(1); + hevm.expectEmit(true, false, false, true); + emit FinalizeBatch(1, keccak256(headers[1]), bytes32(uint256(1001)), bytes32(uint256(2001))); + rollup.resolveStateMismatch(headers[1], false); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 1); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 1); assertEq(rollup.lastFinalizedBatchIndex(), 1); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 2); + assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(1001))); + assertEq(rollup.withdrawRoots(1), bytes32(uint256(2001))); + rollup.enableProofTypes(3); - // revert when ErrorBatchIsAlreadyVerified + // batch 2, tee behind zkp, tee wrong, state root mismatch, withdraw root match hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsAlreadyVerified.selector); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(uint256(2)), bytes32(uint256(3)), new bytes(0)); - hevm.stopPrank(); - } - - function _commitBatchV3() - internal - returns ( - bytes memory batchHeader0, - bytes memory batchHeader1, - bytes memory batchHeader2 - ) - { - // import genesis batch first - batchHeader0 = new bytes(89); - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 1) - } - rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1))); - bytes32 batchHash0 = rollup.committedBatches(0); - - // upgrade to ScrollChainMockBlob - ScrollChainMockBlob impl = new ScrollChainMockBlob( - rollup.layer2ChainId(), - rollup.messageQueue(), - rollup.verifier() - ); - admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); - // from https://etherscan.io/blob/0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757?bid=740652 - bytes32 blobVersionedHash = 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757; - bytes - memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; - ScrollChainMockBlob(address(rollup)).setBlobVersionedHash(blobVersionedHash); - - bytes memory bitmap; - bytes[] memory chunks; - bytes memory chunk0; - bytes memory chunk1; - - // commit batch1, one chunk with one block, 1 tx, 1 L1 message, no skip - // => payload for data hash of chunk0 - // 0000000000000000 - // 0000000000000123 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 0001 - // a2277fd30bbbe74323309023b56035b376d7768ad237ae4fc46ead7dc9591ae1 - // => data hash for chunk0 - // 5972b8fa626c873a97abb6db14fb0cb2085e050a6f80ec90b92bb0bbaa12eb5a - // => data hash for all chunks - // f6166fe668c1e6a04e3c75e864452bb02a31358f285efcb7a4e6603eb5750359 - // => payload for batch header - // 03 - // 0000000000000001 - // 0000000000000001 - // 0000000000000001 - // f6166fe668c1e6a04e3c75e864452bb02a31358f285efcb7a4e6603eb5750359 - // 013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757 - // 119b828c2a2798d2c957228ebeaff7e10bb099ae0d4e224f3eeb779ff61cba61 - // 0000000000000123 - // 2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e687 - // 53ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0 - // => hash for batch header - // 07e1bede8c5047cf8ca7ac84f5390837fb6224953af83d7e967488fa63a2065e - batchHeader1 = new bytes(193); - assembly { - mstore8(add(batchHeader1, 0x20), 3) // version - mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex = 1 - mstore(add(batchHeader1, add(0x20, 9)), shl(192, 1)) // l1MessagePopped = 1 - mstore(add(batchHeader1, add(0x20, 17)), shl(192, 1)) // totalL1MessagePopped = 1 - mstore(add(batchHeader1, add(0x20, 25)), 0xf6166fe668c1e6a04e3c75e864452bb02a31358f285efcb7a4e6603eb5750359) // dataHash - mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash - mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - mstore(add(batchHeader1, add(0x20, 121)), shl(192, 0x123)) // lastBlockTimestamp - mcopy(add(batchHeader1, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof - } - chunk0 = new bytes(1 + 60); - assembly { - mstore(add(chunk0, 0x20), shl(248, 1)) // numBlocks = 1 - mstore(add(chunk0, add(0x21, 8)), shl(192, 0x123)) // timestamp = 0x123 - mstore(add(chunk0, add(0x21, 56)), shl(240, 1)) // numTransactions = 1 - mstore(add(chunk0, add(0x21, 58)), shl(240, 1)) // numL1Messages = 1 - } - chunks = new bytes[](1); - chunks[0] = chunk0; - bitmap = new bytes(32); - hevm.startPrank(address(0)); - hevm.expectEmit(true, true, false, true); - emit CommitBatch(1, keccak256(batchHeader1)); - rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, bitmap, blobDataProof); - hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(1), false); - bytes32 batchHash1 = rollup.committedBatches(1); - assertEq(batchHash1, keccak256(batchHeader1)); - assertEq(1, messageQueue.pendingQueueIndex()); - assertEq(0, messageQueue.nextUnfinalizedQueueIndex()); - assertBoolEq(messageQueue.isMessageSkipped(0), false); - - // commit batch2 with two chunks, correctly - // 1. chunk0 has one block, 3 tx, no L1 messages - // => payload for chunk0 - // 0000000000000000 - // 0000000000000456 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 0003 - // ... (some tx hashes) - // => data hash for chunk0 - // 1c7649f248aed8448fa7997e44db7b7028581deb119c6d6aa1a2d126d62564cf - // 2. chunk1 has three blocks - // 2.1 block0 has 5 tx, 3 L1 messages, no skips - // 2.2 block1 has 10 tx, 5 L1 messages, even is skipped, last is not skipped - // 2.2 block1 has 300 tx, 256 L1 messages, odd position is skipped, last is not skipped - // => payload for chunk1 - // 0000000000000000 - // 0000000000000789 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 0005 - // 0000000000000000 - // 0000000000001234 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 000a - // 0000000000000000 - // 0000000000005678 - // 0000000000000000000000000000000000000000000000000000000000000000 - // 0000000000000000 - // 012c - // => data hash for chunk1 - // 4e82cb576135a69a0ecc2b2070c432abfdeb20076594faaa1aeed77f48d7c856 - // => data hash for all chunks - // 166e9d20206ae8cddcdf0f30093e3acc3866937172df5d7f69fb5567d9595239 - // => payload for batch header - // 03 - // 0000000000000002 - // 0000000000000108 - // 0000000000000109 - // 166e9d20206ae8cddcdf0f30093e3acc3866937172df5d7f69fb5567d9595239 - // 013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757 - // 07e1bede8c5047cf8ca7ac84f5390837fb6224953af83d7e967488fa63a2065e - // 0000000000005678 - // 2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e687 - // 53ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0 - // => hash for batch header - // 8a59f0de6f1071c0f48d6a49d9b794008d28b63cc586da0f44f8b2b4e13cb231 - batchHeader2 = new bytes(193); - assembly { - mstore8(add(batchHeader2, 0x20), 3) // version - mstore(add(batchHeader2, add(0x20, 1)), shl(192, 2)) // batchIndex = 2 - mstore(add(batchHeader2, add(0x20, 9)), shl(192, 264)) // l1MessagePopped = 264 - mstore(add(batchHeader2, add(0x20, 17)), shl(192, 265)) // totalL1MessagePopped = 265 - mstore(add(batchHeader2, add(0x20, 25)), 0x166e9d20206ae8cddcdf0f30093e3acc3866937172df5d7f69fb5567d9595239) // dataHash - mstore(add(batchHeader2, add(0x20, 57)), blobVersionedHash) // blobVersionedHash - mstore(add(batchHeader2, add(0x20, 89)), batchHash1) // parentBatchHash - mstore(add(batchHeader2, add(0x20, 121)), shl(192, 0x5678)) // lastBlockTimestamp - mcopy(add(batchHeader2, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof - } - chunk0 = new bytes(1 + 60); - assembly { - mstore(add(chunk0, 0x20), shl(248, 1)) // numBlocks = 1 - mstore(add(chunk0, add(0x21, 8)), shl(192, 0x456)) // timestamp = 0x456 - mstore(add(chunk0, add(0x21, 56)), shl(240, 3)) // numTransactions = 3 - mstore(add(chunk0, add(0x21, 58)), shl(240, 0)) // numL1Messages = 0 - } - chunk1 = new bytes(1 + 60 * 3); - assembly { - mstore(add(chunk1, 0x20), shl(248, 3)) // numBlocks = 3 - mstore(add(chunk1, add(33, 8)), shl(192, 0x789)) // block0.timestamp = 0x789 - mstore(add(chunk1, add(33, 56)), shl(240, 5)) // block0.numTransactions = 5 - mstore(add(chunk1, add(33, 58)), shl(240, 3)) // block0.numL1Messages = 3 - mstore(add(chunk1, add(93, 8)), shl(192, 0x1234)) // block1.timestamp = 0x1234 - mstore(add(chunk1, add(93, 56)), shl(240, 10)) // block1.numTransactions = 10 - mstore(add(chunk1, add(93, 58)), shl(240, 5)) // block1.numL1Messages = 5 - mstore(add(chunk1, add(153, 8)), shl(192, 0x5678)) // block1.timestamp = 0x5678 - mstore(add(chunk1, add(153, 56)), shl(240, 300)) // block1.numTransactions = 300 - mstore(add(chunk1, add(153, 58)), shl(240, 256)) // block1.numL1Messages = 256 - } - chunks = new bytes[](2); - chunks[0] = chunk0; - chunks[1] = chunk1; - bitmap = new bytes(64); - assembly { - mstore( - add(bitmap, add(0x20, 0)), - 77194726158210796949047323339125271902179989777093709359638389338608753093160 - ) // bitmap0 - mstore(add(bitmap, add(0x20, 32)), 42) // bitmap1 - } - - // too many txs in one chunk, revert - rollup.updateMaxNumTxInChunk(2); // 3 - 1 - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorTooManyTxsInOneChunk.selector); - rollup.commitBatchWithBlobProof(3, batchHeader1, chunks, bitmap, blobDataProof); // first chunk with too many txs - hevm.stopPrank(); - rollup.updateMaxNumTxInChunk(185); // 5+10+300 - 2 - 127 - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorTooManyTxsInOneChunk.selector); - rollup.commitBatchWithBlobProof(3, batchHeader1, chunks, bitmap, blobDataProof); // second chunk with too many txs - hevm.stopPrank(); - - rollup.updateMaxNumTxInChunk(186); - hevm.startPrank(address(0)); - hevm.expectEmit(true, true, false, true); - emit CommitBatch(2, keccak256(batchHeader2)); - rollup.commitBatchWithBlobProof(3, batchHeader1, chunks, bitmap, blobDataProof); - hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(2), false); - bytes32 batchHash2 = rollup.committedBatches(2); - assertEq(batchHash2, keccak256(batchHeader2)); - assertEq(265, messageQueue.pendingQueueIndex()); - assertEq(0, messageQueue.nextUnfinalizedQueueIndex()); - } - - function testCommitAndFinalizeWithL1MessagesV3() external { - rollup.addSequencer(address(0)); - rollup.addProver(address(0)); - - // import 300 L1 messages - for (uint256 i = 0; i < 300; i++) { - messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); - } - - (bytes memory batchHeader0, bytes memory batchHeader1, bytes memory batchHeader2) = _commitBatchV3(); - - // 1 ~ 4, zero - for (uint256 i = 1; i < 4; i++) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } - // 4 ~ 9, even is nonzero, odd is zero - for (uint256 i = 4; i < 9; i++) { - if (i % 2 == 1 || i == 8) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } else { - assertBoolEq(messageQueue.isMessageSkipped(i), true); - } - } - // 9 ~ 265, even is nonzero, odd is zero - for (uint256 i = 9; i < 265; i++) { - if (i % 2 == 1 || i == 264) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } else { - assertBoolEq(messageQueue.isMessageSkipped(i), true); - } - } - - // finalize batch1 and batch2 together - assertBoolEq(rollup.isBatchFinalized(1), false); - assertBoolEq(rollup.isBatchFinalized(2), false); - hevm.startPrank(address(0)); - rollup.finalizeBundleWithProof(batchHeader2, bytes32(uint256(2)), bytes32(uint256(3)), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + rollup.finalizeBundleWithProof(headers[2], bytes32(uint256(1002)), bytes32(uint256(2002)), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit StateMismatch(2, bytes32(uint256(3002)), bytes32(uint256(2002))); + rollup.finalizeBundleWithTeeProof(headers[2], bytes32(uint256(3002)), bytes32(uint256(2002)), new bytes(0)); hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(1), true); - assertBoolEq(rollup.isBatchFinalized(2), true); - assertEq(rollup.finalizedStateRoots(1), bytes32(0)); - assertEq(rollup.withdrawRoots(1), bytes32(0)); - assertEq(rollup.finalizedStateRoots(2), bytes32(uint256(2))); - assertEq(rollup.withdrawRoots(2), bytes32(uint256(3))); - assertEq(rollup.lastFinalizedBatchIndex(), 2); - assertEq(265, messageQueue.nextUnfinalizedQueueIndex()); - } - - function testRevertBatchWithL1Messages() external { - rollup.addSequencer(address(0)); - rollup.addProver(address(0)); - - // import 300 L1 messages - for (uint256 i = 0; i < 300; i++) { - messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); - } - - (bytes memory batchHeader0, bytes memory batchHeader1, bytes memory batchHeader2) = _commitBatchV3(); - - // 1 ~ 4, zero - for (uint256 i = 1; i < 4; i++) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } - // 4 ~ 9, even is nonzero, odd is zero - for (uint256 i = 4; i < 9; i++) { - if (i % 2 == 1 || i == 8) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } else { - assertBoolEq(messageQueue.isMessageSkipped(i), true); - } - } - // 9 ~ 265, even is nonzero, odd is zero - for (uint256 i = 9; i < 265; i++) { - if (i % 2 == 1 || i == 264) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } else { - assertBoolEq(messageQueue.isMessageSkipped(i), true); - } - } + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 1); + assertEq(urBatchIndex, 2); + assertEq(urStateRoot, bytes32(uint256(3002))); + assertEq(urWithdrawRoot, bytes32(uint256(2002))); - // revert batch 1 and batch 2 - rollup.revertBatch(batchHeader1, batchHeader2); - assertEq(0, messageQueue.pendingQueueIndex()); - assertEq(0, messageQueue.nextUnfinalizedQueueIndex()); - for (uint256 i = 0; i < 265; i++) { - assertBoolEq(messageQueue.isMessageSkipped(i), false); - } - } + // resolve mismatch and enable both proof again + hevm.expectEmit(true, false, false, true); + emit ResolveState(2, bytes32(uint256(1002)), bytes32(uint256(2002))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(3); + hevm.expectEmit(true, true, false, true); + emit FinalizeBatch(2, keccak256(headers[2]), bytes32(uint256(1002)), bytes32(uint256(2002))); + rollup.resolveStateMismatch(headers[2], false); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 2); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 2); + assertEq(rollup.lastFinalizedBatchIndex(), 2); + assertEq(rollup.enabledProofTypeMask(), 1); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 4); + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 0); + assertEq(urBatchIndex, 0); + assertEq(urStateRoot, bytes32(uint256(0))); + assertEq(urWithdrawRoot, bytes32(uint256(0))); + assertEq(rollup.finalizedStateRoots(2), bytes32(uint256(1002))); + assertEq(rollup.withdrawRoots(2), bytes32(uint256(2002))); + rollup.enableProofTypes(3); + + // batch 3, tee behind zkp, zkp wrong, state root match, withdraw root mismatch + hevm.startPrank(address(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(3, keccak256(headers[3]), bytes32(uint256(1003)), bytes32(uint256(4003))); + rollup.finalizeBundleWithProof(headers[3], bytes32(uint256(1003)), bytes32(uint256(4003)), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(3, keccak256(headers[3]), bytes32(uint256(1003)), bytes32(uint256(2003))); + hevm.expectEmit(true, false, false, true); + emit StateMismatch(3, bytes32(uint256(1003)), bytes32(uint256(2003))); + rollup.finalizeBundleWithTeeProof(headers[3], bytes32(uint256(1003)), bytes32(uint256(2003)), new bytes(0)); + hevm.stopPrank(); + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 1); + assertEq(urBatchIndex, 3); + assertEq(urStateRoot, bytes32(uint256(1003))); + assertEq(urWithdrawRoot, bytes32(uint256(2003))); - function testSwitchBatchFromV1ToV3() external { - rollup.addSequencer(address(0)); - rollup.addProver(address(0)); + // resolve mismatch and enable both proof again + hevm.expectEmit(true, false, false, true); + emit ResolveState(3, bytes32(uint256(1003)), bytes32(uint256(2003))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(5); + hevm.expectEmit(true, true, false, true); + emit FinalizeBatch(3, keccak256(headers[3]), bytes32(uint256(1003)), bytes32(uint256(2003))); + rollup.resolveStateMismatch(headers[3], true); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 3); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 3); + assertEq(rollup.lastFinalizedBatchIndex(), 3); + assertEq(rollup.enabledProofTypeMask(), 2); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 6); + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 0); + assertEq(urBatchIndex, 0); + assertEq(urStateRoot, bytes32(uint256(0))); + assertEq(urWithdrawRoot, bytes32(uint256(0))); + assertEq(rollup.finalizedStateRoots(3), bytes32(uint256(1003))); + assertEq(rollup.withdrawRoots(3), bytes32(uint256(2003))); + rollup.enableProofTypes(3); + + // batch 4, zkp behind tee, zkp wrong, state root mismatch, withdraw root mismatch + hevm.startPrank(address(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(4, keccak256(headers[4]), bytes32(uint256(1004)), bytes32(uint256(2004))); + rollup.finalizeBundleWithTeeProof(headers[4], bytes32(uint256(1004)), bytes32(uint256(2004)), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(4, keccak256(headers[4]), bytes32(uint256(3004)), bytes32(uint256(4004))); + hevm.expectEmit(true, false, false, true); + emit StateMismatch(4, bytes32(uint256(3004)), bytes32(uint256(4004))); + rollup.finalizeBundleWithProof(headers[4], bytes32(uint256(3004)), bytes32(uint256(4004)), new bytes(0)); + hevm.stopPrank(); + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 0); + assertEq(urBatchIndex, 4); + assertEq(urStateRoot, bytes32(uint256(3004))); + assertEq(urWithdrawRoot, bytes32(uint256(4004))); - // import 300 L1 messages - for (uint256 i = 0; i < 300; i++) { - messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); - } + // resolve mismatch and enable both proof again + hevm.expectEmit(true, false, false, true); + emit ResolveState(4, bytes32(uint256(1004)), bytes32(uint256(2004))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(7); + hevm.expectEmit(true, true, false, true); + emit FinalizeBatch(4, keccak256(headers[4]), bytes32(uint256(1004)), bytes32(uint256(2004))); + rollup.resolveStateMismatch(headers[4], false); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 4); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 4); + assertEq(rollup.lastFinalizedBatchIndex(), 4); + assertEq(rollup.enabledProofTypeMask(), 2); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 8); + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 0); + assertEq(urBatchIndex, 0); + assertEq(urStateRoot, bytes32(uint256(0))); + assertEq(urWithdrawRoot, bytes32(uint256(0))); + assertEq(rollup.finalizedStateRoots(4), bytes32(uint256(1004))); + assertEq(rollup.withdrawRoots(4), bytes32(uint256(2004))); + rollup.enableProofTypes(3); + + // batch 5, zkp behind tee, tee wrong, state root mismatch, withdraw root mismatch + hevm.startPrank(address(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithTee(5, keccak256(headers[5]), bytes32(uint256(3005)), bytes32(uint256(2005))); + rollup.finalizeBundleWithTeeProof(headers[5], bytes32(uint256(3005)), bytes32(uint256(2005)), new bytes(0)); + hevm.expectEmit(true, false, false, true); + emit VerifyBatchWithZkp(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + hevm.expectEmit(true, false, false, true); + emit StateMismatch(5, bytes32(uint256(1005)), bytes32(uint256(2005))); + rollup.finalizeBundleWithProof(headers[5], bytes32(uint256(1005)), bytes32(uint256(2005)), new bytes(0)); + hevm.stopPrank(); + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 0); + assertEq(urBatchIndex, 5); + assertEq(urStateRoot, bytes32(uint256(1005))); + assertEq(urWithdrawRoot, bytes32(uint256(2005))); + + // resolve mismatch and enable both proof again + hevm.expectEmit(true, false, false, true); + emit ResolveState(5, bytes32(uint256(1005)), bytes32(uint256(2005))); + hevm.expectEmit(false, false, false, true); + emit FinalizedDequeuedTransaction(9); + hevm.expectEmit(true, true, false, true); + emit FinalizeBatch(5, keccak256(headers[5]), bytes32(uint256(1005)), bytes32(uint256(2005))); + rollup.resolveStateMismatch(headers[5], true); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 5); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 5); + assertEq(rollup.lastFinalizedBatchIndex(), 5); + assertEq(rollup.enabledProofTypeMask(), 1); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 10); + (urProofType, urBatchIndex, urStateRoot, urWithdrawRoot) = rollup.unresolvedState(); + assertEq(uint8(urProofType), 0); + assertEq(urBatchIndex, 0); + assertEq(urStateRoot, bytes32(uint256(0))); + assertEq(urWithdrawRoot, bytes32(uint256(0))); + assertEq(rollup.finalizedStateRoots(5), bytes32(uint256(1005))); + assertEq(rollup.withdrawRoots(5), bytes32(uint256(2005))); + } + function _commitBatchV3() + internal + returns ( + bytes memory batchHeader0, + bytes memory batchHeader1, + bytes memory batchHeader2 + ) + { // import genesis batch first - bytes memory batchHeader0 = new bytes(89); + batchHeader0 = new bytes(89); assembly { mstore(add(batchHeader0, add(0x20, 25)), 1) } @@ -1203,7 +996,9 @@ contract ScrollChainTest is DSTestPlus { ScrollChainMockBlob impl = new ScrollChainMockBlob( rollup.layer2ChainId(), rollup.messageQueue(), - rollup.verifier() + rollup.zkpVerifier(), + rollup.teeVerifier(), + 0 ); admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); // from https://etherscan.io/blob/0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757?bid=740652 @@ -1217,43 +1012,47 @@ contract ScrollChainTest is DSTestPlus { bytes memory chunk0; bytes memory chunk1; - // commit batch1 with version v1, one chunk with one block, 1 tx, 1 L1 message, no skip + // commit batch1, one chunk with one block, 1 tx, 1 L1 message, no skip // => payload for data hash of chunk0 // 0000000000000000 - // 0000000000000000 + // 0000000000000123 // 0000000000000000000000000000000000000000000000000000000000000000 // 0000000000000000 // 0001 // a2277fd30bbbe74323309023b56035b376d7768ad237ae4fc46ead7dc9591ae1 // => data hash for chunk0 - // 9ef1e5694bdb014a1eea42be756a8f63bfd8781d6332e9ef3b5126d90c62f110 + // 5972b8fa626c873a97abb6db14fb0cb2085e050a6f80ec90b92bb0bbaa12eb5a // => data hash for all chunks - // d9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3 + // f6166fe668c1e6a04e3c75e864452bb02a31358f285efcb7a4e6603eb5750359 // => payload for batch header - // 01 + // 03 // 0000000000000001 // 0000000000000001 // 0000000000000001 - // d9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3 + // f6166fe668c1e6a04e3c75e864452bb02a31358f285efcb7a4e6603eb5750359 // 013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757 // 119b828c2a2798d2c957228ebeaff7e10bb099ae0d4e224f3eeb779ff61cba61 - // 0000000000000000000000000000000000000000000000000000000000000000 + // 0000000000000123 + // 2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e687 + // 53ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0 // => hash for batch header - // 66b68a5092940d88a8c6f203d2071303557c024275d8ceaa2e12662bc61c8d8f - bytes memory batchHeader1 = new bytes(121 + 32); + // 07e1bede8c5047cf8ca7ac84f5390837fb6224953af83d7e967488fa63a2065e + batchHeader1 = new bytes(193); assembly { - mstore8(add(batchHeader1, 0x20), 1) // version + mstore8(add(batchHeader1, 0x20), 3) // version mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex = 1 mstore(add(batchHeader1, add(0x20, 9)), shl(192, 1)) // l1MessagePopped = 1 mstore(add(batchHeader1, add(0x20, 17)), shl(192, 1)) // totalL1MessagePopped = 1 - mstore(add(batchHeader1, add(0x20, 25)), 0xd9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3) // dataHash + mstore(add(batchHeader1, add(0x20, 25)), 0xf6166fe668c1e6a04e3c75e864452bb02a31358f285efcb7a4e6603eb5750359) // dataHash mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - mstore(add(batchHeader1, add(0x20, 121)), 0) // bitmap0 + mstore(add(batchHeader1, add(0x20, 121)), shl(192, 0x123)) // lastBlockTimestamp + mcopy(add(batchHeader1, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof } chunk0 = new bytes(1 + 60); assembly { mstore(add(chunk0, 0x20), shl(248, 1)) // numBlocks = 1 + mstore(add(chunk0, add(0x21, 8)), shl(192, 0x123)) // timestamp = 0x123 mstore(add(chunk0, add(0x21, 56)), shl(240, 1)) // numTransactions = 1 mstore(add(chunk0, add(0x21, 58)), shl(240, 1)) // numL1Messages = 1 } @@ -1263,13 +1062,16 @@ contract ScrollChainTest is DSTestPlus { hevm.startPrank(address(0)); hevm.expectEmit(true, true, false, true); emit CommitBatch(1, keccak256(batchHeader1)); - rollup.commitBatch(1, batchHeader0, chunks, bitmap); + rollup.commitBatchWithBlobProof(3, batchHeader0, chunks, bitmap, blobDataProof); hevm.stopPrank(); assertBoolEq(rollup.isBatchFinalized(1), false); bytes32 batchHash1 = rollup.committedBatches(1); assertEq(batchHash1, keccak256(batchHeader1)); + assertEq(1, messageQueue.pendingQueueIndex()); + assertEq(0, messageQueue.nextUnfinalizedQueueIndex()); + assertBoolEq(messageQueue.isMessageSkipped(0), false); - // commit batch2 with version v2, with two chunks, correctly + // commit batch2 with two chunks, correctly // 1. chunk0 has one block, 3 tx, no L1 messages // => payload for chunk0 // 0000000000000000 @@ -1311,13 +1113,13 @@ contract ScrollChainTest is DSTestPlus { // 0000000000000109 // 166e9d20206ae8cddcdf0f30093e3acc3866937172df5d7f69fb5567d9595239 // 013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757 - // 66b68a5092940d88a8c6f203d2071303557c024275d8ceaa2e12662bc61c8d8f + // 07e1bede8c5047cf8ca7ac84f5390837fb6224953af83d7e967488fa63a2065e // 0000000000005678 // 2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e687 // 53ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0 // => hash for batch header - // f212a256744ca658dfc4eb32665aa0fe845eb757a030bd625cb2880055e3cc92 - bytes memory batchHeader2 = new bytes(193); + // 8a59f0de6f1071c0f48d6a49d9b794008d28b63cc586da0f44f8b2b4e13cb231 + batchHeader2 = new bytes(193); assembly { mstore8(add(batchHeader2, 0x20), 3) // version mstore(add(batchHeader2, add(0x20, 1)), shl(192, 2)) // batchIndex = 2 @@ -1361,53 +1163,128 @@ contract ScrollChainTest is DSTestPlus { mstore(add(bitmap, add(0x20, 32)), 42) // bitmap1 } - rollup.updateMaxNumTxInChunk(186); - // should revert, when all v1 batch not finalized + // too many txs in one chunk, revert + rollup.updateMaxNumTxInChunk(2); // 3 - 1 hevm.startPrank(address(0)); - hevm.expectRevert("start index mismatch"); - rollup.commitBatchWithBlobProof(3, batchHeader1, chunks, bitmap, blobDataProof); + hevm.expectRevert(ScrollChain.ErrorTooManyTxsInOneChunk.selector); + rollup.commitBatchWithBlobProof(3, batchHeader1, chunks, bitmap, blobDataProof); // first chunk with too many txs hevm.stopPrank(); - - // finalize batch1 + rollup.updateMaxNumTxInChunk(185); // 5+10+300 - 2 - 127 hevm.startPrank(address(0)); - hevm.expectEmit(true, true, false, true); - emit FinalizeBatch(1, batchHash1, bytes32(uint256(2)), bytes32(uint256(3))); - rollup.finalizeBatchWithProof4844( - batchHeader1, - bytes32(uint256(1)), - bytes32(uint256(2)), - bytes32(uint256(3)), - blobDataProof, - new bytes(0) - ); + hevm.expectRevert(ScrollChain.ErrorTooManyTxsInOneChunk.selector); + rollup.commitBatchWithBlobProof(3, batchHeader1, chunks, bitmap, blobDataProof); // second chunk with too many txs hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(1), true); - assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(2))); - assertEq(rollup.withdrawRoots(1), bytes32(uint256(3))); - assertEq(rollup.lastFinalizedBatchIndex(), 1); - assertBoolEq(messageQueue.isMessageSkipped(0), false); - assertEq(messageQueue.pendingQueueIndex(), 1); - assertEq(messageQueue.nextUnfinalizedQueueIndex(), 1); + rollup.updateMaxNumTxInChunk(186); hevm.startPrank(address(0)); hevm.expectEmit(true, true, false, true); emit CommitBatch(2, keccak256(batchHeader2)); rollup.commitBatchWithBlobProof(3, batchHeader1, chunks, bitmap, blobDataProof); hevm.stopPrank(); + assertBoolEq(rollup.isBatchFinalized(2), false); bytes32 batchHash2 = rollup.committedBatches(2); assertEq(batchHash2, keccak256(batchHeader2)); - assertEq(messageQueue.pendingQueueIndex(), 265); - assertEq(messageQueue.nextUnfinalizedQueueIndex(), 1); + assertEq(265, messageQueue.pendingQueueIndex()); + assertEq(0, messageQueue.nextUnfinalizedQueueIndex()); + } + + function testCommitAndFinalizeWithL1MessagesV3() external { + rollup.addSequencer(address(0)); + rollup.addProver(address(0)); + + // import 300 L1 messages + for (uint256 i = 0; i < 300; i++) { + messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); + } + + (bytes memory batchHeader0, bytes memory batchHeader1, bytes memory batchHeader2) = _commitBatchV3(); + + // 1 ~ 4, zero + for (uint256 i = 1; i < 4; i++) { + assertBoolEq(messageQueue.isMessageSkipped(i), false); + } + // 4 ~ 9, even is nonzero, odd is zero + for (uint256 i = 4; i < 9; i++) { + if (i % 2 == 1 || i == 8) { + assertBoolEq(messageQueue.isMessageSkipped(i), false); + } else { + assertBoolEq(messageQueue.isMessageSkipped(i), true); + } + } + // 9 ~ 265, even is nonzero, odd is zero + for (uint256 i = 9; i < 265; i++) { + if (i % 2 == 1 || i == 264) { + assertBoolEq(messageQueue.isMessageSkipped(i), false); + } else { + assertBoolEq(messageQueue.isMessageSkipped(i), true); + } + } - // finalize batch2 + rollup.updateBundleSize(2, 1); + // finalize batch1 and batch2 together + assertBoolEq(rollup.isBatchFinalized(1), false); assertBoolEq(rollup.isBatchFinalized(2), false); hevm.startPrank(address(0)); rollup.finalizeBundleWithProof(batchHeader2, bytes32(uint256(2)), bytes32(uint256(3)), new bytes(0)); hevm.stopPrank(); - assertBoolEq(rollup.isBatchFinalized(2), true); + assertBoolEq(rollup.isBatchFinalized(1), false); + assertBoolEq(rollup.isBatchFinalized(2), false); + assertEq(rollup.finalizedStateRoots(1), bytes32(0)); + assertEq(rollup.withdrawRoots(1), bytes32(0)); assertEq(rollup.finalizedStateRoots(2), bytes32(uint256(2))); assertEq(rollup.withdrawRoots(2), bytes32(uint256(3))); + assertEq(rollup.lastFinalizedBatchIndex(), 0); + assertEq(rollup.lastZkpVerifiedBatchIndex(), 2); + assertEq(0, messageQueue.nextUnfinalizedQueueIndex()); + hevm.startPrank(address(0)); + rollup.finalizeBundleWithTeeProof(batchHeader2, bytes32(uint256(2)), bytes32(uint256(3)), new bytes(0)); + hevm.stopPrank(); + assertBoolEq(rollup.isBatchFinalized(1), true); + assertBoolEq(rollup.isBatchFinalized(2), true); assertEq(rollup.lastFinalizedBatchIndex(), 2); + assertEq(rollup.lastTeeVerifiedBatchIndex(), 2); + assertEq(265, messageQueue.nextUnfinalizedQueueIndex()); + } + + function testRevertBatchWithL1Messages() external { + rollup.addSequencer(address(0)); + rollup.addProver(address(0)); + + // import 300 L1 messages + for (uint256 i = 0; i < 300; i++) { + messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); + } + + (bytes memory batchHeader0, bytes memory batchHeader1, bytes memory batchHeader2) = _commitBatchV3(); + + // 1 ~ 4, zero + for (uint256 i = 1; i < 4; i++) { + assertBoolEq(messageQueue.isMessageSkipped(i), false); + } + // 4 ~ 9, even is nonzero, odd is zero + for (uint256 i = 4; i < 9; i++) { + if (i % 2 == 1 || i == 8) { + assertBoolEq(messageQueue.isMessageSkipped(i), false); + } else { + assertBoolEq(messageQueue.isMessageSkipped(i), true); + } + } + // 9 ~ 265, even is nonzero, odd is zero + for (uint256 i = 9; i < 265; i++) { + if (i % 2 == 1 || i == 264) { + assertBoolEq(messageQueue.isMessageSkipped(i), false); + } else { + assertBoolEq(messageQueue.isMessageSkipped(i), true); + } + } + + // revert batch 1 and batch 2 + rollup.revertBatch(batchHeader1, batchHeader2); + assertEq(0, messageQueue.pendingQueueIndex()); + assertEq(0, messageQueue.nextUnfinalizedQueueIndex()); + for (uint256 i = 0; i < 265; i++) { + assertBoolEq(messageQueue.isMessageSkipped(i), false); + } } function testRevertBatch() external { @@ -1415,7 +1292,9 @@ contract ScrollChainTest is DSTestPlus { ScrollChainMockBlob impl = new ScrollChainMockBlob( rollup.layer2ChainId(), rollup.messageQueue(), - rollup.verifier() + rollup.zkpVerifier(), + rollup.teeVerifier(), + 0 ); admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); @@ -1593,9 +1472,9 @@ contract ScrollChainTest is DSTestPlus { hevm.expectRevert("Pausable: paused"); rollup.commitBatchWithBlobProof(3, new bytes(0), new bytes[](0), new bytes(0), new bytes(0)); hevm.expectRevert("Pausable: paused"); - rollup.finalizeBatchWithProof4844(new bytes(0), bytes32(0), bytes32(0), bytes32(0), new bytes(0), new bytes(0)); - hevm.expectRevert("Pausable: paused"); rollup.finalizeBundleWithProof(new bytes(0), bytes32(0), bytes32(0), new bytes(0)); + hevm.expectRevert("Pausable: paused"); + rollup.finalizeBundleWithTeeProof(new bytes(0), bytes32(0), bytes32(0), new bytes(0)); hevm.stopPrank(); // unpause @@ -1619,6 +1498,79 @@ contract ScrollChainTest is DSTestPlus { assertEq(rollup.maxNumTxInChunk(), _maxNumTxInChunk); } + function testUpdateBundleSize() external { + // not owner, revert + hevm.startPrank(address(1)); + hevm.expectRevert("Ownable: caller is not the owner"); + rollup.updateBundleSize(0, 0); + hevm.stopPrank(); + + // upgrade to ScrollChainMockBlob + ScrollChainMockBlob impl = new ScrollChainMockBlob( + rollup.layer2ChainId(), + rollup.messageQueue(), + rollup.zkpVerifier(), + rollup.teeVerifier(), + 0 + ); + admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); + ScrollChainMockBlob(address(rollup)).setLastZkpVerifiedBatchIndex(100); + ScrollChainMockBlob(address(rollup)).setLastTeeVerifiedBatchIndex(90); + + // revert ErrorUseFinalizedBatch, when index < lastTeeVerifiedBatchIndex + hevm.expectRevert(ScrollChain.ErrorUseFinalizedBatch.selector); + rollup.updateBundleSize(1, 89); + // revert ErrorUseFinalizedBatch, when index < lastZkpVerifiedBatchIndex + hevm.expectRevert(ScrollChain.ErrorUseFinalizedBatch.selector); + rollup.updateBundleSize(1, 99); + + // no array item 1 + hevm.expectRevert(new bytes(0)); + rollup.bundleSize(1); + + // update one + hevm.expectEmit(false, false, false, true); + emit ChangeBundleSize(1, 5, 200); + rollup.updateBundleSize(5, 200); + (uint256 size, uint256 index) = rollup.bundleSize(1); + assertEq(size, 5); + assertEq(index, 200); + assertEq(rollup.getBundleSizeGivenEndBatchIndex(200), 1); + assertEq(rollup.getBundleSizeGivenEndBatchIndex(205), 5); + assertEq(rollup.getBundleSizeGivenEndBatchIndex(300), 5); + + ScrollChainMockBlob(address(rollup)).setLastZkpVerifiedBatchIndex(300); + ScrollChainMockBlob(address(rollup)).setLastTeeVerifiedBatchIndex(300); + + // revert ErrorBatchIndexDeltaNotMultipleOfBundleSize, last is past batch index + hevm.expectRevert(ScrollChain.ErrorBatchIndexDeltaNotMultipleOfBundleSize.selector); + rollup.updateBundleSize(6, 401); + + // succeed to append another one + hevm.expectEmit(false, false, false, true); + emit ChangeBundleSize(2, 10, 400); + rollup.updateBundleSize(10, 400); + (size, index) = rollup.bundleSize(2); + assertEq(size, 10); + assertEq(index, 400); + + // revert ErrorBatchIndexDeltaNotMultipleOfBundleSize, replace last with smaller index + hevm.expectRevert(ScrollChain.ErrorBatchIndexDeltaNotMultipleOfBundleSize.selector); + rollup.updateBundleSize(6, 401); + + // succeed to update last one + hevm.expectEmit(false, false, false, true); + emit ChangeBundleSize(2, 20, 350); + rollup.updateBundleSize(20, 350); + (size, index) = rollup.bundleSize(2); + assertEq(size, 20); + assertEq(index, 350); + + // no array item 3 + hevm.expectRevert(new bytes(0)); + rollup.bundleSize(3); + } + function testImportGenesisBlock() external { bytes memory batchHeader; @@ -1693,4 +1645,135 @@ contract ScrollChainTest is DSTestPlus { TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(_logic, address(admin), new bytes(0)); return address(proxy); } + + /// @dev Prepare 10 batches, each of the first 5 has 2 l1 messages, each of the second 5 has no l1 message. + function _prepareFinalizeBundle() internal returns (bytes[] memory headers) { + // grant roles + rollup.addProver(address(0)); + rollup.addSequencer(address(0)); + + headers = new bytes[](11); + + // upgrade to ScrollChainMockBlob for data mocking + ScrollChainMockBlob impl = new ScrollChainMockBlob( + rollup.layer2ChainId(), + rollup.messageQueue(), + rollup.zkpVerifier(), + rollup.teeVerifier(), + 100 + ); + admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); + // from https://etherscan.io/blob/0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757?bid=740652 + bytes32 blobVersionedHash = 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757; + ScrollChainMockBlob(address(rollup)).setBlobVersionedHash(blobVersionedHash); + + // import 10 L1 messages + for (uint256 i = 0; i < 10; i++) { + messageQueue.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); + } + // commit genesis batch + headers[0] = _commitGenesisBatch(); + // commit 5 batches, each has 2 l1 messages + for (uint256 i = 1; i <= 5; ++i) { + headers[i] = _commitBatch(headers[i - 1], 2); + } + // commit 5 batches, each has 0 l1 message + for (uint256 i = 6; i <= 10; ++i) { + headers[i] = _commitBatch(headers[i - 1], 0); + } + } + + function _commitGenesisBatch() internal returns (bytes memory header) { + header = new bytes(89); + assembly { + mstore(add(header, add(0x20, 25)), 1) + } + rollup.importGenesisBatch(header, bytes32(uint256(1))); + assertEq(rollup.committedBatches(0), keccak256(header)); + } + + function _commitBatch(bytes memory parentHeader, uint256 numL1Message) internal returns (bytes memory header) { + uint256 batchPtr; + assembly { + batchPtr := add(parentHeader, 0x20) + } + uint256 index = BatchHeaderV0Codec.getBatchIndex(batchPtr) + 1; + uint256 totalL1MessagePopped = BatchHeaderV0Codec.getTotalL1MessagePopped(batchPtr) + numL1Message; + bytes32 parentHash = keccak256(parentHeader); + bytes + memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; + bytes32[] memory hashes = new bytes32[](numL1Message); + for (uint256 i = 0; i < numL1Message; ++i) { + hashes[i] = messageQueue.getCrossDomainMessage(BatchHeaderV0Codec.getTotalL1MessagePopped(batchPtr) + i); + } + // commit batch, one chunk with one block, 1 + numL1Message tx, numL1Message L1 message + // payload for data hash of chunk0 + // hex(index) // block number + // hex(index) // timestamp + // 0000000000000000000000000000000000000000000000000000000000000000 // baseFee + // 0000000000000000 // gasLimit + // hex(1 + numL1Message) // numTransactions + // ... // l1 messages + // data hash for chunk0 + // keccak256(chunk0) + // data hash for all chunks + // keccak256(keccak256(chunk0)) + // => payload for batch header + // 03 // version + // hex(index) // batchIndex + // hex(numL1Message) // l1MessagePopped + // hex(totalL1MessagePopped) // totalL1MessagePopped + // keccak256(keccak256(chunk0)) // dataHash + // 013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757 // blobVersionedHash + // keccak256(parentHeader) // parentBatchHash + // hex(index) // lastBlockTimestamp + // 2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e687 // blobDataProof + // 53ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0 // blobDataProof + bytes memory bitmap; + if (numL1Message > 0) bitmap = new bytes(32); + bytes[] memory chunks = new bytes[](1); + { + bytes memory chunk0; + chunk0 = new bytes(1 + 60); + assembly { + mstore(add(chunk0, 0x20), shl(248, 1)) // numBlocks = 1 + mstore(add(chunk0, add(0x21, 8)), shl(192, index)) // timestamp = 0x123 + mstore(add(chunk0, add(0x21, 56)), shl(240, add(numL1Message, 1))) // numTransactions = 1 + numL1Message + mstore(add(chunk0, add(0x21, 58)), shl(240, numL1Message)) // numL1Messages + } + chunks[0] = chunk0; + bytes memory chunkData = new bytes(58 + numL1Message * 32); + assembly { + mcopy(add(chunkData, 0x20), add(chunk0, 0x21), 58) + mcopy(add(chunkData, 0x5a), add(hashes, 0x20), mul(32, mload(hashes))) + } + bytes32 dataHash = keccak256(abi.encode(keccak256(chunkData))); + header = new bytes(193); + assembly { + mstore8(add(header, 0x20), 3) // version + mstore(add(header, add(0x20, 1)), shl(192, index)) // batchIndex + mstore(add(header, add(0x20, 9)), shl(192, numL1Message)) // l1MessagePopped + mstore(add(header, add(0x20, 17)), shl(192, totalL1MessagePopped)) // totalL1MessagePopped + mstore(add(header, add(0x20, 25)), dataHash) // dataHash + mstore(add(header, add(0x20, 57)), 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757) // blobVersionedHash + mstore(add(header, add(0x20, 89)), parentHash) // parentBatchHash + mstore(add(header, add(0x20, 121)), shl(192, index)) // lastBlockTimestamp + mcopy(add(header, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof + } + } + + hevm.startPrank(address(0)); + if (numL1Message > 0) { + hevm.expectEmit(false, false, false, true); + emit DequeueTransaction(BatchHeaderV0Codec.getTotalL1MessagePopped(batchPtr), numL1Message, 0); + } + hevm.expectEmit(true, true, false, true); + emit CommitBatch(index, keccak256(header)); + rollup.commitBatchWithBlobProof(3, parentHeader, chunks, bitmap, blobDataProof); + hevm.stopPrank(); + assertBoolEq(rollup.isBatchFinalized(1), false); + assertEq(rollup.committedBatches(index), keccak256(header)); + assertEq(messageQueue.pendingQueueIndex(), totalL1MessagePopped); + assertEq(messageQueue.nextUnfinalizedQueueIndex(), 0); + } } diff --git a/src/test/mocks/MockAttestationVerifier.sol b/src/test/mocks/MockAttestationVerifier.sol new file mode 100644 index 00000000..b4832d95 --- /dev/null +++ b/src/test/mocks/MockAttestationVerifier.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +contract MockAttestationVerifier { + function verifyAttestation(bytes calldata report, bytes32 userData) external view {} +} diff --git a/src/test/mocks/MockDcapAttestation.sol b/src/test/mocks/MockDcapAttestation.sol new file mode 100644 index 00000000..157d1ebe --- /dev/null +++ b/src/test/mocks/MockDcapAttestation.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +contract MockDcapAttestation { + bool public success; + bytes public output; + + function setValue(bool _success, bytes memory _output) external { + success = _success; + output = _output; + } + + function verifyAndAttestOnChain(bytes calldata) external view returns (bool, bytes memory) { + return (success, output); + } +} diff --git a/src/test/mocks/MockRollupVerifier.sol b/src/test/mocks/MockRollupVerifier.sol index 3946b890..76d91878 100644 --- a/src/test/mocks/MockRollupVerifier.sol +++ b/src/test/mocks/MockRollupVerifier.sol @@ -5,6 +5,11 @@ pragma solidity =0.8.24; import {IRollupVerifier} from "../../libraries/verifier/IRollupVerifier.sol"; contract MockRollupVerifier is IRollupVerifier { + /// @inheritdoc IRollupVerifier + function getVerifier(uint256 _version, uint256 _batchIndex) external view returns (address) { + return address(this); + } + /// @inheritdoc IRollupVerifier function verifyAggregateProof( uint256, @@ -27,4 +32,6 @@ contract MockRollupVerifier is IRollupVerifier { bytes calldata, bytes calldata ) external view {} + + function randomSelectNextProver() external returns (address) {} } diff --git a/src/test/mocks/MockScrollChain.sol b/src/test/mocks/MockScrollChain.sol deleted file mode 100644 index da6efa62..00000000 --- a/src/test/mocks/MockScrollChain.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity =0.8.24; - -import {ScrollChain} from "../../L1/rollup/ScrollChain.sol"; - -contract MockScrollChain is ScrollChain { - constructor(address _messageQueue, address _verifier) ScrollChain(0, _messageQueue, _verifier) {} - - function setLastFinalizedBatchIndex(uint256 _lastFinalizedBatchIndex) external { - lastFinalizedBatchIndex = _lastFinalizedBatchIndex; - } -} diff --git a/src/test/sgx-verifier/AttestationVerifier.t.sol b/src/test/sgx-verifier/AttestationVerifier.t.sol new file mode 100644 index 00000000..3ebcce12 --- /dev/null +++ b/src/test/sgx-verifier/AttestationVerifier.t.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; + +import {AttestationVerifier} from "../../sgx-verifier/AttestationVerifier.sol"; +import {ISGXVerifier} from "../../sgx-verifier/ISGXVerifier.sol"; + +import {MockDcapAttestation} from "../mocks/MockDcapAttestation.sol"; + +contract AttestationVerifierTest is DSTestPlus { + event UpdateMrSigner(bytes32 indexed mrSigner, bool status); + event UpdateMrEnclave(bytes32 indexed mrEnclave, bool status); + + MockDcapAttestation private dcap; + AttestationVerifier private attestationVerifier; + + function setUp() public { + hevm.warp(1); + dcap = new MockDcapAttestation(); + attestationVerifier = new AttestationVerifier(address(dcap)); + } + + function testUpdateMrSigner(bytes32[] memory signers) external { + hevm.assume(signers.length > 0); + for (uint256 i = 0; i < signers.length; i++) { + for (uint256 j = 0; j < i; ++j) { + hevm.assume(signers[i] != signers[j]); + } + } + + // not owner, revert + hevm.startPrank(address(1)); + hevm.expectRevert("Ownable: caller is not the owner"); + attestationVerifier.updateMrSigner(signers[0], true); + hevm.stopPrank(); + + for (uint256 i = 0; i < signers.length; i++) { + assertBoolEq(false, attestationVerifier.isTrustedMrSigner(signers[i])); + hevm.expectEmit(true, false, false, true); + emit UpdateMrSigner(signers[i], true); + attestationVerifier.updateMrSigner(signers[i], true); + assertBoolEq(true, attestationVerifier.isTrustedMrSigner(signers[i])); + + bytes32[] memory lists = attestationVerifier.getTrustedMrSigners(); + assertEq(lists.length, i + 1); + for (uint256 j = 0; j <= i; ++j) { + assertEq(lists[j], signers[j]); + } + } + for (uint256 i = 0; i < signers.length; i++) { + assertBoolEq(true, attestationVerifier.isTrustedMrSigner(signers[signers.length - 1 - i])); + hevm.expectEmit(true, false, false, true); + emit UpdateMrSigner(signers[signers.length - 1 - i], false); + attestationVerifier.updateMrSigner(signers[signers.length - 1 - i], false); + assertBoolEq(false, attestationVerifier.isTrustedMrSigner(signers[signers.length - 1 - i])); + + bytes32[] memory lists = attestationVerifier.getTrustedMrSigners(); + assertEq(lists.length, signers.length - 1 - i); + for (uint256 j = 0; j < signers.length - 1 - i; ++j) { + assertEq(lists[j], signers[j]); + } + } + } + + function testUpdateMrEnclave(bytes32[] memory enclaves) external { + hevm.assume(enclaves.length > 0); + for (uint256 i = 0; i < enclaves.length; i++) { + for (uint256 j = 0; j < i; ++j) { + hevm.assume(enclaves[i] != enclaves[j]); + } + } + + // not owner, revert + hevm.startPrank(address(1)); + hevm.expectRevert("Ownable: caller is not the owner"); + attestationVerifier.updateMrEnclave(enclaves[0], true); + hevm.stopPrank(); + + for (uint256 i = 0; i < enclaves.length; i++) { + assertBoolEq(false, attestationVerifier.isTrustedMrEnclave(enclaves[i])); + hevm.expectEmit(true, false, false, true); + emit UpdateMrEnclave(enclaves[i], true); + attestationVerifier.updateMrEnclave(enclaves[i], true); + assertBoolEq(true, attestationVerifier.isTrustedMrEnclave(enclaves[i])); + + bytes32[] memory lists = attestationVerifier.getTrustedMrEnclaves(); + assertEq(lists.length, i + 1); + for (uint256 j = 0; j <= i; ++j) { + assertEq(lists[j], enclaves[j]); + } + } + for (uint256 i = 0; i < enclaves.length; i++) { + assertBoolEq(true, attestationVerifier.isTrustedMrEnclave(enclaves[enclaves.length - 1 - i])); + hevm.expectEmit(true, false, false, true); + emit UpdateMrEnclave(enclaves[enclaves.length - 1 - i], false); + attestationVerifier.updateMrEnclave(enclaves[enclaves.length - 1 - i], false); + assertBoolEq(false, attestationVerifier.isTrustedMrEnclave(enclaves[enclaves.length - 1 - i])); + + bytes32[] memory lists = attestationVerifier.getTrustedMrEnclaves(); + assertEq(lists.length, enclaves.length - 1 - i); + for (uint256 j = 0; j < enclaves.length - 1 - i; ++j) { + assertEq(lists[j], enclaves[j]); + } + } + } + + function testVerifyAttestation() external { + bytes32 hash = 0x1d541a57a16735fca3a2ef49ce5711705a71c26119b5043aeaab70adfcb3868d; + + // revert ErrorInvalidReport + dcap.setValue(false, new bytes(0)); + hevm.expectRevert(AttestationVerifier.ErrorInvalidReport.selector); + attestationVerifier.verifyAttestation(new bytes(0), hash); + dcap.setValue(true, new bytes(0)); + hevm.expectRevert(AttestationVerifier.ErrorInvalidReport.selector); + attestationVerifier.verifyAttestation(new bytes(0), hash); + + // the value comes from 0x76A3657F2d6c5C66733e9b69ACaDadCd0B68788b.verifyAndAttestOnChain + dcap.setValue( + true, + hex"0003000000000100606a0000000e0e100fffff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000e700000000000000de424a88451579c00ad51d75c8f938b3f0bcb42fbb6840ac542f81a90a12dbcf00000000000000000000000000000000000000000000000000000000000000001d7b598f382a365d445d477f0df30bbe13a9a2276c75c9b002dba6a9a925c7030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d541a57a16735fca3a2ef49ce5711705a71c26119b5043aeaab70adfcb3868d" + ); + + // revert ErrorReportDataMismatch + hevm.expectRevert(AttestationVerifier.ErrorReportDataMismatch.selector); + attestationVerifier.verifyAttestation(new bytes(0), bytes32(0)); + + // MrEnclave = 0xde424a88451579c00ad51d75c8f938b3f0bcb42fbb6840ac542f81a90a12dbcf + // revert ErrorInvalidMrEnclave + hevm.expectRevert(AttestationVerifier.ErrorInvalidMrEnclave.selector); + attestationVerifier.verifyAttestation(new bytes(0), hash); + + attestationVerifier.updateMrEnclave(0xde424a88451579c00ad51d75c8f938b3f0bcb42fbb6840ac542f81a90a12dbcf, true); + + // MrSigner = 0x1d7b598f382a365d445d477f0df30bbe13a9a2276c75c9b002dba6a9a925c703 + // revert ErrorInvalidMrSigner + hevm.expectRevert(AttestationVerifier.ErrorInvalidMrSigner.selector); + attestationVerifier.verifyAttestation(new bytes(0), hash); + + attestationVerifier.updateMrSigner(0x1d7b598f382a365d445d477f0df30bbe13a9a2276c75c9b002dba6a9a925c703, true); + + // succeed + attestationVerifier.verifyAttestation(new bytes(0), hash); + } +} diff --git a/src/test/sgx-verifier/SGXVerifier.t.sol b/src/test/sgx-verifier/SGXVerifier.t.sol new file mode 100644 index 00000000..cecb0668 --- /dev/null +++ b/src/test/sgx-verifier/SGXVerifier.t.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.24; + +import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +import {AttestationVerifier} from "../../sgx-verifier/AttestationVerifier.sol"; +import {SGXVerifier, ISGXVerifier} from "../../sgx-verifier/SGXVerifier.sol"; + +import {MockAttestationVerifier} from "../mocks/MockAttestationVerifier.sol"; + +contract SGXVerifierTest is DSTestPlus { + event ProverRegistered(address indexed prover, uint256 validUntil); + + MockAttestationVerifier private attestationVerifier; + SGXVerifier private sgxVerifier; + + function setUp() public { + hevm.warp(1); + attestationVerifier = new MockAttestationVerifier(); + sgxVerifier = new SGXVerifier(address(attestationVerifier), 86400 * 7, 60, 86400); + + sgxVerifier.grantRole(sgxVerifier.PROVER_REGISTER_ROLE(), address(this)); + } + + function testRegister() external { + // revert when no PROVER_REGISTER_ROLE + hevm.startPrank(address(1)); + hevm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(1)), + " is missing role ", + Strings.toHexString(uint256(sgxVerifier.PROVER_REGISTER_ROLE()), 32) + ) + ); + sgxVerifier.register(new bytes(0), ISGXVerifier.ReportData(address(0), 0, bytes32(0))); + hevm.stopPrank(); + + // revert when ErrorInvalidBlockNumber + hevm.expectRevert(SGXVerifier.ErrorInvalidBlockNumber.selector); + sgxVerifier.register(new bytes(0), ISGXVerifier.ReportData(address(0), block.number, bytes32(0))); + + hevm.roll(100); + + // revert when ErrorBlockNumberOutOfDate + hevm.expectRevert(SGXVerifier.ErrorBlockNumberOutOfDate.selector); + sgxVerifier.register(new bytes(0), ISGXVerifier.ReportData(address(0), block.number - 60, bytes32(0))); + + // revert when ErrorBlockHashMismatch + hevm.expectRevert(SGXVerifier.ErrorBlockHashMismatch.selector); + sgxVerifier.register(new bytes(0), ISGXVerifier.ReportData(address(0), block.number - 59, bytes32(0))); + + // first register, succeed + (address nextProver, uint256 expireTime) = sgxVerifier.nextProver(); + assertEq(sgxVerifier.proverQueueHead(), 0); + assertEq(sgxVerifier.proverQueueTail(), 0); + assertEq(nextProver, address(0)); + assertEq(expireTime, 0); + sgxVerifier.register( + new bytes(0), + ISGXVerifier.ReportData(address(1), block.number - 59, blockhash(block.number - 59)) + ); + assertEq(sgxVerifier.proverQueueHead(), 0); + assertEq(sgxVerifier.proverQueueTail(), 1); + assertEq(sgxVerifier.proverQueue(0), address(1)); + assertEq(sgxVerifier.attestedProverExpireTime(address(1)), block.timestamp + 86400 * 7); + (nextProver, expireTime) = sgxVerifier.nextProver(); + assertEq(nextProver, address(1)); + assertEq(expireTime, block.timestamp + 86400); + + // revert when ErrorReportUsed + hevm.expectRevert(SGXVerifier.ErrorReportUsed.selector); + sgxVerifier.register( + new bytes(0), + ISGXVerifier.ReportData(address(1), block.number - 59, blockhash(block.number - 59)) + ); + + // second register, succeed + sgxVerifier.register( + new bytes(1), + ISGXVerifier.ReportData(address(2), block.number - 59, blockhash(block.number - 59)) + ); + assertEq(sgxVerifier.proverQueueHead(), 0); + assertEq(sgxVerifier.proverQueueTail(), 2); + assertEq(sgxVerifier.proverQueue(0), address(1)); + assertEq(sgxVerifier.proverQueue(1), address(2)); + assertEq(sgxVerifier.attestedProverExpireTime(address(2)), block.timestamp + 86400 * 7); + (nextProver, expireTime) = sgxVerifier.nextProver(); + assertEq(nextProver, address(1)); + assertEq(expireTime, block.timestamp + 86400); + + // third register, succeed + sgxVerifier.register( + new bytes(2), + ISGXVerifier.ReportData(address(3), block.number - 59, blockhash(block.number - 59)) + ); + assertEq(sgxVerifier.proverQueueHead(), 0); + assertEq(sgxVerifier.proverQueueTail(), 3); + assertEq(sgxVerifier.proverQueue(0), address(1)); + assertEq(sgxVerifier.proverQueue(1), address(2)); + assertEq(sgxVerifier.proverQueue(2), address(3)); + assertEq(sgxVerifier.attestedProverExpireTime(address(3)), block.timestamp + 86400 * 7); + (nextProver, expireTime) = sgxVerifier.nextProver(); + assertEq(nextProver, address(1)); + assertEq(expireTime, block.timestamp + 86400); + + // all expired + sgxVerifier.grantRole(sgxVerifier.PROVER_SELECTION_ROLE(), address(this)); + hevm.warp(block.timestamp + 86400 * 10); + assertEq(address(0), sgxVerifier.randomSelectNextProver()); + assertEq(sgxVerifier.proverQueueHead(), 3); + assertEq(sgxVerifier.proverQueueTail(), 3); + + // forth register, succeed + sgxVerifier.register( + new bytes(3), + ISGXVerifier.ReportData(address(4), block.number - 59, blockhash(block.number - 59)) + ); + assertEq(sgxVerifier.proverQueueHead(), 3); + assertEq(sgxVerifier.proverQueueTail(), 4); + assertEq(sgxVerifier.attestedProverExpireTime(address(4)), block.timestamp + 86400 * 7); + (nextProver, expireTime) = sgxVerifier.nextProver(); + assertEq(nextProver, address(4)); + assertEq(expireTime, block.timestamp + 86400); + } + + function testVerify( + uint64 layer2ChainId, + uint32 numBatches, + bytes32 prevStateRoot, + bytes32 prevBatchHash, + bytes32 postStateRoot, + bytes32 batchHash, + bytes32 postWithdrawRoot + ) external { + bytes memory publicInput = abi.encodePacked( + layer2ChainId, + numBatches, + prevStateRoot, + prevBatchHash, + postStateRoot, + batchHash, + postWithdrawRoot + ); + bytes32 hash = sgxVerifier.getProveBundleSignatureDataHash( + layer2ChainId, + numBatches, + prevStateRoot, + prevBatchHash, + postStateRoot, + batchHash, + postWithdrawRoot + ); + bytes memory signature; + { + (uint8 v, bytes32 r, bytes32 s) = hevm.sign(1, hash); + signature = abi.encodePacked(r, s, v); + } + + // register + hevm.roll(100); + sgxVerifier.register( + new bytes(0), + ISGXVerifier.ReportData(hevm.addr(1), block.number - 1, blockhash(block.number - 1)) + ); + sgxVerifier.register( + new bytes(1), + ISGXVerifier.ReportData(hevm.addr(2), block.number - 1, blockhash(block.number - 1)) + ); + + // verify ok + sgxVerifier.verify(signature, publicInput); + + // revert `ErrorNotSelectedProver` + { + (uint8 v, bytes32 r, bytes32 s) = hevm.sign(2, hash); + signature = abi.encodePacked(r, s, v); + } + hevm.expectRevert(SGXVerifier.ErrorNotSelectedProver.selector); + sgxVerifier.verify(signature, publicInput); + + // verify ok, when selected prover not submit in time + (, uint256 expireTime) = sgxVerifier.nextProver(); + hevm.warp(expireTime + 1); + sgxVerifier.verify(signature, publicInput); + + // revert `ErrorProverOutOfDate` + { + (uint8 v, bytes32 r, bytes32 s) = hevm.sign(1, hash); + signature = abi.encodePacked(r, s, v); + } + hevm.warp(sgxVerifier.attestedProverExpireTime(hevm.addr(1)) + 1); + hevm.expectRevert(SGXVerifier.ErrorProverOutOfDate.selector); + sgxVerifier.verify(signature, publicInput); + } + + function testRandomSelectNextProver() external { + hevm.roll(100); + + // revert when no PROVER_SELECTION_ROLE + hevm.startPrank(address(1)); + hevm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(1)), + " is missing role ", + Strings.toHexString(uint256(sgxVerifier.PROVER_SELECTION_ROLE()), 32) + ) + ); + sgxVerifier.randomSelectNextProver(); + hevm.stopPrank(); + + // empty queue return address(0) + sgxVerifier.grantRole(sgxVerifier.PROVER_SELECTION_ROLE(), address(this)); + assertEq(address(0), sgxVerifier.randomSelectNextProver()); + + uint256 startTime = block.timestamp; + // register 3 prover, selected is first one + sgxVerifier.register( + new bytes(0), + ISGXVerifier.ReportData(address(1), block.number - 1, blockhash(block.number - 1)) + ); + hevm.warp(startTime + 86400); + sgxVerifier.register( + new bytes(1), + ISGXVerifier.ReportData(address(2), block.number - 1, blockhash(block.number - 1)) + ); + hevm.warp(startTime + 86400 * 2); + sgxVerifier.register( + new bytes(2), + ISGXVerifier.ReportData(address(3), block.number - 1, blockhash(block.number - 1)) + ); + (address nextProver, uint256 expireTime) = sgxVerifier.nextProver(); + assertEq(nextProver, address(1)); + assertEq(expireTime, startTime + 86400); + + // first expired + hevm.warp(startTime + 86400 * 6); + for (uint256 i = 0; i < 10; ++i) { + sgxVerifier.randomSelectNextProver(); + } + assertEq(sgxVerifier.proverQueueHead(), 1); + (nextProver, expireTime) = sgxVerifier.nextProver(); + assertTrue(nextProver != address(1)); + assertEq(expireTime, block.timestamp + 86400); + } +}