diff --git a/scripts/foundry/InitializeL1ScrollOwner.s.sol b/scripts/foundry/InitializeL1ScrollOwner.s.sol index b2af8e9d..e6f454fa 100644 --- a/scripts/foundry/InitializeL1ScrollOwner.s.sol +++ b/scripts/foundry/InitializeL1ScrollOwner.s.sol @@ -138,11 +138,11 @@ contract InitializeL1ScrollOwner is Script { bytes4[] memory _selectors; // no delay, scroll multisig and emergency multisig - _selectors = new bytes4[](4); - _selectors[0] = ScrollChain.revertBatch.selector; - _selectors[1] = ScrollChain.removeSequencer.selector; - _selectors[2] = ScrollChain.removeProver.selector; - _selectors[3] = ScrollChain.setPause.selector; + _selectors = new bytes4[](3); + // _selectors[0] = ScrollChain.revertBatch.selector; + _selectors[0] = ScrollChain.removeSequencer.selector; + _selectors[1] = ScrollChain.removeProver.selector; + _selectors[2] = ScrollChain.setPause.selector; owner.updateAccess(L1_SCROLL_CHAIN_PROXY_ADDR, _selectors, SCROLL_MULTISIG_NO_DELAY_ROLE, true); owner.updateAccess(L1_SCROLL_CHAIN_PROXY_ADDR, _selectors, EMERGENCY_MULTISIG_NO_DELAY_ROLE, true); @@ -154,7 +154,7 @@ contract InitializeL1ScrollOwner is Script { // delay 7 day, scroll multisig _selectors = new bytes4[](1); - _selectors[0] = ScrollChain.updateMaxNumTxInChunk.selector; + // _selectors[0] = ScrollChain.updateMaxNumTxInChunk.selector; owner.updateAccess(L1_SCROLL_CHAIN_PROXY_ADDR, _selectors, TIMELOCK_7DAY_DELAY_ROLE, true); } diff --git a/src/L1/rollup/IL1MessageQueue.sol b/src/L1/rollup/IL1MessageQueue.sol index 422e11bc..200e89d5 100644 --- a/src/L1/rollup/IL1MessageQueue.sol +++ b/src/L1/rollup/IL1MessageQueue.sol @@ -62,6 +62,12 @@ interface IL1MessageQueue { * Public View Functions * *************************/ + /// @notice The enqueue timestamp of first un-inclusion message. + function getFirstPendingMessageTimestamp() external view returns (uint256); + + /// @notice The enqueue timestamp of first unfinalized message. + function getFirstUnfinalizedMessageTimestamp() external view returns (uint256); + /// @notice The start index of all pending inclusion messages. function pendingQueueIndex() external view returns (uint256); @@ -77,6 +83,10 @@ interface IL1MessageQueue { /// @param queueIndex The index to query. function getCrossDomainMessage(uint256 queueIndex) external view returns (bytes32); + /// @notice Return the message enqueue timestamp of in `queueIndex`. + /// @param queueIndex The index to query. + function getMessageTimestamp(uint256 queueIndex) external view returns (uint256); + /// @notice Return the amount of ETH should pay for cross domain message. /// @param gasLimit Gas limit required to complete the message relay on L2. function estimateCrossDomainMessageFee(uint256 gasLimit) external view returns (uint256); @@ -140,24 +150,8 @@ interface IL1MessageQueue { /// @notice Pop messages from queue. /// - /// @dev We can pop at most 256 messages each time. And if the message is not skipped, - /// the corresponding entry will be cleared. - /// - /// @param startIndex The start index to pop. /// @param count The number of messages to pop. - /// @param skippedBitmap A bitmap indicates whether a message is skipped. - function popCrossDomainMessage( - uint256 startIndex, - uint256 count, - uint256 skippedBitmap - ) external; - - /// @notice Reset status of popped messages. - /// - /// @dev We can only reset unfinalized popped messages. - /// - /// @param startIndex The start index to reset. - function resetPoppedCrossDomainMessage(uint256 startIndex) external; + function popCrossDomainMessage(uint256 count) external; /// @notice Finalize status of popped messages. /// @param newFinalizedQueueIndexPlusOne The index of message to finalize plus one. diff --git a/src/L1/rollup/IL1MessageQueueWithGasPriceOracle.sol b/src/L1/rollup/IL1MessageQueueWithGasPriceOracle.sol index 61010443..c49385dc 100644 --- a/src/L1/rollup/IL1MessageQueueWithGasPriceOracle.sol +++ b/src/L1/rollup/IL1MessageQueueWithGasPriceOracle.sol @@ -9,22 +9,10 @@ interface IL1MessageQueueWithGasPriceOracle is IL1MessageQueue { * Events * **********/ - /// @notice Emitted when owner updates whitelist checker contract. - /// @param _oldWhitelistChecker The address of old whitelist checker contract. - /// @param _newWhitelistChecker The address of new whitelist checker contract. - event UpdateWhitelistChecker(address indexed _oldWhitelistChecker, address indexed _newWhitelistChecker); - - /// @notice Emitted when current l2 base fee is updated. - /// @param oldL2BaseFee The original l2 base fee before update. - /// @param newL2BaseFee The current l2 base fee updated. - event UpdateL2BaseFee(uint256 oldL2BaseFee, uint256 newL2BaseFee); - - /********** - * Errors * - **********/ - - /// @dev Thrown when the caller is not whitelisted. - error ErrorNotWhitelistedSender(); + /// @notice Emitted when current l2 base fee parameters are updated. + /// @param overhead The value of overhead. + /// @param scalar The value of scalar to `block.basefee`. + event UpdateL2BaseFeeParameters(uint256 overhead, uint256 scalar); /************************* * Public View Functions * @@ -32,7 +20,4 @@ interface IL1MessageQueueWithGasPriceOracle is IL1MessageQueue { /// @notice Return the latest known l2 base fee. function l2BaseFee() external view returns (uint256); - - /// @notice Return the address of whitelist checker contract. - function whitelistChecker() external view returns (address); } diff --git a/src/L1/rollup/IScrollChain.sol b/src/L1/rollup/IScrollChain.sol index 95f48da1..6a17a3fc 100644 --- a/src/L1/rollup/IScrollChain.sol +++ b/src/L1/rollup/IScrollChain.sol @@ -138,19 +138,6 @@ interface IScrollChain { * Public Mutating Functions * *****************************/ - /// @notice Commit a batch of transactions on layer 1. - /// - /// @param version The version of current batch. - /// @param parentBatchHeader The header of parent batch, see the comments of `BatchHeaderV0Codec`. - /// @param chunks The list of encoded chunks, see the comments of `ChunkCodec`. - /// @param skippedL1MessageBitmap The bitmap indicates whether each L1 message is skipped or not. - function commitBatch( - uint8 version, - bytes calldata parentBatchHeader, - bytes[] memory chunks, - bytes calldata skippedL1MessageBitmap - ) external; - /// @notice Commit a batch of transactions on layer 1 with blob data proof. /// /// @dev Memory layout of `blobDataProof`: @@ -171,40 +158,28 @@ interface IScrollChain { bytes calldata blobDataProof ) external; - /// @notice Revert pending batches. - /// @dev one can only revert unfinalized batches. - /// @param firstBatchHeader The header of first batch to revert, see the encoding in comments of `commitBatch`. - /// @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. + /// @notice Commit a batch of transactions on layer 1 with blob data proof. /// /// @dev Memory layout of `blobDataProof`: /// | z | y | kzg_commitment | kzg_proof | /// |---------|---------|----------------|-----------| /// | bytes32 | bytes32 | bytes48 | bytes48 | /// - /// @param batchHeader The header of current batch, see the encoding in comments of `commitBatch. - /// @param prevStateRoot The state root of parent batch. - /// @param postStateRoot The state root of current batch. - /// @param withdrawRoot The withdraw trie root of current batch. + /// @param version The version of current batch. + /// @param parentBatchHeader The header of parent batch, see the comments of `BatchHeaderV0Codec`. + /// @param chunks The list of encoded chunks, see the comments of `ChunkCodec`. /// @param blobDataProof The proof for blob data. - /// @param aggrProof The aggregation proof for current batch. - function finalizeBatchWithProof4844( - bytes calldata batchHeader, - bytes32 prevStateRoot, - bytes32 postStateRoot, - bytes32 withdrawRoot, - bytes calldata blobDataProof, - bytes calldata aggrProof + function commitBatchWithBlobProof( + uint8 version, + bytes calldata parentBatchHeader, + bytes[] memory chunks, + bytes calldata blobDataProof ) 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 postStateRoot The state root after current bundle. - /// @param withdrawRoot The withdraw trie root after current batch. + /// @param withdrawRoot The withdraw trie root after current bundle. /// @param aggrProof The aggregation proof for current bundle. function finalizeBundleWithProof( bytes calldata batchHeader, @@ -216,7 +191,7 @@ interface IScrollChain { /// @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 withdrawRoot The withdraw trie root after current bundle. /// @param teeProof The tee proof for current bundle. function finalizeBundleWithTeeProof( bytes calldata batchHeader, @@ -224,4 +199,36 @@ interface IScrollChain { bytes32 withdrawRoot, bytes calldata teeProof ) external; + + /// @param The struct for batch committing. + /// @param version The version of current batch. + /// @param parentBatchHeader The header of parent batch, see the comments of `BatchHeaderV0Codec`. + /// @param chunks The list of encoded chunks, see the comments of `ChunkCodec`. + /// @param blobDataProof The proof for blob data. + struct CommitStruct { + uint8 version; + bytes parentBatchHeader; + bytes[] chunks; + bytes blobDataProof; + } + + /// @param The struct for batch finalization. + /// @param batchHeader The header of current batch, see the encoding in comments of `commitBatch`. + /// @param postStateRoot The state root after current batch. + /// @param withdrawRoot The withdraw trie root after current batch. + /// @param zkProof The zk proof for current batch (single-batch bundle). + /// @param teeProof The tee proof for current batch (single-batch bundle). + struct FinalizeStruct { + bytes batchHeader; + bytes32 postStateRoot; + bytes32 withdrawRoot; + bytes zkProof; + bytes teeProof; + } + + /// @notice Commit a batch of transactions on layer 1 with blob data proof and finalize it. + /// @param commitStruct The data needed for commit. + /// @param finalizeStruct The data needed for finalize. + function commitAndFinalizeBatch(CommitStruct calldata commitStruct, FinalizeStruct calldata finalizeStruct) + external; } diff --git a/src/L1/rollup/L1MessageQueue.sol b/src/L1/rollup/L1MessageQueue.sol index 9f015102..d96a1faf 100644 --- a/src/L1/rollup/L1MessageQueue.sol +++ b/src/L1/rollup/L1MessageQueue.sol @@ -20,6 +20,9 @@ import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol" contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue { using BitMapsUpgradeable for BitMapsUpgradeable.BitMap; + /// @notice Emitted when the number of messages popped exceed queue length. + error ErrorPopMoreThanQueued(); + /************* * Constants * *************/ @@ -67,6 +70,10 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue { /// @inheritdoc IL1MessageQueue uint256 public nextUnfinalizedQueueIndex; + /// @dev Mapping from message queue index to enqueue timestamp. + mapping(uint256 => uint256) private enqueueTimestamp; + + // todo change gap slots /// @dev The storage slots for future usage. uint256[40] private __gap; @@ -138,6 +145,22 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue { * Public View Functions * *************************/ + /// @inheritdoc IL1MessageQueue + function getFirstPendingMessageTimestamp() external view returns (uint256) { + uint256 length = messageQueue.length; + uint256 index = pendingQueueIndex; + if (index == length) return block.timestamp; + else return enqueueTimestamp[index]; + } + + /// @inheritdoc IL1MessageQueue + function getFirstUnfinalizedMessageTimestamp() external view returns (uint256) { + uint256 length = messageQueue.length; + uint256 index = nextUnfinalizedQueueIndex; + if (index == length) return block.timestamp; + else return enqueueTimestamp[index]; + } + /// @inheritdoc IL1MessageQueue function nextCrossDomainMessageIndex() external view returns (uint256) { return messageQueue.length; @@ -148,6 +171,11 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue { return messageQueue[_queueIndex]; } + /// @inheritdoc IL1MessageQueue + function getMessageTimestamp(uint256 _queueIndex) external view returns (uint256) { + return enqueueTimestamp[_queueIndex]; + } + /// @inheritdoc IL1MessageQueue function estimateCrossDomainMessageFee(uint256 _gasLimit) external view virtual override returns (uint256) { address _oracle = gasOracle; @@ -347,57 +375,15 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue { } /// @inheritdoc IL1MessageQueue - function popCrossDomainMessage( - uint256 _startIndex, - uint256 _count, - uint256 _skippedBitmap - ) external override onlyScrollChain { - require(_count <= 256, "pop too many messages"); - require(pendingQueueIndex == _startIndex, "start index mismatch"); - + function popCrossDomainMessage(uint256 count) external override onlyScrollChain { unchecked { - // clear extra bits in `_skippedBitmap`, and if _count = 256, it's designed to overflow. - uint256 mask = (1 << _count) - 1; - _skippedBitmap &= mask; - - uint256 bucket = _startIndex >> 8; - uint256 offset = _startIndex & 0xff; - skippedMessageBitmap[bucket] |= _skippedBitmap << offset; - if (offset + _count > 256) { - skippedMessageBitmap[bucket + 1] = _skippedBitmap >> (256 - offset); - } + uint256 startIndex = pendingQueueIndex; + uint256 cachedPendingQueueIndex = startIndex + count; + uint256 length = messageQueue.length; + if (cachedPendingQueueIndex > length) revert ErrorPopMoreThanQueued(); - pendingQueueIndex = _startIndex + _count; + pendingQueueIndex = cachedPendingQueueIndex; } - - emit DequeueTransaction(_startIndex, _count, _skippedBitmap); - } - - /// @inheritdoc IL1MessageQueue - /// @dev Caller should make sure `_startIndex < pendingQueueIndex` to reduce unnecessary contract call. - function resetPoppedCrossDomainMessage(uint256 _startIndex) external override onlyScrollChain { - uint256 cachedPendingQueueIndex = pendingQueueIndex; - if (_startIndex == cachedPendingQueueIndex) return; - - require(_startIndex >= nextUnfinalizedQueueIndex, "reset finalized messages"); - require(_startIndex < cachedPendingQueueIndex, "reset pending messages"); - - unchecked { - uint256 count = cachedPendingQueueIndex - _startIndex; - uint256 bucket = _startIndex >> 8; - uint256 offset = _startIndex & 0xff; - skippedMessageBitmap[bucket] &= (1 << offset) - 1; - uint256 numResetMessages = 256 - offset; - while (numResetMessages < count) { - bucket += 1; - uint256 bitmap = skippedMessageBitmap[bucket]; - if (bitmap > 0) skippedMessageBitmap[bucket] = 0; - numResetMessages += 256; - } - } - - pendingQueueIndex = _startIndex; - emit ResetDequeuedTransaction(_startIndex); } /// @inheritdoc IL1MessageQueue @@ -472,6 +458,7 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue { // compute transaction hash uint256 _queueIndex = messageQueue.length; bytes32 _hash = computeTransactionHash(_sender, _queueIndex, _value, _target, _gasLimit, _data); + enqueueTimestamp[_queueIndex] = block.timestamp; messageQueue.push(_hash); // emit event diff --git a/src/L1/rollup/L1MessageQueueWithGasPriceOracle.sol b/src/L1/rollup/L1MessageQueueWithGasPriceOracle.sol index dbe49512..46974ced 100644 --- a/src/L1/rollup/L1MessageQueueWithGasPriceOracle.sol +++ b/src/L1/rollup/L1MessageQueueWithGasPriceOracle.sol @@ -20,15 +20,30 @@ contract L1MessageQueueWithGasPriceOracle is L1MessageQueue, IL1MessageQueueWith /// @notice The appropriate intrinsic gas for each byte. uint256 private constant APPROPRIATE_INTRINSIC_GAS_PER_BYTE = 16; + uint256 private constant PRECISION = 1e18; + + /*********** + * Structs * + ***********/ + + struct L2BaseFeeParameters { + uint128 overhead; + uint128 scalar; + } + /************* * Variables * *************/ - /// @inheritdoc IL1MessageQueueWithGasPriceOracle - uint256 public override l2BaseFee; + /// @dev The storage slot used as `l2BaseFee`, which is deprecated now. + /// @custom:deprecated + uint256 private __deprecated_l2BaseFee; - /// @inheritdoc IL1MessageQueueWithGasPriceOracle - address public override whitelistChecker; + /// @dev The storage slot used as `whitelistChecker`, which is deprecated now. + /// @custom:deprecated + address private __deprecated__whitelistChecker; + + L2BaseFeeParameters public l2BaseFeeParameters; /*************** * Constructor * @@ -47,8 +62,8 @@ contract L1MessageQueueWithGasPriceOracle is L1MessageQueue, IL1MessageQueueWith /// @notice Initialize the storage of L1MessageQueueWithGasPriceOracle. function initializeV2() external reinitializer(2) { - l2BaseFee = IL2GasPriceOracle(gasOracle).l2BaseFee(); - whitelistChecker = IL2GasPriceOracle(gasOracle).whitelist(); + // __deprecated_l2BaseFee = IL2GasPriceOracle(gasOracle).l2BaseFee(); + // whitelistChecker = IL2GasPriceOracle(gasOracle).whitelist(); } function initializeV3() external reinitializer(3) { @@ -59,6 +74,15 @@ contract L1MessageQueueWithGasPriceOracle is L1MessageQueue, IL1MessageQueueWith * Public View Functions * *************************/ + /// @inheritdoc IL1MessageQueueWithGasPriceOracle + function l2BaseFee() public view returns (uint256) { + L2BaseFeeParameters memory parameters = l2BaseFeeParameters; + // this is unlikely to happen, use unchecked here + unchecked { + return (block.basefee * parameters.scalar) / PRECISION + parameters.overhead; + } + } + /// @inheritdoc IL1MessageQueue function estimateCrossDomainMessageFee(uint256 _gasLimit) external @@ -66,7 +90,7 @@ contract L1MessageQueueWithGasPriceOracle is L1MessageQueue, IL1MessageQueueWith override(IL1MessageQueue, L1MessageQueue) returns (uint256) { - return _gasLimit * l2BaseFee; + return _gasLimit * l2BaseFee(); } /// @inheritdoc IL1MessageQueue @@ -82,33 +106,17 @@ contract L1MessageQueueWithGasPriceOracle is L1MessageQueue, IL1MessageQueueWith } } - /***************************** - * Public Mutating Functions * - *****************************/ - - /// @notice Allows whitelistCheckered caller to modify the l2 base fee. - /// @param _newL2BaseFee The new l2 base fee. - function setL2BaseFee(uint256 _newL2BaseFee) external { - if (!IWhitelist(whitelistChecker).isSenderAllowed(_msgSender())) { - revert ErrorNotWhitelistedSender(); - } - - uint256 _oldL2BaseFee = l2BaseFee; - l2BaseFee = _newL2BaseFee; - - emit UpdateL2BaseFee(_oldL2BaseFee, _newL2BaseFee); - } - /************************ * Restricted Functions * ************************/ /// @notice Update whitelist checker contract. /// @dev This function can only called by contract owner. - /// @param _newWhitelistChecker The address of new whitelist checker contract. - function updateWhitelistChecker(address _newWhitelistChecker) external onlyOwner { - address _oldWhitelistChecker = whitelistChecker; - whitelistChecker = _newWhitelistChecker; - emit UpdateWhitelistChecker(_oldWhitelistChecker, _newWhitelistChecker); + /// @param overhead The address of new whitelist checker contract. + /// @param scalar The address of new whitelist checker contract. + function updateL2BaseFeeParameters(uint128 overhead, uint128 scalar) external onlyOwner { + l2BaseFeeParameters = L2BaseFeeParameters(overhead, scalar); + + emit UpdateL2BaseFeeParameters(overhead, scalar); } } diff --git a/src/L1/rollup/ScrollChain.sol b/src/L1/rollup/ScrollChain.sol index 39cacc1a..2d2f94c5 100644 --- a/src/L1/rollup/ScrollChain.sol +++ b/src/L1/rollup/ScrollChain.sol @@ -100,9 +100,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @dev Thrown when the given state root is zero. error ErrorStateRootIsZero(); - /// @dev Thrown when a chunk contains too many transactions. - error ErrorTooManyTxsInOneChunk(); - /// @dev Thrown when the precompile output is incorrect. error ErrorUnexpectedPointEvaluationPrecompileOutput(); @@ -130,6 +127,14 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @dev Thrown when the proof type mask is greater than 3. error ErrorInvalidProofTypeMask(); + error ErrorCannotDowngradeVersion(); + + error ErrorNotInEnforcedBatchMode(); + + error ErrorNotIncludeAllExpiredMessages(); + + error ErrorMessageNotFinalizedBeforeMaxDelay(); + /************* * Constants * *************/ @@ -171,6 +176,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { * Structs * ***********/ + /// @notice Struct for unresolved state mismatch. /// @param proofType The type of proof for the state roots. /// @param batchIndex The index of mismatched batch. /// @param stateRoot The mismatched state root. @@ -182,6 +188,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { bytes32 withdrawRoot; } + /// @notice Struct for bundle size. /// @param bundleSize The number of batches in each bundle in current setting. /// @param batchIndex The start batch index for current setting. struct BundleSizeStruct { @@ -189,6 +196,23 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { uint128 batchIndex; } + /// @dev Assume one message is enqueued at timestamp `t`. The `maxInclusionDelay` means if this + /// message wasn't included by timestamp `t+maxInclusionDelay` in batch committing, the sequencer + /// cannot commit any batches unless it includes all expired messages before any L2 transactions. + /// The `maxFinalizeDelay` means if this message wasn't finalized by timestamp `t+maxFinalizeDelay`, + /// the prover cannot finalize any batches. The `maxDelayEnterEnforcedMode` means if this message + /// wasn't finalized by timestamp `t+maxDelayEnterEnforcedMode`, we will enter enforced mode. Anyone + /// can commit and finalize a batch without permission. + /// So if the sequencer or prover encountered some recoverable problem, we must resolve it between + /// timestamp `[t+maxFinalizeDelay, t+maxDelayEnterEnforcedMode]`. + struct EnforcedBatchParameters { + uint64 maxInclusionDelay; + uint64 maxFinalizeDelay; + uint64 maxDelayEnterEnforcedMode; + uint56 lastCommittedBatchIndex; + bool enforcedModeEnabled; + } + /************* * Variables * *************/ @@ -245,6 +269,10 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// should be satisfied: `(y - b[i]) % s[i] = 0` and `y - x + 1 = s[i]`. BundleSizeStruct[] public bundleSize; + /// @notice The parameters related to enforced batch feature. + /// @dev The value of `lastCommittedBatchIndex` will be initialized in the first batch commit after upgrade. + EnforcedBatchParameters public enforcedBatchParameters; + /********************** * Function Modifiers * **********************/ @@ -268,6 +296,11 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { _; } + modifier whenEnforcedBatchNotEnable() { + if (enforcedBatchParameters.enforcedModeEnabled) revert ErrorNotInEnforcedBatchMode(); + _; + } + /*************** * Constructor * ***************/ @@ -424,281 +457,33 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { emit FinalizeBatch(0, _batchHash, _stateRoot, bytes32(0)); } - /// @inheritdoc IScrollChain - function commitBatch( - uint8 _version, - bytes calldata _parentBatchHeader, - bytes[] memory _chunks, - bytes calldata _skippedL1MessageBitmap - ) external override OnlySequencer whenNotPaused { - (bytes32 _parentBatchHash, uint256 _batchIndex, uint256 _totalL1MessagesPoppedOverall) = _beforeCommitBatch( - _parentBatchHeader, - _chunks - ); - - bytes32 _batchHash; - uint256 batchPtr; - bytes32 _dataHash; - uint256 _totalL1MessagesPoppedInBatch; - if (1 <= _version && _version <= 2) { - // versions 1 and 2 both use ChunkCodecV1 and BatchHeaderV1Codec, - // but they use different blob encoding and different verifiers. - (_dataHash, _totalL1MessagesPoppedInBatch) = _commitChunksV1( - _totalL1MessagesPoppedOverall, - _chunks, - _skippedL1MessageBitmap - ); - assembly { - batchPtr := mload(0x40) - _totalL1MessagesPoppedOverall := add(_totalL1MessagesPoppedOverall, _totalL1MessagesPoppedInBatch) - } - - // store entries, the order matters - // Some are using `BatchHeaderV0Codec`, see comments of `BatchHeaderV1Codec`. - BatchHeaderV0Codec.storeVersion(batchPtr, _version); - BatchHeaderV0Codec.storeBatchIndex(batchPtr, _batchIndex); - BatchHeaderV0Codec.storeL1MessagePopped(batchPtr, _totalL1MessagesPoppedInBatch); - BatchHeaderV0Codec.storeTotalL1MessagePopped(batchPtr, _totalL1MessagesPoppedOverall); - BatchHeaderV0Codec.storeDataHash(batchPtr, _dataHash); - BatchHeaderV1Codec.storeBlobVersionedHash(batchPtr, _getBlobVersionedHash()); - BatchHeaderV1Codec.storeParentBatchHash(batchPtr, _parentBatchHash); - BatchHeaderV1Codec.storeSkippedBitmap(batchPtr, _skippedL1MessageBitmap); - // compute batch hash, V1 and V2 has same code as V0 - _batchHash = BatchHeaderV0Codec.computeBatchHash( - batchPtr, - BatchHeaderV1Codec.BATCH_HEADER_FIXED_LENGTH + _skippedL1MessageBitmap.length - ); - } else { - // we don't allow v0 and other versions - revert ErrorIncorrectBatchVersion(); - } - - // verify skippedL1MessageBitmap - _checkSkippedL1MessageBitmap( - _totalL1MessagesPoppedOverall, - _totalL1MessagesPoppedInBatch, - _skippedL1MessageBitmap, - false - ); - - _afterCommitBatch(_batchIndex, _batchHash); - } - /// @inheritdoc IScrollChain /// /// @dev This function will revert unless all V0/V1/V2 batches are finalized. This is because we start to /// pop L1 messages in `commitBatchWithBlobProof` but not in `commitBatch`. We also introduce `finalizedQueueIndex` /// in `L1MessageQueue`. If one of V0/V1/V2 batches not finalized, `L1MessageQueue.pendingQueueIndex` will not /// match `parentBatchHeader.totalL1MessagePopped` and thus revert. + /// + /// @dev `_skippedL1MessageBitmap` is no longer used, will remove in next version. function commitBatchWithBlobProof( uint8 _version, bytes calldata _parentBatchHeader, bytes[] memory _chunks, - bytes calldata _skippedL1MessageBitmap, + bytes calldata, /*_skippedL1MessageBitmap*/ bytes calldata _blobDataProof - ) external override OnlySequencer whenNotPaused { - if (_version <= 2) { - revert ErrorIncorrectBatchVersion(); - } - - // allocate memory of batch header and store entries if necessary, the order matters - // @note why store entries if necessary, to avoid stack overflow problem. - // The codes for `version`, `batchIndex`, `l1MessagePopped`, `totalL1MessagePopped` and `dataHash` - // are the same as `BatchHeaderV0Codec`. - // The codes for `blobVersionedHash`, and `parentBatchHash` are the same as `BatchHeaderV1Codec`. - uint256 batchPtr; - assembly { - batchPtr := mload(0x40) - // This is `BatchHeaderV3Codec.BATCH_HEADER_FIXED_LENGTH`, use `193` here to reduce code - // complexity. Be careful that the length may changed in future versions. - mstore(0x40, add(batchPtr, 193)) - } - BatchHeaderV0Codec.storeVersion(batchPtr, _version); - - (bytes32 _parentBatchHash, uint256 _batchIndex, uint256 _totalL1MessagesPoppedOverall) = _beforeCommitBatch( - _parentBatchHeader, - _chunks - ); - BatchHeaderV0Codec.storeBatchIndex(batchPtr, _batchIndex); - - // versions 2 and 3 both use ChunkCodecV1 - (bytes32 _dataHash, uint256 _totalL1MessagesPoppedInBatch) = _commitChunksV1( - _totalL1MessagesPoppedOverall, - _chunks, - _skippedL1MessageBitmap - ); - unchecked { - _totalL1MessagesPoppedOverall += _totalL1MessagesPoppedInBatch; - } - - // verify skippedL1MessageBitmap - _checkSkippedL1MessageBitmap( - _totalL1MessagesPoppedOverall, - _totalL1MessagesPoppedInBatch, - _skippedL1MessageBitmap, - true - ); - BatchHeaderV0Codec.storeL1MessagePopped(batchPtr, _totalL1MessagesPoppedInBatch); - BatchHeaderV0Codec.storeTotalL1MessagePopped(batchPtr, _totalL1MessagesPoppedOverall); - BatchHeaderV0Codec.storeDataHash(batchPtr, _dataHash); - - // verify blob versioned hash - bytes32 _blobVersionedHash = _getBlobVersionedHash(); - _checkBlobVersionedHash(_blobVersionedHash, _blobDataProof); - BatchHeaderV1Codec.storeBlobVersionedHash(batchPtr, _blobVersionedHash); - BatchHeaderV1Codec.storeParentBatchHash(batchPtr, _parentBatchHash); - - uint256 lastBlockTimestamp; - { - bytes memory lastChunk = _chunks[_chunks.length - 1]; - lastBlockTimestamp = ChunkCodecV1.getLastBlockTimestamp(lastChunk); - } - BatchHeaderV3Codec.storeLastBlockTimestamp(batchPtr, lastBlockTimestamp); - BatchHeaderV3Codec.storeBlobDataProof(batchPtr, _blobDataProof); - - // compute batch hash, V3 has same code as V0 - bytes32 _batchHash = BatchHeaderV0Codec.computeBatchHash( - batchPtr, - BatchHeaderV3Codec.BATCH_HEADER_FIXED_LENGTH - ); - - _afterCommitBatch(_batchIndex, _batchHash); - } - - /// @inheritdoc IScrollChain - /// @dev If the owner want to revert a sequence of batches by sending multiple transactions, - /// make sure to revert recent batches first. - function revertBatch(bytes calldata _firstBatchHeader, bytes calldata _lastBatchHeader) external onlyOwner { - ( - uint256 firstBatchPtr, - , - uint256 _firstBatchIndex, - uint256 _totalL1MessagesPoppedOverallFirstBatch - ) = _loadBatchHeader(_firstBatchHeader); - (, , uint256 _lastBatchIndex, ) = _loadBatchHeader(_lastBatchHeader); - if (_firstBatchIndex > _lastBatchIndex) revert ErrorRevertZeroBatches(); - - // make sure no gap is left when reverting from the ending to the beginning. - if (committedBatches[_lastBatchIndex + 1] != bytes32(0)) revert ErrorRevertNotStartFromEnd(); - - // check finalization - if (_firstBatchIndex <= lastFinalizedBatchIndex()) revert ErrorRevertFinalizedBatch(); - // check unresolved state - if (_firstBatchIndex <= unresolvedState.batchIndex) revert ErrorRevertUnresolvedState(); - - // actual revert - for (uint256 _batchIndex = _lastBatchIndex; _batchIndex >= _firstBatchIndex; --_batchIndex) { - bytes32 _batchHash = committedBatches[_batchIndex]; - committedBatches[_batchIndex] = bytes32(0); - - emit RevertBatch(_batchIndex, _batchHash); - } - - // `getL1MessagePopped` codes are the same in V0, V1, V2, V3 - uint256 l1MessagePoppedFirstBatch = BatchHeaderV0Codec.getL1MessagePopped(firstBatchPtr); - unchecked { - IL1MessageQueue(messageQueue).resetPoppedCrossDomainMessage( - _totalL1MessagesPoppedOverallFirstBatch - l1MessagePoppedFirstBatch - ); - } + ) external override { + commitBatchWithBlobProof(_version, _parentBatchHeader, _chunks, _blobDataProof); } - /* 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, - bytes32 _prevStateRoot, - bytes32 _postStateRoot, - bytes32 _withdrawRoot, - bytes calldata _aggrProof - ) external override OnlyProver whenNotPaused { - (uint256 batchPtr, bytes32 _batchHash, uint256 _batchIndex) = _beforeFinalizeBatch( - _batchHeader, - _postStateRoot - ); - - // compute public input hash - bytes32 _publicInputHash; - { - bytes32 _dataHash = BatchHeaderV0Codec.getDataHash(batchPtr); - bytes32 _prevStateRoot = finalizedStateRoots[_batchIndex - 1]; - _publicInputHash = keccak256( - abi.encodePacked(layer2ChainId, _prevStateRoot, _postStateRoot, _withdrawRoot, _dataHash) - ); - } - // verify batch - IRollupVerifier(verifier).verifyAggregateProof(0, _batchIndex, _aggrProof, _publicInputHash); - - // Pop finalized and non-skipped message from L1MessageQueue. - uint256 _totalL1MessagesPoppedOverall = BatchHeaderV0Codec.getTotalL1MessagePopped(batchPtr); - _popL1MessagesMemory( - BatchHeaderV0Codec.getSkippedBitmapPtr(batchPtr), - _totalL1MessagesPoppedOverall, - BatchHeaderV0Codec.getL1MessagePopped(batchPtr) - ); - - _afterFinalizeBatch(_totalL1MessagesPoppedOverall, _batchIndex, _batchHash, _postStateRoot, _withdrawRoot); - } - */ - - /* 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 - /// | z | y | kzg_commitment | kzg_proof | - /// |---------|---------|----------------|-----------| - /// | bytes32 | bytes32 | bytes48 | bytes48 | - /// ``` - function finalizeBatchWithProof4844( - bytes calldata _batchHeader, - bytes32 _prevStateRoot, - bytes32 _postStateRoot, - bytes32 _withdrawRoot, - bytes calldata _blobDataProof, - bytes calldata _aggrProof - ) external override OnlyProver whenNotPaused { - (uint256 batchPtr, bytes32 _batchHash, uint256 _batchIndex) = _beforeFinalizeBatch( - _batchHeader, - _postStateRoot - ); - - // compute public input hash - bytes32 _publicInputHash; - { - bytes32 _dataHash = BatchHeaderV0Codec.getDataHash(batchPtr); - bytes32 _blobVersionedHash = BatchHeaderV1Codec.getBlobVersionedHash(batchPtr); - bytes32 _prevStateRoot = finalizedStateRoots[_batchIndex - 1]; - // verify blob versioned hash - _checkBlobVersionedHash(_blobVersionedHash, _blobDataProof); - _publicInputHash = keccak256( - abi.encodePacked( - layer2ChainId, - _prevStateRoot, - _postStateRoot, - _withdrawRoot, - _dataHash, - _blobDataProof[0:64], - _blobVersionedHash - ) - ); - } - - // load version from batch header, it is always the first byte. - uint256 batchVersion = BatchHeaderV0Codec.getVersion(batchPtr); - // verify batch - IRollupVerifier(verifier).verifyAggregateProof(batchVersion, _batchIndex, _aggrProof, _publicInputHash); - - // Pop finalized and non-skipped message from L1MessageQueue. - uint256 _totalL1MessagesPoppedOverall = BatchHeaderV0Codec.getTotalL1MessagePopped(batchPtr); - _popL1MessagesMemory( - BatchHeaderV1Codec.getSkippedBitmapPtr(batchPtr), - _totalL1MessagesPoppedOverall, - BatchHeaderV0Codec.getL1MessagePopped(batchPtr) - ); - - _afterFinalizeBatch(_totalL1MessagesPoppedOverall, _batchIndex, _batchHash, _postStateRoot, _withdrawRoot); + function commitBatchWithBlobProof( + uint8 _version, + bytes calldata _parentBatchHeader, + bytes[] memory _chunks, + bytes calldata _blobDataProof + ) public override OnlySequencer whenEnforcedBatchNotEnable { + _commitBatchWithBlobProof(_version, _parentBatchHeader, _chunks, _blobDataProof); } - */ /// @inheritdoc IScrollChain function finalizeBundleWithProof( @@ -707,28 +492,8 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { bytes32 _withdrawRoot, bytes calldata _aggrProof ) external override OnlyProver whenNotPaused whenFinalizeNotPaused(ProofType.ZkProof) { - // verify bundle logic - (uint256 _batchIndex, bytes32 _batchHash, uint256 _totalL1MessagesPoppedOverall) = _verifyBundle( - ProofType.ZkProof, - _batchHeader, - _postStateRoot, - _withdrawRoot, - _aggrProof - ); - - // verify successfully, record state and emit event first - lastZkpVerifiedBatchIndex = _batchIndex; - emit VerifyBatchWithZkp(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); - - // post actions after bundle verification - _afterVerifyBundle( - ProofType.ZkProof, - _batchIndex, - _batchHash, - _postStateRoot, - _withdrawRoot, - _totalL1MessagesPoppedOverall - ); + _checkFinalizationAllowed(enforcedBatchParameters.maxFinalizeDelay); + _finalizeBundleWithZkProof(_batchHeader, _postStateRoot, _withdrawRoot, _aggrProof); } /// @inheritdoc IScrollChain @@ -738,27 +503,61 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { 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 + _checkFinalizationAllowed(enforcedBatchParameters.maxFinalizeDelay); + _finalizeBundleWithTeeProof(_batchHeader, _postStateRoot, _withdrawRoot, _teeProof); + } + + /// @inheritdoc IScrollChain + function commitAndFinalizeBatch(CommitStruct calldata commitStruct, FinalizeStruct calldata finalizeStruct) + external + { + // + EnforcedBatchParameters memory parameters = enforcedBatchParameters; + if (!parameters.enforcedModeEnabled) { + uint256 timestamp = IL1MessageQueue(messageQueue).getFirstUnfinalizedMessageTimestamp(); + if (timestamp > 0 && timestamp + parameters.maxDelayEnterEnforcedMode < block.timestamp) { + // explicit set enforce batch enable + parameters.enforcedModeEnabled = true; + // reset `lastCommittedBatchIndex` + uint256 zkpIndex = lastZkpVerifiedBatchIndex; + uint256 teeIndex = lastTeeVerifiedBatchIndex; + if (zkpIndex < teeIndex) { + parameters.lastCommittedBatchIndex = uint56(zkpIndex); + } else { + parameters.lastCommittedBatchIndex = uint56(teeIndex); + } + enforcedBatchParameters = parameters; + } else { + revert ErrorNotInEnforcedBatchMode(); + } + } + + // commit batch + bytes32 batchHash = _commitBatchWithBlobProof( + commitStruct.version, + commitStruct.parentBatchHeader, + commitStruct.chunks, + commitStruct.blobDataProof ); - // verify successfully, record state and emit event first - lastTeeVerifiedBatchIndex = _batchIndex; - emit VerifyBatchWithTee(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); + if (batchHash != keccak256(finalizeStruct.batchHeader)) { + revert ErrorIncorrectBatchHash(); + } - // post actions after bundle verification - _afterVerifyBundle( - ProofType.TeeProof, - _batchIndex, - _batchHash, - _postStateRoot, - _withdrawRoot, - _totalL1MessagesPoppedOverall + // finalize with zk proof + _finalizeBundleWithZkProof( + finalizeStruct.batchHeader, + finalizeStruct.postStateRoot, + finalizeStruct.withdrawRoot, + finalizeStruct.zkProof + ); + + // finalize with tee proof + _finalizeBundleWithTeeProof( + finalizeStruct.batchHeader, + finalizeStruct.postStateRoot, + finalizeStruct.withdrawRoot, + finalizeStruct.teeProof ); } @@ -864,15 +663,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { emit UpdateProver(_account, false); } - /// @notice Update the value of `maxNumTxInChunk`. - /// @param _maxNumTxInChunk The new value of `maxNumTxInChunk`. - function updateMaxNumTxInChunk(uint256 _maxNumTxInChunk) external onlyOwner { - uint256 _oldMaxNumTxInChunk = maxNumTxInChunk; - maxNumTxInChunk = _maxNumTxInChunk; - - emit UpdateMaxNumTxInChunk(_oldMaxNumTxInChunk, _maxNumTxInChunk); - } - /// @notice Pause the contract /// @param _status The pause status to update. function setPause(bool _status) external onlyOwner { @@ -883,6 +673,11 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } + /// @notice Exit from enforced batch mode. + function disableEnforcedBatch() external onlyOwner { + enforcedBatchParameters.enforcedModeEnabled = false; + } + /// @notice Update the bundle size /// @param size The new bundle size. /// @param batchIndex The start batch index for new bundle size. @@ -933,6 +728,145 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { * Internal Functions * **********************/ + function _commitBatchWithBlobProof( + uint8 _version, + bytes calldata _parentBatchHeader, + bytes[] memory _chunks, + bytes calldata _blobDataProof + ) internal returns (bytes32) { + // only accept version >= 3 + if (_version <= 3) revert ErrorIncorrectBatchVersion(); + + // allocate memory of batch header and store entries if necessary, the order matters + // @note why store entries if necessary, to avoid stack overflow problem. + // The codes for `version`, `batchIndex`, `l1MessagePopped`, `totalL1MessagePopped` and `dataHash` + // are the same as `BatchHeaderV0Codec`. + // The codes for `blobVersionedHash`, and `parentBatchHash` are the same as `BatchHeaderV1Codec`. + uint256 batchPtr = BatchHeaderV3Codec.alloc(); + BatchHeaderV0Codec.storeVersion(batchPtr, _version); + + EnforcedBatchParameters memory parameters = enforcedBatchParameters; + (bytes32 _parentBatchHash, uint256 _batchIndex, uint256 _totalL1MessagesPoppedOverall) = _beforeCommitBatch( + _version, + _parentBatchHeader, + _chunks, + parameters.lastCommittedBatchIndex + ); + BatchHeaderV0Codec.storeBatchIndex(batchPtr, _batchIndex); + + // versions 2 and 3 both use ChunkCodecV1 + (bytes32 _dataHash, uint256 _totalL1MessagesPoppedInBatch) = _commitChunksV1( + _totalL1MessagesPoppedOverall, + _chunks, + parameters.maxInclusionDelay + ); + unchecked { + _totalL1MessagesPoppedOverall += _totalL1MessagesPoppedInBatch; + } + + // pop messages + if (_totalL1MessagesPoppedInBatch > 0) { + IL1MessageQueue(messageQueue).popCrossDomainMessage(_totalL1MessagesPoppedInBatch); + } + BatchHeaderV0Codec.storeL1MessagePopped(batchPtr, _totalL1MessagesPoppedInBatch); + BatchHeaderV0Codec.storeTotalL1MessagePopped(batchPtr, _totalL1MessagesPoppedOverall); + BatchHeaderV0Codec.storeDataHash(batchPtr, _dataHash); + + // verify blob versioned hash + bytes32 _blobVersionedHash = _getBlobVersionedHash(); + _checkBlobVersionedHash(_blobVersionedHash, _blobDataProof); + BatchHeaderV1Codec.storeBlobVersionedHash(batchPtr, _blobVersionedHash); + BatchHeaderV1Codec.storeParentBatchHash(batchPtr, _parentBatchHash); + + uint256 lastBlockTimestamp; + { + bytes memory lastChunk = _chunks[_chunks.length - 1]; + lastBlockTimestamp = ChunkCodecV1.getLastBlockTimestamp(lastChunk); + } + BatchHeaderV3Codec.storeLastBlockTimestamp(batchPtr, lastBlockTimestamp); + BatchHeaderV3Codec.storeBlobDataProof(batchPtr, _blobDataProof); + + // compute batch hash, V3 has same code as V0 + bytes32 _batchHash = BatchHeaderV0Codec.computeBatchHash( + batchPtr, + BatchHeaderV3Codec.BATCH_HEADER_FIXED_LENGTH + ); + + // store state and emit event + enforcedBatchParameters.lastCommittedBatchIndex = uint56(_batchIndex); + committedBatches[_batchIndex] = _batchHash; + emit CommitBatch(_batchIndex, _batchHash); + + return _batchHash; + } + + function _finalizeBundleWithZkProof( + bytes calldata _batchHeader, + bytes32 _postStateRoot, + bytes32 _withdrawRoot, + bytes calldata _aggrProof + ) internal { + // verify bundle logic + (uint256 _batchIndex, bytes32 _batchHash, uint256 _totalL1MessagesPoppedOverall) = _verifyBundle( + ProofType.ZkProof, + _batchHeader, + _postStateRoot, + _withdrawRoot, + _aggrProof + ); + + // verify successfully, record state and emit event first + lastZkpVerifiedBatchIndex = _batchIndex; + emit VerifyBatchWithZkp(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); + + // post actions after bundle verification + _afterVerifyBundle( + ProofType.ZkProof, + _batchIndex, + _batchHash, + _postStateRoot, + _withdrawRoot, + _totalL1MessagesPoppedOverall + ); + } + + function _finalizeBundleWithTeeProof( + bytes calldata _batchHeader, + bytes32 _postStateRoot, + bytes32 _withdrawRoot, + bytes calldata _teeProof + ) internal { + // 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, + _postStateRoot, + _withdrawRoot, + _totalL1MessagesPoppedOverall + ); + } + + function _checkFinalizationAllowed(uint256 maxFinalizeDelay) private view { + uint256 timestamp = IL1MessageQueue(messageQueue).getFirstUnfinalizedMessageTimestamp(); + if (timestamp > 0 && timestamp + maxFinalizeDelay < block.timestamp) { + revert ErrorMessageNotFinalizedBeforeMaxDelay(); + } + } + /// @dev Internal function to enable proof types. /// @param mask The mask for new enabled proof types. function _enableProofTypes(uint256 mask) internal { @@ -948,10 +882,16 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @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. + /// @param lastCommittedBatchIndex The index of last committed batch. /// @return _parentBatchHash The batch hash of parent batch header. /// @return _batchIndex The index of current batch. /// @return _totalL1MessagesPoppedOverall The total number of L1 messages popped before current batch. - function _beforeCommitBatch(bytes calldata _parentBatchHeader, bytes[] memory _chunks) + function _beforeCommitBatch( + uint8 _version, + bytes calldata _parentBatchHeader, + bytes[] memory _chunks, + uint256 lastCommittedBatchIndex + ) private view returns ( @@ -962,20 +902,29 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { { // check whether the batch is empty if (_chunks.length == 0) revert ErrorBatchIsEmpty(); - (, _parentBatchHash, _batchIndex, _totalL1MessagesPoppedOverall) = _loadBatchHeader(_parentBatchHeader); + uint256 batchPtr; + (batchPtr, _parentBatchHash, _batchIndex, _totalL1MessagesPoppedOverall) = _loadBatchHeader(_parentBatchHeader); + // version should non-decreasing + if (BatchHeaderV0Codec.getVersion(batchPtr) > _version) revert ErrorCannotDowngradeVersion(); + unchecked { _batchIndex += 1; } - if (committedBatches[_batchIndex] != 0) revert ErrorBatchIsAlreadyCommitted(); - } - - /// @dev Internal function to do common checks after actual batch committing. - /// @param _batchIndex The index of current batch. - /// @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); + // Since the value of `lastCommittedBatchIndex` will be initialized in the first batch commit after upgrade, + // we won't use it until it is initialized. Instead, we will check `committedBatches[_batchIndex]`. + if (lastCommittedBatchIndex != 0) { + unchecked { + if (_batchIndex != lastCommittedBatchIndex + 1) { + revert ErrorBatchIsAlreadyCommitted(); + } + } + } else { + // This is only used when `lastCommittedBatchIndex` is uninitialized. Because, when enter enforced batch + // mode, it is not true and we are allowed to override committed but unfinalized batch. + if (committedBatches[_batchIndex] != 0) { + revert ErrorBatchIsAlreadyCommitted(); + } + } } /// @dev Internal function to verify bundle. @@ -1100,58 +1049,6 @@ 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. - /// @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) { - if (_postStateRoot == bytes32(0)) revert ErrorStateRootIsZero(); - - // compute batch hash and verify - (batchPtr, _batchHash, _batchIndex, ) = _loadBatchHeader(_batchHeader); - - // 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. - /// @param _batchHash The hash of current batch. - /// @param _postStateRoot The state root after current batch. - /// @param _withdrawRoot The withdraw trie root after current batch. - function _afterFinalizeBatch( - uint256 _totalL1MessagesPoppedOverall, - uint256 _batchIndex, - bytes32 _batchHash, - bytes32 _postStateRoot, - bytes32 _withdrawRoot - ) internal { - // check and update lastFinalizedBatchIndex - unchecked { - if (lastFinalizedBatchIndex + 1 != _batchIndex) revert ErrorIncorrectBatchIndex(); - lastFinalizedBatchIndex = _batchIndex; - } - - // record state root and withdraw root - finalizedStateRoots[_batchIndex] = _postStateRoot; - withdrawRoots[_batchIndex] = _withdrawRoot; - - // Pop finalized and non-skipped message from L1MessageQueue. - _finalizePoppedL1Messages(_totalL1MessagesPoppedOverall); - - emit FinalizeBatch(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot); - } - */ - /// @dev Internal function to check blob versioned hash. /// @param _blobVersionedHash The blob versioned hash to check. /// @param _blobDataProof The blob data proof used to verify the blob versioned hash. @@ -1167,32 +1064,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { if (result != BLS_MODULUS) revert ErrorUnexpectedPointEvaluationPrecompileOutput(); } - /// @dev Internal function to check the `SkippedL1MessageBitmap`. - /// @param _totalL1MessagesPoppedOverall The total number of L1 messages popped after current batch. - /// @param _totalL1MessagesPoppedInBatch The total number of L1 messages popped in current batch. - /// @param _skippedL1MessageBitmap The skipped L1 message bitmap in calldata. - /// @param _doPopMessage Whether we actually pop the messages from message queue. - function _checkSkippedL1MessageBitmap( - uint256 _totalL1MessagesPoppedOverall, - uint256 _totalL1MessagesPoppedInBatch, - bytes calldata _skippedL1MessageBitmap, - bool _doPopMessage - ) private { - // check the length of bitmap - unchecked { - if (((_totalL1MessagesPoppedInBatch + 255) / 256) * 32 != _skippedL1MessageBitmap.length) { - revert ErrorIncorrectBitmapLength(); - } - } - if (_doPopMessage) { - _popL1MessagesCalldata( - _skippedL1MessageBitmap, - _totalL1MessagesPoppedOverall, - _totalL1MessagesPoppedInBatch - ); - } - } - /// @dev Internal function to get the blob versioned hash. /// @return _blobVersionedHash The retrieved blob versioned hash. function _getBlobVersionedHash() internal virtual returns (bytes32 _blobVersionedHash) { @@ -1206,17 +1077,15 @@ 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 + /// @dev Internal function to commit chunks with version 1 /// @param _totalL1MessagesPoppedOverall The number of L1 messages popped before the list of chunks. /// @param _chunks The list of chunks to commit. - /// @param _skippedL1MessageBitmap The bitmap indicates whether each L1 message is skipped or not. /// @return _batchDataHash The computed data hash for the list of chunks. /// @return _totalL1MessagesPoppedInBatch The total number of L1 messages popped in this batch, including skipped one. - function _commitChunksV0( + function _commitChunksV1( uint256 _totalL1MessagesPoppedOverall, bytes[] memory _chunks, - bytes calldata _skippedL1MessageBitmap + uint256 maxInclusionDelay ) internal view returns (bytes32 _batchDataHash, uint256 _totalL1MessagesPoppedInBatch) { uint256 _chunksLength = _chunks.length; @@ -1227,19 +1096,21 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { mstore(0x40, add(batchDataHashPtr, mul(_chunksLength, 32))) } + uint256 _totalNumTransactionsInBatch; // compute the data hash for each chunk for (uint256 i = 0; i < _chunksLength; i++) { uint256 _totalNumL1MessagesInChunk; + uint256 _totalNumTransactionsInChunk; bytes32 _chunkDataHash; - (_chunkDataHash, _totalNumL1MessagesInChunk) = _commitChunkV0( + (_chunkDataHash, _totalNumL1MessagesInChunk, _totalNumTransactionsInChunk) = _commitChunkV1( _chunks[i], _totalL1MessagesPoppedInBatch, - _totalL1MessagesPoppedOverall, - _skippedL1MessageBitmap + _totalL1MessagesPoppedOverall ); unchecked { _totalL1MessagesPoppedInBatch += _totalNumL1MessagesInChunk; _totalL1MessagesPoppedOverall += _totalNumL1MessagesInChunk; + _totalNumTransactionsInBatch += _totalNumTransactionsInChunk; } assembly { mstore(batchDataHashPtr, _chunkDataHash) @@ -1247,50 +1118,22 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } - assembly { - let dataLen := mul(_chunksLength, 0x20) - _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. - /// @param _chunks The list of chunks to commit. - /// @param _skippedL1MessageBitmap The bitmap indicates whether each L1 message is skipped or not. - /// @return _batchDataHash The computed data hash for the list of chunks. - /// @return _totalL1MessagesPoppedInBatch The total number of L1 messages popped in this batch, including skipped one. - function _commitChunksV1( - uint256 _totalL1MessagesPoppedOverall, - bytes[] memory _chunks, - bytes calldata _skippedL1MessageBitmap - ) internal view returns (bytes32 _batchDataHash, uint256 _totalL1MessagesPoppedInBatch) { - uint256 _chunksLength = _chunks.length; - - // load `batchDataHashPtr` and reserve the memory region for chunk data hashes - uint256 batchDataHashPtr; - assembly { - batchDataHashPtr := mload(0x40) - mstore(0x40, add(batchDataHashPtr, mul(_chunksLength, 32))) - } - - // compute the data hash for each chunk - for (uint256 i = 0; i < _chunksLength; i++) { - uint256 _totalNumL1MessagesInChunk; - bytes32 _chunkDataHash; - (_chunkDataHash, _totalNumL1MessagesInChunk) = _commitChunkV1( - _chunks[i], - _totalL1MessagesPoppedInBatch, - _totalL1MessagesPoppedOverall, - _skippedL1MessageBitmap - ); - unchecked { - _totalL1MessagesPoppedInBatch += _totalNumL1MessagesInChunk; - _totalL1MessagesPoppedOverall += _totalNumL1MessagesInChunk; - } - assembly { - mstore(batchDataHashPtr, _chunkDataHash) - batchDataHashPtr := add(batchDataHashPtr, 0x20) + // Check expired message status here. + // If some messages expired for inclusion, all expired messages should be included in current batch, + // unless this batch cannot include that many messages (i.e. totalNumTransactionsInBatch=totalL1MessagesPoppedInBatch). + { + uint256 timestamp = IL1MessageQueue(messageQueue).getFirstPendingMessageTimestamp(); + if ( + timestamp > 0 && + timestamp + maxInclusionDelay < block.timestamp && + _totalNumTransactionsInBatch > _totalL1MessagesPoppedInBatch + ) { + timestamp = IL1MessageQueue(messageQueue).getMessageTimestamp( + _totalL1MessagesPoppedOverall + _totalL1MessagesPoppedInBatch + ); + if (timestamp + maxInclusionDelay < block.timestamp) { + revert ErrorNotIncludeAllExpiredMessages(); + } } } @@ -1318,11 +1161,8 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { uint256 _totalL1MessagesPoppedOverall ) { - // load version from batch header, it is always the first byte. - uint256 version; - assembly { - version := shr(248, calldataload(_batchHeader.offset)) - } + // load version from batch header + uint256 version = BatchHeaderV0Codec.getVersion(_batchHeader); uint256 _length; if (version == 0) { @@ -1345,113 +1185,26 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } - /// @dev Internal function to commit a chunk with version 0. - /// @param _chunk The encoded chunk to commit. - /// @param _totalL1MessagesPoppedInBatch The total number of L1 messages popped in the current batch before this chunk. - /// @param _totalL1MessagesPoppedOverall The total number of L1 messages popped in all batches including the current batch, before this chunk. - /// @param _skippedL1MessageBitmap The bitmap indicates whether each L1 message is skipped or not. - /// @return _dataHash The computed data hash for this chunk. - /// @return _totalNumL1MessagesInChunk The total number of L1 message popped in current chunk - function _commitChunkV0( - bytes memory _chunk, - uint256 _totalL1MessagesPoppedInBatch, - uint256 _totalL1MessagesPoppedOverall, - bytes calldata _skippedL1MessageBitmap - ) internal view returns (bytes32 _dataHash, uint256 _totalNumL1MessagesInChunk) { - uint256 chunkPtr; - uint256 startDataPtr; - uint256 dataPtr; - - assembly { - dataPtr := mload(0x40) - startDataPtr := dataPtr - chunkPtr := add(_chunk, 0x20) // skip chunkLength - } - - uint256 _numBlocks = ChunkCodecV0.validateChunkLength(chunkPtr, _chunk.length); - - // concatenate block contexts, use scope to avoid stack too deep - { - uint256 _totalTransactionsInChunk; - for (uint256 i = 0; i < _numBlocks; i++) { - dataPtr = ChunkCodecV0.copyBlockContext(chunkPtr, dataPtr, i); - uint256 blockPtr = chunkPtr + 1 + i * ChunkCodecV0.BLOCK_CONTEXT_LENGTH; - uint256 _numTransactionsInBlock = ChunkCodecV0.getNumTransactions(blockPtr); - unchecked { - _totalTransactionsInChunk += _numTransactionsInBlock; - } - } - assembly { - mstore(0x40, add(dataPtr, mul(_totalTransactionsInChunk, 0x20))) // reserve memory for tx hashes - } - } - - // It is used to compute the actual number of transactions in chunk. - uint256 txHashStartDataPtr = dataPtr; - // concatenate tx hashes - uint256 l2TxPtr = ChunkCodecV0.getL2TxPtr(chunkPtr, _numBlocks); - chunkPtr += 1; - while (_numBlocks > 0) { - // concatenate l1 message hashes - uint256 _numL1MessagesInBlock = ChunkCodecV0.getNumL1Messages(chunkPtr); - dataPtr = _loadL1MessageHashes( - dataPtr, - _numL1MessagesInBlock, - _totalL1MessagesPoppedInBatch, - _totalL1MessagesPoppedOverall, - _skippedL1MessageBitmap - ); - - // concatenate l2 transaction hashes - uint256 _numTransactionsInBlock = ChunkCodecV0.getNumTransactions(chunkPtr); - if (_numTransactionsInBlock < _numL1MessagesInBlock) revert ErrorNumTxsLessThanNumL1Msgs(); - for (uint256 j = _numL1MessagesInBlock; j < _numTransactionsInBlock; j++) { - bytes32 txHash; - (txHash, l2TxPtr) = ChunkCodecV0.loadL2TxHash(l2TxPtr); - assembly { - mstore(dataPtr, txHash) - dataPtr := add(dataPtr, 0x20) - } - } - - unchecked { - _totalNumL1MessagesInChunk += _numL1MessagesInBlock; - _totalL1MessagesPoppedInBatch += _numL1MessagesInBlock; - _totalL1MessagesPoppedOverall += _numL1MessagesInBlock; - - _numBlocks -= 1; - chunkPtr += ChunkCodecV0.BLOCK_CONTEXT_LENGTH; - } - } - - // check the actual number of transactions in the chunk - if ((dataPtr - txHashStartDataPtr) / 32 > maxNumTxInChunk) revert ErrorTooManyTxsInOneChunk(); - - assembly { - chunkPtr := add(_chunk, 0x20) - } - // check chunk has correct length - if (l2TxPtr - chunkPtr != _chunk.length) revert ErrorIncompleteL2TransactionData(); - - // compute data hash and store to memory - assembly { - _dataHash := keccak256(startDataPtr, sub(dataPtr, startDataPtr)) - } - } - /// @dev Internal function to commit a chunk with version 1. /// @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 _dataHash The computed data hash for this chunk. /// @return _totalNumL1MessagesInChunk The total number of L1 message popped in current chunk + /// @return _totalTransactionsInChunk The total number of transactions (non-skipped l1 messages + l2 txs) in current chunk. function _commitChunkV1( bytes memory _chunk, uint256 _totalL1MessagesPoppedInBatch, - uint256 _totalL1MessagesPoppedOverall, - bytes calldata _skippedL1MessageBitmap - ) internal view returns (bytes32 _dataHash, uint256 _totalNumL1MessagesInChunk) { + uint256 _totalL1MessagesPoppedOverall + ) + internal + view + returns ( + bytes32 _dataHash, + uint256 _totalNumL1MessagesInChunk, + uint256 _totalTransactionsInChunk + ) + { uint256 chunkPtr; uint256 startDataPtr; uint256 dataPtr; @@ -1477,8 +1230,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { chunkPtr := add(chunkPtr, 1) } - // the number of actual transactions in one chunk: non-skipped l1 messages + l2 txs - uint256 _totalTransactionsInChunk; // concatenate tx hashes while (_numBlocks > 0) { // concatenate l1 message hashes @@ -1488,8 +1239,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { dataPtr, _numL1MessagesInBlock, _totalL1MessagesPoppedInBatch, - _totalL1MessagesPoppedOverall, - _skippedL1MessageBitmap + _totalL1MessagesPoppedOverall ); uint256 _numTransactionsInBlock = ChunkCodecV1.getNumTransactions(chunkPtr); if (_numTransactionsInBlock < _numL1MessagesInBlock) revert ErrorNumTxsLessThanNumL1Msgs(); @@ -1504,11 +1254,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } - // check the actual number of transactions in the chunk - if (_totalTransactionsInChunk > maxNumTxInChunk) { - revert ErrorTooManyTxsInOneChunk(); - } - // compute data hash and store to memory assembly { _dataHash := keccak256(startDataPtr, sub(dataPtr, startDataPtr)) @@ -1520,47 +1265,27 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @param _numL1Messages The number of L1 messages to load. /// @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 uint256 The new memory offset after loading. + /// @return ptr The new memory offset after loading. function _loadL1MessageHashes( uint256 _ptr, uint256 _numL1Messages, uint256 _totalL1MessagesPoppedInBatch, - uint256 _totalL1MessagesPoppedOverall, - bytes calldata _skippedL1MessageBitmap + uint256 _totalL1MessagesPoppedOverall ) internal view returns (uint256) { if (_numL1Messages == 0) return _ptr; IL1MessageQueue _messageQueue = IL1MessageQueue(messageQueue); unchecked { - uint256 _bitmap; - uint256 rem; + // all messages are non-skipped for (uint256 i = 0; i < _numL1Messages; i++) { - uint256 quo = _totalL1MessagesPoppedInBatch >> 8; - rem = _totalL1MessagesPoppedInBatch & 0xff; - - // load bitmap every 256 bits - if (i == 0 || rem == 0) { - assembly { - _bitmap := calldataload(add(_skippedL1MessageBitmap.offset, mul(0x20, quo))) - } - } - if (((_bitmap >> rem) & 1) == 0) { - // message not skipped - bytes32 _hash = _messageQueue.getCrossDomainMessage(_totalL1MessagesPoppedOverall); - assembly { - mstore(_ptr, _hash) - _ptr := add(_ptr, 0x20) - } + bytes32 _hash = _messageQueue.getCrossDomainMessage(_totalL1MessagesPoppedOverall); + assembly { + mstore(_ptr, _hash) + _ptr := add(_ptr, 0x20) } - - _totalL1MessagesPoppedInBatch += 1; _totalL1MessagesPoppedOverall += 1; } - - // check last L1 message is not skipped, _totalL1MessagesPoppedInBatch must > 0 - rem = (_totalL1MessagesPoppedInBatch - 1) & 0xff; - if (((_bitmap >> rem) & 1) > 0) revert ErrorLastL1MessageSkipped(); + _totalL1MessagesPoppedInBatch += _numL1Messages; } return _ptr; @@ -1574,72 +1299,4 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } } - - /// @dev Internal function to pop l1 messages from `skippedL1MessageBitmap` in memory. - /// @param bitmapPtr The memory offset of `skippedL1MessageBitmap` in memory. - /// @param totalL1MessagesPoppedOverall The total number of L1 messages popped in all batches including current batch. - /// @param totalL1MessagesPoppedInBatch The number of L1 messages popped in current batch. - function _popL1MessagesMemory( - uint256 bitmapPtr, - uint256 totalL1MessagesPoppedOverall, - uint256 totalL1MessagesPoppedInBatch - ) internal { - if (totalL1MessagesPoppedInBatch == 0) return; - _popL1Messages(false, bitmapPtr, totalL1MessagesPoppedOverall, totalL1MessagesPoppedInBatch); - } - - /// @dev Internal function to pop l1 messages from `skippedL1MessageBitmap` in calldata. - /// @param skippedL1MessageBitmap The `skippedL1MessageBitmap` in calldata. - /// @param totalL1MessagesPoppedOverall The total number of L1 messages popped in all batches including current batch. - /// @param totalL1MessagesPoppedInBatch The number of L1 messages popped in current batch. - function _popL1MessagesCalldata( - bytes calldata skippedL1MessageBitmap, - uint256 totalL1MessagesPoppedOverall, - uint256 totalL1MessagesPoppedInBatch - ) internal { - if (totalL1MessagesPoppedInBatch == 0) return; - uint256 bitmapPtr; - assembly { - bitmapPtr := skippedL1MessageBitmap.offset - } - _popL1Messages(true, bitmapPtr, totalL1MessagesPoppedOverall, totalL1MessagesPoppedInBatch); - } - - /// @dev Internal function to pop l1 messages from `skippedL1MessageBitmap` in calldata or memory. - /// @param isCalldata Whether the `skippedL1MessageBitmap` is in calldata or memory. - /// @param bitmapPtr The offset of `skippedL1MessageBitmap` in calldata or memory. - /// @param totalL1MessagesPoppedOverall The total number of L1 messages popped in all batches including current batch. - /// @param totalL1MessagesPoppedInBatch The number of L1 messages popped in current batch. - function _popL1Messages( - bool isCalldata, - uint256 bitmapPtr, - uint256 totalL1MessagesPoppedOverall, - uint256 totalL1MessagesPoppedInBatch - ) internal { - if (totalL1MessagesPoppedInBatch == 0) return; - - unchecked { - uint256 startIndex = totalL1MessagesPoppedOverall - totalL1MessagesPoppedInBatch; - uint256 bitmap; - - for (uint256 i = 0; i < totalL1MessagesPoppedInBatch; i += 256) { - uint256 _count = 256; - if (totalL1MessagesPoppedInBatch - i < _count) { - _count = totalL1MessagesPoppedInBatch - i; - } - assembly { - switch isCalldata - case 1 { - bitmap := calldataload(bitmapPtr) - } - default { - bitmap := mload(bitmapPtr) - } - bitmapPtr := add(bitmapPtr, 0x20) - } - IL1MessageQueue(messageQueue).popCrossDomainMessage(startIndex, _count, bitmap); - startIndex += 256; - } - } - } } diff --git a/src/libraries/codec/BatchHeaderV0Codec.sol b/src/libraries/codec/BatchHeaderV0Codec.sol index 36685868..1d76bb9c 100644 --- a/src/libraries/codec/BatchHeaderV0Codec.sol +++ b/src/libraries/codec/BatchHeaderV0Codec.sol @@ -59,6 +59,15 @@ library BatchHeaderV0Codec { } } + /// @notice Get the version of the batch header from calldata. + /// @param header The the batch header in calldata. + /// @return _version The version of the batch header. + function getVersion(bytes calldata header) internal pure returns (uint256 _version) { + assembly { + _version := shr(248, calldataload(header.offset)) + } + } + /// @notice Get the batch index of the batch. /// @param batchPtr The start memory offset of the batch header in memory. /// @return _batchIndex The batch index of the batch. diff --git a/src/libraries/codec/BatchHeaderV3Codec.sol b/src/libraries/codec/BatchHeaderV3Codec.sol index b8f44dc4..fec99d81 100644 --- a/src/libraries/codec/BatchHeaderV3Codec.sol +++ b/src/libraries/codec/BatchHeaderV3Codec.sol @@ -28,6 +28,15 @@ library BatchHeaderV3Codec { /// @dev The length of fixed parts of the batch header. uint256 internal constant BATCH_HEADER_FIXED_LENGTH = 193; + /// @notice Alloc memory for batch header. + /// @return batchPtr The start memory offset of the batch header in memory. + function alloc() internal pure returns (uint256 batchPtr) { + assembly { + batchPtr := mload(0x40) + mstore(0x40, add(batchPtr, BATCH_HEADER_FIXED_LENGTH)) + } + } + /// @notice Load batch header in calldata to memory. /// @param _batchHeader The encoded batch header bytes in calldata. /// @return batchPtr The start memory offset of the batch header in memory. diff --git a/src/test/L1CustomERC20Gateway.t.sol b/src/test/L1CustomERC20Gateway.t.sol index 879fcb66..64b78241 100644 --- a/src/test/L1CustomERC20Gateway.t.sol +++ b/src/test/L1CustomERC20Gateway.t.sol @@ -173,6 +173,7 @@ contract L1CustomERC20GatewayTest is L1GatewayTestBase { ); } + /* function testDropMessage( uint256 amount, address recipient, @@ -217,6 +218,7 @@ contract L1CustomERC20GatewayTest is L1GatewayTestBase { l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 1, message); assertEq(balance + amount, l1Token.balanceOf(address(this))); } + */ function testFinalizeWithdrawERC20FailedMocking( address sender, @@ -421,7 +423,7 @@ contract L1CustomERC20GatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -501,7 +503,7 @@ contract L1CustomERC20GatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -582,7 +584,7 @@ contract L1CustomERC20GatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( diff --git a/src/test/L1ERC1155Gateway.t.sol b/src/test/L1ERC1155Gateway.t.sol index 96b44970..d418d27b 100644 --- a/src/test/L1ERC1155Gateway.t.sol +++ b/src/test/L1ERC1155Gateway.t.sol @@ -203,6 +203,7 @@ contract L1ERC1155GatewayTest is L1GatewayTestBase, ERC1155TokenReceiver { ); } + /* function testDropMessage(uint256 tokenId, uint256 amount) public { gateway.updateTokenMapping(address(l1Token), address(l2Token)); @@ -280,6 +281,7 @@ contract L1ERC1155GatewayTest is L1GatewayTestBase, ERC1155TokenReceiver { assertEq(balances[i] + _amounts[i], l1Token.balanceOf(address(this), _tokenIds[i])); } } + */ function testFinalizeWithdrawERC1155FailedMocking( address sender, @@ -746,7 +748,7 @@ contract L1ERC1155GatewayTest is L1GatewayTestBase, ERC1155TokenReceiver { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -814,7 +816,7 @@ contract L1ERC1155GatewayTest is L1GatewayTestBase, ERC1155TokenReceiver { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -893,7 +895,7 @@ contract L1ERC1155GatewayTest is L1GatewayTestBase, ERC1155TokenReceiver { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; uint256[] memory _tokenIds = new uint256[](tokenCount); @@ -978,7 +980,7 @@ contract L1ERC1155GatewayTest is L1GatewayTestBase, ERC1155TokenReceiver { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; uint256[] memory _tokenIds = new uint256[](tokenCount); diff --git a/src/test/L1ERC721Gateway.t.sol b/src/test/L1ERC721Gateway.t.sol index 5f568f06..507c13c4 100644 --- a/src/test/L1ERC721Gateway.t.sol +++ b/src/test/L1ERC721Gateway.t.sol @@ -193,6 +193,7 @@ contract L1ERC721GatewayTest is L1GatewayTestBase, ERC721TokenReceiver { ); } + /* function testDropMessage(uint256 tokenId) public { gateway.updateTokenMapping(address(l1Token), address(l2Token)); @@ -263,6 +264,7 @@ contract L1ERC721GatewayTest is L1GatewayTestBase, ERC721TokenReceiver { assertEq(l1Token.ownerOf(_tokenIds[i]), address(this)); } } + */ function testFinalizeWithdrawERC721FailedMocking( address sender, @@ -680,7 +682,7 @@ contract L1ERC721GatewayTest is L1GatewayTestBase, ERC721TokenReceiver { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; hevm.expectRevert("no corresponding l2 token"); @@ -743,7 +745,7 @@ contract L1ERC721GatewayTest is L1GatewayTestBase, ERC721TokenReceiver { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; hevm.expectRevert("no corresponding l2 token"); @@ -805,7 +807,7 @@ contract L1ERC721GatewayTest is L1GatewayTestBase, ERC721TokenReceiver { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; uint256[] memory _tokenIds = new uint256[](tokenCount); @@ -880,7 +882,7 @@ contract L1ERC721GatewayTest is L1GatewayTestBase, ERC721TokenReceiver { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; uint256[] memory _tokenIds = new uint256[](tokenCount); diff --git a/src/test/L1ETHGateway.t.sol b/src/test/L1ETHGateway.t.sol index 565d5d5a..57a7163a 100644 --- a/src/test/L1ETHGateway.t.sol +++ b/src/test/L1ETHGateway.t.sol @@ -145,6 +145,7 @@ contract L1ETHGatewayTest is L1GatewayTestBase { ); } + /* function testDropMessage( uint256 amount, address recipient, @@ -182,6 +183,7 @@ contract L1ETHGatewayTest is L1GatewayTestBase { l1Messenger.dropMessage(address(gateway), address(counterpartGateway), amount, 0, message); assertEq(balance + amount, address(this).balance); } + */ function testFinalizeWithdrawETHFailedMocking( address sender, @@ -343,7 +345,7 @@ contract L1ETHGatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -412,7 +414,7 @@ contract L1ETHGatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -482,7 +484,7 @@ contract L1ETHGatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( diff --git a/src/test/L1GatewayTestBase.t.sol b/src/test/L1GatewayTestBase.t.sol index e1c1263a..12eeb8d7 100644 --- a/src/test/L1GatewayTestBase.t.sol +++ b/src/test/L1GatewayTestBase.t.sol @@ -164,7 +164,7 @@ abstract contract L1GatewayTestBase is ScrollTestBase { chunk0[0] = bytes1(uint8(1)); // one block in this chunk chunks[0] = chunk0; hevm.startPrank(address(0)); - rollup.commitBatch(1, batchHeader0, chunks, new bytes(0)); + // rollup.commitBatch(1, batchHeader0, chunks, new bytes(0)); hevm.stopPrank(); bytes memory batchHeader1 = new bytes(121); @@ -185,4 +185,10 @@ abstract contract L1GatewayTestBase is ScrollTestBase { rollup.lastFinalizedBatchIndex(); } + + function _setL2BaseFee(uint256 feePerGas) internal { + // l2 base fee = scalar * block.basefee + overhead + messageQueue.updateL2BaseFeeParameters(0, 1e18); + hevm.fee(feePerGas); + } } diff --git a/src/test/L1MessageQueue.t.sol b/src/test/L1MessageQueue.t.sol index b2a3fb89..3bf46eb7 100644 --- a/src/test/L1MessageQueue.t.sol +++ b/src/test/L1MessageQueue.t.sol @@ -179,6 +179,7 @@ contract L1MessageQueueTest is ScrollTestBase { hevm.stopPrank(); } + /* function testPopCrossDomainMessage(uint256 bitmap) external { // should revert, when non-scrollChain call hevm.expectRevert("Only callable by the ScrollChain"); @@ -340,7 +341,7 @@ contract L1MessageQueueTest is ScrollTestBase { hevm.startPrank(FakeScrollChain); hevm.expectEmit(false, false, false, true); emit DequeueTransaction(0, 256, 0); - queue.popCrossDomainMessage(0, 256, 0); + queue.popCrossDomainMessage(256); assertEq(queue.pendingQueueIndex(), 256); assertEq(queue.nextUnfinalizedQueueIndex(), 0); hevm.stopPrank(); @@ -478,6 +479,7 @@ contract L1MessageQueueTest is ScrollTestBase { } } } + */ function testFinalizePoppedCrossDomainMessage() external { // should revert, when non-scrollChain call @@ -495,7 +497,7 @@ contract L1MessageQueueTest is ScrollTestBase { hevm.startPrank(FakeScrollChain); hevm.expectEmit(false, false, false, true); emit DequeueTransaction(0, 5, 0); - queue.popCrossDomainMessage(0, 5, 0); + queue.popCrossDomainMessage(5); assertEq(queue.pendingQueueIndex(), 5); assertEq(queue.nextUnfinalizedQueueIndex(), 0); hevm.stopPrank(); @@ -544,7 +546,7 @@ contract L1MessageQueueTest is ScrollTestBase { hevm.startPrank(FakeScrollChain); hevm.expectEmit(false, false, false, true); emit DequeueTransaction(0, 5, 0); - queue.popCrossDomainMessage(0, 5, 0); + queue.popCrossDomainMessage(5); assertEq(queue.pendingQueueIndex(), 5); assertEq(queue.nextUnfinalizedQueueIndex(), 0); hevm.stopPrank(); @@ -580,6 +582,7 @@ contract L1MessageQueueTest is ScrollTestBase { hevm.stopPrank(); } + /* function testDropCrossDomainMessageSucceed() external { // append 10 messages hevm.startPrank(FakeMessenger); @@ -625,4 +628,5 @@ contract L1MessageQueueTest is ScrollTestBase { hevm.stopPrank(); } } + */ } diff --git a/src/test/L1MessageQueueWithGasPriceOracle.t.sol b/src/test/L1MessageQueueWithGasPriceOracle.t.sol index 536d85ea..545951ef 100644 --- a/src/test/L1MessageQueueWithGasPriceOracle.t.sol +++ b/src/test/L1MessageQueueWithGasPriceOracle.t.sol @@ -15,8 +15,7 @@ import {ScrollTestBase} from "./ScrollTestBase.t.sol"; contract L1MessageQueueWithGasPriceOracleTest is ScrollTestBase { // events - event UpdateWhitelistChecker(address indexed _oldWhitelistChecker, address indexed _newWhitelistChecker); - event UpdateL2BaseFee(uint256 oldL2BaseFee, uint256 newL2BaseFee); + event UpdateL2BaseFeeParameters(uint256 overhead, uint256 scalar); L1MessageQueueWithGasPriceOracle private queue; L2GasPriceOracle internal gasOracle; @@ -47,51 +46,48 @@ contract L1MessageQueueWithGasPriceOracleTest is ScrollTestBase { queue.initializeV2(); } - function testUpdateWhitelistChecker(address _newWhitelistChecker) external { - hevm.assume(_newWhitelistChecker != address(whitelist)); + function testUpdateL2BaseFeeParameters( + uint256 basefee, + uint128 overhead, + uint128 scalar + ) external { + basefee = bound(basefee, 1, 1e18); + hevm.fee(basefee); // call by non-owner, should revert hevm.startPrank(address(1)); hevm.expectRevert("Ownable: caller is not the owner"); - queue.updateWhitelistChecker(_newWhitelistChecker); + queue.updateL2BaseFeeParameters(overhead, scalar); hevm.stopPrank(); // call by owner, should succeed - assertEq(address(queue.whitelistChecker()), address(whitelist)); - hevm.expectEmit(true, true, false, true); - emit UpdateWhitelistChecker(address(whitelist), _newWhitelistChecker); - queue.updateWhitelistChecker(_newWhitelistChecker); - assertEq(address(queue.whitelistChecker()), _newWhitelistChecker); - } - - function testSetL2BaseFee(uint256 _baseFee1, uint256 _baseFee2) external { - // call by non-whitelister, should revert - hevm.startPrank(address(1)); - hevm.expectRevert(IL1MessageQueueWithGasPriceOracle.ErrorNotWhitelistedSender.selector); - queue.setL2BaseFee(_baseFee1); - hevm.stopPrank(); - - // call by owner, should succeed - assertEq(queue.l2BaseFee(), 0); + (uint128 x, uint128 y) = queue.l2BaseFeeParameters(); + assertEq(x, 0); + assertEq(y, 0); hevm.expectEmit(false, false, false, true); - emit UpdateL2BaseFee(0, _baseFee1); - queue.setL2BaseFee(_baseFee1); - assertEq(queue.l2BaseFee(), _baseFee1); + emit UpdateL2BaseFeeParameters(overhead, scalar); + queue.updateL2BaseFeeParameters(overhead, scalar); + (x, y) = queue.l2BaseFeeParameters(); + assertEq(x, overhead); + assertEq(y, scalar); - hevm.expectEmit(false, false, false, true); - emit UpdateL2BaseFee(_baseFee1, _baseFee2); - queue.setL2BaseFee(_baseFee2); - assertEq(queue.l2BaseFee(), _baseFee2); + assertEq(queue.l2BaseFee(), overhead + (scalar * block.basefee) / 1e18); } - function testEstimateCrossDomainMessageFee(uint256 baseFee, uint256 gasLimit) external { + function testEstimateCrossDomainMessageFee( + uint256 basefee, + uint128 overhead, + uint128 scalar, + uint256 gasLimit + ) external { + basefee = bound(basefee, 0, 1 ether); gasLimit = bound(gasLimit, 0, 3000000); - baseFee = bound(baseFee, 0, 1000000000); assertEq(queue.estimateCrossDomainMessageFee(gasLimit), 0); - queue.setL2BaseFee(baseFee); - assertEq(queue.estimateCrossDomainMessageFee(gasLimit), baseFee * gasLimit); + hevm.fee(basefee); + queue.updateL2BaseFeeParameters(overhead, scalar); + assertEq(queue.estimateCrossDomainMessageFee(gasLimit), ((basefee * scalar) / 1e18 + overhead) * gasLimit); } function testCalculateIntrinsicGasFee(bytes memory data) external { diff --git a/src/test/L1ReverseCustomERC20Gateway.t.sol b/src/test/L1ReverseCustomERC20Gateway.t.sol index 81913ecb..7e77c2ae 100644 --- a/src/test/L1ReverseCustomERC20Gateway.t.sol +++ b/src/test/L1ReverseCustomERC20Gateway.t.sol @@ -147,6 +147,7 @@ contract L1ReverseCustomERC20GatewayTest is L1GatewayTestBase { _depositERC20(true, 2, amount, recipient, dataToCall, gasLimit, feePerGas); } + /* function testDropMessage( uint256 amount, address recipient, @@ -193,6 +194,7 @@ contract L1ReverseCustomERC20GatewayTest is L1GatewayTestBase { assertEq(balance + amount, l1Token.balanceOf(address(this))); assertEq(gatewayBalance, l1Token.balanceOf(address(gateway))); } + */ function testFinalizeWithdrawERC20( address sender, @@ -273,7 +275,7 @@ contract L1ReverseCustomERC20GatewayTest is L1GatewayTestBase { amount = bound(amount, 1, l1Token.balanceOf(address(this))); gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); feePerGas = feePerGas * gasLimit; // revert when reentrant diff --git a/src/test/L1ScrollMessengerTest.t.sol b/src/test/L1ScrollMessengerTest.t.sol index aae33cbd..e9316abb 100644 --- a/src/test/L1ScrollMessengerTest.t.sol +++ b/src/test/L1ScrollMessengerTest.t.sol @@ -95,7 +95,7 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { hevm.expectRevert("Provided message has not been enqueued"); l1Messenger.replayMessage(address(this), address(0), 101, 0, new bytes(0), defaultGasLimit, refundAddress); - messageQueue.setL2BaseFee(1); + _setL2BaseFee(1); // Insufficient msg.value hevm.expectRevert("Insufficient msg.value for fee"); l1Messenger.replayMessage(address(this), address(0), 100, 0, new bytes(0), defaultGasLimit, refundAddress); @@ -134,7 +134,7 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { // test replay list // 1. send a message with nonce 2 // 2. replay 3 times - messageQueue.setL2BaseFee(0); + _setL2BaseFee(0); l1Messenger.updateMaxReplayTimes(100); l1Messenger.sendMessage{value: 100}(address(0), 100, new bytes(0), defaultGasLimit, refundAddress); bytes32 hash = keccak256( @@ -234,6 +234,7 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { l1Messenger.sendMessage{value: _fee + value}(address(0), value, hex"0011220033", gasLimit); } + /* function testDropMessage() external { // Provided message has not been enqueued, revert hevm.expectRevert("Provided message has not been enqueued"); @@ -334,6 +335,7 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { hevm.expectRevert("Message already dropped"); l1Messenger.replayMessage(address(this), address(0), 0, 6, new bytes(0), defaultGasLimit, address(0)); } + */ function onDropMessage(bytes memory message) external payable { emit OnDropMessageCalled(message); diff --git a/src/test/L1StandardERC20Gateway.t.sol b/src/test/L1StandardERC20Gateway.t.sol index 548ef492..9d6e0d2b 100644 --- a/src/test/L1StandardERC20Gateway.t.sol +++ b/src/test/L1StandardERC20Gateway.t.sol @@ -267,6 +267,7 @@ contract L1StandardERC20GatewayTest is L1GatewayTestBase { ); } + /* function testDropMessage( uint256 amount, address recipient, @@ -301,6 +302,7 @@ contract L1StandardERC20GatewayTest is L1GatewayTestBase { l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 1, message); assertEq(balance + amount, l1Token.balanceOf(address(this))); } + */ function testFinalizeWithdrawERC20FailedMocking( address sender, @@ -490,7 +492,7 @@ contract L1StandardERC20GatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -561,7 +563,7 @@ contract L1StandardERC20GatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -633,7 +635,7 @@ contract L1StandardERC20GatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( diff --git a/src/test/L1USDCGateway.t.sol b/src/test/L1USDCGateway.t.sol index 585a6b3e..817db0bb 100644 --- a/src/test/L1USDCGateway.t.sol +++ b/src/test/L1USDCGateway.t.sol @@ -376,7 +376,7 @@ contract L1USDCGatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -453,7 +453,7 @@ contract L1USDCGatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( diff --git a/src/test/L1WETHGateway.t.sol b/src/test/L1WETHGateway.t.sol index 3cbebc57..57c4d858 100644 --- a/src/test/L1WETHGateway.t.sol +++ b/src/test/L1WETHGateway.t.sol @@ -204,6 +204,7 @@ contract L1WETHGatewayTest is L1GatewayTestBase { ); } + /* function testDropMessage( uint256 amount, address recipient, @@ -237,6 +238,7 @@ contract L1WETHGatewayTest is L1GatewayTestBase { l1Messenger.dropMessage(address(gateway), address(counterpartGateway), amount, 0, message); assertEq(balance + amount, l1weth.balanceOf(address(this))); } + */ function testFinalizeWithdrawERC20FailedMocking( address sender, @@ -451,7 +453,7 @@ contract L1WETHGatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -526,7 +528,7 @@ contract L1WETHGatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( @@ -602,7 +604,7 @@ contract L1WETHGatewayTest is L1GatewayTestBase { gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); uint256 feeToPay = feePerGas * gasLimit; bytes memory message = abi.encodeWithSelector( diff --git a/src/test/ScrollChain.t.sol b/src/test/ScrollChain.t.sol index 1c7cd01b..4bac417c 100644 --- a/src/test/ScrollChain.t.sol +++ b/src/test/ScrollChain.t.sol @@ -92,171 +92,6 @@ contract ScrollChainTest is DSTestPlus { rollup.initialize(address(messageQueue), address(0), 100); } - function testCommitBatchV1() 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.commitBatch(1, batchHeader0, new bytes[](0), new bytes(0)); - - rollup.addSequencer(address(0)); - - // batch is empty, revert - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsEmpty.selector); - rollup.commitBatch(1, batchHeader0, new bytes[](0), new bytes(0)); - hevm.stopPrank(); - - // batch header length too small, revert - bytes memory header = new bytes(120); - assembly { - mstore8(add(header, 0x20), 1) // version - } - hevm.startPrank(address(0)); - hevm.expectRevert(BatchHeaderV1Codec.ErrorBatchHeaderV1LengthTooSmall.selector); - rollup.commitBatch(1, header, new bytes[](1), new bytes(0)); - hevm.stopPrank(); - - // wrong bitmap length, revert - header = new bytes(122); - assembly { - mstore8(add(header, 0x20), 1) // version - } - hevm.startPrank(address(0)); - hevm.expectRevert(BatchHeaderV1Codec.ErrorIncorrectBitmapLengthV1.selector); - rollup.commitBatch(1, header, new bytes[](1), new bytes(0)); - hevm.stopPrank(); - - // incorrect parent batch hash, revert - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 2) // change data hash for batch0 - } - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); - rollup.commitBatch(1, batchHeader0, new bytes[](1), 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.commitBatch(1, batchHeader0, chunks, 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.commitBatch(1, batchHeader0, chunks, 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.commitBatch(1, batchHeader0, chunks, bitmap); - 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.commitBatch(1, batchHeader0, chunks, bitmap); - hevm.stopPrank(); - - // 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.commitBatch(1, batchHeader0, chunks, new bytes(0)); - hevm.stopPrank(); - - // @note we cannot check `ErrorFoundMultipleBlobs` here - - // upgrade to ScrollChainMockBlob - ScrollChainMockBlob impl = new ScrollChainMockBlob( - rollup.layer2ChainId(), - rollup.messageQueue(), - rollup.zkpVerifier(), - rollup.teeVerifier(), - 0 - ); - admin.upgrade(ITransparentUpgradeableProxy(address(rollup)), address(impl)); - // this is keccak(""); - ScrollChainMockBlob(address(rollup)).setBlobVersionedHash( - 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 - ); - - bytes32 batchHash0 = rollup.committedBatches(0); - bytes memory batchHeader1 = new bytes(121); - assembly { - mstore8(add(batchHeader1, 0x20), 1) // 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)), 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) // blobVersionedHash - mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - } - - // commit batch with one chunk, no tx, correctly - 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.commitBatch(1, batchHeader0, chunks, new bytes(0)); - hevm.stopPrank(); - assertEq(rollup.committedBatches(1), keccak256(batchHeader1)); - - // batch is already committed, revert - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsAlreadyCommitted.selector); - rollup.commitBatch(1, batchHeader0, chunks, new bytes(0)); - hevm.stopPrank(); - - // revert when ErrorIncorrectBatchVersion - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchVersion.selector); - rollup.commitBatch(3, batchHeader1, chunks, new bytes(0)); - hevm.stopPrank(); - } - function testCommitBatchV3() external { bytes memory batchHeader0 = new bytes(89); @@ -1163,19 +998,6 @@ contract ScrollChainTest is DSTestPlus { 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)); @@ -1246,6 +1068,7 @@ contract ScrollChainTest is DSTestPlus { assertEq(265, messageQueue.nextUnfinalizedQueueIndex()); } + /* function testRevertBatchWithL1Messages() external { rollup.addSequencer(address(0)); rollup.addProver(address(0)); @@ -1397,6 +1220,7 @@ contract ScrollChainTest is DSTestPlus { assertEq(uint256(rollup.committedBatches(1)), 0); assertEq(uint256(rollup.committedBatches(2)), 0); } + */ function testAddAndRemoveSequencer(address _sequencer) external { // set by non-owner, should revert @@ -1468,8 +1292,6 @@ contract ScrollChainTest is DSTestPlus { hevm.startPrank(address(0)); hevm.expectRevert("Pausable: paused"); - rollup.commitBatch(1, new bytes(0), new bytes[](0), new bytes(0)); - hevm.expectRevert("Pausable: paused"); rollup.commitBatchWithBlobProof(3, new bytes(0), new bytes[](0), new bytes(0), new bytes(0)); hevm.expectRevert("Pausable: paused"); rollup.finalizeBundleWithProof(new bytes(0), bytes32(0), bytes32(0), new bytes(0)); @@ -1482,22 +1304,6 @@ contract ScrollChainTest is DSTestPlus { assertBoolEq(false, rollup.paused()); } - function testUpdateMaxNumTxInChunk(uint256 _maxNumTxInChunk) external { - // set by non-owner, should revert - hevm.startPrank(address(1)); - hevm.expectRevert("Ownable: caller is not the owner"); - rollup.updateMaxNumTxInChunk(_maxNumTxInChunk); - hevm.stopPrank(); - - // change to random operator - hevm.expectEmit(false, false, false, true); - emit UpdateMaxNumTxInChunk(100, _maxNumTxInChunk); - - assertEq(rollup.maxNumTxInChunk(), 100); - rollup.updateMaxNumTxInChunk(_maxNumTxInChunk); - assertEq(rollup.maxNumTxInChunk(), _maxNumTxInChunk); - } - function testUpdateBundleSize() external { // not owner, revert hevm.startPrank(address(1)); diff --git a/src/test/batch-bridge/L1BatchBridgeGateway.t.sol b/src/test/batch-bridge/L1BatchBridgeGateway.t.sol index a71f195f..93c68550 100644 --- a/src/test/batch-bridge/L1BatchBridgeGateway.t.sol +++ b/src/test/batch-bridge/L1BatchBridgeGateway.t.sol @@ -91,7 +91,7 @@ contract L1BatchBridgeGatewayTest is L1GatewayTestBase { ); batch.initialize(batchFeeVault); router.initialize(address(0), address(gateway)); - messageQueue.setL2BaseFee(L2_GAS_PRICE); + _setL2BaseFee(L2_GAS_PRICE); // Prepare token balances l1Token.mint(address(this), type(uint128).max); diff --git a/src/test/lido/L1LidoGateway.t.sol b/src/test/lido/L1LidoGateway.t.sol index 6e2ff277..737cc6d3 100644 --- a/src/test/lido/L1LidoGateway.t.sol +++ b/src/test/lido/L1LidoGateway.t.sol @@ -330,6 +330,7 @@ contract L1LidoGatewayTest is L1GatewayTestBase { _depositERC20(true, 2, amount, recipient, dataToCall, gasLimit, feePerGas); } + /* function testDropMessage(uint256 amount, address recipient) public { hevm.assume(recipient != address(0)); @@ -409,6 +410,7 @@ contract L1LidoGatewayTest is L1GatewayTestBase { assertEq(gatewayBalance - amount, l1Token.balanceOf(address(gateway))); assertEq(balance + amount, l1Token.balanceOf(address(this))); } + */ function testFinalizeWithdrawERC20( address sender, @@ -522,7 +524,7 @@ contract L1LidoGatewayTest is L1GatewayTestBase { amount = bound(amount, 1, l1Token.balanceOf(address(this))); gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit); feePerGas = bound(feePerGas, 0, 1000); - messageQueue.setL2BaseFee(feePerGas); + _setL2BaseFee(feePerGas); feePerGas = feePerGas * gasLimit; // revert when reentrant