diff --git a/contracts/src/L1/rollup/ZKRollup.sol b/contracts/src/L1/rollup/ZKRollup.sol index bbd64ba97..38d3bd1ca 100644 --- a/contracts/src/L1/rollup/ZKRollup.sol +++ b/contracts/src/L1/rollup/ZKRollup.sol @@ -300,6 +300,20 @@ contract ZKRollup is OwnableUpgradeable, IZKRollup { /**************************************** Restricted Functions ****************************************/ + /// @notice Explicit finalize batch by owner. + /// @dev This is temporary workaround to skip batch proving in some situations. + /// And it will be removed on mainnet launch. + /// @param _batchId The id of batch to finalize. + function forceBatchFinalization(bytes32 _batchId) external onlyOwner { + Layer2BatchStored storage _batch = batches[_batchId]; + require(_batch.batchHash != bytes32(0), "No such batch"); + require(!_batch.verified, "Batch already verified"); + + _batch.verified = true; + + emit FinalizeBatch(_batchId, _batch.batchHash, _batch.batchIndex, _batch.parentHash); + } + /// @notice Update the address of operator. /// @dev This function can only called by contract owner. /// @param _newOperator The new operator address to update. diff --git a/contracts/src/test/ZKRollup.t.sol b/contracts/src/test/ZKRollup.t.sol index 95bd0ac88..fcdb20f95 100644 --- a/contracts/src/test/ZKRollup.t.sol +++ b/contracts/src/test/ZKRollup.t.sol @@ -255,4 +255,50 @@ contract ZKRollupTest is DSTestPlus { assertEq(rollup.finalizedBatches(1), bytes32(0)); } } + + function testForceBatchFinalization() public { + // not owner + hevm.startPrank(address(1)); + hevm.expectRevert("Ownable: caller is not the owner"); + rollup.forceBatchFinalization(bytes32(0)); + hevm.stopPrank(); + + IZKRollup.Layer2BlockHeader memory _header; + + // import fake genesis + _header.blockHash = bytes32(uint256(1)); + rollup.importGenesisBlock(_header); + + // No such batch + hevm.expectRevert("No such batch"); + rollup.forceBatchFinalization(bytes32(0)); + + // Batch already verified + bytes32 _batchId = keccak256(abi.encode(_header.blockHash, bytes32(0), 0)); + hevm.expectRevert("Batch already verified"); + rollup.forceBatchFinalization(_batchId); + + // import fake batch + _header.blockHeight = 1; + _header.parentHash = bytes32(uint256(1)); + _header.blockHash = bytes32(uint256(2)); + IZKRollup.Layer2Batch memory _batch; + _batch.blocks = new IZKRollup.Layer2BlockHeader[](1); + _batch.blocks[0] = _header; + _batch.batchIndex = 1; + _batch.parentHash = _header.parentHash; + rollup.updateOperator(address(1)); + hevm.startPrank(address(1)); + rollup.commitBatch(_batch); + hevm.stopPrank(); + + // force finalize + _batchId = keccak256(abi.encode(_header.blockHash, _header.parentHash, 1)); + bool verified; + (, , , verified) = rollup.batches(_batchId); + assertBoolEq(verified, false); + rollup.forceBatchFinalization(_batchId); + (, , , verified) = rollup.batches(_batchId); + assertBoolEq(verified, true); + } }