diff --git a/contracts/scripts/foundry/InitializeL1BridgeContracts.s.sol b/contracts/scripts/foundry/InitializeL1BridgeContracts.s.sol index e833b811ee..5482adaa77 100644 --- a/contracts/scripts/foundry/InitializeL1BridgeContracts.s.sol +++ b/contracts/scripts/foundry/InitializeL1BridgeContracts.s.sol @@ -27,6 +27,7 @@ contract InitializeL1BridgeContracts is Script { address L1_FINALIZE_SENDER_ADDRESS = vm.envAddress("L1_FINALIZE_SENDER_ADDRESS"); address L1_FEE_VAULT_ADDR = vm.envAddress("L1_FEE_VAULT_ADDR"); address L1_WETH_ADDR = vm.envAddress("L1_WETH_ADDR"); + address L1_VIEW_ORACLE_ADDR = vm.envAddress("L1_VIEW_ORACLE_ADDR"); address L1_WHITELIST_ADDR = vm.envAddress("L1_WHITELIST_ADDR"); address L1_SCROLL_CHAIN_PROXY_ADDR = vm.envAddress("L1_SCROLL_CHAIN_PROXY_ADDR"); @@ -61,7 +62,8 @@ contract InitializeL1BridgeContracts is Script { ScrollChain(L1_SCROLL_CHAIN_PROXY_ADDR).initialize( L1_MESSAGE_QUEUE_PROXY_ADDR, L1_MULTIPLE_VERSION_ROLLUP_VERIFIER_ADDR, - MAX_TX_IN_CHUNK + MAX_TX_IN_CHUNK, + L1_VIEW_ORACLE_ADDR ); ScrollChain(L1_SCROLL_CHAIN_PROXY_ADDR).addSequencer(L1_COMMIT_SENDER_ADDRESS); ScrollChain(L1_SCROLL_CHAIN_PROXY_ADDR).addProver(L1_FINALIZE_SENDER_ADDRESS); diff --git a/contracts/src/L1/rollup/IScrollChain.sol b/contracts/src/L1/rollup/IScrollChain.sol index 9f86c2046e..715f784017 100644 --- a/contracts/src/L1/rollup/IScrollChain.sol +++ b/contracts/src/L1/rollup/IScrollChain.sol @@ -61,7 +61,8 @@ interface IScrollChain { uint8 version, bytes calldata parentBatchHeader, bytes[] memory chunks, - bytes calldata skippedL1MessageBitmap + bytes calldata skippedL1MessageBitmap, + uint64 _prevLastAppliedL1Block ) external; /// @notice Revert a pending batch. diff --git a/contracts/src/L1/rollup/ScrollChain.sol b/contracts/src/L1/rollup/ScrollChain.sol index dd6c2db50b..803cf992ad 100644 --- a/contracts/src/L1/rollup/ScrollChain.sol +++ b/contracts/src/L1/rollup/ScrollChain.sol @@ -10,6 +10,7 @@ import {IScrollChain} from "./IScrollChain.sol"; import {BatchHeaderV0Codec} from "../../libraries/codec/BatchHeaderV0Codec.sol"; import {ChunkCodec} from "../../libraries/codec/ChunkCodec.sol"; import {IRollupVerifier} from "../../libraries/verifier/IRollupVerifier.sol"; +import {IL1ViewOracle} from "../L1ViewOracle.sol"; // solhint-disable no-inline-assembly // solhint-disable reason-string @@ -79,6 +80,28 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @inheritdoc IScrollChain mapping(uint256 => bytes32) public override withdrawRoots; + /// @notice The address of L1ViewOracle. + address public l1ViewOracle; + + // stack too deep + struct CommitChunksResult { + bytes32 dataHash; + uint256 totalL1MessagesPoppedOverall; + uint256 totalL1MessagesPoppedInBatch; + uint64 lastAppliedL1Block; + bytes32 l1BlockRangeHashInBatch; + } + + // stack too deep + struct ChunkResult { + // _totalNumL1MessagesInChunk The total number of L1 messages popped in current chunk + uint256 _totalNumL1MessagesInChunk; + // _lastAppliedL1BlockInChunk The last applied L1 Block Number in current chunk + uint64 _lastAppliedL1BlockInChunk; + // _l1BlockRangeHashInChunk The keccak256 of all the l1 block range hashes in current chunk + bytes32 _l1BlockRangeHashInChunk; + } + /********************** * Function Modifiers * **********************/ @@ -107,13 +130,15 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { function initialize( address _messageQueue, address _verifier, - uint256 _maxNumTxInChunk + uint256 _maxNumTxInChunk, + address _l1ViewOracle ) public initializer { OwnableUpgradeable.__Ownable_init(); messageQueue = _messageQueue; verifier = _verifier; maxNumTxInChunk = _maxNumTxInChunk; + l1ViewOracle = _l1ViewOracle; emit UpdateVerifier(address(0), _verifier); emit UpdateMaxNumTxInChunk(0, _maxNumTxInChunk); @@ -165,13 +190,13 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { uint8 _version, bytes calldata _parentBatchHeader, bytes[] memory _chunks, - bytes calldata _skippedL1MessageBitmap + bytes calldata _skippedL1MessageBitmap, + uint64 _prevLastAppliedL1Block ) external override OnlySequencer whenNotPaused { require(_version == 0, "invalid version"); // check whether the batch is empty - uint256 _chunksLength = _chunks.length; - require(_chunksLength > 0, "batch is empty"); + require(_chunks.length > 0, "batch is empty"); // The overall memory layout in this function is organized as follows // +---------------------+-------------------+------------------+ @@ -195,45 +220,14 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { require(committedBatches[_batchIndex] == _parentBatchHash, "incorrect parent batch hash"); require(committedBatches[_batchIndex + 1] == 0, "batch already committed"); - // load `dataPtr` and reserve the memory region for chunk data hashes - uint256 dataPtr; - assembly { - dataPtr := mload(0x40) - mstore(0x40, add(dataPtr, mul(_chunksLength, 32))) - } - - // compute the data hash for each chunk - uint256 _totalL1MessagesPoppedInBatch; - for (uint256 i = 0; i < _chunksLength; i++) { - uint256 _totalNumL1MessagesInChunk = _commitChunk( - dataPtr, - _chunks[i], - _totalL1MessagesPoppedInBatch, - _totalL1MessagesPoppedOverall, - _skippedL1MessageBitmap - ); - - unchecked { - _totalL1MessagesPoppedInBatch += _totalNumL1MessagesInChunk; - _totalL1MessagesPoppedOverall += _totalNumL1MessagesInChunk; - dataPtr += 32; - } - } - - // check the length of bitmap - unchecked { - require( - ((_totalL1MessagesPoppedInBatch + 255) / 256) * 32 == _skippedL1MessageBitmap.length, - "wrong bitmap length" - ); - } + CommitChunksResult memory chunksResult = _commitChunks( + _chunks, + _totalL1MessagesPoppedOverall, + _skippedL1MessageBitmap, + _prevLastAppliedL1Block + ); - // compute the data hash for current batch - bytes32 _dataHash; assembly { - let dataLen := mul(_chunksLength, 0x20) - _dataHash := keccak256(sub(dataPtr, dataLen), dataLen) - batchPtr := mload(0x40) // reset batchPtr _batchIndex := add(_batchIndex, 1) // increase batch index } @@ -241,14 +235,24 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { // store entries, the order matters BatchHeaderV0Codec.storeVersion(batchPtr, _version); BatchHeaderV0Codec.storeBatchIndex(batchPtr, _batchIndex); - BatchHeaderV0Codec.storeL1MessagePopped(batchPtr, _totalL1MessagesPoppedInBatch); - BatchHeaderV0Codec.storeTotalL1MessagePopped(batchPtr, _totalL1MessagesPoppedOverall); - BatchHeaderV0Codec.storeDataHash(batchPtr, _dataHash); + BatchHeaderV0Codec.storeL1MessagePopped(batchPtr, chunksResult.totalL1MessagesPoppedInBatch); + BatchHeaderV0Codec.storeTotalL1MessagePopped(batchPtr, chunksResult.totalL1MessagesPoppedOverall); + BatchHeaderV0Codec.storeDataHash(batchPtr, chunksResult.dataHash); BatchHeaderV0Codec.storeParentBatchHash(batchPtr, _parentBatchHash); BatchHeaderV0Codec.storeSkippedBitmap(batchPtr, _skippedL1MessageBitmap); + BatchHeaderV0Codec.storeLastAppliedL1Block( + batchPtr, + _skippedL1MessageBitmap.length, + chunksResult.lastAppliedL1Block + ); + BatchHeaderV0Codec.storeL1BlockRangeHash( + batchPtr, + _skippedL1MessageBitmap.length, + chunksResult.l1BlockRangeHashInBatch + ); // compute batch hash - bytes32 _batchHash = BatchHeaderV0Codec.computeBatchHash(batchPtr, 89 + _skippedL1MessageBitmap.length); + bytes32 _batchHash = BatchHeaderV0Codec.computeBatchHash(batchPtr, 129 + _skippedL1MessageBitmap.length); committedBatches[_batchIndex] = _batchHash; emit CommitBatch(_batchIndex, _batchHash); @@ -302,6 +306,10 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { bytes32 _dataHash = BatchHeaderV0Codec.dataHash(memPtr); uint256 _batchIndex = BatchHeaderV0Codec.batchIndex(memPtr); + uint256 _l1MessagePopped = BatchHeaderV0Codec.l1MessagePopped(memPtr); + uint256 _skippedBitmapLength = _l1MessagePopped * 256; + uint256 _lastAppliedL1Block = BatchHeaderV0Codec.lastAppliedL1Block(memPtr, _skippedBitmapLength); + bytes32 _l1BlockRangeHash = BatchHeaderV0Codec.l1BlockRangeHash(memPtr, _skippedBitmapLength); require(committedBatches[_batchIndex] == _batchHash, "incorrect batch hash"); // verify previous state root. @@ -312,7 +320,15 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { // compute public input hash bytes32 _publicInputHash = keccak256( - abi.encodePacked(layer2ChainId, _prevStateRoot, _postStateRoot, _withdrawRoot, _dataHash) + abi.encodePacked( + layer2ChainId, + _prevStateRoot, + _postStateRoot, + _withdrawRoot, + _dataHash, + _lastAppliedL1Block, + _l1BlockRangeHash + ) ); // verify batch @@ -329,7 +345,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { withdrawRoots[_batchIndex] = _withdrawRoot; // Pop finalized and non-skipped message from L1MessageQueue. - uint256 _l1MessagePopped = BatchHeaderV0Codec.l1MessagePopped(memPtr); if (_l1MessagePopped > 0) { IL1MessageQueue _queue = IL1MessageQueue(messageQueue); @@ -441,20 +456,98 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { _batchHash = BatchHeaderV0Codec.computeBatchHash(memPtr, _length); } + function _commitChunks( + bytes[] memory _chunks, + uint256 _totalL1MessagesPoppedOverall, + bytes calldata _skippedL1MessageBitmap, + uint64 _prevLastAppliedL1Block + ) internal view returns (CommitChunksResult memory) { + uint256 _chunksLength = _chunks.length; + // load `dataPtr` and reserve the memory region for chunk data hashes + uint256 dataPtr; + assembly { + dataPtr := mload(0x40) + mstore(0x40, add(dataPtr, mul(_chunksLength, 32))) + } + + uint256 _totalL1MessagesPoppedInBatch; + uint64 _lastAppliedL1Block; + bytes32[] memory _l1BlockRangeHashes = new bytes32[](_chunksLength); + + for (uint256 i = 0; i < _chunksLength; i++) { + ChunkResult memory chunkResult = _commitChunk( + dataPtr, + _chunks[i], + _totalL1MessagesPoppedInBatch, + _totalL1MessagesPoppedOverall, + _skippedL1MessageBitmap + ); + + if (_prevLastAppliedL1Block != 0) { + bytes32 _l1BlockRangeHash = IL1ViewOracle(l1ViewOracle).blockRangeHash( + _prevLastAppliedL1Block + 1, + chunkResult._lastAppliedL1BlockInChunk + ); + + require(_l1BlockRangeHash == chunkResult._l1BlockRangeHashInChunk, "incorrect l1 block range hash"); + _l1BlockRangeHashes[i] = chunkResult._l1BlockRangeHashInChunk; + _prevLastAppliedL1Block = chunkResult._lastAppliedL1BlockInChunk; + } + + // if it is the last chunk, update the last applied L1 block + if (i == _chunksLength - 1) { + _lastAppliedL1Block = chunkResult._lastAppliedL1BlockInChunk; + } + + unchecked { + _totalL1MessagesPoppedInBatch += chunkResult._totalNumL1MessagesInChunk; + _totalL1MessagesPoppedOverall += chunkResult._totalNumL1MessagesInChunk; + dataPtr += 32; + } + } + + // check the length of bitmap + unchecked { + require( + ((_totalL1MessagesPoppedInBatch + 255) / 256) * 32 == _skippedL1MessageBitmap.length, + "wrong bitmap length" + ); + } + + // compute the data hash for current batch + bytes32 _dataHash; + assembly { + let dataLen := mul(_chunksLength, 0x20) + _dataHash := keccak256(sub(dataPtr, dataLen), dataLen) + } + + bytes32 _l1BlockRangeHashInBatch = keccak256(abi.encodePacked(_l1BlockRangeHashes)); + + return + CommitChunksResult({ + dataHash: _dataHash, + totalL1MessagesPoppedOverall: _totalL1MessagesPoppedOverall, + totalL1MessagesPoppedInBatch: _totalL1MessagesPoppedInBatch, + lastAppliedL1Block: _lastAppliedL1Block, + l1BlockRangeHashInBatch: _l1BlockRangeHashInBatch + }); + } + /// @dev Internal function to commit a chunk. /// @param memPtr The start memory offset to store list of `dataHash`. /// @param _chunk The encoded chunk to commit. /// @param _totalL1MessagesPoppedInBatch The total number of L1 messages popped in current batch. /// @param _totalL1MessagesPoppedOverall The total number of L1 messages popped in all batches including current batch. /// @param _skippedL1MessageBitmap The bitmap indicates whether each L1 message is skipped or not. - /// @return _totalNumL1MessagesInChunk The total number of L1 message popped in current chunk + /// @return _chunkResult Contains the total number of L1 message popped, the last applied l1 block + /// and the keccak256 of the block range hashes for the current chunk. function _commitChunk( uint256 memPtr, bytes memory _chunk, uint256 _totalL1MessagesPoppedInBatch, uint256 _totalL1MessagesPoppedOverall, bytes calldata _skippedL1MessageBitmap - ) internal view returns (uint256 _totalNumL1MessagesInChunk) { + ) internal view returns (ChunkResult memory _chunkResult) { uint256 chunkPtr; uint256 startDataPtr; uint256 dataPtr; @@ -481,7 +574,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } assembly { - mstore(0x40, add(dataPtr, mul(_totalTransactionsInChunk, 0x20))) // reserve memory for tx hashes + mstore(0x40, add(add(dataPtr, mul(_totalTransactionsInChunk, 0x20)), 0x28)) // reserve memory for tx hashes and l1 block hashes data } } @@ -517,8 +610,13 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } + if (_numBlocks == 1) { + // check last block + _chunkResult._lastAppliedL1BlockInChunk = ChunkCodec.lastAppliedL1BlockInBlock(blockPtr); + } + unchecked { - _totalNumL1MessagesInChunk += _numL1MessagesInBlock; + _chunkResult._totalNumL1MessagesInChunk += _numL1MessagesInBlock; _totalL1MessagesPoppedInBatch += _numL1MessagesInBlock; _totalL1MessagesPoppedOverall += _numL1MessagesInBlock; @@ -527,11 +625,34 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } + // stack too deep + { + uint64 lastAppliedL1BlockInChunk = ChunkCodec.lastAppliedL1BlockInChunk(l2TxPtr); + _chunkResult._l1BlockRangeHashInChunk = ChunkCodec.l1BlockRangeHashInChunk(l2TxPtr); + + require( + lastAppliedL1BlockInChunk == _chunkResult._lastAppliedL1BlockInChunk, + "incorrect lastAppliedL1Block in chunk" + ); + } + // check the actual number of transactions in the chunk require((dataPtr - txHashStartDataPtr) / 32 <= maxNumTxInChunk, "too many txs in one chunk"); - // check chunk has correct length - require(l2TxPtr - chunkPtr == _chunk.length, "incomplete l2 transaction data"); + // check chunk has correct length. + // 40 is the size of lastAppliedL1Block and l1BlockRangeHash. + require(l2TxPtr - chunkPtr + 40 == _chunk.length, "incomplete l2 transaction data"); + + // stack too deep + { + uint256 _lastAppliedL1BlockInChunk = _chunkResult._lastAppliedL1BlockInChunk; + bytes32 _l1BlockRangeHashInChunk = _chunkResult._l1BlockRangeHashInChunk; + assembly { + mstore(dataPtr, _lastAppliedL1BlockInChunk) + mstore(dataPtr, _l1BlockRangeHashInChunk) + dataPtr := add(dataPtr, 0x28) + } + } // compute data hash and store to memory assembly { @@ -539,7 +660,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { mstore(memPtr, dataHash) } - return _totalNumL1MessagesInChunk; + return _chunkResult; } /// @dev Internal function to load L1 message hashes from the message queue. diff --git a/contracts/src/libraries/codec/BatchHeaderV0Codec.sol b/contracts/src/libraries/codec/BatchHeaderV0Codec.sol index 93004b40ce..a867ef710c 100644 --- a/contracts/src/libraries/codec/BatchHeaderV0Codec.sol +++ b/contracts/src/libraries/codec/BatchHeaderV0Codec.sol @@ -4,16 +4,18 @@ pragma solidity ^0.8.16; // solhint-disable no-inline-assembly -/// @dev Below is the encoding for `BatchHeader` V0, total 89 + ceil(l1MessagePopped / 256) * 32 bytes. +/// @dev Below is the encoding for `BatchHeader` V0, total 129 + ceil(l1MessagePopped / 256) * 32 bytes. /// ```text -/// * Field Bytes Type Index Comments -/// * version 1 uint8 0 The batch version -/// * batchIndex 8 uint64 1 The index of the batch -/// * l1MessagePopped 8 uint64 9 Number of L1 messages popped in the batch -/// * totalL1MessagePopped 8 uint64 17 Number of total L1 message popped after the batch -/// * dataHash 32 bytes32 25 The data hash of the batch -/// * parentBatchHash 32 bytes32 57 The parent batch hash -/// * skippedL1MessageBitmap dynamic uint256[] 89 A bitmap to indicate which L1 messages are skipped in the batch +/// * Field Bytes Type Index Comments +/// * version 1 uint8 0 The batch version +/// * batchIndex 8 uint64 1 The index of the batch +/// * l1MessagePopped 8 uint64 9 Number of L1 messages popped in the batch +/// * totalL1MessagePopped 8 uint64 17 Number of total L1 message popped after the batch +/// * dataHash 32 bytes32 25 The data hash of the batch +/// * parentBatchHash 32 bytes32 57 The parent batch hash +/// * skippedL1MessageBitmap dynamic uint256[] 89 A bitmap to indicate which L1 messages are skipped in the batch +/// * lastAppliedL1Block 8 uint64 89 + ceil(skippedL1MessageBitmap / 256) * 32 The last applied L1 block number +/// * blockRangeHash 32 bytes32 97 + ceil(skippedL1MessageBitmap / 256) * 32 The batch l1 block range hash /// ``` library BatchHeaderV0Codec { /// @notice Load batch header in calldata to memory. @@ -22,7 +24,7 @@ library BatchHeaderV0Codec { /// @return length The length in bytes of the batch header. function loadAndValidate(bytes calldata _batchHeader) internal pure returns (uint256 batchPtr, uint256 length) { length = _batchHeader.length; - require(length >= 89, "batch header length too small"); + require(length >= 129, "batch header length too small"); // copy batch header to memory. assembly { @@ -35,7 +37,7 @@ library BatchHeaderV0Codec { uint256 _l1MessagePopped = BatchHeaderV0Codec.l1MessagePopped(batchPtr); unchecked { - require(length == 89 + ((_l1MessagePopped + 255) / 256) * 32, "wrong bitmap length"); + require(length == 129 + ((_l1MessagePopped + 255) / 256) * 32, "wrong bitmap length"); } } @@ -104,6 +106,36 @@ library BatchHeaderV0Codec { } } + /// @notice Get the last applied L1 block number. + /// @param batchPtr The start memory offset of the batch header in memory. + /// @param _skippedBitmapLength The length of the skipped L1 message bitmap. + /// @return _lastAppliedL1Block The last applied L1 block number. + function lastAppliedL1Block(uint256 batchPtr, uint256 _skippedBitmapLength) + internal + pure + returns (uint256 _lastAppliedL1Block) + { + assembly { + batchPtr := add(batchPtr, 89) + _lastAppliedL1Block := mload(add(batchPtr, _skippedBitmapLength)) + } + } + + /// @notice Get the l1 block range hash. + /// @param batchPtr The start memory offset of the batch header in memory. + /// @param _skippedBitmapLength The length of the skipped L1 message bitmap. + /// @return _l1BlockRangeHash The l1 block range hash. + function l1BlockRangeHash(uint256 batchPtr, uint256 _skippedBitmapLength) + internal + pure + returns (bytes32 _l1BlockRangeHash) + { + assembly { + batchPtr := add(batchPtr, 97) + _l1BlockRangeHash := mload(add(batchPtr, _skippedBitmapLength)) + } + } + /// @notice Store the version of batch header. /// @param batchPtr The start memory offset of the batch header in memory. /// @param _version The version of batch header. @@ -173,6 +205,35 @@ library BatchHeaderV0Codec { } } + /// @notice Store the last applied L1 block number. + /// @param batchPtr The start memory offset of the batch header in memory. + /// @param _skippedL1MessageBitmapLength The length of the skipped L1 message bitmap. + /// @param _lastAppliedL1Block The last applied L1 block number. + function storeLastAppliedL1Block( + uint256 batchPtr, + uint256 _skippedL1MessageBitmapLength, + uint256 _lastAppliedL1Block + ) internal pure { + assembly { + mstore(add(batchPtr, _skippedL1MessageBitmapLength), shl(224, _lastAppliedL1Block)) + } + } + + /// @notice Store the l1 block range hash of batch header. + /// @param batchPtr The start memory offset of the batch header in memory. + /// @param _skippedL1MessageBitmapLength The length of the skipped L1 message bitmap. + /// @param _l1BlockRangeHash The l1 block range hash. + function storeL1BlockRangeHash( + uint256 batchPtr, + uint256 _skippedL1MessageBitmapLength, + bytes32 _l1BlockRangeHash + ) internal pure { + assembly { + batchPtr := add(batchPtr, 8) + mstore(add(batchPtr, _skippedL1MessageBitmapLength), _l1BlockRangeHash) + } + } + /// @notice Compute the batch hash. /// @dev Caller should make sure that the encoded batch header is correct. /// diff --git a/contracts/src/libraries/codec/ChunkCodec.sol b/contracts/src/libraries/codec/ChunkCodec.sol index 0da4d95252..0688ff642c 100644 --- a/contracts/src/libraries/codec/ChunkCodec.sol +++ b/contracts/src/libraries/codec/ChunkCodec.sol @@ -2,19 +2,21 @@ pragma solidity ^0.8.16; -/// @dev Below is the encoding for `Chunk`, total 60*n+1+m bytes. +/// @dev Below is the encoding for `Chunk`, total 68*n+9+m bytes. /// ```text -/// * Field Bytes Type Index Comments -/// * numBlocks 1 uint8 0 The number of blocks in this chunk -/// * block[0] 60 BlockContext 1 The first block in this chunk +/// * Field Bytes Type Index Comments +/// * numBlocks 1 uint8 0 The number of blocks in this chunk +/// * block[0] 68 BlockContext 1 The first block in this chunk /// * ...... -/// * block[i] 60 BlockContext 60*i+1 The (i+1)'th block in this chunk +/// * block[i] 68 BlockContext 68*i+1 The (i+1)'th block in this chunk /// * ...... -/// * block[n-1] 60 BlockContext 60*n-59 The last block in this chunk -/// * l2Transactions dynamic bytes 60*n+1 +/// * block[n-1] 68 BlockContext 68*n-67 The last block in this chunk +/// * l2Transactions dynamic bytes 68*n+1 +/// * lastAppliedL1Block 8 uint64 68*n+1+m The last applied L1 block number. +/// * l1BlockRangeHash 32 bytes 68*n+9+m The hash of the L1 block range. /// ``` /// -/// @dev Below is the encoding for `BlockContext`, total 60 bytes. +/// @dev Below is the encoding for `BlockContext`, total 68 bytes. /// ```text /// * Field Bytes Type Index Comments /// * blockNumber 8 uint64 0 The height of this block. @@ -23,9 +25,10 @@ pragma solidity ^0.8.16; /// * gasLimit 8 uint64 48 The gas limit of this block. /// * numTransactions 2 uint16 56 The number of transactions in this block, both L1 & L2 txs. /// * numL1Messages 2 uint16 58 The number of l1 messages in this block. +/// * lastAppliedL1Block 8 uint64 60 The last applied L1 block number. /// ``` library ChunkCodec { - uint256 internal constant BLOCK_CONTEXT_LENGTH = 60; + uint256 internal constant BLOCK_CONTEXT_LENGTH = 68; /// @notice Validate the length of chunk. /// @param chunkPtr The start memory offset of the chunk in memory. @@ -61,6 +64,24 @@ library ChunkCodec { } } + /// @notice Return the number of last applied L1 block. + /// @param l2TxEndPtr The end memory offset of `l2Transactions`. + /// @return _lastAppliedL1Block The number of last applied L1 block. + function lastAppliedL1BlockInChunk(uint256 l2TxEndPtr) internal pure returns (uint64 _lastAppliedL1Block) { + assembly { + _lastAppliedL1Block := shr(248, mload(l2TxEndPtr)) + } + } + + /// @notice Return the number of last applied L1 block. + /// @param l2TxEndPtr The end memory offset of `l2Transactions`. + /// @return _l1BlockRangeHash The hash of the L1 block range. + function l1BlockRangeHashInChunk(uint256 l2TxEndPtr) internal pure returns (bytes32 _l1BlockRangeHash) { + assembly { + _l1BlockRangeHash := shr(224, mload(add(l2TxEndPtr, 8))) + } + } + /// @notice Copy the block context to another memory. /// @param chunkPtr The start memory offset of the chunk in memory. /// @param dstPtr The destination memory offset to store the block context. @@ -86,6 +107,15 @@ library ChunkCodec { return dstPtr; } + /// @notice Return the number of last applied L1 block. + /// @param blockPtr The start memory offset of the block context in memory. + /// @return _lastAppliedL1Block The number of last applied L1 block. + function lastAppliedL1BlockInBlock(uint256 blockPtr) internal pure returns (uint64 _lastAppliedL1Block) { + assembly { + _lastAppliedL1Block := shr(240, mload(add(blockPtr, 60))) + } + } + /// @notice Return the number of transactions in current block. /// @param blockPtr The start memory offset of the block context in memory. /// @return _numTransactions The number of transactions in current block. diff --git a/contracts/src/test/L1GatewayTestBase.t.sol b/contracts/src/test/L1GatewayTestBase.t.sol index 2d7f2c8adc..1f8a361146 100644 --- a/contracts/src/test/L1GatewayTestBase.t.sol +++ b/contracts/src/test/L1GatewayTestBase.t.sol @@ -10,6 +10,7 @@ import {EnforcedTxGateway} from "../L1/gateways/EnforcedTxGateway.sol"; import {L1MessageQueue} from "../L1/rollup/L1MessageQueue.sol"; import {L2GasPriceOracle} from "../L1/rollup/L2GasPriceOracle.sol"; import {ScrollChain, IScrollChain} from "../L1/rollup/ScrollChain.sol"; +import {L1ViewOracle} from "../L1/L1ViewOracle.sol"; import {Whitelist} from "../L2/predeploys/Whitelist.sol"; import {L1ScrollMessenger} from "../L1/L1ScrollMessenger.sol"; import {L2ScrollMessenger} from "../L2/L2ScrollMessenger.sol"; @@ -46,6 +47,7 @@ abstract contract L1GatewayTestBase is DSTestPlus { uint32 internal constant defaultGasLimit = 1000000; + L1ViewOracle internal l1ViewOracle; L1ScrollMessenger internal l1Messenger; L1MessageQueue internal messageQueue; L2GasPriceOracle internal gasOracle; @@ -71,6 +73,7 @@ abstract contract L1GatewayTestBase is DSTestPlus { feeVault = address(uint160(address(this)) - 1); // Deploy L1 contracts + l1ViewOracle = new L1ViewOracle(); l1Messenger = L1ScrollMessenger(payable(new ERC1967Proxy(address(new L1ScrollMessenger()), new bytes(0)))); messageQueue = L1MessageQueue(address(new ERC1967Proxy(address(new L1MessageQueue()), new bytes(0)))); gasOracle = L2GasPriceOracle(address(new ERC1967Proxy(address(new L2GasPriceOracle()), new bytes(0)))); @@ -95,7 +98,7 @@ abstract contract L1GatewayTestBase is DSTestPlus { ); gasOracle.initialize(1, 2, 1, 1); gasOracle.updateWhitelist(address(whitelist)); - rollup.initialize(address(messageQueue), address(verifier), 44); + rollup.initialize(address(messageQueue), address(verifier), 44, address(l1ViewOracle)); address[] memory _accounts = new address[](1); _accounts[0] = address(this); @@ -123,7 +126,7 @@ abstract contract L1GatewayTestBase is DSTestPlus { chunk0[0] = bytes1(uint8(1)); // one block in this chunk chunks[0] = chunk0; hevm.startPrank(address(0)); - rollup.commitBatch(0, batchHeader0, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader0, chunks, new bytes(0), 0); hevm.stopPrank(); bytes memory batchHeader1 = new bytes(89); diff --git a/contracts/src/test/ScrollChain.t.sol b/contracts/src/test/ScrollChain.t.sol index a3b07b2934..7fbddd0e4f 100644 --- a/contracts/src/test/ScrollChain.t.sol +++ b/contracts/src/test/ScrollChain.t.sol @@ -7,6 +7,7 @@ import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {L1MessageQueue} from "../L1/rollup/L1MessageQueue.sol"; +import {L1ViewOracle} from "../L1/L1ViewOracle.sol"; import {ScrollChain, IScrollChain} from "../L1/rollup/ScrollChain.sol"; import {MockScrollChain} from "./mocks/MockScrollChain.sol"; @@ -25,17 +26,19 @@ contract ScrollChainTest is DSTestPlus { event FinalizeBatch(uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot); event RevertBatch(uint256 indexed batchIndex, bytes32 indexed batchHash); + L1ViewOracle private l1ViewOracle; ScrollChain private rollup; L1MessageQueue internal messageQueue; MockScrollChain internal chain; MockRollupVerifier internal verifier; function setUp() public { + l1ViewOracle = new L1ViewOracle(); messageQueue = L1MessageQueue(address(new ERC1967Proxy(address(new L1MessageQueue()), new bytes(0)))); rollup = ScrollChain(address(new ERC1967Proxy(address(new ScrollChain(233)), new bytes(0)))); verifier = new MockRollupVerifier(); - rollup.initialize(address(messageQueue), address(verifier), 100); + rollup.initialize(address(messageQueue), address(verifier), 100, address(l1ViewOracle)); messageQueue.initialize(address(this), address(rollup), address(0), address(0), 1000000); chain = new MockScrollChain(); @@ -46,7 +49,7 @@ contract ScrollChainTest is DSTestPlus { assertEq(rollup.layer2ChainId(), 233); hevm.expectRevert("Initializable: contract is already initialized"); - rollup.initialize(address(messageQueue), address(0), 100); + rollup.initialize(address(messageQueue), address(0), 100, address(l1ViewOracle)); } function testCommitBatch() public { @@ -65,32 +68,32 @@ contract ScrollChainTest is DSTestPlus { // caller not sequencer, revert hevm.expectRevert("caller not sequencer"); - rollup.commitBatch(0, batchHeader0, new bytes[](0), new bytes(0)); + rollup.commitBatch(0, batchHeader0, new bytes[](0), new bytes(0), 0); rollup.addSequencer(address(0)); // invalid version, revert hevm.startPrank(address(0)); hevm.expectRevert("invalid version"); - rollup.commitBatch(1, batchHeader0, new bytes[](0), new bytes(0)); + rollup.commitBatch(1, batchHeader0, new bytes[](0), new bytes(0), 0); hevm.stopPrank(); // batch is empty, revert hevm.startPrank(address(0)); hevm.expectRevert("batch is empty"); - rollup.commitBatch(0, batchHeader0, new bytes[](0), new bytes(0)); + rollup.commitBatch(0, batchHeader0, new bytes[](0), new bytes(0), 0); hevm.stopPrank(); // batch header length too small, revert hevm.startPrank(address(0)); hevm.expectRevert("batch header length too small"); - rollup.commitBatch(0, new bytes(88), new bytes[](1), new bytes(0)); + rollup.commitBatch(0, new bytes(88), new bytes[](1), new bytes(0), 0); hevm.stopPrank(); // wrong bitmap length, revert hevm.startPrank(address(0)); hevm.expectRevert("wrong bitmap length"); - rollup.commitBatch(0, new bytes(90), new bytes[](1), new bytes(0)); + rollup.commitBatch(0, new bytes(90), new bytes[](1), new bytes(0), 0); hevm.stopPrank(); // incorrect parent batch hash, revert @@ -99,7 +102,7 @@ contract ScrollChainTest is DSTestPlus { } hevm.startPrank(address(0)); hevm.expectRevert("incorrect parent batch hash"); - rollup.commitBatch(0, batchHeader0, new bytes[](1), new bytes(0)); + rollup.commitBatch(0, batchHeader0, new bytes[](1), new bytes(0), 0); hevm.stopPrank(); assembly { mstore(add(batchHeader0, add(0x20, 25)), 1) // change back @@ -113,7 +116,7 @@ contract ScrollChainTest is DSTestPlus { chunks[0] = chunk0; hevm.startPrank(address(0)); hevm.expectRevert("no block in chunk"); - rollup.commitBatch(0, batchHeader0, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader0, chunks, new bytes(0), 0); hevm.stopPrank(); // invalid chunk length, revert @@ -122,7 +125,7 @@ contract ScrollChainTest is DSTestPlus { chunks[0] = chunk0; hevm.startPrank(address(0)); hevm.expectRevert("invalid chunk length"); - rollup.commitBatch(0, batchHeader0, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader0, chunks, new bytes(0), 0); hevm.stopPrank(); // cannot skip last L1 message, revert @@ -135,7 +138,7 @@ contract ScrollChainTest is DSTestPlus { chunks[0] = chunk0; hevm.startPrank(address(0)); hevm.expectRevert("cannot skip last L1 message"); - rollup.commitBatch(0, batchHeader0, chunks, bitmap); + rollup.commitBatch(0, batchHeader0, chunks, bitmap, 0); hevm.stopPrank(); // num txs less than num L1 msgs, revert @@ -148,7 +151,7 @@ contract ScrollChainTest is DSTestPlus { chunks[0] = chunk0; hevm.startPrank(address(0)); hevm.expectRevert("num txs less than num L1 msgs"); - rollup.commitBatch(0, batchHeader0, chunks, bitmap); + rollup.commitBatch(0, batchHeader0, chunks, bitmap, 0); hevm.stopPrank(); // incomplete l2 transaction data, revert @@ -157,7 +160,7 @@ contract ScrollChainTest is DSTestPlus { chunks[0] = chunk0; hevm.startPrank(address(0)); hevm.expectRevert("incomplete l2 transaction data"); - rollup.commitBatch(0, batchHeader0, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader0, chunks, new bytes(0), 0); hevm.stopPrank(); // commit batch with one chunk, no tx, correctly @@ -165,14 +168,14 @@ contract ScrollChainTest is DSTestPlus { chunk0[0] = bytes1(uint8(1)); // one block in this chunk chunks[0] = chunk0; hevm.startPrank(address(0)); - rollup.commitBatch(0, batchHeader0, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader0, chunks, new bytes(0), 0); hevm.stopPrank(); assertGt(uint256(rollup.committedBatches(1)), 0); // batch is already committed, revert hevm.startPrank(address(0)); hevm.expectRevert("batch already committed"); - rollup.commitBatch(0, batchHeader0, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader0, chunks, new bytes(0), 0); hevm.stopPrank(); } @@ -201,7 +204,7 @@ contract ScrollChainTest is DSTestPlus { chunk0[0] = bytes1(uint8(1)); // one block in this chunk chunks[0] = chunk0; hevm.startPrank(address(0)); - rollup.commitBatch(0, batchHeader0, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader0, chunks, new bytes(0), 0); hevm.stopPrank(); assertGt(uint256(rollup.committedBatches(1)), 0); @@ -348,7 +351,7 @@ contract ScrollChainTest is DSTestPlus { hevm.startPrank(address(0)); hevm.expectEmit(true, true, false, true); emit CommitBatch(1, bytes32(0x00847173b29b238cf319cde79512b7c213e5a8b4138daa7051914c4592b6dfc7)); - rollup.commitBatch(0, batchHeader0, chunks, bitmap); + rollup.commitBatch(0, batchHeader0, chunks, bitmap, 0); hevm.stopPrank(); assertBoolEq(rollup.isBatchFinalized(1), false); bytes32 batchHash1 = rollup.committedBatches(1); @@ -475,19 +478,19 @@ contract ScrollChainTest is DSTestPlus { rollup.updateMaxNumTxInChunk(2); // 3 - 1 hevm.startPrank(address(0)); hevm.expectRevert("too many txs in one chunk"); - rollup.commitBatch(0, batchHeader1, chunks, bitmap); // first chunk with too many txs + rollup.commitBatch(0, batchHeader1, chunks, bitmap, 0); // first chunk with too many txs hevm.stopPrank(); rollup.updateMaxNumTxInChunk(185); // 5+10+300 - 2 - 127 hevm.startPrank(address(0)); hevm.expectRevert("too many txs in one chunk"); - rollup.commitBatch(0, batchHeader1, chunks, bitmap); // second chunk with too many txs + rollup.commitBatch(0, batchHeader1, chunks, bitmap, 0); // second chunk with too many txs hevm.stopPrank(); rollup.updateMaxNumTxInChunk(186); hevm.startPrank(address(0)); hevm.expectEmit(true, true, false, true); emit CommitBatch(2, bytes32(0x03a9cdcb9d582251acf60937db006ec99f3505fd4751b7c1f92c9a8ef413e873)); - rollup.commitBatch(0, batchHeader1, chunks, bitmap); + rollup.commitBatch(0, batchHeader1, chunks, bitmap, 0); hevm.stopPrank(); assertBoolEq(rollup.isBatchFinalized(2), false); bytes32 batchHash2 = rollup.committedBatches(2); @@ -558,7 +561,7 @@ contract ScrollChainTest is DSTestPlus { chunk0[0] = bytes1(uint8(1)); // one block in this chunk chunks[0] = chunk0; hevm.startPrank(address(0)); - rollup.commitBatch(0, batchHeader0, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader0, chunks, new bytes(0), 0); hevm.stopPrank(); bytes memory batchHeader1 = new bytes(89); @@ -573,7 +576,7 @@ contract ScrollChainTest is DSTestPlus { // commit another batch hevm.startPrank(address(0)); - rollup.commitBatch(0, batchHeader1, chunks, new bytes(0)); + rollup.commitBatch(0, batchHeader1, chunks, new bytes(0), 0); hevm.stopPrank(); // count must be nonzero, revert @@ -678,7 +681,7 @@ contract ScrollChainTest is DSTestPlus { hevm.startPrank(address(0)); hevm.expectRevert("Pausable: paused"); - rollup.commitBatch(0, new bytes(0), new bytes[](0), new bytes(0)); + rollup.commitBatch(0, new bytes(0), new bytes[](0), new bytes(0), 0); hevm.expectRevert("Pausable: paused"); rollup.finalizeBatchWithProof(new bytes(0), bytes32(0), bytes32(0), bytes32(0), new bytes(0)); hevm.stopPrank();