diff --git a/hardhat-test/GasSwap.spec.ts b/hardhat-test/GasSwap.spec.ts deleted file mode 100644 index 4c98ab4a..00000000 --- a/hardhat-test/GasSwap.spec.ts +++ /dev/null @@ -1,329 +0,0 @@ -/* eslint-disable node/no-unpublished-import */ -/* eslint-disable node/no-missing-import */ -import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { expect } from "chai"; -import { MaxUint256, Signature, ZeroAddress, ZeroHash, toBigInt } from "ethers"; -import { ethers } from "hardhat"; - -import { GasSwap, ERC2771Forwarder, MockERC20, MockGasSwapTarget } from "../typechain"; - -describe("GasSwap.spec", async () => { - let deployer: HardhatEthersSigner; - let signer: HardhatEthersSigner; - - let forwarder: ERC2771Forwarder; - let swap: GasSwap; - let target: MockGasSwapTarget; - let token: MockERC20; - - beforeEach(async () => { - [deployer, signer] = await ethers.getSigners(); - - const ERC2771Forwarder = await ethers.getContractFactory("ERC2771Forwarder", deployer); - forwarder = await ERC2771Forwarder.deploy("ERC2771Forwarder"); - - const GasSwap = await ethers.getContractFactory("GasSwap", deployer); - swap = await GasSwap.deploy(forwarder.getAddress()); - - const MockGasSwapTarget = await ethers.getContractFactory("MockGasSwapTarget", deployer); - target = await MockGasSwapTarget.deploy(); - - const MockERC20 = await ethers.getContractFactory("MockERC20", deployer); - token = await MockERC20.deploy("x", "y", 18); - }); - - context("auth", async () => { - it("should initialize correctly", async () => { - expect(await swap.owner()).to.eq(deployer.address); - }); - - context("#updateFeeRatio", async () => { - it("should revert, when non-owner call", async () => { - await expect(swap.connect(signer).updateFeeRatio(1)).to.revertedWith("Ownable: caller is not the owner"); - }); - - it("should succeed", async () => { - expect(await swap.feeRatio()).to.eq(ZeroAddress); - await expect(swap.updateFeeRatio(100)).to.emit(swap, "UpdateFeeRatio").withArgs(100); - expect(await swap.feeRatio()).to.eq(100); - }); - }); - - context("#updateApprovedTarget", async () => { - it("should revert, when non-owner call", async () => { - await expect(swap.connect(signer).updateApprovedTarget(target.getAddress(), false)).to.revertedWith( - "Ownable: caller is not the owner" - ); - }); - - it("should succeed", async () => { - expect(await swap.approvedTargets(target.getAddress())).to.eq(false); - await expect(swap.updateApprovedTarget(target.getAddress(), true)) - .to.emit(swap, "UpdateApprovedTarget") - .withArgs(await target.getAddress(), true); - expect(await swap.approvedTargets(target.getAddress())).to.eq(true); - await expect(swap.updateApprovedTarget(target.getAddress(), false)) - .to.emit(swap, "UpdateApprovedTarget") - .withArgs(await target.getAddress(), false); - expect(await swap.approvedTargets(target.getAddress())).to.eq(false); - }); - }); - - context("#withdraw", async () => { - it("should revert, when non-owner call", async () => { - await expect(swap.connect(signer).withdraw(ZeroAddress, 0)).to.revertedWith("Ownable: caller is not the owner"); - }); - - it("should succeed, when withdraw ETH", async () => { - await deployer.sendTransaction({ to: swap.getAddress(), value: ethers.parseEther("1") }); - const balanceBefore = await ethers.provider.getBalance(deployer.address); - const tx = await swap.withdraw(ZeroAddress, ethers.parseEther("1")); - const receipt = await tx.wait(); - const balanceAfter = await ethers.provider.getBalance(deployer.address); - expect(balanceAfter - balanceBefore).to.eq(ethers.parseEther("1") - receipt!.gasUsed * receipt!.gasPrice); - }); - - it("should succeed, when withdraw token", async () => { - await token.mint(swap.getAddress(), ethers.parseEther("1")); - const balanceBefore = await token.balanceOf(deployer.address); - await swap.withdraw(token.getAddress(), ethers.parseEther("1")); - const balanceAfter = await token.balanceOf(deployer.address); - expect(balanceAfter - balanceBefore).to.eq(ethers.parseEther("1")); - }); - }); - }); - - const permit = async (amount: bigint) => { - const value = { - owner: signer.address, - spender: await swap.getAddress(), - value: amount, - nonce: await token.nonces(signer.address), - deadline: MaxUint256, - }; - - const domain = { - name: await token.name(), - version: "1", - chainId: (await ethers.provider.getNetwork()).chainId, - verifyingContract: await token.getAddress(), - }; - - const types = { - Permit: [ - { - name: "owner", - type: "address", - }, - { - name: "spender", - type: "address", - }, - { - name: "value", - type: "uint256", - }, - { - name: "nonce", - type: "uint256", - }, - { - name: "deadline", - type: "uint256", - }, - ], - }; - - const signature = Signature.from(await signer.signTypedData(domain, types, value)); - return signature; - }; - - context("swap", async () => { - it("should revert, when target not approved", async () => { - await expect( - swap.swap( - { - token: token.getAddress(), - value: 0, - deadline: 0, - r: ZeroHash, - s: ZeroHash, - v: 0, - }, - { - target: target.getAddress(), - data: "0x", - minOutput: 0, - } - ) - ).to.revertedWith("target not approved"); - }); - - it("should revert, when insufficient output amount", async () => { - const amountIn = ethers.parseEther("1"); - const amountOut = ethers.parseEther("2"); - await token.mint(signer.address, amountIn); - await deployer.sendTransaction({ to: target.getAddress(), value: amountOut }); - const signature = await permit(amountIn); - - await target.setToken(token.getAddress()); - await target.setAmountIn(amountIn); - - await swap.updateApprovedTarget(target.getAddress(), true); - await expect( - swap.connect(signer).swap( - { - token: await token.getAddress(), - value: amountIn, - deadline: MaxUint256, - r: signature.r, - s: signature.s, - v: signature.v, - }, - { - target: target.getAddress(), - data: "0x8119c065", - minOutput: amountOut + 1n, - } - ) - ).to.revertedWith("insufficient output amount"); - }); - - for (const refundRatio of [0n, 1n, 5n]) { - for (const feeRatio of ["0", "5", "50"]) { - it(`should succeed, when swap by signer directly, with feeRatio[${feeRatio}%] refundRatio[${refundRatio}%]`, async () => { - const amountIn = ethers.parseEther("1"); - const amountOut = ethers.parseEther("2"); - await token.mint(signer.address, amountIn); - await deployer.sendTransaction({ to: target.getAddress(), value: amountOut }); - const signature = await permit(amountIn); - - await target.setToken(token.getAddress()); - await target.setAmountIn(amountIn); - await target.setRefund((amountIn * refundRatio) / 100n); - - await swap.updateApprovedTarget(target.getAddress(), true); - await swap.updateFeeRatio(ethers.parseEther(feeRatio) / 100n); - const fee = (amountOut * toBigInt(feeRatio)) / 100n; - - const balanceBefore = await ethers.provider.getBalance(signer.address); - const tx = await swap.connect(signer).swap( - { - token: await token.getAddress(), - value: amountIn, - deadline: MaxUint256, - r: signature.r, - s: signature.s, - v: signature.v, - }, - { - target: target.getAddress(), - data: "0x8119c065", - minOutput: amountOut - fee, - } - ); - const receipt = await tx.wait(); - const balanceAfter = await ethers.provider.getBalance(signer.address); - expect(balanceAfter - balanceBefore).to.eq(amountOut - fee - receipt!.gasUsed * receipt!.gasPrice); - expect(await token.balanceOf(signer.address)).to.eq((amountIn * refundRatio) / 100n); - }); - - it(`should succeed, when swap by signer with forwarder, with feeRatio[${feeRatio}%] refundRatio[${refundRatio}%]`, async () => { - const amountIn = ethers.parseEther("1"); - const amountOut = ethers.parseEther("2"); - await token.mint(signer.address, amountIn); - await deployer.sendTransaction({ to: await target.getAddress(), value: amountOut }); - const permitSignature = await permit(amountIn); - - await target.setToken(token.getAddress()); - await target.setAmountIn(amountIn); - await target.setRefund((amountIn * refundRatio) / 100n); - - await swap.updateApprovedTarget(target.getAddress(), true); - await swap.updateFeeRatio(ethers.parseEther(feeRatio) / 100n); - const fee = (amountOut * toBigInt(feeRatio)) / 100n; - - const reqWithoutSignature = { - from: signer.address, - to: await swap.getAddress(), - value: 0n, - gas: 1000000, - nonce: await forwarder.nonces(signer.address), - deadline: 2000000000, - data: swap.interface.encodeFunctionData("swap", [ - { - token: await token.getAddress(), - value: amountIn, - deadline: MaxUint256, - r: permitSignature.r, - s: permitSignature.s, - v: permitSignature.v, - }, - { - target: await target.getAddress(), - data: "0x8119c065", - minOutput: amountOut - fee, - }, - ]), - }; - - const signature = await signer.signTypedData( - { - name: "ERC2771Forwarder", - version: "1", - chainId: (await ethers.provider.getNetwork()).chainId, - verifyingContract: await forwarder.getAddress(), - }, - { - ForwardRequest: [ - { - name: "from", - type: "address", - }, - { - name: "to", - type: "address", - }, - { - name: "value", - type: "uint256", - }, - { - name: "gas", - type: "uint256", - }, - { - name: "nonce", - type: "uint256", - }, - { - name: "deadline", - type: "uint48", - }, - { - name: "data", - type: "bytes", - }, - ], - }, - reqWithoutSignature - ); - - const balanceBefore = await ethers.provider.getBalance(signer.address); - await forwarder.execute({ - from: reqWithoutSignature.from, - to: reqWithoutSignature.to, - value: reqWithoutSignature.value, - gas: reqWithoutSignature.gas, - deadline: reqWithoutSignature.deadline, - data: reqWithoutSignature.data, - signature, - }); - const balanceAfter = await ethers.provider.getBalance(signer.address); - expect(balanceAfter - balanceBefore).to.eq(amountOut - fee); - expect(await token.balanceOf(signer.address)).to.eq((amountIn * refundRatio) / 100n); - }); - } - } - }); -}); diff --git a/hardhat-test/ZkEvmVerifierV2.spec.ts b/hardhat-test/ZkEvmVerifierV2.spec.ts index 72fd9dd1..286ca3d1 100644 --- a/hardhat-test/ZkEvmVerifierV2.spec.ts +++ b/hardhat-test/ZkEvmVerifierV2.spec.ts @@ -122,7 +122,7 @@ describe("ZkEvmVerifierV2", async () => { await expect(zkEvmVerifier.verify(proof, publicInputs.reverse())).to.reverted; }); - it("should succeed when call through ScrollChain", async () => { + it.skip("should succeed when call through ScrollChain", async () => { const proof = hexlify(fs.readFileSync(`./hardhat-test/testdata/plonk-verifier/${version}_proof.data`)); const lastFinalizedBatchIndex = 1; diff --git a/scripts/foundry/InitializeL1ScrollOwner.s.sol b/scripts/foundry/InitializeL1ScrollOwner.s.sol index 4e07eb8d..0055df38 100644 --- a/scripts/foundry/InitializeL1ScrollOwner.s.sol +++ b/scripts/foundry/InitializeL1ScrollOwner.s.sol @@ -180,11 +180,6 @@ contract InitializeL1ScrollOwner is Script { _selectors[0] = ScrollChain.addSequencer.selector; _selectors[1] = ScrollChain.addProver.selector; owner.updateAccess(L1_SCROLL_CHAIN_PROXY_ADDR, _selectors, TIMELOCK_1DAY_DELAY_ROLE, true); - - // delay 7 day, scroll multisig - _selectors = new bytes4[](1); - _selectors[0] = ScrollChain.updateMaxNumTxInChunk.selector; - owner.updateAccess(L1_SCROLL_CHAIN_PROXY_ADDR, _selectors, TIMELOCK_7DAY_DELAY_ROLE, true); } function configL1MessageQueue() internal { @@ -207,11 +202,6 @@ contract InitializeL1ScrollOwner is Script { _selectors[0] = ScrollMessengerBase.setPause.selector; owner.updateAccess(L1_SCROLL_MESSENGER_PROXY_ADDR, _selectors, SCROLL_MULTISIG_NO_DELAY_ROLE, true); owner.updateAccess(L1_SCROLL_MESSENGER_PROXY_ADDR, _selectors, EMERGENCY_MULTISIG_NO_DELAY_ROLE, true); - - // delay 1 day, scroll multisig - _selectors = new bytes4[](1); - _selectors[0] = L1ScrollMessenger.updateMaxReplayTimes.selector; - owner.updateAccess(L1_SCROLL_MESSENGER_PROXY_ADDR, _selectors, TIMELOCK_1DAY_DELAY_ROLE, true); } function configL2GasPriceOracle() internal { diff --git a/src/L1/IL1ScrollMessenger.sol b/src/L1/IL1ScrollMessenger.sol index 4a8f2b9f..0af44027 100644 --- a/src/L1/IL1ScrollMessenger.sol +++ b/src/L1/IL1ScrollMessenger.sol @@ -5,15 +5,6 @@ pragma solidity ^0.8.24; import {IScrollMessenger} from "../libraries/IScrollMessenger.sol"; interface IL1ScrollMessenger is IScrollMessenger { - /********** - * Events * - **********/ - - /// @notice Emitted when the maximum number of times each message can be replayed is updated. - /// @param oldMaxReplayTimes The old maximum number of times each message can be replayed. - /// @param newMaxReplayTimes The new maximum number of times each message can be replayed. - event UpdateMaxReplayTimes(uint256 oldMaxReplayTimes, uint256 newMaxReplayTimes); - /*********** * Structs * ***********/ @@ -62,18 +53,4 @@ interface IL1ScrollMessenger is IScrollMessenger { uint32 newGasLimit, address refundAddress ) external payable; - - /// @notice Drop a skipped message. - /// @param from The address of the sender of the message. - /// @param to The address of the recipient of the message. - /// @param value The msg.value passed to the message call. - /// @param messageNonce The nonce for the message to drop. - /// @param message The content of the message. - function dropMessage( - address from, - address to, - uint256 value, - uint256 messageNonce, - bytes memory message - ) external; } diff --git a/src/L1/L1ScrollMessenger.sol b/src/L1/L1ScrollMessenger.sol index a54f467c..a023dcf0 100644 --- a/src/L1/L1ScrollMessenger.sol +++ b/src/L1/L1ScrollMessenger.sol @@ -10,8 +10,6 @@ import {IScrollMessenger} from "../libraries/IScrollMessenger.sol"; import {ScrollMessengerBase} from "../libraries/ScrollMessengerBase.sol"; import {WithdrawTrieVerifier} from "../libraries/verifier/WithdrawTrieVerifier.sol"; -import {IMessageDropCallback} from "../libraries/callbacks/IMessageDropCallback.sol"; - // solhint-disable avoid-low-level-calls // solhint-disable not-rely-on-time // solhint-disable reason-string @@ -78,16 +76,21 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { mapping(bytes32 => bool) public isL2MessageExecuted; /// @notice Mapping from L1 message hash to drop status. - mapping(bytes32 => bool) public isL1MessageDropped; + /// @custom:deprecated This is no longer used. + // slither-disable-next-line uninitialized-state + mapping(bytes32 => bool) private __isL1MessageDropped; /// @dev The storage slot used as Rollup contract, which is deprecated now. + /// @custom:deprecated This is no longer used. address private __rollup; /// @dev The storage slot used as L1MessageQueue contract, which is deprecated now. + /// @custom:deprecated This is no longer used. address private __messageQueue; - /// @notice The maximum number of times each L1 message can be replayed. - uint256 public maxReplayTimes; + /// @dev The maximum number of times each L1 message can be replayed. + /// @custom:deprecated This is no longer used. + uint256 private __maxReplayTimes; /// @notice Mapping from L1 message hash to replay state. mapping(bytes32 => ReplayState) public replayStates; @@ -142,9 +145,6 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { __rollup = _rollup; __messageQueue = _messageQueue; - - maxReplayTimes = 3; - emit UpdateMaxReplayTimes(0, 3); } /***************************** @@ -236,7 +236,7 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { require(messageSendTimestamp[_xDomainCalldataHash] > 0, "Provided message has not been enqueued"); // cannot replay dropped message - require(!isL1MessageDropped[_xDomainCalldataHash], "Message already dropped"); + require(!__isL1MessageDropped[_xDomainCalldataHash], "Message already dropped"); // compute and deduct the messaging fee to fee vault. uint256 _fee = IL1MessageQueueV2(messageQueueV2).estimateCrossDomainMessageFee(_newGasLimit); @@ -264,8 +264,6 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { } _replayState.lastIndex = uint128(_nextQueueIndex); - // update replay times - require(_replayState.times < maxReplayTimes, "Exceed maximum replay times"); unchecked { _replayState.times += 1; } @@ -281,78 +279,6 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { } } - /// @inheritdoc IL1ScrollMessenger - /// @dev Since we don't skip any messages in `L1MessageQueueV2`, only messages from `L1MessageQueueV1` can be dropped. - function dropMessage( - address _from, - address _to, - uint256 _value, - uint256 _messageNonce, - bytes memory _message - ) external override whenNotPaused notInExecution { - // The criteria for dropping a message: - // 1. The message is a L1 message. - // 2. The message has not been dropped before. - // 3. the message and all of its replacement are finalized in L1. - // 4. the message and all of its replacement are skipped. - // - // Possible denial of service attack: - // + replayMessage is called every time someone want to drop the message. - // + replayMessage is called so many times for a skipped message, thus results a long list. - // - // We limit the number of `replayMessage` calls of each message, which may solve the above problem. - - // check message exists - bytes memory _xDomainCalldata = _encodeXDomainCalldata(_from, _to, _value, _messageNonce, _message); - bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata); - require(messageSendTimestamp[_xDomainCalldataHash] > 0, "Provided message has not been enqueued"); - - // check message not dropped - require(!isL1MessageDropped[_xDomainCalldataHash], "Message already dropped"); - - // check message is finalized - uint256 _lastIndex = replayStates[_xDomainCalldataHash].lastIndex; - if (_lastIndex == 0) _lastIndex = _messageNonce; - - // check message is skipped and drop it. - // @note If the list is very long, the message may never be dropped. - while (true) { - // If the `_lastIndex` is from `messageQueueV2`, it will revert in `messageQueueV1.dropCrossDomainMessage`. - // call to messageQueueV1 is safe. - // slither-disable-next-line reentrancy-no-eth - IL1MessageQueueV1(messageQueueV1).dropCrossDomainMessage(_lastIndex); - _lastIndex = prevReplayIndex[_lastIndex]; - if (_lastIndex == 0) break; - unchecked { - _lastIndex = _lastIndex - 1; - } - } - - isL1MessageDropped[_xDomainCalldataHash] = true; - - // set execution context - xDomainMessageSender = ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER; - // xDomainMessageSender serves as reentrancy guard (notInExecution modifier). - // slither-disable-next-line reentrancy-eth - IMessageDropCallback(_from).onDropMessage{value: _value}(_message); - // clear execution context - xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; - } - - /************************ - * Restricted Functions * - ************************/ - - /// @notice Update max replay times. - /// @dev This function can only called by contract owner. - /// @param _newMaxReplayTimes The new max replay times. - function updateMaxReplayTimes(uint256 _newMaxReplayTimes) external onlyOwner { - uint256 _oldMaxReplayTimes = maxReplayTimes; - maxReplayTimes = _newMaxReplayTimes; - - emit UpdateMaxReplayTimes(_oldMaxReplayTimes, _newMaxReplayTimes); - } - /********************** * Internal Functions * **********************/ diff --git a/src/L1/gateways/L1ERC1155Gateway.sol b/src/L1/gateways/L1ERC1155Gateway.sol index 794d43ce..9b0b4162 100644 --- a/src/L1/gateways/L1ERC1155Gateway.sol +++ b/src/L1/gateways/L1ERC1155Gateway.sol @@ -9,7 +9,6 @@ import {IL2ERC1155Gateway} from "../../L2/gateways/IL2ERC1155Gateway.sol"; import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol"; import {IL1ERC1155Gateway} from "./IL1ERC1155Gateway.sol"; -import {IMessageDropCallback} from "../../libraries/callbacks/IMessageDropCallback.sol"; import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; /// @title L1ERC1155Gateway @@ -19,7 +18,7 @@ import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; /// NFT will be transfer to the recipient directly. /// /// This will be changed if we have more specific scenarios. -contract L1ERC1155Gateway is ERC1155HolderUpgradeable, ScrollGatewayBase, IL1ERC1155Gateway, IMessageDropCallback { +contract L1ERC1155Gateway is ERC1155HolderUpgradeable, ScrollGatewayBase, IL1ERC1155Gateway { /********** * Events * **********/ @@ -139,31 +138,6 @@ contract L1ERC1155Gateway is ERC1155HolderUpgradeable, ScrollGatewayBase, IL1ERC emit FinalizeBatchWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenIds, _amounts); } - /// @inheritdoc IMessageDropCallback - function onDropMessage(bytes calldata _message) external payable virtual onlyInDropContext nonReentrant { - require(msg.value == 0, "nonzero msg.value"); - - if (bytes4(_message[0:4]) == IL2ERC1155Gateway.finalizeDepositERC1155.selector) { - (address _token, , address _sender, , uint256 _tokenId, uint256 _amount) = abi.decode( - _message[4:], - (address, address, address, address, uint256, uint256) - ); - IERC1155Upgradeable(_token).safeTransferFrom(address(this), _sender, _tokenId, _amount, ""); - - emit RefundERC1155(_token, _sender, _tokenId, _amount); - } else if (bytes4(_message[0:4]) == IL2ERC1155Gateway.finalizeBatchDepositERC1155.selector) { - (address _token, , address _sender, , uint256[] memory _tokenIds, uint256[] memory _amounts) = abi.decode( - _message[4:], - (address, address, address, address, uint256[], uint256[]) - ); - IERC1155Upgradeable(_token).safeBatchTransferFrom(address(this), _sender, _tokenIds, _amounts, ""); - - emit BatchRefundERC1155(_token, _sender, _tokenIds, _amounts); - } else { - revert("invalid selector"); - } - } - /************************ * Restricted Functions * ************************/ diff --git a/src/L1/gateways/L1ERC20Gateway.sol b/src/L1/gateways/L1ERC20Gateway.sol index 42c51144..c9c5820a 100644 --- a/src/L1/gateways/L1ERC20Gateway.sol +++ b/src/L1/gateways/L1ERC20Gateway.sol @@ -10,12 +10,11 @@ import {IL1GatewayRouter} from "./IL1GatewayRouter.sol"; import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol"; import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; -import {IMessageDropCallback} from "../../libraries/callbacks/IMessageDropCallback.sol"; /// @title L1ERC20Gateway /// @notice The `L1ERC20Gateway` as a base contract for ERC20 gateways in L1. /// It has implementation of common used functions for ERC20 gateways. -abstract contract L1ERC20Gateway is IL1ERC20Gateway, IMessageDropCallback, ScrollGatewayBase { +abstract contract L1ERC20Gateway is IL1ERC20Gateway, ScrollGatewayBase { using SafeERC20Upgradeable for IERC20Upgradeable; /************* @@ -79,25 +78,6 @@ abstract contract L1ERC20Gateway is IL1ERC20Gateway, IMessageDropCallback, Scrol emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data); } - /// @inheritdoc IMessageDropCallback - function onDropMessage(bytes calldata _message) external payable virtual onlyInDropContext nonReentrant { - // _message should start with 0x8431f5c1 => finalizeDepositERC20(address,address,address,address,uint256,bytes) - require(bytes4(_message[0:4]) == IL2ERC20Gateway.finalizeDepositERC20.selector, "invalid selector"); - - // decode (token, receiver, amount) - (address _token, , address _receiver, , uint256 _amount, ) = abi.decode( - _message[4:], - (address, address, address, address, uint256, bytes) - ); - - // do dome check for each custom gateway - _beforeDropMessage(_token, _receiver, _amount); - - IERC20Upgradeable(_token).safeTransfer(_receiver, _amount); - - emit RefundERC20(_token, _receiver, _amount); - } - /********************** * Internal Functions * **********************/ diff --git a/src/L1/gateways/L1ERC721Gateway.sol b/src/L1/gateways/L1ERC721Gateway.sol index f8ee6113..9055512b 100644 --- a/src/L1/gateways/L1ERC721Gateway.sol +++ b/src/L1/gateways/L1ERC721Gateway.sol @@ -9,7 +9,6 @@ import {IL2ERC721Gateway} from "../../L2/gateways/IL2ERC721Gateway.sol"; import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol"; import {IL1ERC721Gateway} from "./IL1ERC721Gateway.sol"; -import {IMessageDropCallback} from "../../libraries/callbacks/IMessageDropCallback.sol"; import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; /// @title L1ERC721Gateway @@ -19,7 +18,7 @@ import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; /// NFT will be transfer to the recipient directly. /// /// This will be changed if we have more specific scenarios. -contract L1ERC721Gateway is ERC721HolderUpgradeable, ScrollGatewayBase, IL1ERC721Gateway, IMessageDropCallback { +contract L1ERC721Gateway is ERC721HolderUpgradeable, ScrollGatewayBase, IL1ERC721Gateway { /********** * Events * **********/ @@ -137,32 +136,6 @@ contract L1ERC721Gateway is ERC721HolderUpgradeable, ScrollGatewayBase, IL1ERC72 emit FinalizeBatchWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenIds); } - /// @inheritdoc IMessageDropCallback - function onDropMessage(bytes calldata _message) external payable virtual onlyInDropContext nonReentrant { - require(msg.value == 0, "nonzero msg.value"); - - if (bytes4(_message[0:4]) == IL2ERC721Gateway.finalizeDepositERC721.selector) { - (address _token, , address _receiver, , uint256 _tokenId) = abi.decode( - _message[4:], - (address, address, address, address, uint256) - ); - IERC721Upgradeable(_token).safeTransferFrom(address(this), _receiver, _tokenId); - - emit RefundERC721(_token, _receiver, _tokenId); - } else if (bytes4(_message[0:4]) == IL2ERC721Gateway.finalizeBatchDepositERC721.selector) { - (address _token, , address _receiver, , uint256[] memory _tokenIds) = abi.decode( - _message[4:], - (address, address, address, address, uint256[]) - ); - for (uint256 i = 0; i < _tokenIds.length; i++) { - IERC721Upgradeable(_token).safeTransferFrom(address(this), _receiver, _tokenIds[i]); - } - emit BatchRefundERC721(_token, _receiver, _tokenIds); - } else { - revert("invalid selector"); - } - } - /************************ * Restricted Functions * ************************/ diff --git a/src/L1/gateways/L1ETHGateway.sol b/src/L1/gateways/L1ETHGateway.sol index 09c41ff9..e6e4a5a4 100644 --- a/src/L1/gateways/L1ETHGateway.sol +++ b/src/L1/gateways/L1ETHGateway.sol @@ -6,7 +6,6 @@ import {IL2ETHGateway} from "../../L2/gateways/IL2ETHGateway.sol"; import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol"; import {IL1ETHGateway} from "./IL1ETHGateway.sol"; -import {IMessageDropCallback} from "../../libraries/callbacks/IMessageDropCallback.sol"; import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; // solhint-disable avoid-low-level-calls @@ -16,7 +15,7 @@ import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; /// finalize withdraw ETH from layer 2. /// @dev The deposited ETH tokens are held in this gateway. On finalizing withdraw, the corresponding /// ETH will be transfer to the recipient directly. -contract L1ETHGateway is ScrollGatewayBase, IL1ETHGateway, IMessageDropCallback { +contract L1ETHGateway is ScrollGatewayBase, IL1ETHGateway { /*************** * Constructor * ***************/ @@ -100,24 +99,6 @@ contract L1ETHGateway is ScrollGatewayBase, IL1ETHGateway, IMessageDropCallback emit FinalizeWithdrawETH(_from, _to, _amount, _data); } - /// @inheritdoc IMessageDropCallback - function onDropMessage(bytes calldata _message) external payable virtual onlyInDropContext nonReentrant { - // _message should start with 0x232e8748 => finalizeDepositETH(address,address,uint256,bytes) - require(bytes4(_message[0:4]) == IL2ETHGateway.finalizeDepositETH.selector, "invalid selector"); - - // decode (receiver, amount) - (address _receiver, , uint256 _amount, ) = abi.decode(_message[4:], (address, address, uint256, bytes)); - - require(_amount == msg.value, "msg.value mismatch"); - - // no reentrancy risk (nonReentrant modifier). - // slither-disable-next-line arbitrary-send-eth - (bool _success, ) = _receiver.call{value: _amount}(""); - require(_success, "ETH transfer failed"); - - emit RefundETH(_receiver, _amount); - } - /********************** * Internal Functions * **********************/ diff --git a/src/L1/rollup/IL1MessageQueueV1.sol b/src/L1/rollup/IL1MessageQueueV1.sol index e90b60c7..41c2de8e 100644 --- a/src/L1/rollup/IL1MessageQueueV1.sol +++ b/src/L1/rollup/IL1MessageQueueV1.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; +/// @custom:deprecated This contract is no longer used in production. interface IL1MessageQueueV1 { /********** * Events * @@ -63,26 +64,32 @@ interface IL1MessageQueueV1 { *************************/ /// @notice The start index of all pending inclusion messages. + /// @custom:deprecated Please use `IL1MessageQueueV2.pendingQueueIndex` instead. function pendingQueueIndex() external view returns (uint256); /// @notice The start index of all unfinalized messages. /// @dev All messages from `nextUnfinalizedQueueIndex` to `pendingQueueIndex-1` are committed but not finalized. + /// @custom:deprecated Please use `IL1MessageQueueV2.nextUnfinalizedQueueIndex` instead. function nextUnfinalizedQueueIndex() external view returns (uint256); /// @notice Return the index of next appended message. /// @dev Also the total number of appended messages. + /// @custom:deprecated Please use `IL1MessageQueueV2.nextCrossDomainMessageIndex` instead. function nextCrossDomainMessageIndex() external view returns (uint256); /// @notice Return the message of in `queueIndex`. /// @param queueIndex The index to query. + /// @custom:deprecated Please use `IL1MessageQueueV2.getCrossDomainMessage` instead. function getCrossDomainMessage(uint256 queueIndex) external view returns (bytes32); /// @notice Return the amount of ETH should pay for cross domain message. /// @param gasLimit Gas limit required to complete the message relay on L2. + /// @custom:deprecated Please use `IL1MessageQueueV2.estimateCrossDomainMessageFee` instead. function estimateCrossDomainMessageFee(uint256 gasLimit) external view returns (uint256); /// @notice Return the amount of intrinsic gas fee should pay for cross domain message. /// @param _calldata The calldata of L1-initiated transaction. + /// @custom:deprecated Please use `IL1MessageQueueV2.calculateIntrinsicGasFee` instead. function calculateIntrinsicGasFee(bytes calldata _calldata) external view returns (uint256); /// @notice Return the hash of a L1 message. @@ -92,6 +99,7 @@ interface IL1MessageQueueV1 { /// @param target The address of target. /// @param gasLimit The gas limit provided. /// @param data The calldata passed to target address. + /// @custom:deprecated Please use `IL1MessageQueueV2.computeTransactionHash` instead. function computeTransactionHash( address sender, uint256 queueIndex, @@ -103,10 +111,12 @@ interface IL1MessageQueueV1 { /// @notice Return whether the message is skipped. /// @param queueIndex The queue index of the message to check. + /// @custom:deprecated function isMessageSkipped(uint256 queueIndex) external view returns (bool); /// @notice Return whether the message is dropped. /// @param queueIndex The queue index of the message to check. + /// @custom:deprecated function isMessageDropped(uint256 queueIndex) external view returns (bool); /***************************** @@ -117,6 +127,7 @@ interface IL1MessageQueueV1 { /// @param target The address of target contract to call in L2. /// @param gasLimit The maximum gas should be used for relay this message in L2. /// @param data The calldata passed to target contract. + /// @custom:deprecated Please use `IL1MessageQueueV2.appendCrossDomainMessage` instead. function appendCrossDomainMessage( address target, uint256 gasLimit, @@ -130,6 +141,7 @@ interface IL1MessageQueueV1 { /// @param value The value passed /// @param gasLimit The maximum gas should be used for this transaction in L2. /// @param data The calldata passed to target contract. + /// @custom:deprecated Please use `IL1MessageQueueV2.appendEnforcedTransaction` instead. function appendEnforcedTransaction( address sender, address target, @@ -146,6 +158,7 @@ interface IL1MessageQueueV1 { /// @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. + /// @custom:deprecated function popCrossDomainMessage( uint256 startIndex, uint256 count, @@ -157,12 +170,15 @@ interface IL1MessageQueueV1 { /// @dev We can only reset unfinalized popped messages. /// /// @param startIndex The start index to reset. + /// @custom:deprecated function resetPoppedCrossDomainMessage(uint256 startIndex) external; /// @notice Finalize status of popped messages. /// @param newFinalizedQueueIndexPlusOne The index of message to finalize plus one. + /// @custom:deprecated function finalizePoppedCrossDomainMessage(uint256 newFinalizedQueueIndexPlusOne) external; /// @notice Drop a skipped message from the queue. + /// @custom:deprecated function dropCrossDomainMessage(uint256 index) external; } diff --git a/src/L1/rollup/IL1MessageQueueWithGasPriceOracle.sol b/src/L1/rollup/IL1MessageQueueWithGasPriceOracle.sol index c4f697d9..88937ffd 100644 --- a/src/L1/rollup/IL1MessageQueueWithGasPriceOracle.sol +++ b/src/L1/rollup/IL1MessageQueueWithGasPriceOracle.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import {IL1MessageQueueV1} from "./IL1MessageQueueV1.sol"; +/// @custom:deprecated This contract is no longer used in production. interface IL1MessageQueueWithGasPriceOracle is IL1MessageQueueV1 { /********** * Events * diff --git a/src/L1/rollup/IL2GasPriceOracle.sol b/src/L1/rollup/IL2GasPriceOracle.sol index 773c1c95..e7da38ec 100644 --- a/src/L1/rollup/IL2GasPriceOracle.sol +++ b/src/L1/rollup/IL2GasPriceOracle.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; +/// @custom:deprecated This contract is no longer used in production. interface IL2GasPriceOracle { /// @notice Return the latest known l2 base fee. function l2BaseFee() external view returns (uint256); diff --git a/src/L1/rollup/IScrollChain.sol b/src/L1/rollup/IScrollChain.sol index 453dd169..b22a061b 100644 --- a/src/L1/rollup/IScrollChain.sol +++ b/src/L1/rollup/IScrollChain.sol @@ -41,11 +41,6 @@ interface IScrollChain { /// @param status The status of the account updated. event UpdateProver(address indexed account, bool status); - /// @notice Emitted when the value of `maxNumTxInChunk` is updated. - /// @param oldMaxNumTxInChunk The old value of `maxNumTxInChunk`. - /// @param newMaxNumTxInChunk The new value of `maxNumTxInChunk`. - event UpdateMaxNumTxInChunk(uint256 oldMaxNumTxInChunk, uint256 newMaxNumTxInChunk); - /// @notice Emitted when we enter or exit enforced batch mode. /// @param enabled True if we are entering enforced batch mode, false otherwise. /// @param lastCommittedBatchIndex The index of the last committed batch. @@ -78,26 +73,6 @@ interface IScrollChain { * Public Mutating Functions * *****************************/ - /// @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 version The version of current batch. - /// @param parentBatchHeader The header of parent batch. - /// @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. - /// @param blobDataProof The proof for blob data. - function commitBatchWithBlobProof( - uint8 version, - bytes calldata parentBatchHeader, - bytes[] memory chunks, - bytes calldata skippedL1MessageBitmap, - bytes calldata blobDataProof - ) external; - /// @notice Commit one or more batches after the EuclidV2 upgrade. /// @param version The version of the committed batches. /// @param parentBatchHash The hash of parent batch. @@ -114,18 +89,6 @@ interface IScrollChain { /// @param batchHeader The header of the last batch we want to keep. function revertBatch(bytes calldata batchHeader) 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 aggrProof The aggregation proof for current bundle. - function finalizeBundleWithProof( - bytes calldata batchHeader, - bytes32 postStateRoot, - bytes32 withdrawRoot, - bytes calldata aggrProof - ) external; - /// @notice Finalize a list of committed batches (i.e. bundle) on layer 1 after the EuclidV2 upgrade. /// @param batchHeader The header of the last batch in this bundle. /// @param totalL1MessagesPoppedOverall The number of messages processed after this bundle. diff --git a/src/L1/rollup/L1MessageQueueV1.sol b/src/L1/rollup/L1MessageQueueV1.sol index bc24be86..dc5d8ffb 100644 --- a/src/L1/rollup/L1MessageQueueV1.sol +++ b/src/L1/rollup/L1MessageQueueV1.sol @@ -17,6 +17,7 @@ import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol" /// @title L1MessageQueue /// @notice This contract will hold all L1 to L2 messages. /// Each appended message is assigned with a unique and increasing `uint256` index. +/// @custom:deprecated This contract is no longer used in production. contract L1MessageQueueV1 is OwnableUpgradeable, IL1MessageQueueV1 { using BitMapsUpgradeable for BitMapsUpgradeable.BitMap; diff --git a/src/L1/rollup/L1MessageQueueV1WithGasPriceOracle.sol b/src/L1/rollup/L1MessageQueueV1WithGasPriceOracle.sol index 40873d94..b84f2bd0 100644 --- a/src/L1/rollup/L1MessageQueueV1WithGasPriceOracle.sol +++ b/src/L1/rollup/L1MessageQueueV1WithGasPriceOracle.sol @@ -9,6 +9,7 @@ import {IL2GasPriceOracle} from "./IL2GasPriceOracle.sol"; import {L1MessageQueueV1} from "./L1MessageQueueV1.sol"; +/// @custom:deprecated This contract is no longer used in production. contract L1MessageQueueV1WithGasPriceOracle is L1MessageQueueV1, IL1MessageQueueWithGasPriceOracle { /************* * Constants * diff --git a/src/L1/rollup/L2GasPriceOracle.sol b/src/L1/rollup/L2GasPriceOracle.sol index 4585621a..7fe65029 100644 --- a/src/L1/rollup/L2GasPriceOracle.sol +++ b/src/L1/rollup/L2GasPriceOracle.sol @@ -10,6 +10,7 @@ import {IL2GasPriceOracle} from "./IL2GasPriceOracle.sol"; // solhint-disable reason-string +/// @custom:deprecated This contract is no longer used in production. contract L2GasPriceOracle is OwnableUpgradeable, IL2GasPriceOracle { /********** * Events * diff --git a/src/L1/rollup/ScrollChain.sol b/src/L1/rollup/ScrollChain.sol index 653ada04..c30c7efb 100644 --- a/src/L1/rollup/ScrollChain.sol +++ b/src/L1/rollup/ScrollChain.sol @@ -12,8 +12,6 @@ import {BatchHeaderV0Codec} from "../../libraries/codec/BatchHeaderV0Codec.sol"; import {BatchHeaderV1Codec} from "../../libraries/codec/BatchHeaderV1Codec.sol"; import {BatchHeaderV3Codec} from "../../libraries/codec/BatchHeaderV3Codec.sol"; import {BatchHeaderV7Codec} from "../../libraries/codec/BatchHeaderV7Codec.sol"; -import {ChunkCodecV0} from "../../libraries/codec/ChunkCodecV0.sol"; -import {ChunkCodecV1} from "../../libraries/codec/ChunkCodecV1.sol"; import {IRollupVerifier} from "../../libraries/verifier/IRollupVerifier.sol"; import {SystemConfig} from "../system-contract/SystemConfig.sol"; @@ -31,27 +29,18 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @dev Thrown when the given account is not EOA account. error ErrorAccountIsNotEOA(); - /// @dev Thrown when committing a committed batch. - error ErrorBatchIsAlreadyCommitted(); - /// @dev Thrown when finalizing a verified batch. error ErrorBatchIsAlreadyVerified(); /// @dev Thrown when committing empty batch (batch without chunks) error ErrorBatchIsEmpty(); - /// @dev Thrown when call precompile failed. - error ErrorCallPointEvaluationPrecompileFailed(); - /// @dev Thrown when the caller is not prover. error ErrorCallerIsNotProver(); /// @dev Thrown when the caller is not sequencer. error ErrorCallerIsNotSequencer(); - /// @dev Thrown when the transaction has multiple blobs. - error ErrorFoundMultipleBlobs(); - /// @dev Thrown when some fields are not zero in genesis batch. error ErrorGenesisBatchHasNonZeroField(); @@ -70,57 +59,21 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @dev Thrown when the batch version is incorrect. error ErrorIncorrectBatchVersion(); - /// @dev Thrown when the bitmap length is incorrect. - error ErrorIncorrectBitmapLength(); - - /// @dev Thrown when the last message is skipped. - error ErrorLastL1MessageSkipped(); - - /// @dev Thrown when no blob found in the transaction. - error ErrorNoBlobFound(); - - /// @dev Thrown when the number of transactions is less than number of L1 message in one block. - error ErrorNumTxsLessThanNumL1Msgs(); - /// @dev Thrown when reverting a finalized batch. error ErrorRevertFinalizedBatch(); /// @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(); - /// @dev Thrown when the given address is `address(0)`. error ErrorZeroAddress(); - /// @dev Thrown when commit batch with lower version. - error ErrorCannotDowngradeVersion(); - /// @dev Thrown when we try to commit or finalize normal batch in enforced batch mode. error ErrorInEnforcedBatchMode(); /// @dev Thrown when we try to commit enforced batch while not in enforced batch mode. error ErrorNotInEnforcedBatchMode(); - /// @dev Thrown when commit old batch after Euclid fork is enabled. - error ErrorEuclidForkEnabled(); - - /// @dev Thrown when the committed v5 batch doesn't contain only one chunk. - error ErrorV5BatchNotContainsOnlyOneChunk(); - - /// @dev Thrown when the committed v5 batch doesn't contain only one block. - error ErrorV5BatchNotContainsOnlyOneBlock(); - - /// @dev Thrown when the committed v5 batch contains some transactions (L1 or L2). - error ErrorV5BatchContainsTransactions(); - - /// @dev Thrown when finalize v4/v5, v5/v6, v4/v5/v6 batches in the same bundle. - error ErrorFinalizePreAndPostEuclidBatchInOneBundle(); - /// @dev Thrown when finalize v7 batches while some v1 messages still unfinalized. error ErrorNotAllV1MessagesAreFinalized(); @@ -138,14 +91,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { * Constants * *************/ - /// @dev Address of the point evaluation precompile used for EIP-4844 blob verification. - address internal constant POINT_EVALUATION_PRECOMPILE_ADDR = address(0x0A); - - /// @dev BLS Modulus value defined in EIP-4844 and the magic value returned from a successful call to the - /// point evaluation precompile - uint256 internal constant BLS_MODULUS = - 52435875175126190479447740508185965837690552500527637822603658699938581184513; - /// @dev offsets in miscData.flags uint256 private constant V1_MESSAGES_FINALIZED_OFFSET = 0; uint256 private constant ENFORCED_MODE_OFFSET = 1; @@ -188,13 +133,16 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { * Variables * *************/ - /// @notice The maximum number of transactions allowed in each chunk. - uint256 public maxNumTxInChunk; + /// @dev The maximum number of transactions allowed in each chunk. + /// @custom:deprecated This is no longer used. + uint256 private __maxNumTxInChunk; /// @dev The storage slot used as L1MessageQueue contract, which is deprecated now. + /// @custom:deprecated This is no longer used. address private __messageQueue; /// @dev The storage slot used as RollupVerifier contract, which is deprecated now. + /// @custom:deprecated This is no longer used. address private __verifier; /// @notice Whether an account is a sequencer. @@ -204,6 +152,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { mapping(address => bool) public isProver; /// @dev The storage slot used as `lastFinalizedBatchIndex`, which is deprecated now. + /// @custom:deprecated This is no longer used. uint256 private __lastFinalizedBatchIndex; /// @inheritdoc IScrollChain @@ -224,6 +173,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { /// @notice The index of first Euclid batch. uint256 public initialEuclidBatchIndex; + /// @notice The misc data of ScrollChain. ScrollChainMiscData public miscData; /********************** @@ -302,11 +252,9 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { ) external initializer { OwnableUpgradeable.__Ownable_init(); - maxNumTxInChunk = _maxNumTxInChunk; + __maxNumTxInChunk = _maxNumTxInChunk; __verifier = _verifier; __messageQueue = _messageQueue; - - emit UpdateMaxNumTxInChunk(0, _maxNumTxInChunk); } function initializeV2() external reinitializer(2) { @@ -388,53 +336,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { emit FinalizeBatch(0, _batchHash, _stateRoot, bytes32(0)); } - /// @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 This function now only accept batches with 4 <= version <= 6. And for `_version=5`, we should make sure this - /// batch contains only one empty block, since it is the Euclid initial batch for zkt/mpt transition. - function commitBatchWithBlobProof( - uint8 _version, - bytes calldata _parentBatchHeader, - bytes[] memory _chunks, - bytes calldata _skippedL1MessageBitmap, - bytes calldata _blobDataProof - ) external override OnlySequencer whenNotPaused whenEnforcedBatchNotEnabled { - // only accept 4 <= version <= 6 - if (_version < 4) { - revert ErrorIncorrectBatchVersion(); - } else if (_version == 5) { - // only commit once for Euclid initial batch - if (initialEuclidBatchIndex != 0) revert ErrorBatchIsAlreadyCommitted(); - } else if (_version > 6) { - revert ErrorIncorrectBatchVersion(); - } - // @note We suppose to check v6 batches cannot be committed without initial Euclid Batch. - // However it will introduce extra sload (2000 gas), we let the sequencer to do this check offchain. - // Even if the sequencer commits v6 batches without v5 batch, the security council can still revert it. - - uint256 batchIndex = _commitBatchFromV2ToV6( - _version, - _parentBatchHeader, - _chunks, - _skippedL1MessageBitmap, - _blobDataProof - ); - // Don't allow to commit version 4 after Euclid upgrade. - // This check is to avoid sequencer committing wrong batch due to human error. - // And This check won't introduce much gas overhead (likely less than 100). - if (_version == 4) { - uint256 euclidForkBatchIndex = initialEuclidBatchIndex; - if (euclidForkBatchIndex > 0 && batchIndex > euclidForkBatchIndex) revert ErrorEuclidForkEnabled(); - } else if (_version == 5) { - initialEuclidBatchIndex = batchIndex; - } - } - /// @inheritdoc IScrollChain function commitBatches( uint8 version, @@ -467,48 +368,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { miscData.lastCommittedBatchIndex = uint64(startBatchIndex); } - /// @inheritdoc IScrollChain - /// @dev All batches in the given bundle should have the same version and version <= 4 or version >= 6. - function finalizeBundleWithProof( - bytes calldata batchHeader, - bytes32 postStateRoot, - bytes32 withdrawRoot, - bytes calldata aggrProof - ) external override OnlyProver whenNotPaused whenEnforcedBatchNotEnabled { - // actions before verification - ( - uint256 version, - bytes32 batchHash, - uint256 batchIndex, - uint256 totalL1MessagesPoppedOverall, - uint256 prevBatchIndex - ) = _beforeFinalizeBatch(batchHeader, postStateRoot); - - uint256 euclidForkBatchIndex = initialEuclidBatchIndex; - // Make sure we don't finalize v4, v5 and v6 batches in the same bundle, that - // means `batchIndex < euclidForkBatchIndex` or `prevBatchIndex >= euclidForkBatchIndex`. - if (prevBatchIndex < euclidForkBatchIndex && euclidForkBatchIndex <= batchIndex) { - revert ErrorFinalizePreAndPostEuclidBatchInOneBundle(); - } - - bytes memory publicInputs = abi.encodePacked( - layer2ChainId, - uint32(batchIndex - prevBatchIndex), // numBatches - finalizedStateRoots[prevBatchIndex], // _prevStateRoot - committedBatches[prevBatchIndex], // _prevBatchHash - postStateRoot, - batchHash, - withdrawRoot - ); - - // verify bundle, choose the correct verifier based on the last batch - // our off-chain service will make sure all unfinalized batches have the same batch version. - IRollupVerifier(verifier).verifyBundleProof(version, batchIndex, aggrProof, publicInputs); - - // actions after verification - _afterFinalizeBatch(batchIndex, batchHash, totalL1MessagesPoppedOverall, postStateRoot, withdrawRoot, true); - } - /// @inheritdoc IScrollChain function finalizeBundlePostEuclidV2( bytes calldata batchHeader, @@ -631,15 +490,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 { @@ -678,53 +528,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { return flag; } - /// @dev Internal function to do common checks before actual batch committing. - /// @param _version The version of the batch to commit. - /// @param _parentBatchHeader The parent batch header in calldata. - /// @param _chunks The list of chunks in memory. - /// @param _lastCommittedBatchIndex The index of the 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( - uint8 _version, - bytes calldata _parentBatchHeader, - bytes[] memory _chunks, - uint256 _lastCommittedBatchIndex - ) - private - view - returns ( - bytes32 _parentBatchHash, - uint256 _batchIndex, - uint256 _totalL1MessagesPoppedOverall - ) - { - // check whether the batch is empty - if (_chunks.length == 0) revert ErrorBatchIsEmpty(); - uint256 batchPtr; - (batchPtr, _parentBatchHash, _batchIndex, _totalL1MessagesPoppedOverall) = _loadBatchHeader( - _parentBatchHeader, - _lastCommittedBatchIndex - ); - // version should non-decreasing - if (BatchHeaderV0Codec.getVersion(batchPtr) > _version) revert ErrorCannotDowngradeVersion(); - - if (_batchIndex != _lastCommittedBatchIndex) revert ErrorBatchIsAlreadyCommitted(); - unchecked { - _batchIndex += 1; - } - } - - /// @dev Internal function to do common actions 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 { - miscData.lastCommittedBatchIndex = uint64(_batchIndex); - committedBatches[_batchIndex] = _batchHash; - emit CommitBatch(_batchIndex, _batchHash); - } - /// @dev Internal function to do common actions before actual batch finalization. function _beforeFinalizeBatch(bytes calldata batchHeader, bytes32 postStateRoot) internal @@ -777,65 +580,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { emit FinalizeBatch(batchIndex, batchHash, postStateRoot, withdrawRoot); } - /// @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 and check the blob versioned hash. - /// @param _blobDataProof The blob data proof passing to point evaluation precompile. - /// @return _blobVersionedHash The retrieved blob versioned hash. - function _getAndCheckBlobVersionedHash(bytes calldata _blobDataProof) - internal - returns (bytes32 _blobVersionedHash) - { - _blobVersionedHash = _getBlobVersionedHash(); - - // Calls the point evaluation precompile and verifies the output - (bool success, bytes memory data) = POINT_EVALUATION_PRECOMPILE_ADDR.staticcall( - abi.encodePacked(_blobVersionedHash, _blobDataProof) - ); - // We verify that the point evaluation precompile call was successful by testing the latter 32 bytes of the - // response is equal to BLS_MODULUS as defined in https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile - if (!success) revert ErrorCallPointEvaluationPrecompileFailed(); - (, uint256 result) = abi.decode(data, (uint256, uint256)); - if (result != BLS_MODULUS) revert ErrorUnexpectedPointEvaluationPrecompileOutput(); - } - - /// @dev Internal function to get the blob versioned hash. - /// @return _blobVersionedHash The retrieved blob versioned hash. - function _getBlobVersionedHash() internal virtual returns (bytes32 _blobVersionedHash) { - bytes32 _secondBlob; - // Get blob's versioned hash - assembly { - _blobVersionedHash := blobhash(0) - _secondBlob := blobhash(1) - } - if (_blobVersionedHash == bytes32(0)) revert ErrorNoBlobFound(); - if (_secondBlob != bytes32(0)) revert ErrorFoundMultipleBlobs(); - } - /// @dev Internal function to get the blob versioned hash. /// @return _blobVersionedHash The retrieved blob versioned hash. function _getBlobVersionedHash(uint256 index) internal virtual returns (bytes32 _blobVersionedHash) { @@ -845,23 +589,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } - /// @dev We make sure v5 batch only contains one empty block here. - function _validateV5Batch(bytes[] memory chunks) internal pure { - if (chunks.length != 1) revert ErrorV5BatchNotContainsOnlyOneChunk(); - bytes memory chunk = chunks[0]; - uint256 chunkPtr; - uint256 blockPtr; - assembly { - chunkPtr := add(chunk, 0x20) // skip chunkLength - blockPtr := add(chunkPtr, 1) - } - - uint256 numBlocks = ChunkCodecV1.validateChunkLength(chunkPtr, chunk.length); - if (numBlocks != 1) revert ErrorV5BatchNotContainsOnlyOneBlock(); - uint256 numTransactions = ChunkCodecV1.getNumTransactions(blockPtr); - if (numTransactions != 0) revert ErrorV5BatchContainsTransactions(); - } - /// @dev Internal function to commit one ore more batches after the EuclidV2 upgrade. /// @param version The version of the batches (version >= 7). /// @param parentBatchHash The hash of parent batch. @@ -960,126 +687,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { _afterFinalizeBatch(batchIndex, batchHash, totalL1MessagesPoppedOverall, postStateRoot, withdrawRoot, false); } - /// @dev Internal function to commit batches from V2 to V6 (except V5, since it is Euclid initial batch) - function _commitBatchFromV2ToV6( - uint8 _version, - bytes calldata _parentBatchHeader, - bytes[] memory _chunks, - bytes calldata _skippedL1MessageBitmap, - bytes calldata _blobDataProof - ) internal returns (uint256) { - // do extra checks for batch v5. - if (_version == 5) { - _validateV5Batch(_chunks); - } - - // 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.allocate(); - BatchHeaderV0Codec.storeVersion(batchPtr, _version); - - (bytes32 _parentBatchHash, uint256 _batchIndex, uint256 _totalL1MessagesPoppedOverall) = _beforeCommitBatch( - _version, - _parentBatchHeader, - _chunks, - miscData.lastCommittedBatchIndex - ); - BatchHeaderV0Codec.storeBatchIndex(batchPtr, _batchIndex); - - // versions 2 to 6 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 - BatchHeaderV1Codec.storeBlobVersionedHash(batchPtr, _getAndCheckBlobVersionedHash(_blobDataProof)); - 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, V2~V6 has same code as V0 - bytes32 _batchHash = BatchHeaderV0Codec.computeBatchHash( - batchPtr, - BatchHeaderV3Codec.BATCH_HEADER_FIXED_LENGTH - ); - - _afterCommitBatch(_batchIndex, _batchHash); - - return _batchIndex; - } - - /// @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) - } - } - - // compute the data hash for current batch - assembly { - let dataLen := mul(_chunksLength, 0x20) - _batchDataHash := keccak256(sub(batchDataHashPtr, dataLen), dataLen) - } - } - /// @dev Internal function to load batch header from calldata to memory. /// @param _batchHeader The batch header in calldata. /// @param _lastCommittedBatchIndex The index of the last committed batch. @@ -1133,133 +740,6 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } - /// @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 - function _commitChunkV1( - 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 = ChunkCodecV1.validateChunkLength(chunkPtr, _chunk.length); - // concatenate block contexts, use scope to avoid stack too deep - for (uint256 i = 0; i < _numBlocks; i++) { - dataPtr = ChunkCodecV1.copyBlockContext(chunkPtr, dataPtr, i); - uint256 blockPtr = chunkPtr + 1 + i * ChunkCodecV1.BLOCK_CONTEXT_LENGTH; - uint256 _numL1MessagesInBlock = ChunkCodecV1.getNumL1Messages(blockPtr); - unchecked { - _totalNumL1MessagesInChunk += _numL1MessagesInBlock; - } - } - assembly { - mstore(0x40, add(dataPtr, mul(_totalNumL1MessagesInChunk, 0x20))) // reserve memory for l1 message hashes - 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 - uint256 _numL1MessagesInBlock = ChunkCodecV1.getNumL1Messages(chunkPtr); - uint256 startPtr = dataPtr; - dataPtr = _loadL1MessageHashes( - dataPtr, - _numL1MessagesInBlock, - _totalL1MessagesPoppedInBatch, - _totalL1MessagesPoppedOverall, - _skippedL1MessageBitmap - ); - uint256 _numTransactionsInBlock = ChunkCodecV1.getNumTransactions(chunkPtr); - if (_numTransactionsInBlock < _numL1MessagesInBlock) revert ErrorNumTxsLessThanNumL1Msgs(); - unchecked { - _totalTransactionsInChunk += (dataPtr - startPtr) / 32; // number of non-skipped l1 messages - _totalTransactionsInChunk += _numTransactionsInBlock - _numL1MessagesInBlock; // number of l2 txs - _totalL1MessagesPoppedInBatch += _numL1MessagesInBlock; - _totalL1MessagesPoppedOverall += _numL1MessagesInBlock; - - _numBlocks -= 1; - chunkPtr += ChunkCodecV1.BLOCK_CONTEXT_LENGTH; - } - } - - // 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)) - } - } - - /// @dev Internal function to load L1 message hashes from the message queue. - /// @param _ptr The memory offset to store the transaction hash. - /// @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. - function _loadL1MessageHashes( - uint256 _ptr, - uint256 _numL1Messages, - uint256 _totalL1MessagesPoppedInBatch, - uint256 _totalL1MessagesPoppedOverall, - bytes calldata _skippedL1MessageBitmap - ) internal view returns (uint256) { - if (_numL1Messages == 0) return _ptr; - IL1MessageQueueV1 _messageQueue = IL1MessageQueueV1(messageQueueV1); - - unchecked { - uint256 _bitmap; - uint256 rem; - 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) - } - } - - _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(); - } - - return _ptr; - } - /// @param totalL1MessagesPoppedOverall The total number of L1 messages popped in all batches including current batch. function _finalizePoppedL1Messages(uint256 totalL1MessagesPoppedOverall, bool isV1) internal { if (totalL1MessagesPoppedOverall > 0) { @@ -1270,59 +750,4 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain { } } } - - /// @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) - } - IL1MessageQueueV1(messageQueueV1).popCrossDomainMessage(startIndex, _count, bitmap); - startIndex += 256; - } - } - } } diff --git a/src/batch-bridge/L1BatchBridgeGateway.sol b/src/batch-bridge/L1BatchBridgeGateway.sol index 6f1dcbb3..75b46b2a 100644 --- a/src/batch-bridge/L1BatchBridgeGateway.sol +++ b/src/batch-bridge/L1BatchBridgeGateway.sol @@ -16,6 +16,7 @@ import {BatchBridgeCodec} from "./BatchBridgeCodec.sol"; import {L2BatchBridgeGateway} from "./L2BatchBridgeGateway.sol"; /// @title L1BatchBridgeGateway +/// @custom:deprecated This contract is no longer used in production. contract L1BatchBridgeGateway is AccessControlEnumerableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20Upgradeable for IERC20Upgradeable; diff --git a/src/batch-bridge/L2BatchBridgeGateway.sol b/src/batch-bridge/L2BatchBridgeGateway.sol index e069411a..4e01e68f 100644 --- a/src/batch-bridge/L2BatchBridgeGateway.sol +++ b/src/batch-bridge/L2BatchBridgeGateway.sol @@ -9,6 +9,7 @@ import {IL2ScrollMessenger} from "../L2/IL2ScrollMessenger.sol"; import {BatchBridgeCodec} from "./BatchBridgeCodec.sol"; /// @title L2BatchBridgeGateway +/// @custom:deprecated This contract is no longer used in production. contract L2BatchBridgeGateway is AccessControlEnumerableUpgradeable { /********** * Events * diff --git a/src/gas-swap/GasSwap.sol b/src/gas-swap/GasSwap.sol deleted file mode 100644 index 61bea33c..00000000 --- a/src/gas-swap/GasSwap.sol +++ /dev/null @@ -1,201 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity =0.8.24; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol"; -import {Context} from "@openzeppelin/contracts/utils/Context.sol"; - -// solhint-disable no-empty-blocks - -contract GasSwap is ERC2771Context, Ownable, ReentrancyGuard { - using SafeERC20 for IERC20; - using SafeERC20 for IERC20Permit; - - /********** - * Events * - **********/ - - /// @notice Emitted when the fee ratio is updated. - /// @param feeRatio The new fee ratio, multiplied by 1e18. - event UpdateFeeRatio(uint256 feeRatio); - - /// @notice Emitted when the status of target is updated. - /// @param target The address of target contract. - /// @param status The status updated. - event UpdateApprovedTarget(address target, bool status); - - /************* - * Constants * - *************/ - - /// @dev The fee precision. - uint256 private constant PRECISION = 1e18; - - /*********** - * Structs * - ***********/ - - struct PermitData { - // The address of token to spend. - address token; - // The amount of token to spend. - uint256 value; - // The deadline of the permit. - uint256 deadline; - // Below three are signatures. - uint8 v; - bytes32 r; - bytes32 s; - } - - struct SwapData { - // The address of target contract to call. - address target; - // The calldata passed to target contract. - bytes data; - // The minimum amount of Ether should receive. - uint256 minOutput; - } - - /************* - * Variables * - *************/ - - /// @notice Keep track whether an address is approved. - mapping(address => bool) public approvedTargets; - - /// @notice The fee ratio charged for each swap, multiplied by 1e18. - uint256 public feeRatio; - - /*************** - * Constructor * - ***************/ - - constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {} - - /***************************** - * Public Mutating Functions * - *****************************/ - - receive() external payable {} - - /// @notice Swap some token for ether. - /// @param _permit The permit data, see comments from `PermitData`. - /// @param _swap The swap data, see comments from `SwapData`. - function swap(PermitData memory _permit, SwapData memory _swap) external nonReentrant { - require(approvedTargets[_swap.target], "target not approved"); - address _sender = _msgSender(); - - // do permit - IERC20Permit(_permit.token).safePermit( - _sender, - address(this), - _permit.value, - _permit.deadline, - _permit.v, - _permit.r, - _permit.s - ); - - // record token balance in this contract - uint256 _balance = IERC20(_permit.token).balanceOf(address(this)); - - // transfer token - IERC20(_permit.token).safeTransferFrom(_sender, address(this), _permit.value); - - // approve - IERC20(_permit.token).safeApprove(_swap.target, 0); - IERC20(_permit.token).safeApprove(_swap.target, _permit.value); - - // do swap - uint256 _outputTokenAmount = address(this).balance; - // solhint-disable-next-line avoid-low-level-calls - (bool _success, bytes memory _res) = _swap.target.call(_swap.data); - require(_success, string(concat(bytes("swap failed: "), bytes(getRevertMsg(_res))))); - _outputTokenAmount = address(this).balance - _outputTokenAmount; - - // take fee - uint256 _fee = (_outputTokenAmount * feeRatio) / PRECISION; - _outputTokenAmount = _outputTokenAmount - _fee; - require(_outputTokenAmount >= _swap.minOutput, "insufficient output amount"); - - // transfer ETH to sender - (_success, ) = _sender.call{value: _outputTokenAmount}(""); - require(_success, "transfer ETH failed"); - - // refund rest token - uint256 _dust = IERC20(_permit.token).balanceOf(address(this)) - _balance; - if (_dust > 0) { - IERC20(_permit.token).safeTransfer(_sender, _dust); - } - } - - /************************ - * Restricted Functions * - ************************/ - - /// @notice Withdraw stucked tokens. - /// @param _token The address of token to withdraw. Use `address(0)` if you want to withdraw Ether. - /// @param _amount The amount of token to withdraw. - function withdraw(address _token, uint256 _amount) external onlyOwner { - if (_token == address(0)) { - (bool success, ) = _msgSender().call{value: _amount}(""); - require(success, "ETH transfer failed"); - } else { - IERC20(_token).safeTransfer(_msgSender(), _amount); - } - } - - /// @notice Update the fee ratio. - /// @param _feeRatio The new fee ratio. - function updateFeeRatio(uint256 _feeRatio) external onlyOwner { - feeRatio = _feeRatio; - - emit UpdateFeeRatio(_feeRatio); - } - - /// @notice Update the status of a target address. - /// @param _target The address of target to update. - /// @param _status The new status. - function updateApprovedTarget(address _target, bool _status) external onlyOwner { - approvedTargets[_target] = _status; - - emit UpdateApprovedTarget(_target, _status); - } - - /********************** - * Internal Functions * - **********************/ - - /// @inheritdoc Context - function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) { - return ERC2771Context._msgData(); - } - - /// @inheritdoc Context - function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address) { - return ERC2771Context._msgSender(); - } - - /// @dev Internal function to concat two bytes array. - function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory) { - return abi.encodePacked(a, b); - } - - /// @dev Internal function decode revert message from return data. - function getRevertMsg(bytes memory _returnData) internal pure returns (string memory) { - if (_returnData.length < 68) return "Transaction reverted silently"; - - // solhint-disable-next-line no-inline-assembly - assembly { - _returnData := add(_returnData, 0x04) - } - - return abi.decode(_returnData, (string)); - } -} diff --git a/src/libraries/callbacks/IMessageDropCallback.sol b/src/libraries/callbacks/IMessageDropCallback.sol deleted file mode 100644 index e23f3cda..00000000 --- a/src/libraries/callbacks/IMessageDropCallback.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.24; - -interface IMessageDropCallback { - function onDropMessage(bytes memory message) external payable; -} diff --git a/src/libraries/gateway/ScrollGatewayBase.sol b/src/libraries/gateway/ScrollGatewayBase.sol index 4bc49ebc..59aa65d1 100644 --- a/src/libraries/gateway/ScrollGatewayBase.sol +++ b/src/libraries/gateway/ScrollGatewayBase.sol @@ -9,7 +9,6 @@ import {IScrollGateway} from "./IScrollGateway.sol"; import {IScrollMessenger} from "../IScrollMessenger.sol"; import {IScrollGatewayCallback} from "../callbacks/IScrollGatewayCallback.sol"; import {ScrollConstants} from "../constants/ScrollConstants.sol"; -import {ITokenRateLimiter} from "../../rate-limiter/ITokenRateLimiter.sol"; /// @title ScrollGatewayBase /// @notice The `ScrollGatewayBase` is a base contract for gateway contracts used in both in L1 and L2. diff --git a/src/misc/ERC2771Forwarder.sol b/src/misc/ERC2771Forwarder.sol deleted file mode 100644 index 8e1b4d69..00000000 --- a/src/misc/ERC2771Forwarder.sol +++ /dev/null @@ -1,391 +0,0 @@ -// SPDX-License-Identifier: MIT - -// @note This file is directly copied from OpenZeppelin's master branch: -// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/ERC2771Forwarder.sol -// Modifications are made to make it compatible with solidity 0.8.16. - -pragma solidity =0.8.24; - -import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -import {Nonces} from "./Nonces.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; - -/** - * @dev A forwarder compatible with ERC2771 contracts. See {ERC2771Context}. - * - * This forwarder operates on forward requests that include: - * - * * `from`: An address to operate on behalf of. It is required to be equal to the request signer. - * * `to`: The address that should be called. - * * `value`: The amount of native token to attach with the requested call. - * * `gas`: The amount of gas limit that will be forwarded with the requested call. - * * `nonce`: A unique transaction ordering identifier to avoid replayability and request invalidation. - * * `deadline`: A timestamp after which the request is not executable anymore. - * * `data`: Encoded `msg.data` to send with the requested call. - * - * Relayers are able to submit batches if they are processing a high volume of requests. With high - * throughput, relayers may run into limitations of the chain such as limits on the number of - * transactions in the mempool. In these cases the recommendation is to distribute the load among - * multiple accounts. - * - * NOTE: Batching requests includes an optional refund for unused `msg.value` that is achieved by - * performing a call with empty calldata. While this is within the bounds of ERC-2771 compliance, - * if the refund receiver happens to consider the forwarder a trusted forwarder, it MUST properly - * handle `msg.data.length == 0`. `ERC2771Context` in OpenZeppelin Contracts versions prior to 4.9.3 - * do not handle this properly. - * - * ==== Security Considerations - * - * If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount - * specified in the request. This contract does not implement any kind of retribution for this gas, - * and it is assumed that there is an out of band incentive for relayers to pay for execution on - * behalf of signers. Often, the relayer is operated by a project that will consider it a user - * acquisition cost. - * - * By offering to pay for gas, relayers are at risk of having that gas used by an attacker toward - * some other purpose that is not aligned with the expected out of band incentives. If you operate a - * relayer, consider whitelisting target contracts and function selectors. When relaying ERC-721 or - * ERC-1155 transfers specifically, consider rejecting the use of the `data` field, since it can be - * used to execute arbitrary code. - */ -contract ERC2771Forwarder is EIP712, Nonces { - using ECDSA for bytes32; - - struct ForwardRequestData { - address from; - address to; - uint256 value; - uint256 gas; - uint48 deadline; - bytes data; - bytes signature; - } - - bytes32 internal constant _FORWARD_REQUEST_TYPEHASH = - keccak256( - "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,uint48 deadline,bytes data)" - ); - - /** - * @dev Emitted when a `ForwardRequest` is executed. - * - * NOTE: An unsuccessful forward request could be due to an invalid signature, an expired deadline, - * or simply a revert in the requested call. The contract guarantees that the relayer is not able to force - * the requested call to run out of gas. - */ - event ExecutedForwardRequest(address indexed signer, uint256 nonce, bool success); - - /** - * @dev The request `from` doesn't match with the recovered `signer`. - */ - error ERC2771ForwarderInvalidSigner(address signer, address from); - - /** - * @dev The `requestedValue` doesn't match with the available `msgValue`. - */ - error ERC2771ForwarderMismatchedValue(uint256 requestedValue, uint256 msgValue); - - /** - * @dev The request `deadline` has expired. - */ - error ERC2771ForwarderExpiredRequest(uint48 deadline); - - /** - * @dev The request target doesn't trust the `forwarder`. - */ - error ERC2771UntrustfulTarget(address target, address forwarder); - - /** - * @dev A call to an address target failed. The target may have reverted. - */ - error FailedInnerCall(); - - /** - * @dev See {EIP712-constructor}. - */ - constructor(string memory name) EIP712(name, "1") {} - - /** - * @dev Returns `true` if a request is valid for a provided `signature` at the current block timestamp. - * - * A transaction is considered valid when the target trusts this forwarder, the request hasn't expired - * (deadline is not met), and the signer matches the `from` parameter of the signed request. - * - * NOTE: A request may return false here but it won't cause {executeBatch} to revert if a refund - * receiver is provided. - */ - function verify(ForwardRequestData calldata request) public view virtual returns (bool) { - (bool isTrustedForwarder, bool active, bool signerMatch, ) = _validate(request); - return isTrustedForwarder && active && signerMatch; - } - - /** - * @dev Executes a `request` on behalf of `signature`'s signer using the ERC-2771 protocol. The gas - * provided to the requested call may not be exactly the amount requested, but the call will not run - * out of gas. Will revert if the request is invalid or the call reverts, in this case the nonce is not consumed. - * - * Requirements: - * - * - The request value should be equal to the provided `msg.value`. - * - The request should be valid according to {verify}. - */ - function execute(ForwardRequestData calldata request) public payable virtual { - // We make sure that msg.value and request.value match exactly. - // If the request is invalid or the call reverts, this whole function - // will revert, ensuring value isn't stuck. - if (msg.value != request.value) { - revert ERC2771ForwarderMismatchedValue(request.value, msg.value); - } - - if (!_execute(request, true)) { - revert FailedInnerCall(); - } - } - - /** - * @dev Batch version of {execute} with optional refunding and atomic execution. - * - * In case a batch contains at least one invalid request (see {verify}), the - * request will be skipped and the `refundReceiver` parameter will receive back the - * unused requested value at the end of the execution. This is done to prevent reverting - * the entire batch when a request is invalid or has already been submitted. - * - * If the `refundReceiver` is the `address(0)`, this function will revert when at least - * one of the requests was not valid instead of skipping it. This could be useful if - * a batch is required to get executed atomically (at least at the top-level). For example, - * refunding (and thus atomicity) can be opt-out if the relayer is using a service that avoids - * including reverted transactions. - * - * Requirements: - * - * - The sum of the requests' values should be equal to the provided `msg.value`. - * - All of the requests should be valid (see {verify}) when `refundReceiver` is the zero address. - * - * NOTE: Setting a zero `refundReceiver` guarantees an all-or-nothing requests execution only for - * the first-level forwarded calls. In case a forwarded request calls to a contract with another - * subcall, the second-level call may revert without the top-level call reverting. - */ - function executeBatch(ForwardRequestData[] calldata requests, address payable refundReceiver) - public - payable - virtual - { - bool atomic = refundReceiver == address(0); - - uint256 requestsValue; - uint256 refundValue; - - for (uint256 i; i < requests.length; ++i) { - requestsValue += requests[i].value; - bool success = _execute(requests[i], atomic); - if (!success) { - refundValue += requests[i].value; - } - } - - // The batch should revert if there's a mismatched msg.value provided - // to avoid request value tampering - if (requestsValue != msg.value) { - revert ERC2771ForwarderMismatchedValue(requestsValue, msg.value); - } - - // Some requests with value were invalid (possibly due to frontrunning). - // To avoid leaving ETH in the contract this value is refunded. - if (refundValue != 0) { - // We know refundReceiver != address(0) && requestsValue == msg.value - // meaning we can ensure refundValue is not taken from the original contract's balance - // and refundReceiver is a known account. - Address.sendValue(refundReceiver, refundValue); - } - } - - /** - * @dev Validates if the provided request can be executed at current block timestamp with - * the given `request.signature` on behalf of `request.signer`. - */ - function _validate(ForwardRequestData calldata request) - internal - view - virtual - returns ( - bool isTrustedForwarder, - bool active, - bool signerMatch, - address signer - ) - { - (bool isValid, address recovered) = _recoverForwardRequestSigner(request); - - return ( - _isTrustedByTarget(request.to), - request.deadline >= block.timestamp, - isValid && recovered == request.from, - recovered - ); - } - - /** - * @dev Returns a tuple with the recovered the signer of an EIP712 forward request message hash - * and a boolean indicating if the signature is valid. - * - * NOTE: The signature is considered valid if {ECDSA-tryRecover} indicates no recover error for it. - */ - function _recoverForwardRequestSigner(ForwardRequestData calldata request) - internal - view - virtual - returns (bool, address) - { - (address recovered, ECDSA.RecoverError err) = _hashTypedDataV4( - keccak256( - abi.encode( - _FORWARD_REQUEST_TYPEHASH, - request.from, - request.to, - request.value, - request.gas, - nonces(request.from), - request.deadline, - keccak256(request.data) - ) - ) - ).tryRecover(request.signature); - - return (err == ECDSA.RecoverError.NoError, recovered); - } - - /** - * @dev Validates and executes a signed request returning the request call `success` value. - * - * Internal function without msg.value validation. - * - * Requirements: - * - * - The caller must have provided enough gas to forward with the call. - * - The request must be valid (see {verify}) if the `requireValidRequest` is true. - * - * Emits an {ExecutedForwardRequest} event. - * - * IMPORTANT: Using this function doesn't check that all the `msg.value` was sent, potentially - * leaving value stuck in the contract. - */ - function _execute(ForwardRequestData calldata request, bool requireValidRequest) - internal - virtual - returns (bool success) - { - (bool isTrustedForwarder, bool active, bool signerMatch, address signer) = _validate(request); - - // Need to explicitly specify if a revert is required since non-reverting is default for - // batches and reversion is opt-in since it could be useful in some scenarios - if (requireValidRequest) { - if (!isTrustedForwarder) { - revert ERC2771UntrustfulTarget(request.to, address(this)); - } - - if (!active) { - revert ERC2771ForwarderExpiredRequest(request.deadline); - } - - if (!signerMatch) { - revert ERC2771ForwarderInvalidSigner(signer, request.from); - } - } - - // Ignore an invalid request because requireValidRequest = false - if (isTrustedForwarder && signerMatch && active) { - // Nonce should be used before the call to prevent reusing by reentrancy - uint256 currentNonce = _useNonce(signer); - - uint256 reqGas = request.gas; - address to = request.to; - uint256 value = request.value; - bytes memory data = abi.encodePacked(request.data, request.from); - - uint256 gasLeft; - - assembly { - success := call(reqGas, to, value, add(data, 0x20), mload(data), 0, 0) - gasLeft := gas() - } - - _checkForwardedGas(gasLeft, request); - - emit ExecutedForwardRequest(signer, currentNonce, success); - } - } - - /** - * @dev Returns whether the target trusts this forwarder. - * - * This function performs a static call to the target contract calling the - * {ERC2771Context-isTrustedForwarder} function. - */ - function _isTrustedByTarget(address target) private view returns (bool) { - bytes memory encodedParams = abi.encodeCall(ERC2771Context.isTrustedForwarder, (address(this))); - - bool success; - uint256 returnSize; - uint256 returnValue; - /// @solidity memory-safe-assembly - assembly { - // Perform the staticcal and save the result in the scratch space. - // | Location | Content | Content (Hex) | - // |-----------|----------|--------------------------------------------------------------------| - // | | | result ↓ | - // | 0x00:0x1F | selector | 0x0000000000000000000000000000000000000000000000000000000000000001 | - success := staticcall(gas(), target, add(encodedParams, 0x20), mload(encodedParams), 0, 0x20) - returnSize := returndatasize() - returnValue := mload(0) - } - - return success && returnSize >= 0x20 && returnValue > 0; - } - - /** - * @dev Checks if the requested gas was correctly forwarded to the callee. - * - * As a consequence of https://eips.ethereum.org/EIPS/eip-150[EIP-150]: - * - At most `gasleft() - floor(gasleft() / 64)` is forwarded to the callee. - * - At least `floor(gasleft() / 64)` is kept in the caller. - * - * It reverts consuming all the available gas if the forwarded gas is not the requested gas. - * - * IMPORTANT: The `gasLeft` parameter should be measured exactly at the end of the forwarded call. - * Any gas consumed in between will make room for bypassing this check. - */ - function _checkForwardedGas(uint256 gasLeft, ForwardRequestData calldata request) private pure { - // To avoid insufficient gas griefing attacks, as referenced in https://ronan.eth.limo/blog/ethereum-gas-dangers/ - // - // A malicious relayer can attempt to shrink the gas forwarded so that the underlying call reverts out-of-gas - // but the forwarding itself still succeeds. In order to make sure that the subcall received sufficient gas, - // we will inspect gasleft() after the forwarding. - // - // Let X be the gas available before the subcall, such that the subcall gets at most X * 63 / 64. - // We can't know X after CALL dynamic costs, but we want it to be such that X * 63 / 64 >= req.gas. - // Let Y be the gas used in the subcall. gasleft() measured immediately after the subcall will be gasleft() = X - Y. - // If the subcall ran out of gas, then Y = X * 63 / 64 and gasleft() = X - Y = X / 64. - // Under this assumption req.gas / 63 > gasleft() is true is true if and only if - // req.gas / 63 > X / 64, or equivalently req.gas > X * 63 / 64. - // This means that if the subcall runs out of gas we are able to detect that insufficient gas was passed. - // - // We will now also see that req.gas / 63 > gasleft() implies that req.gas >= X * 63 / 64. - // The contract guarantees Y <= req.gas, thus gasleft() = X - Y >= X - req.gas. - // - req.gas / 63 > gasleft() - // - req.gas / 63 >= X - req.gas - // - req.gas >= X * 63 / 64 - // In other words if req.gas < X * 63 / 64 then req.gas / 63 <= gasleft(), thus if the relayer behaves honestly - // the forwarding does not revert. - if (gasLeft < request.gas / 63) { - // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since - // neither revert or assert consume all gas since Solidity 0.8.20 - // https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require - /// @solidity memory-safe-assembly - assembly { - invalid() - } - } - } -} diff --git a/src/misc/Nonces.sol b/src/misc/Nonces.sol deleted file mode 100644 index 7c761498..00000000 --- a/src/misc/Nonces.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MIT - -// @note This file is directly copied from OpenZeppelin's master branch: -// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Nonces.sol -// Modifications are made to make it compatible with solidity 0.8.16. - -pragma solidity ^0.8.24; - -/** - * @dev Provides tracking nonces for addresses. Nonces will only increment. - */ -abstract contract Nonces { - /** - * @dev The nonce used for an `account` is not the expected current nonce. - */ - error InvalidAccountNonce(address account, uint256 currentNonce); - - mapping(address => uint256) private _nonces; - - /** - * @dev Returns an the next unused nonce for an address. - */ - function nonces(address owner) public view virtual returns (uint256) { - return _nonces[owner]; - } - - /** - * @dev Consumes a nonce. - * - * Returns the current value and increments nonce. - */ - function _useNonce(address owner) internal virtual returns (uint256) { - // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be - // decremented or reset. This guarantees that the nonce never overflows. - unchecked { - // It is important to do x++ and not ++x here. - return _nonces[owner]++; - } - } - - /** - * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. - */ - function _useCheckedNonce(address owner, uint256 nonce) internal virtual returns (uint256) { - uint256 current = _useNonce(owner); - if (nonce != current) { - revert InvalidAccountNonce(owner, current); - } - return current; - } -} diff --git a/src/mocks/ScrollChainMockBlob.sol b/src/mocks/ScrollChainMockBlob.sol index 3490f06a..d52c2f34 100644 --- a/src/mocks/ScrollChainMockBlob.sol +++ b/src/mocks/ScrollChainMockBlob.sol @@ -48,10 +48,6 @@ contract ScrollChainMockBlob is ScrollChain { overrideBatchHashCheck = status; } - function _getBlobVersionedHash() internal virtual override returns (bytes32 _blobVersionedHash) { - _blobVersionedHash = blobhashes[0]; - } - function _getBlobVersionedHash(uint256 index) internal virtual override returns (bytes32 _blobVersionedHash) { _blobVersionedHash = blobhashes[index]; } diff --git a/src/rate-limiter/ETHRateLimiter.sol b/src/rate-limiter/ETHRateLimiter.sol deleted file mode 100644 index 31ba904c..00000000 --- a/src/rate-limiter/ETHRateLimiter.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity =0.8.24; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import {IETHRateLimiter} from "./IETHRateLimiter.sol"; - -// solhint-disable func-name-mixedcase -// solhint-disable not-rely-on-time - -contract ETHRateLimiter is Ownable, IETHRateLimiter { - /*********** - * Structs * - ***********/ - - struct ETHAmount { - // The timestamp when the amount is updated. - uint48 lastUpdateTs; - // The ETH limit in wei. - uint104 limit; - // The amount of ETH in current period. - uint104 amount; - } - - /************* - * Constants * - *************/ - - /// @notice The period length in seconds. - /// @dev The time frame for the `k`-th period is `[periodDuration * k, periodDuration * (k + 1))`. - uint256 public immutable periodDuration; - - /// @notice The address of ETH spender. - address public immutable spender; - - /************* - * Variables * - *************/ - - /// @notice The ETH amount used in current period. - ETHAmount public currentPeriod; - - /*************** - * Constructor * - ***************/ - - constructor( - uint256 _periodDuration, - address _spender, - uint104 _totalLimit - ) { - if (_periodDuration == 0) { - revert PeriodIsZero(); - } - - periodDuration = _periodDuration; - spender = _spender; - - _updateTotalLimit(_totalLimit); - } - - /***************************** - * Public Mutating Functions * - *****************************/ - - /// @inheritdoc IETHRateLimiter - function addUsedAmount(uint256 _amount) external override { - if (_msgSender() != spender) { - revert CallerNotSpender(); - } - if (_amount == 0) return; - - uint256 _currentPeriodStart = (block.timestamp / periodDuration) * periodDuration; - - // check total limit - uint256 _currentTotalAmount; - ETHAmount memory _currentPeriod = currentPeriod; - - if (uint256(_currentPeriod.lastUpdateTs) < _currentPeriodStart) { - _currentTotalAmount = _amount; - } else { - _currentTotalAmount = _currentPeriod.amount + _amount; - } - if (_currentTotalAmount > _currentPeriod.limit) { - revert ExceedTotalLimit(); - } - - _currentPeriod.lastUpdateTs = uint48(block.timestamp); - _currentPeriod.amount = SafeCast.toUint104(_currentTotalAmount); - - currentPeriod = _currentPeriod; - } - - /************************ - * Restricted Functions * - ************************/ - - /// @notice Update the total ETH amount limit. - /// @param _newTotalLimit The new total limit. - function updateTotalLimit(uint104 _newTotalLimit) external onlyOwner { - _updateTotalLimit(_newTotalLimit); - } - - /********************** - * Internal Functions * - **********************/ - - /// @dev Internal function to update the total token amount limit. - /// @param _newTotalLimit The new total limit. - function _updateTotalLimit(uint104 _newTotalLimit) private { - if (_newTotalLimit == 0) { - revert TotalLimitIsZero(); - } - - uint256 _oldTotalLimit = currentPeriod.limit; - currentPeriod.limit = _newTotalLimit; - - emit UpdateTotalLimit(_oldTotalLimit, _newTotalLimit); - } -} diff --git a/src/rate-limiter/IETHRateLimiter.sol b/src/rate-limiter/IETHRateLimiter.sol deleted file mode 100644 index 43db682e..00000000 --- a/src/rate-limiter/IETHRateLimiter.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.24; - -interface IETHRateLimiter { - /********** - * Events * - **********/ - - /// @notice Emitted when the total limit is updated. - /// @param oldTotalLimit The previous value of total limit before updating. - /// @param newTotalLimit The current value of total limit after updating. - event UpdateTotalLimit(uint256 oldTotalLimit, uint256 newTotalLimit); - - /********** - * Errors * - **********/ - - /// @dev Thrown when the `periodDuration` is initialized to zero. - error PeriodIsZero(); - - /// @dev Thrown when the `totalAmount` is initialized to zero. - error TotalLimitIsZero(); - - /// @dev Thrown when an amount breaches the total limit in the period. - error ExceedTotalLimit(); - - /// @dev Thrown when the call is not spender. - error CallerNotSpender(); - - /***************************** - * Public Mutating Functions * - *****************************/ - - /// @notice Request some ETH usage for `sender`. - /// @param _amount The amount of ETH to use. - function addUsedAmount(uint256 _amount) external; -} diff --git a/src/rate-limiter/ITokenRateLimiter.sol b/src/rate-limiter/ITokenRateLimiter.sol deleted file mode 100644 index 050680ef..00000000 --- a/src/rate-limiter/ITokenRateLimiter.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.24; - -interface ITokenRateLimiter { - /********** - * Events * - **********/ - - /// @notice Emitted when the total limit is updated. - /// @param oldTotalLimit The previous value of total limit before updating. - /// @param newTotalLimit The current value of total limit after updating. - event UpdateTotalLimit(address indexed token, uint256 oldTotalLimit, uint256 newTotalLimit); - - /********** - * Errors * - **********/ - - /// @dev Thrown when the `periodDuration` is initialized to zero. - error PeriodIsZero(); - - /// @dev Thrown when the `totalAmount` is initialized to zero. - /// @param token The address of the token. - error TotalLimitIsZero(address token); - - /// @dev Thrown when an amount breaches the total limit in the period. - /// @param token The address of the token. - error ExceedTotalLimit(address token); - - /***************************** - * Public Mutating Functions * - *****************************/ - - /// @notice Request some token usage for `sender`. - /// @param token The address of the token. - /// @param amount The amount of token to use. - function addUsedAmount(address token, uint256 amount) external; -} diff --git a/src/rate-limiter/TokenRateLimiter.sol b/src/rate-limiter/TokenRateLimiter.sol deleted file mode 100644 index dab08e8a..00000000 --- a/src/rate-limiter/TokenRateLimiter.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity =0.8.24; - -import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import {ITokenRateLimiter} from "./ITokenRateLimiter.sol"; - -// solhint-disable func-name-mixedcase -// solhint-disable not-rely-on-time - -contract TokenRateLimiter is AccessControlEnumerable, ITokenRateLimiter { - /*********** - * Structs * - ***********/ - - struct TokenAmount { - // The timestamp when the amount is updated. - uint48 lastUpdateTs; - // The token limit. - uint104 limit; - // The amount of token in current period. - uint104 amount; - } - - /************* - * Constants * - *************/ - - /// @notice The role for token spender. - bytes32 public constant TOKEN_SPENDER_ROLE = keccak256("TOKEN_SPENDER_ROLE"); - - /// @notice The period length in seconds. - /// @dev The time frame for the `k`-th period is `[periodDuration * k, periodDuration * (k + 1))`. - uint256 public immutable periodDuration; - - /************* - * Variables * - *************/ - - /// @notice Mapping from token address to the total amounts used in current period and total token amount limit. - mapping(address => TokenAmount) public currentPeriod; - - /// @dev The storage slots for future usage. - uint256[49] private __gap; - - /*************** - * Constructor * - ***************/ - - constructor(uint256 _periodDuration) { - if (_periodDuration == 0) { - revert PeriodIsZero(); - } - - _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); - - periodDuration = _periodDuration; - } - - /***************************** - * Public Mutating Functions * - *****************************/ - - /// @inheritdoc ITokenRateLimiter - function addUsedAmount(address _token, uint256 _amount) external override onlyRole(TOKEN_SPENDER_ROLE) { - if (_amount == 0) return; - - uint256 _currentPeriodStart = (block.timestamp / periodDuration) * periodDuration; - - // check total limit, `0` means no limit at all. - uint256 _currentTotalAmount; - TokenAmount memory _currentPeriod = currentPeriod[_token]; - if (uint256(_currentPeriod.lastUpdateTs) < _currentPeriodStart) { - _currentTotalAmount = _amount; - } else { - _currentTotalAmount = _currentPeriod.amount + _amount; - } - if (_currentPeriod.limit != 0 && _currentTotalAmount > _currentPeriod.limit) { - revert ExceedTotalLimit(_token); - } - - _currentPeriod.lastUpdateTs = uint48(block.timestamp); - _currentPeriod.amount = SafeCast.toUint104(_currentTotalAmount); - - currentPeriod[_token] = _currentPeriod; - } - - /************************ - * Restricted Functions * - ************************/ - - /// @notice Update the total token amount limit. - /// @param _newTotalLimit The new total limit. - function updateTotalLimit(address _token, uint104 _newTotalLimit) external onlyRole(DEFAULT_ADMIN_ROLE) { - if (_newTotalLimit == 0) { - revert TotalLimitIsZero(_token); - } - - uint256 _oldTotalLimit = currentPeriod[_token].limit; - currentPeriod[_token].limit = _newTotalLimit; - - emit UpdateTotalLimit(_token, _oldTotalLimit, _newTotalLimit); - } -} diff --git a/src/test/ETHRateLimiter.t.sol b/src/test/ETHRateLimiter.t.sol deleted file mode 100644 index 31c53725..00000000 --- a/src/test/ETHRateLimiter.t.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity =0.8.24; - -import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; - -import {ETHRateLimiter} from "../rate-limiter/ETHRateLimiter.sol"; -import {IETHRateLimiter} from "../rate-limiter/IETHRateLimiter.sol"; - -contract ETHRateLimiterTest is DSTestPlus { - event UpdateTotalLimit(uint256 oldTotalLimit, uint256 newTotalLimit); - - ETHRateLimiter private limiter; - - function setUp() public { - hevm.warp(86400); - limiter = new ETHRateLimiter(86400, address(this), 100 ether); - } - - function testUpdateTotalLimit(uint104 _newTotalLimit) external { - hevm.assume(_newTotalLimit > 0); - - // not owner, revert - hevm.startPrank(address(1)); - hevm.expectRevert("Ownable: caller is not the owner"); - limiter.updateTotalLimit(_newTotalLimit); - hevm.stopPrank(); - - // zero revert - hevm.expectRevert(IETHRateLimiter.TotalLimitIsZero.selector); - limiter.updateTotalLimit(0); - - // success - hevm.expectEmit(false, false, false, true); - emit UpdateTotalLimit(100 ether, _newTotalLimit); - limiter.updateTotalLimit(_newTotalLimit); - (, uint104 _totalLimit, ) = limiter.currentPeriod(); - assertEq(_totalLimit, _newTotalLimit); - } - - function testAddUsedAmount() external { - // non-spender, revert - hevm.startPrank(address(1)); - hevm.expectRevert(IETHRateLimiter.CallerNotSpender.selector); - limiter.addUsedAmount(0); - hevm.stopPrank(); - - // exceed total limit on first call - hevm.expectRevert(IETHRateLimiter.ExceedTotalLimit.selector); - limiter.addUsedAmount(100 ether + 1); - _checkTotalCurrentPeriodAmountAmount(0); - - // exceed total limit on second call - limiter.addUsedAmount(50 ether); - _checkTotalCurrentPeriodAmountAmount(50 ether); - hevm.expectRevert(IETHRateLimiter.ExceedTotalLimit.selector); - limiter.addUsedAmount(50 ether + 1); - _checkTotalCurrentPeriodAmountAmount(50 ether); - - // one period passed - hevm.warp(86400 * 2); - limiter.addUsedAmount(1 ether); - _checkTotalCurrentPeriodAmountAmount(1 ether); - - // exceed - hevm.expectRevert(IETHRateLimiter.ExceedTotalLimit.selector); - limiter.addUsedAmount(99 ether + 1); - _checkTotalCurrentPeriodAmountAmount(1 ether); - } - - function _checkTotalCurrentPeriodAmountAmount(uint256 expected) internal { - (, , uint256 totalAmount) = limiter.currentPeriod(); - assertEq(totalAmount, expected); - } -} diff --git a/src/test/L1CustomERC20Gateway.t.sol b/src/test/L1CustomERC20Gateway.t.sol index 98e0a75b..1443061d 100644 --- a/src/test/L1CustomERC20Gateway.t.sol +++ b/src/test/L1CustomERC20Gateway.t.sol @@ -130,94 +130,6 @@ contract L1CustomERC20GatewayTest is L1GatewayTestBase { _depositERC20WithRecipientAndCalldata(false, amount, recipient, dataToCall, gasLimit, feePerGas); } - function testDropMessageMocking() public { - MockScrollMessenger mockMessenger = new MockScrollMessenger(); - gateway = _deployGateway(address(mockMessenger)); - gateway.initialize(address(counterpartGateway), address(router), address(mockMessenger)); - - // only messenger can call, revert - hevm.expectRevert(ErrorCallerIsNotMessenger.selector); - gateway.onDropMessage(new bytes(0)); - - // only called in drop context, revert - hevm.expectRevert(ErrorNotInDropMessageContext.selector); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(0)) - ); - - mockMessenger.setXDomainMessageSender(ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER); - - // invalid selector, revert - hevm.expectRevert("invalid selector"); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(4)) - ); - - bytes memory message = abi.encodeWithSelector( - IL2ERC20Gateway.finalizeDepositERC20.selector, - address(l1Token), - address(l2Token), - address(this), - address(this), - 100, - new bytes(0) - ); - - // nonzero msg.value, revert - hevm.expectRevert("nonzero msg.value"); - mockMessenger.callTarget{value: 1}( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, message) - ); - } - - function testDropMessage( - uint256 amount, - address recipient, - bytes memory dataToCall - ) public { - // message 0 is append here - gateway.updateTokenMapping{value: 1 ether}(address(l1Token), address(l2Token)); - - // finalize message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - hevm.stopPrank(); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - - amount = bound(amount, 1, l1Token.balanceOf(address(this))); - bytes memory message = abi.encodeWithSelector( - IL2ERC20Gateway.finalizeDepositERC20.selector, - address(l1Token), - address(l2Token), - address(this), - recipient, - amount, - dataToCall - ); - gateway.depositERC20AndCall(address(l1Token), recipient, amount, dataToCall, defaultGasLimit); - - // skip message 1 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(1, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(2); - assertEq(messageQueueV1.pendingQueueIndex(), 2); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 2); - hevm.stopPrank(); - - // drop message 1 - hevm.expectEmit(true, true, false, true); - emit RefundERC20(address(l1Token), address(this), amount); - - uint256 balance = l1Token.balanceOf(address(this)); - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 1, message); - assertEq(balance + amount, l1Token.balanceOf(address(this))); - } - function testFinalizeWithdrawERC20FailedMocking( address sender, address recipient, diff --git a/src/test/L1ERC1155Gateway.t.sol b/src/test/L1ERC1155Gateway.t.sol index ef707693..ad4c9589 100644 --- a/src/test/L1ERC1155Gateway.t.sol +++ b/src/test/L1ERC1155Gateway.t.sol @@ -160,127 +160,6 @@ contract L1ERC1155GatewayTest is L1GatewayTestBase, ERC1155TokenReceiver { _testBatchDepositERC1155WithRecipient(tokenCount, amount, recipient, gasLimit, feePerGas); } - function testDropMessageMocking() public { - MockScrollMessenger mockMessenger = new MockScrollMessenger(); - gateway = _deployGateway(address(mockMessenger)); - gateway.initialize(address(counterpartGateway), address(mockMessenger)); - - // only messenger can call, revert - hevm.expectRevert(ErrorCallerIsNotMessenger.selector); - gateway.onDropMessage(new bytes(0)); - - // only called in drop context, revert - hevm.expectRevert(ErrorNotInDropMessageContext.selector); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(0)) - ); - - mockMessenger.setXDomainMessageSender(ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER); - - // invalid selector, revert - hevm.expectRevert("invalid selector"); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(4)) - ); - - bytes memory message = abi.encodeWithSelector( - IL2ERC1155Gateway.finalizeDepositERC1155.selector, - address(l1Token), - address(l2Token), - address(this), - address(this), - 0, - 0 - ); - - // nonzero msg.value, revert - hevm.expectRevert("nonzero msg.value"); - mockMessenger.callTarget{value: 1}( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, message) - ); - } - - function testDropMessage(uint256 tokenId, uint256 amount) public { - gateway.updateTokenMapping(address(l1Token), address(l2Token)); - - tokenId = bound(tokenId, 0, TOKEN_COUNT - 1); - amount = bound(amount, 1, MAX_TOKEN_BALANCE); - bytes memory message = abi.encodeWithSelector( - IL2ERC1155Gateway.finalizeDepositERC1155.selector, - address(l1Token), - address(l2Token), - address(this), - address(this), - tokenId, - amount - ); - gateway.depositERC1155(address(l1Token), tokenId, amount, defaultGasLimit); - - // skip message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - hevm.stopPrank(); - - // drop message 0 - hevm.expectEmit(true, true, false, true); - emit RefundERC1155(address(l1Token), address(this), tokenId, amount); - - uint256 balance = l1Token.balanceOf(address(this), tokenId); - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 0, message); - assertEq(balance + amount, l1Token.balanceOf(address(this), tokenId)); - } - - function testDropMessageBatch(uint256 tokenCount, uint256 amount) public { - tokenCount = bound(tokenCount, 1, TOKEN_COUNT); - amount = bound(amount, 1, MAX_TOKEN_BALANCE); - gateway.updateTokenMapping(address(l1Token), address(l2Token)); - - uint256[] memory _tokenIds = new uint256[](tokenCount); - uint256[] memory _amounts = new uint256[](tokenCount); - for (uint256 i = 0; i < tokenCount; i++) { - _tokenIds[i] = i; - _amounts[i] = amount; - } - - bytes memory message = abi.encodeWithSelector( - IL2ERC1155Gateway.finalizeBatchDepositERC1155.selector, - address(l1Token), - address(l2Token), - address(this), - address(this), - _tokenIds, - _amounts - ); - gateway.batchDepositERC1155(address(l1Token), _tokenIds, _amounts, defaultGasLimit); - - // skip message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - hevm.stopPrank(); - - // drop message 0 - hevm.expectEmit(true, true, false, true); - emit BatchRefundERC1155(address(l1Token), address(this), _tokenIds, _amounts); - - uint256[] memory balances = new uint256[](tokenCount); - for (uint256 i = 0; i < tokenCount; i++) { - balances[i] = l1Token.balanceOf(address(this), _tokenIds[i]); - } - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 0, message); - for (uint256 i = 0; i < tokenCount; i++) { - assertEq(balances[i] + _amounts[i], l1Token.balanceOf(address(this), _tokenIds[i])); - } - } - function testFinalizeWithdrawERC1155FailedMocking( address sender, address recipient, diff --git a/src/test/L1ERC721Gateway.t.sol b/src/test/L1ERC721Gateway.t.sol index edebe9ea..9cd52de7 100644 --- a/src/test/L1ERC721Gateway.t.sol +++ b/src/test/L1ERC721Gateway.t.sol @@ -151,119 +151,6 @@ contract L1ERC721GatewayTest is L1GatewayTestBase, ERC721TokenReceiver { _testBatchDepositERC721WithRecipient(tokenCount, recipient, gasLimit, feePerGas); } - function testDropMessageMocking() public { - MockScrollMessenger mockMessenger = new MockScrollMessenger(); - gateway = _deployGateway(address(mockMessenger)); - gateway.initialize(address(counterpartGateway), address(mockMessenger)); - - // only messenger can call, revert - hevm.expectRevert(ErrorCallerIsNotMessenger.selector); - gateway.onDropMessage(new bytes(0)); - - // only called in drop context, revert - hevm.expectRevert(ErrorNotInDropMessageContext.selector); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(0)) - ); - - mockMessenger.setXDomainMessageSender(ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER); - - // invalid selector, revert - hevm.expectRevert("invalid selector"); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(4)) - ); - - bytes memory message = abi.encodeWithSelector( - IL2ERC721Gateway.finalizeDepositERC721.selector, - address(l1Token), - address(l2Token), - address(this), - address(this), - 0 - ); - - // nonzero msg.value, revert - hevm.expectRevert("nonzero msg.value"); - mockMessenger.callTarget{value: 1}( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, message) - ); - } - - function testDropMessage(uint256 tokenId) public { - gateway.updateTokenMapping(address(l1Token), address(l2Token)); - - tokenId = bound(tokenId, 0, TOKEN_COUNT - 1); - bytes memory message = abi.encodeWithSelector( - IL2ERC721Gateway.finalizeDepositERC721.selector, - address(l1Token), - address(l2Token), - address(this), - address(this), - tokenId - ); - gateway.depositERC721(address(l1Token), tokenId, defaultGasLimit); - - // skip message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - hevm.stopPrank(); - - // drop message 0 - hevm.expectEmit(true, true, false, true); - emit RefundERC721(address(l1Token), address(this), tokenId); - - assertEq(l1Token.ownerOf(tokenId), address(gateway)); - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 0, message); - assertEq(l1Token.ownerOf(tokenId), address(this)); - } - - function testDropMessageBatch(uint256 tokenCount) public { - tokenCount = bound(tokenCount, 1, TOKEN_COUNT); - gateway.updateTokenMapping(address(l1Token), address(l2Token)); - - uint256[] memory _tokenIds = new uint256[](tokenCount); - for (uint256 i = 0; i < tokenCount; i++) { - _tokenIds[i] = i; - } - - bytes memory message = abi.encodeWithSelector( - IL2ERC721Gateway.finalizeBatchDepositERC721.selector, - address(l1Token), - address(l2Token), - address(this), - address(this), - _tokenIds - ); - gateway.batchDepositERC721(address(l1Token), _tokenIds, defaultGasLimit); - - // skip message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - hevm.stopPrank(); - - // drop message 0 - hevm.expectEmit(true, true, false, true); - emit BatchRefundERC721(address(l1Token), address(this), _tokenIds); - for (uint256 i = 0; i < tokenCount; i++) { - assertEq(l1Token.ownerOf(_tokenIds[i]), address(gateway)); - } - - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 0, message); - for (uint256 i = 0; i < tokenCount; i++) { - assertEq(l1Token.ownerOf(_tokenIds[i]), address(this)); - } - } - function testFinalizeWithdrawERC721FailedMocking( address sender, address recipient, diff --git a/src/test/L1ETHGateway.t.sol b/src/test/L1ETHGateway.t.sol index 4b5ebe83..8c5460c6 100644 --- a/src/test/L1ETHGateway.t.sol +++ b/src/test/L1ETHGateway.t.sol @@ -104,85 +104,6 @@ contract L1ETHGatewayTest is L1GatewayTestBase { _depositETHWithRecipientAndCalldata(true, amount, recipient, dataToCall, gasLimit, feePerGas); } - function testDropMessageMocking() public { - MockScrollMessenger mockMessenger = new MockScrollMessenger(); - gateway = _deployGateway(address(mockMessenger)); - gateway.initialize(address(counterpartGateway), address(router), address(mockMessenger)); - - // only messenger can call, revert - hevm.expectRevert(ErrorCallerIsNotMessenger.selector); - gateway.onDropMessage(new bytes(0)); - - // only called in drop context, revert - hevm.expectRevert(ErrorNotInDropMessageContext.selector); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(0)) - ); - - mockMessenger.setXDomainMessageSender(ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER); - - // invalid selector, revert - hevm.expectRevert("invalid selector"); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(4)) - ); - - bytes memory message = abi.encodeWithSelector( - IL2ETHGateway.finalizeDepositETH.selector, - address(this), - address(this), - 100, - new bytes(0) - ); - - // msg.value mismatch, revert - hevm.expectRevert("msg.value mismatch"); - mockMessenger.callTarget{value: 99}( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, message) - ); - } - - function testDropMessage( - uint256 amount, - address recipient, - bytes memory dataToCall - ) public { - amount = bound(amount, 1, address(this).balance); - bytes memory message = abi.encodeWithSelector( - IL2ETHGateway.finalizeDepositETH.selector, - address(this), - recipient, - amount, - dataToCall - ); - gateway.depositETHAndCall{value: amount}(recipient, amount, dataToCall, defaultGasLimit); - - // skip message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - hevm.stopPrank(); - - // ETH transfer failed, revert - revertOnReceive = true; - hevm.expectRevert("ETH transfer failed"); - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), amount, 0, message); - - // drop message 0 - hevm.expectEmit(true, true, false, true); - emit RefundETH(address(this), amount); - - revertOnReceive = false; - uint256 balance = address(this).balance; - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), amount, 0, message); - assertEq(balance + amount, address(this).balance); - } - function testFinalizeWithdrawETHFailedMocking( address sender, address recipient, diff --git a/src/test/L1GatewayTestBase.t.sol b/src/test/L1GatewayTestBase.t.sol index 26618168..2d04fdec 100644 --- a/src/test/L1GatewayTestBase.t.sol +++ b/src/test/L1GatewayTestBase.t.sol @@ -204,33 +204,26 @@ abstract contract L1GatewayTestBase is ScrollTestBase { memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; rollup.setBlobVersionedHash(0, blobVersionedHash); + bytes memory batchHeader1 = new bytes(73); + assembly { + mstore8(add(batchHeader1, 0x20), 7) // version + mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex + mstore(add(batchHeader1, add(0x20, 9)), 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757) // blobVersionedHash + mstore(add(batchHeader1, add(0x20, 41)), batchHash0) // parentBatchHash + } + // commit one batch bytes[] memory chunks = new bytes[](1); bytes memory chunk0 = new bytes(1 + 60); chunk0[0] = bytes1(uint8(1)); // one block in this chunk chunks[0] = chunk0; hevm.startPrank(address(0)); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, new bytes(0), blobDataProof); + rollup.commitBatches(7, batchHash0, keccak256(batchHeader1)); hevm.stopPrank(); - bytes memory batchHeader1 = new bytes(193); - assembly { - mstore8(add(batchHeader1, 0x20), 4) // version - mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex - mstore(add(batchHeader1, add(0x20, 9)), shl(192, 0)) // l1MessagePopped - mstore(add(batchHeader1, add(0x20, 17)), shl(192, 0)) // totalL1MessagePopped - mstore(add(batchHeader1, add(0x20, 25)), 0x246394445f4fe64ed5598554d55d1682d6fb3fe04bf58eb54ef81d1189fafb51) // dataHash - mstore(add(batchHeader1, add(0x20, 57)), 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757) // blobVersionedHash - mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - mstore(add(batchHeader1, add(0x20, 121)), 0) // lastBlockTimestamp - mcopy(add(batchHeader1, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof - } - hevm.startPrank(address(0)); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(uint256(2)), messageHash, new bytes(0)); + rollup.finalizeBundlePostEuclidV2(batchHeader1, 0, bytes32(uint256(2)), messageHash, new bytes(0)); hevm.stopPrank(); - - rollup.lastFinalizedBatchIndex(); } function setL2BaseFee(uint256 feePerGas) internal { diff --git a/src/test/L1ReverseCustomERC20Gateway.t.sol b/src/test/L1ReverseCustomERC20Gateway.t.sol index 0886a1f5..d4972191 100644 --- a/src/test/L1ReverseCustomERC20Gateway.t.sol +++ b/src/test/L1ReverseCustomERC20Gateway.t.sol @@ -147,53 +147,6 @@ contract L1ReverseCustomERC20GatewayTest is L1GatewayTestBase { _depositERC20(true, 2, amount, recipient, dataToCall, gasLimit, feePerGas); } - function testDropMessage( - uint256 amount, - address recipient, - bytes memory dataToCall - ) public { - // message 0 is append here - gateway.updateTokenMapping{value: 1 ether}(address(l1Token), address(l2Token)); - - // finalize message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - hevm.stopPrank(); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - - amount = bound(amount, 1, l1Token.balanceOf(address(this))); - bytes memory message = abi.encodeWithSelector( - IL2ERC20Gateway.finalizeDepositERC20.selector, - address(l1Token), - address(l2Token), - address(this), - recipient, - amount, - dataToCall - ); - gateway.depositERC20AndCall(address(l1Token), recipient, amount, dataToCall, defaultGasLimit); - - // skip message 1 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(1, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(2); - assertEq(messageQueueV1.pendingQueueIndex(), 2); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 2); - hevm.stopPrank(); - - // drop message 1 - hevm.expectEmit(true, true, false, true); - emit RefundERC20(address(l1Token), address(this), amount); - - uint256 balance = l1Token.balanceOf(address(this)); - uint256 gatewayBalance = l1Token.balanceOf(address(gateway)); - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 1, message); - assertEq(balance + amount, l1Token.balanceOf(address(this))); - assertEq(gatewayBalance, l1Token.balanceOf(address(gateway))); - } - function testFinalizeWithdrawERC20( address sender, uint256 amount, diff --git a/src/test/L1ScrollMessengerTest.t.sol b/src/test/L1ScrollMessengerTest.t.sol index d29c70a7..306804c0 100644 --- a/src/test/L1ScrollMessengerTest.t.sol +++ b/src/test/L1ScrollMessengerTest.t.sol @@ -126,8 +126,6 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { exceedValue = bound(exceedValue, 1, address(this).balance / 2); - l1Messenger.updateMaxReplayTimes(0); - // append a message l1Messenger.sendMessage{value: 100}(address(0), 100, new bytes(0), defaultGasLimit, refundAddress); @@ -142,20 +140,6 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { uint256 _fee = messageQueueV2.estimateL2BaseFee() * defaultGasLimit; - // Exceed maximum replay times - hevm.expectRevert("Exceed maximum replay times"); - l1Messenger.replayMessage{value: _fee}( - address(this), - address(0), - 100, - 0, - new bytes(0), - defaultGasLimit, - refundAddress - ); - - l1Messenger.updateMaxReplayTimes(1); - // refund exceed fee uint256 balanceBefore = refundAddress.balance; uint256 feeVaultBefore = feeVault.balance; @@ -175,7 +159,6 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { // 1. send a message with nonce 2 // 2. replay 3 times setL2BaseFee(0); - l1Messenger.updateMaxReplayTimes(100); l1Messenger.sendMessage{value: 100}(address(0), 100, new bytes(0), defaultGasLimit, refundAddress); bytes32 hash = keccak256( abi.encodeWithSignature( @@ -202,21 +185,6 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { } } - function testUpdateMaxReplayTimes(uint256 _maxReplayTimes) external { - // not owner, revert - hevm.startPrank(address(1)); - hevm.expectRevert("Ownable: caller is not the owner"); - l1Messenger.updateMaxReplayTimes(_maxReplayTimes); - hevm.stopPrank(); - - hevm.expectEmit(false, false, false, true); - emit UpdateMaxReplayTimes(3, _maxReplayTimes); - - assertEq(l1Messenger.maxReplayTimes(), 3); - l1Messenger.updateMaxReplayTimes(_maxReplayTimes); - assertEq(l1Messenger.maxReplayTimes(), _maxReplayTimes); - } - function testSetPause() external { // not owner, revert hevm.startPrank(address(1)); @@ -237,8 +205,6 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { l1Messenger.relayMessageWithProof(address(0), address(0), 0, 0, new bytes(0), _proof); hevm.expectRevert("Pausable: paused"); l1Messenger.replayMessage(address(0), address(0), 0, 0, new bytes(0), 0, address(0)); - hevm.expectRevert("Pausable: paused"); - l1Messenger.dropMessage(address(0), address(0), 0, 0, new bytes(0)); // unpause l1Messenger.setPause(false); @@ -275,111 +241,4 @@ contract L1ScrollMessengerTest is L1GatewayTestBase { _fee = messageQueueV2.estimateL2BaseFee() * gasLimit; l1Messenger.sendMessage{value: _fee + value}(address(0), value, hex"0011220033", gasLimit); } - - /* comments out, it is tested in `src/test/MessageQueueSwitch.t.sol`. - function testDropMessage() external { - // Provided message has not been enqueued, revert - hevm.expectRevert("Provided message has not been enqueued"); - l1Messenger.dropMessage(address(0), address(0), 0, 0, new bytes(0)); - - // send one message with nonce 0 - l1Messenger.sendMessage(address(0), 0, new bytes(0), defaultGasLimit); - assertEq(messageQueueV1.nextCrossDomainMessageIndex(), 1); - - // drop pending message, revert - hevm.expectRevert("cannot drop pending message"); - l1Messenger.dropMessage(address(this), address(0), 0, 0, new bytes(0)); - - l1Messenger.updateMaxReplayTimes(10); - - // replay 1 time - l1Messenger.replayMessage(address(this), address(0), 0, 0, new bytes(0), defaultGasLimit, address(0)); - assertEq(messageQueueV1.nextCrossDomainMessageIndex(), 2); - - // skip all 2 messages - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 2, 0x3); - messageQueueV1.finalizePoppedCrossDomainMessage(2); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 2); - assertEq(messageQueueV1.pendingQueueIndex(), 2); - hevm.stopPrank(); - for (uint256 i = 0; i < 2; ++i) { - assertBoolEq(messageQueueV1.isMessageSkipped(i), true); - assertBoolEq(messageQueueV1.isMessageDropped(i), false); - } - hevm.expectEmit(false, false, false, true); - emit OnDropMessageCalled(new bytes(0)); - l1Messenger.dropMessage(address(this), address(0), 0, 0, new bytes(0)); - for (uint256 i = 0; i < 2; ++i) { - assertBoolEq(messageQueueV1.isMessageSkipped(i), true); - assertBoolEq(messageQueueV1.isMessageDropped(i), true); - } - - // send one message with nonce 2 and replay 3 times - l1Messenger.sendMessage(address(0), 0, new bytes(0), defaultGasLimit); - assertEq(messageQueueV1.nextCrossDomainMessageIndex(), 3); - for (uint256 i = 0; i < 3; i++) { - l1Messenger.replayMessage(address(this), address(0), 0, 2, new bytes(0), defaultGasLimit, address(0)); - } - assertEq(messageQueueV1.nextCrossDomainMessageIndex(), 6); - - // only first 3 are skipped - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(2, 4, 0x7); - messageQueueV1.finalizePoppedCrossDomainMessage(6); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 6); - assertEq(messageQueueV1.pendingQueueIndex(), 6); - hevm.stopPrank(); - for (uint256 i = 2; i < 6; i++) { - assertBoolEq(messageQueueV1.isMessageSkipped(i), i < 5); - assertBoolEq(messageQueueV1.isMessageDropped(i), false); - } - - // drop non-skipped message, revert - hevm.expectRevert("drop non-skipped message"); - l1Messenger.dropMessage(address(this), address(0), 0, 2, new bytes(0)); - - // send one message with nonce 6 and replay 4 times - l1Messenger.sendMessage(address(0), 0, new bytes(0), defaultGasLimit); - for (uint256 i = 0; i < 4; i++) { - l1Messenger.replayMessage(address(this), address(0), 0, 6, new bytes(0), defaultGasLimit, address(0)); - } - assertEq(messageQueueV1.nextCrossDomainMessageIndex(), 11); - - // skip all 5 messages - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(6, 5, 0x1f); - messageQueueV1.finalizePoppedCrossDomainMessage(11); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 11); - assertEq(messageQueueV1.pendingQueueIndex(), 11); - hevm.stopPrank(); - for (uint256 i = 6; i < 11; ++i) { - assertBoolEq(messageQueueV1.isMessageSkipped(i), true); - assertBoolEq(messageQueueV1.isMessageDropped(i), false); - } - hevm.expectEmit(false, false, false, true); - emit OnDropMessageCalled(new bytes(0)); - l1Messenger.dropMessage(address(this), address(0), 0, 6, new bytes(0)); - for (uint256 i = 6; i < 11; ++i) { - assertBoolEq(messageQueueV1.isMessageSkipped(i), true); - assertBoolEq(messageQueueV1.isMessageDropped(i), true); - } - - // Message already dropped, revert - hevm.expectRevert("Message already dropped"); - l1Messenger.dropMessage(address(this), address(0), 0, 0, new bytes(0)); - hevm.expectRevert("Message already dropped"); - l1Messenger.dropMessage(address(this), address(0), 0, 6, new bytes(0)); - - // replay dropped message, revert - hevm.expectRevert("Message already dropped"); - l1Messenger.replayMessage(address(this), address(0), 0, 0, new bytes(0), defaultGasLimit, address(0)); - 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 909b1f3f..08043b2e 100644 --- a/src/test/L1StandardERC20Gateway.t.sol +++ b/src/test/L1StandardERC20Gateway.t.sol @@ -218,90 +218,6 @@ contract L1StandardERC20GatewayTest is L1GatewayTestBase { assertEq(balanceBefore + amount - fee, balanceAfter); } - function testDropMessageMocking() public { - MockScrollMessenger mockMessenger = new MockScrollMessenger(); - gateway = _deployGateway(address(mockMessenger)); - gateway.initialize( - address(counterpartGateway), - address(router), - address(mockMessenger), - address(template), - address(factory) - ); - - // only messenger can call, revert - hevm.expectRevert(ErrorCallerIsNotMessenger.selector); - gateway.onDropMessage(new bytes(0)); - - // only called in drop context, revert - hevm.expectRevert(ErrorNotInDropMessageContext.selector); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(0)) - ); - - mockMessenger.setXDomainMessageSender(ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER); - - // invalid selector, revert - hevm.expectRevert("invalid selector"); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(4)) - ); - - bytes memory message = abi.encodeWithSelector( - IL2ERC20Gateway.finalizeDepositERC20.selector, - address(l1Token), - address(l2Token), - address(this), - address(this), - 100, - new bytes(0) - ); - - // nonzero msg.value, revert - hevm.expectRevert("nonzero msg.value"); - mockMessenger.callTarget{value: 1}( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, message) - ); - } - - function testDropMessage( - uint256 amount, - address recipient, - bytes memory dataToCall - ) public { - amount = bound(amount, 1, l1Token.balanceOf(address(this)) / 2); - bytes memory message = abi.encodeWithSelector( - IL2ERC20Gateway.finalizeDepositERC20.selector, - address(l1Token), - address(l2Token), - address(this), - recipient, - amount, - abi.encode(true, abi.encode(dataToCall, abi.encode(l1Token.symbol(), l1Token.name(), l1Token.decimals()))) - ); - gateway.depositERC20AndCall(address(l1Token), recipient, amount, dataToCall, defaultGasLimit); - gateway.depositERC20AndCall(address(l1Token), recipient, amount, dataToCall, defaultGasLimit); - - // skip message 0 and 1 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 2, 0x3); - messageQueueV1.finalizePoppedCrossDomainMessage(2); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 2); - assertEq(messageQueueV1.pendingQueueIndex(), 2); - hevm.stopPrank(); - - // drop message 1 - hevm.expectEmit(true, true, false, true); - emit RefundERC20(address(l1Token), address(this), amount); - - uint256 balance = l1Token.balanceOf(address(this)); - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 1, message); - assertEq(balance + amount, l1Token.balanceOf(address(this))); - } - function testFinalizeWithdrawERC20FailedMocking( address sender, address recipient, diff --git a/src/test/L1WETHGateway.t.sol b/src/test/L1WETHGateway.t.sol index d93ab583..a4f3fb6f 100644 --- a/src/test/L1WETHGateway.t.sol +++ b/src/test/L1WETHGateway.t.sol @@ -143,101 +143,6 @@ contract L1WETHGatewayTest is L1GatewayTestBase { _depositERC20WithRecipientAndCalldata(true, amount, recipient, dataToCall, gasLimit, feePerGas); } - function testDropMessageMocking() public { - MockScrollMessenger mockMessenger = new MockScrollMessenger(); - gateway = _deployGateway(address(mockMessenger)); - gateway.initialize(address(counterpartGateway), address(router), address(mockMessenger)); - - // only messenger can call, revert - hevm.expectRevert(ErrorCallerIsNotMessenger.selector); - gateway.onDropMessage(new bytes(0)); - - // only called in drop context, revert - hevm.expectRevert(ErrorNotInDropMessageContext.selector); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(0)) - ); - - mockMessenger.setXDomainMessageSender(ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER); - - // invalid selector, revert - hevm.expectRevert("invalid selector"); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, new bytes(4)) - ); - - bytes memory message = abi.encodeWithSelector( - IL2ERC20Gateway.finalizeDepositERC20.selector, - address(l1weth), - address(l2weth), - address(this), - address(this), - 100, - new bytes(0) - ); - - // token not WETH, revert - hevm.expectRevert("token not WETH"); - mockMessenger.callTarget( - address(gateway), - abi.encodeWithSelector( - gateway.onDropMessage.selector, - abi.encodeWithSelector( - IL2ERC20Gateway.finalizeDepositERC20.selector, - address(l2weth), - address(l2weth), - address(this), - address(this), - 100, - new bytes(0) - ) - ) - ); - - // msg.value mismatch, revert - hevm.expectRevert("msg.value mismatch"); - mockMessenger.callTarget{value: 99}( - address(gateway), - abi.encodeWithSelector(gateway.onDropMessage.selector, message) - ); - } - - function testDropMessage( - uint256 amount, - address recipient, - bytes memory dataToCall - ) public { - amount = bound(amount, 1, l1weth.balanceOf(address(this))); - bytes memory message = abi.encodeWithSelector( - IL2ERC20Gateway.finalizeDepositERC20.selector, - address(l1weth), - address(l2weth), - address(this), - recipient, - amount, - dataToCall - ); - gateway.depositERC20AndCall(address(l1weth), recipient, amount, dataToCall, defaultGasLimit); - - // skip message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - hevm.stopPrank(); - - // drop message 0 - hevm.expectEmit(true, true, false, true); - emit RefundERC20(address(l1weth), address(this), amount); - - uint256 balance = l1weth.balanceOf(address(this)); - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), amount, 0, message); - assertEq(balance + amount, l1weth.balanceOf(address(this))); - } - function testFinalizeWithdrawERC20FailedMocking( address sender, address recipient, diff --git a/src/test/ScrollChain.t.sol b/src/test/ScrollChain.t.sol index b6faa19f..5930d486 100644 --- a/src/test/ScrollChain.t.sol +++ b/src/test/ScrollChain.t.sol @@ -126,7 +126,7 @@ contract ScrollChainTest is DSTestPlus { bytes memory header = _commitGenesisBatch(); for (uint256 i = 0; i < batches; ++i) { - header = _commitBatchV3Codec(6, header, 0, 0); + header = _commitBatchV7Codec(7, header); } assertEq(rollup.committedBatches(batches), keccak256(header)); @@ -140,221 +140,8 @@ contract ScrollChainTest is DSTestPlus { assertBoolEq(rollup.isEnforcedModeEnabled(), false); } - function testCommitBatchV3Codec() external { - bytes memory batchHeader0 = new bytes(89); - - // import 10 L1 messages - for (uint256 i = 0; i < 10; i++) { - messageQueueV1.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); - } - // import genesis batch first - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 1) - } - rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1))); - assertEq(rollup.committedBatches(0), keccak256(batchHeader0)); - - // caller not sequencer, revert - hevm.expectRevert(ScrollChain.ErrorCallerIsNotSequencer.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); - rollup.addSequencer(address(0)); - - // revert when ErrorIncorrectBatchVersion - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchVersion.selector); - rollup.commitBatchWithBlobProof(2, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchVersion.selector); - rollup.commitBatchWithBlobProof(3, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); - hevm.stopPrank(); - - // revert when ErrorBatchIsEmpty - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsEmpty.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, new bytes[](0), new bytes(0), new bytes(0)); - hevm.stopPrank(); - - // revert when ErrorBatchHeaderV3LengthMismatch - bytes memory header = new bytes(192); - assembly { - mstore8(add(header, 0x20), 4) // version - } - hevm.startPrank(address(0)); - hevm.expectRevert(BatchHeaderV3Codec.ErrorBatchHeaderV3LengthMismatch.selector); - rollup.commitBatchWithBlobProof(4, header, new bytes[](1), new bytes(0), new bytes(0)); - hevm.stopPrank(); - - // revert when ErrorIncorrectBatchHash - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 2) // change data hash for batch0 - } - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, new bytes[](1), new bytes(0), new bytes(0)); - hevm.stopPrank(); - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 1) // change back - } - - bytes[] memory chunks = new bytes[](1); - bytes memory chunk0; - - // no block in chunk, revert - chunk0 = new bytes(1); - chunks[0] = chunk0; - hevm.startPrank(address(0)); - hevm.expectRevert(ChunkCodecV1.ErrorNoBlockInChunkV1.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, new bytes(0), new bytes(0)); - hevm.stopPrank(); - - // invalid chunk length, revert - chunk0 = new bytes(1); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunks[0] = chunk0; - hevm.startPrank(address(0)); - hevm.expectRevert(ChunkCodecV1.ErrorIncorrectChunkLengthV1.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, new bytes(0), new bytes(0)); - hevm.stopPrank(); - - // cannot skip last L1 message, revert - chunk0 = new bytes(1 + 60); - bytes memory bitmap = new bytes(32); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunk0[58] = bytes1(uint8(1)); // numTransactions = 1 - chunk0[60] = bytes1(uint8(1)); // numL1Messages = 1 - bitmap[31] = bytes1(uint8(1)); - chunks[0] = chunk0; - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorLastL1MessageSkipped.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, bitmap, new bytes(0)); - hevm.stopPrank(); - - // num txs less than num L1 msgs, revert - chunk0 = new bytes(1 + 60); - bitmap = new bytes(32); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunk0[58] = bytes1(uint8(1)); // numTransactions = 1 - chunk0[60] = bytes1(uint8(3)); // numL1Messages = 3 - bitmap[31] = bytes1(uint8(3)); - chunks[0] = chunk0; - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorNumTxsLessThanNumL1Msgs.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, bitmap, new bytes(0)); - 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.commitBatchWithBlobProof(4, batchHeader0, chunks, new bytes(0), new bytes(0)); - hevm.stopPrank(); - - // @note we cannot check `ErrorFoundMultipleBlobs` here - - // upgrade to ScrollChainMockBlob - _upgradeToMockBlob(); - bytes - memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; - - chunk0 = new bytes(1 + 60); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunks[0] = chunk0; - // revert when ErrorCallPointEvaluationPrecompileFailed - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorCallPointEvaluationPrecompileFailed.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, new bytes(0), new bytes(0)); - hevm.stopPrank(); - - bytes32 batchHash0 = rollup.committedBatches(0); - bytes memory batchHeader1 = new bytes(193); - assembly { - mstore8(add(batchHeader1, 0x20), 4) // version - mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex - mstore(add(batchHeader1, add(0x20, 9)), 0) // l1MessagePopped - mstore(add(batchHeader1, add(0x20, 17)), 0) // totalL1MessagePopped - mstore(add(batchHeader1, add(0x20, 25)), 0x246394445f4fe64ed5598554d55d1682d6fb3fe04bf58eb54ef81d1189fafb51) // dataHash - mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash - mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - mstore(add(batchHeader1, add(0x20, 121)), 0) // lastBlockTimestamp - mcopy(add(batchHeader1, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof - } - // hash is ed32768c5f910a11edaf1c1ec0c0da847def9d24e0a24567c3c3d284061cf935 - - // succeed - hevm.startPrank(address(0)); - assertEq(rollup.committedBatches(1), bytes32(0)); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, new bytes(0), blobDataProof); - hevm.stopPrank(); - assertEq(rollup.committedBatches(1), keccak256(batchHeader1)); - - // revert when ErrorBatchIsAlreadyCommitted - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsAlreadyCommitted.selector); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, new bytes(0), blobDataProof); - hevm.stopPrank(); - } - - function testCommitBatchV5() external { - bytes[] memory headers = _prepareBatchesV3Codec(4); - - // revert when ErrorV5BatchNotContainsOnlyOneChunk - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorV5BatchNotContainsOnlyOneChunk.selector); // 0 chunk - rollup.commitBatchWithBlobProof(5, headers[10], new bytes[](0), new bytes(0), new bytes(0)); - hevm.expectRevert(ScrollChain.ErrorV5BatchNotContainsOnlyOneChunk.selector); // 2 chunks - rollup.commitBatchWithBlobProof(5, headers[10], new bytes[](2), new bytes(0), new bytes(0)); - hevm.stopPrank(); - - bytes[] memory chunks = new bytes[](1); - // revert when ErrorV5BatchNotContainsOnlyOneBlock - hevm.startPrank(address(0)); - chunks[0] = new bytes(1); - hevm.expectRevert(ChunkCodecV1.ErrorNoBlockInChunkV1.selector); // 1 chunk, 0 block - rollup.commitBatchWithBlobProof(5, headers[10], chunks, new bytes(0), new bytes(0)); - for (uint256 i = 2; i < 256; ++i) { - chunks[0] = new bytes(1 + 60 * i); - chunks[0][0] = bytes1(uint8(i)); - hevm.expectRevert(ScrollChain.ErrorV5BatchNotContainsOnlyOneBlock.selector); // 1 chunk, i block - rollup.commitBatchWithBlobProof(5, headers[10], chunks, new bytes(0), new bytes(0)); - } - hevm.stopPrank(); - - // revert when ErrorV5BatchContainsTransactions - hevm.startPrank(address(0)); - for (uint256 x = 0; x < 5; ++x) { - for (uint256 y = 0; y < 5; ++y) { - if (x + y == 0) continue; - bytes memory chunk = new bytes(1 + 60); - chunk[0] = bytes1(uint8(1)); - uint256 blockPtr; - assembly { - blockPtr := add(chunk, 0x21) - mstore(add(blockPtr, 56), shl(240, add(x, y))) - mstore(add(blockPtr, 58), shl(240, y)) - } - assertEq(x + y, ChunkCodecV1.getNumTransactions(blockPtr)); - assertEq(y, ChunkCodecV1.getNumL1Messages(blockPtr)); - chunks[0] = chunk; - hevm.expectRevert(ScrollChain.ErrorV5BatchContainsTransactions.selector); // 1 chunk, 1 nonempty block - rollup.commitBatchWithBlobProof(5, headers[10], chunks, new bytes(0), new bytes(0)); - } - } - hevm.stopPrank(); - - assertEq(rollup.initialEuclidBatchIndex(), 0); - bytes memory v5Header = _commitBatchV3Codec(5, headers[10], 0, 0); - assertEq(rollup.initialEuclidBatchIndex(), 11); - - // revert when commit again - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsAlreadyCommitted.selector); - rollup.commitBatchWithBlobProof(5, v5Header, new bytes[](0), new bytes(0), new bytes(0)); - hevm.stopPrank(); - } - function testCommitBatchV7Codec() external { - bytes[] memory headers = _prepareBatchesV3Codec(6); + bytes[] memory headers = _prepareBatchesV7Codec(7); (, bytes memory h11) = _constructBatchStructCodecV7(7, headers[10]); (, bytes memory h12) = _constructBatchStructCodecV7(7, h11); (, bytes memory h13) = _constructBatchStructCodecV7(7, h12); @@ -401,113 +188,23 @@ contract ScrollChainTest is DSTestPlus { assertGt(uint256(rollup.committedBatches(13)), 0); } - function testFinalizeBundleWithProof() external { - // caller not prover, revert - hevm.expectRevert(ScrollChain.ErrorCallerIsNotProver.selector); - rollup.finalizeBundleWithProof(new bytes(0), bytes32(0), bytes32(0), new bytes(0)); - - rollup.addProver(address(0)); - rollup.addSequencer(address(0)); - - // import genesis batch - bytes memory batchHeader0 = new bytes(89); - assembly { - mstore(add(batchHeader0, add(0x20, 25)), 1) - } - rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1))); - - // upgrade to ScrollChainMockBlob - _upgradeToMockBlob(); - bytes - memory blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; - - bytes[] memory chunks = new bytes[](1); - bytes memory chunk0; - - bytes32 batchHash0 = rollup.committedBatches(0); - bytes memory batchHeader1 = new bytes(193); - assembly { - mstore8(add(batchHeader1, 0x20), 4) // version - mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex - mstore(add(batchHeader1, add(0x20, 9)), 0) // l1MessagePopped - mstore(add(batchHeader1, add(0x20, 17)), 0) // totalL1MessagePopped - mstore(add(batchHeader1, add(0x20, 25)), 0x246394445f4fe64ed5598554d55d1682d6fb3fe04bf58eb54ef81d1189fafb51) // dataHash - mstore(add(batchHeader1, add(0x20, 57)), blobVersionedHash) // blobVersionedHash - mstore(add(batchHeader1, add(0x20, 89)), batchHash0) // parentBatchHash - mstore(add(batchHeader1, add(0x20, 121)), 0) // lastBlockTimestamp - mcopy(add(batchHeader1, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof - } - // hash is ed32768c5f910a11edaf1c1ec0c0da847def9d24e0a24567c3c3d284061cf935 - - // commit one batch - chunk0 = new bytes(1 + 60); - chunk0[0] = bytes1(uint8(1)); // one block in this chunk - chunks[0] = chunk0; - hevm.startPrank(address(0)); - assertEq(rollup.committedBatches(1), bytes32(0)); - rollup.commitBatchWithBlobProof(4, batchHeader0, chunks, new bytes(0), blobDataProof); - hevm.stopPrank(); - assertEq(rollup.committedBatches(1), keccak256(batchHeader1)); - - // revert when ErrorStateRootIsZero - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorStateRootIsZero.selector); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(0), bytes32(0), new bytes(0)); - hevm.stopPrank(); - - // revert when ErrorBatchHeaderV3LengthMismatch - bytes memory header = new bytes(192); - assembly { - mstore8(add(header, 0x20), 4) // version - } - hevm.startPrank(address(0)); - hevm.expectRevert(BatchHeaderV3Codec.ErrorBatchHeaderV3LengthMismatch.selector); - rollup.finalizeBundleWithProof(header, bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); - hevm.stopPrank(); - - // revert when ErrorIncorrectBatchHash - batchHeader1[10] = bytes1(uint8(1)); // change random byte - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchHash.selector); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(uint256(1)), bytes32(uint256(2)), new bytes(0)); - hevm.stopPrank(); - batchHeader1[10] = bytes1(uint8(0)); // change back - - // verify success - (, , uint256 lastFinalizeTimestamp, , ) = rollup.miscData(); - assertEq(lastFinalizeTimestamp, 0); - hevm.warp(100); - assertBoolEq(rollup.isBatchFinalized(1), false); - hevm.startPrank(address(0)); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(uint256(2)), bytes32(uint256(3)), new bytes(0)); - hevm.stopPrank(); - (, , lastFinalizeTimestamp, , ) = rollup.miscData(); - assertEq(lastFinalizeTimestamp, 100); - assertBoolEq(rollup.isBatchFinalized(1), true); - assertEq(rollup.finalizedStateRoots(1), bytes32(uint256(2))); - assertEq(rollup.withdrawRoots(1), bytes32(uint256(3))); - assertEq(rollup.lastFinalizedBatchIndex(), 1); - - // revert when ErrorBatchIsAlreadyVerified - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorBatchIsAlreadyVerified.selector); - rollup.finalizeBundleWithProof(batchHeader1, bytes32(uint256(2)), bytes32(uint256(3)), new bytes(0)); - hevm.stopPrank(); - } - function testFinalizeBundlePostEuclidV2() external { + messageQueueV2.initialize(); + rollup.initializeV2(); + // import 10 L1 messages into message queue V2 + for (uint256 i = 0; i < 10; i++) { + messageQueueV2.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); + } // 0: genesis // 1-10: v6 // 11-20: v7 bytes[] memory headers = new bytes[](21); { - bytes[] memory headersV6 = _prepareBatchesV3Codec(6); + bytes[] memory headersV7 = _prepareBatchesV7Codec(7); for (uint256 i = 0; i <= 10; ++i) { - headers[i] = headersV6[i]; + headers[i] = headersV7[i]; } } - messageQueueV2.initialize(); - rollup.initializeV2(); for (uint256 i = 11; i < 21; ++i) { headers[i] = _commitBatchV7Codec(7, headers[i - 1]); } @@ -517,10 +214,10 @@ contract ScrollChainTest is DSTestPlus { rollup.finalizeBundlePostEuclidV2(new bytes(0), 0, bytes32(0), bytes32(0), new bytes(0)); // revert ErrorNotAllV1MessagesAreFinalized - hevm.startPrank(address(0)); - hevm.expectRevert(ScrollChain.ErrorNotAllV1MessagesAreFinalized.selector); - rollup.finalizeBundlePostEuclidV2(headers[20], 0, bytes32(0), bytes32(0), new bytes(0)); - hevm.stopPrank(); + // hevm.startPrank(address(0)); + // hevm.expectRevert(ScrollChain.ErrorNotAllV1MessagesAreFinalized.selector); + // rollup.finalizeBundlePostEuclidV2(headers[20], 0, bytes32(0), bytes32(0), new bytes(0)); + // hevm.stopPrank(); // finalize all v6 batches (, uint256 lastFinalizedBatchIndex, uint256 lastFinalizeTimestamp, uint256 flags, ) = rollup.miscData(); @@ -529,16 +226,16 @@ contract ScrollChainTest is DSTestPlus { assertEq(flags, 0); hevm.warp(100); hevm.startPrank(address(0)); - rollup.finalizeBundleWithProof(headers[10], keccak256("x10"), keccak256("y10"), new bytes(0)); + rollup.finalizeBundlePostEuclidV2(headers[10], 10, keccak256("x10"), keccak256("y10"), new bytes(0)); hevm.stopPrank(); (, lastFinalizedBatchIndex, lastFinalizeTimestamp, flags, ) = rollup.miscData(); assertEq(lastFinalizedBatchIndex, 10); assertEq(lastFinalizeTimestamp, 100); - assertEq(flags, 0); + assertEq(flags, 1); assertEq(rollup.lastFinalizedBatchIndex(), lastFinalizedBatchIndex); assertEq(rollup.finalizedStateRoots(10), keccak256("x10")); assertEq(rollup.withdrawRoots(10), keccak256("y10")); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 10); + // assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 10); // revert ErrorStateRootIsZero hevm.startPrank(address(0)); @@ -612,24 +309,28 @@ contract ScrollChainTest is DSTestPlus { } function testCommitAndFinalizeBatchByExpiredMessage() external { + messageQueueV2.initialize(); + rollup.initializeV2(); + // import 10 L1 messages into message queue V2 + for (uint256 i = 0; i < 10; i++) { + messageQueueV2.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); + } // 0: genesis // 1-10: v6 // 11-20: v7 bytes[] memory headers = new bytes[](21); { - bytes[] memory headersV6 = _prepareBatchesV3Codec(6); + bytes[] memory headersV7 = _prepareBatchesV7Codec(7); for (uint256 i = 0; i <= 10; ++i) { - headers[i] = headersV6[i]; + headers[i] = headersV7[i]; } } - messageQueueV2.initialize(); - rollup.initializeV2(); for (uint256 i = 11; i < 21; ++i) { headers[i] = _commitBatchV7Codec(7, headers[i - 1]); } // finalize all v6 batches hevm.startPrank(address(0)); - rollup.finalizeBundleWithProof(headers[10], keccak256("x10"), keccak256("y10"), new bytes(0)); + rollup.finalizeBundlePostEuclidV2(headers[10], 10, keccak256("x10"), keccak256("y10"), new bytes(0)); hevm.stopPrank(); // finalize two v7 batches hevm.startPrank(address(0)); @@ -697,16 +398,12 @@ contract ScrollChainTest is DSTestPlus { // revert when do commit hevm.startPrank(address(0)); hevm.expectRevert(ScrollChain.ErrorInEnforcedBatchMode.selector); - rollup.commitBatchWithBlobProof(0, new bytes(0), new bytes[](0), new bytes(0), new bytes(0)); - hevm.expectRevert(ScrollChain.ErrorInEnforcedBatchMode.selector); rollup.commitBatches(0, bytes32(0), bytes32(0)); hevm.stopPrank(); // revert when do finalize hevm.startPrank(address(0)); hevm.expectRevert(ScrollChain.ErrorInEnforcedBatchMode.selector); - rollup.finalizeBundleWithProof(new bytes(0), bytes32(0), bytes32(0), new bytes(0)); - hevm.expectRevert(ScrollChain.ErrorInEnforcedBatchMode.selector); rollup.finalizeBundlePostEuclidV2(new bytes(0), 0, bytes32(0), bytes32(0), new bytes(0)); hevm.stopPrank(); @@ -762,24 +459,28 @@ contract ScrollChainTest is DSTestPlus { function testCommitAndFinalizeBatchByExpiredBatch() external { hevm.warp(100); + messageQueueV2.initialize(); + rollup.initializeV2(); + // import 10 L1 messages into message queue V2 + for (uint256 i = 0; i < 10; i++) { + messageQueueV2.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); + } // 0: genesis // 1-10: v6 // 11-20: v7 bytes[] memory headers = new bytes[](21); { - bytes[] memory headersV6 = _prepareBatchesV3Codec(6); + bytes[] memory headersV7 = _prepareBatchesV7Codec(7); for (uint256 i = 0; i <= 10; ++i) { - headers[i] = headersV6[i]; + headers[i] = headersV7[i]; } } - messageQueueV2.initialize(); - rollup.initializeV2(); for (uint256 i = 11; i < 21; ++i) { headers[i] = _commitBatchV7Codec(7, headers[i - 1]); } // finalize all v6 batches hevm.startPrank(address(0)); - rollup.finalizeBundleWithProof(headers[10], keccak256("x10"), keccak256("y10"), new bytes(0)); + rollup.finalizeBundlePostEuclidV2(headers[10], 10, keccak256("x10"), keccak256("y10"), new bytes(0)); hevm.stopPrank(); // finalize two v7 batches hevm.startPrank(address(0)); @@ -838,25 +539,25 @@ contract ScrollChainTest is DSTestPlus { } function testRevertBatch() external { + messageQueueV2.initialize(); + rollup.initializeV2(); + // import 10 L1 messages into message queue V2 + for (uint256 i = 0; i < 10; i++) { + messageQueueV2.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); + } // 0: genesis // 1-10: v6 // 11-20: v7 bytes[] memory headers = new bytes[](21); { - bytes[] memory headersV6 = _prepareBatchesV3Codec(6); + bytes[] memory headersV7 = _prepareBatchesV7Codec(7); for (uint256 i = 0; i <= 10; ++i) { - headers[i] = headersV6[i]; + headers[i] = headersV7[i]; } } - messageQueueV2.initialize(); - rollup.initializeV2(); for (uint256 i = 11; i < 21; ++i) { headers[i] = _commitBatchV7Codec(7, headers[i - 1]); } - // finalize all v6 batches - hevm.startPrank(address(0)); - rollup.finalizeBundleWithProof(headers[10], keccak256("x10"), keccak256("y10"), new bytes(0)); - hevm.stopPrank(); // finalize two v7 batches hevm.startPrank(address(0)); rollup.finalizeBundlePostEuclidV2(headers[12], 10, keccak256("x12"), keccak256("y12"), new bytes(0)); @@ -869,8 +570,8 @@ contract ScrollChainTest is DSTestPlus { hevm.stopPrank(); // revert ErrorIncorrectBatchVersion - hevm.expectRevert(ScrollChain.ErrorIncorrectBatchVersion.selector); - rollup.revertBatch(headers[10]); + // hevm.expectRevert(ScrollChain.ErrorIncorrectBatchVersion.selector); + // rollup.revertBatch(headers[10]); // revert ErrorRevertFinalizedBatch hevm.expectRevert(ScrollChain.ErrorRevertFinalizedBatch.selector); @@ -969,14 +670,6 @@ contract ScrollChainTest is DSTestPlus { hevm.startPrank(address(0)); hevm.expectRevert("Pausable: paused"); - rollup.commitBatchWithBlobProof(4, new bytes(0), new bytes[](0), new bytes(0), new bytes(0)); - hevm.expectRevert("Pausable: paused"); - rollup.commitBatchWithBlobProof(5, new bytes(0), new bytes[](0), new bytes(0), new bytes(0)); - hevm.expectRevert("Pausable: paused"); - rollup.commitBatchWithBlobProof(6, 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)); - hevm.expectRevert("Pausable: paused"); rollup.commitBatches(7, bytes32(0), bytes32(0)); hevm.expectRevert("Pausable: paused"); rollup.finalizeBundlePostEuclidV2(new bytes(0), 0, bytes32(0), bytes32(0), new bytes(0)); @@ -987,22 +680,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 testImportGenesisBlock() external { bytes memory batchHeader; @@ -1091,30 +768,6 @@ contract ScrollChainTest is DSTestPlus { ScrollChainMockBlob(address(rollup)).setBlobVersionedHash(0, blobVersionedHash); } - /// @dev Prepare 10 batches, each of the first 5 has 2 l1 messages, each of the second 5 has no l1 message. - function _prepareBatchesV3Codec(uint8 version) internal returns (bytes[] memory headers) { - // grant roles - rollup.addProver(address(0)); - rollup.addSequencer(address(0)); - _upgradeToMockBlob(); - - headers = new bytes[](11); - // import 10 L1 messages into message queue V1 - for (uint256 i = 0; i < 10; i++) { - messageQueueV1.appendCrossDomainMessage(address(this), 1000000, new bytes(0)); - } - // commit genesis batch - headers[0] = _commitGenesisBatch(); - // commit 5 batches, each has 2 l1 messages - for (uint256 i = 1; i <= 5; ++i) { - headers[i] = _commitBatchV3Codec(version, headers[i - 1], 2, 1); - } - // commit 5 batches, each has 0 l1 message - for (uint256 i = 6; i <= 10; ++i) { - headers[i] = _commitBatchV3Codec(version, headers[i - 1], 0, 1); - } - } - function _commitGenesisBatch() internal returns (bytes memory header) { header = new bytes(89); assembly { @@ -1124,117 +777,6 @@ contract ScrollChainTest is DSTestPlus { assertEq(rollup.committedBatches(0), keccak256(header)); } - function _constructBatchStructCodecV3( - uint8 version, - bytes memory parentHeader, - uint256 numL1Message, - uint256 numL2Transaction - ) - internal - view - returns ( - bytes memory bitmap, - bytes[] memory chunks, - bytes memory blobDataProof, - uint256 index, - uint256 totalL1MessagePopped, - bytes memory header - ) - { - uint256 batchPtr; - assembly { - batchPtr := add(parentHeader, 0x20) - } - index = BatchHeaderV0Codec.getBatchIndex(batchPtr) + 1; - totalL1MessagePopped = BatchHeaderV0Codec.getTotalL1MessagePopped(batchPtr) + numL1Message; - bytes32 parentHash = keccak256(parentHeader); - blobDataProof = hex"2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e68753ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0a5a0c9e8a145c5ef6e415c245690effa2914ec9393f58a7251d30c0657da1453d9ad906eae8b97dd60c9a216f81b4df7af34d01e214e1ec5865f0133ecc16d7459e49dab66087340677751e82097fbdd20551d66076f425775d1758a9dfd186b"; - bytes32[] memory hashes = new bytes32[](numL1Message); - for (uint256 i = 0; i < numL1Message; ++i) { - hashes[i] = messageQueueV1.getCrossDomainMessage(BatchHeaderV0Codec.getTotalL1MessagePopped(batchPtr) + i); - } - // commit batch, one chunk with one block, 1 + numL1Message tx, numL1Message L1 message - // payload for data hash of chunk0 - // hex(index) // block number - // hex(index) // timestamp - // 0000000000000000000000000000000000000000000000000000000000000000 // baseFee - // 0000000000000000 // gasLimit - // hex(numL2Transaction + numL1Message) // numTransactions - // ... // l1 messages - // data hash for chunk0 - // keccak256(chunk0) - // data hash for all chunks - // keccak256(keccak256(chunk0)) - // => payload for batch header - // 03 // version - // hex(index) // batchIndex - // hex(numL1Message) // l1MessagePopped - // hex(totalL1MessagePopped) // totalL1MessagePopped - // keccak256(keccak256(chunk0)) // dataHash - // 013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757 // blobVersionedHash - // keccak256(parentHeader) // parentBatchHash - // hex(index) // lastBlockTimestamp - // 2c9d777660f14ad49803a6442935c0d24a0d83551de5995890bf70a17d24e687 // blobDataProof - // 53ab0fe6807c7081f0885fe7da741554d658a03730b1fa006f8319f8b993bcb0 // blobDataProof - if (numL1Message > 0) bitmap = new bytes(32); - chunks = new bytes[](1); - bytes memory chunk0; - chunk0 = new bytes(1 + 60); - assembly { - mstore(add(chunk0, 0x20), shl(248, 1)) // numBlocks = 1 - mstore(add(chunk0, add(0x21, 8)), shl(192, index)) // timestamp = 0x123 - mstore(add(chunk0, add(0x21, 56)), shl(240, add(numL1Message, numL2Transaction))) // numTransactions = numL2Transaction + numL1Message - mstore(add(chunk0, add(0x21, 58)), shl(240, numL1Message)) // numL1Messages - } - chunks[0] = chunk0; - bytes memory chunkData = new bytes(58 + numL1Message * 32); - assembly { - mcopy(add(chunkData, 0x20), add(chunk0, 0x21), 58) - mcopy(add(chunkData, 0x5a), add(hashes, 0x20), mul(32, mload(hashes))) - } - bytes32 dataHash = keccak256(abi.encode(keccak256(chunkData))); - header = new bytes(193); - assembly { - mstore8(add(header, 0x20), version) // version - mstore(add(header, add(0x20, 1)), shl(192, index)) // batchIndex - mstore(add(header, add(0x20, 9)), shl(192, numL1Message)) // l1MessagePopped - mstore(add(header, add(0x20, 17)), shl(192, totalL1MessagePopped)) // totalL1MessagePopped - mstore(add(header, add(0x20, 25)), dataHash) // dataHash - mstore(add(header, add(0x20, 57)), 0x013590dc3544d56629ba81bb14d4d31248f825001653aa575eb8e3a719046757) // blobVersionedHash - mstore(add(header, add(0x20, 89)), parentHash) // parentBatchHash - mstore(add(header, add(0x20, 121)), shl(192, index)) // lastBlockTimestamp - mcopy(add(header, add(0x20, 129)), add(blobDataProof, 0x20), 64) // blobDataProof - } - } - - function _commitBatchV3Codec( - uint8 version, - bytes memory parentHeader, - uint256 numL1Message, - uint256 numL2Transaction - ) internal returns (bytes memory) { - ( - bytes memory bitmap, - bytes[] memory chunks, - bytes memory blobDataProof, - uint256 index, - uint256 totalL1MessagePopped, - bytes memory header - ) = _constructBatchStructCodecV3(version, parentHeader, numL1Message, numL2Transaction); - hevm.startPrank(address(0)); - if (numL1Message > 0) { - hevm.expectEmit(false, false, false, true); - emit DequeueTransaction(totalL1MessagePopped - numL1Message, numL1Message, 0); - } - hevm.expectEmit(true, true, false, true); - emit CommitBatch(index, keccak256(header)); - rollup.commitBatchWithBlobProof(version, parentHeader, chunks, bitmap, blobDataProof); - hevm.stopPrank(); - assertEq(rollup.committedBatches(index), keccak256(header)); - assertEq(messageQueueV1.pendingQueueIndex(), totalL1MessagePopped); - return header; - } - function _constructBatchStructCodecV7(uint8 version, bytes memory parentHeader) internal pure @@ -1265,4 +807,24 @@ contract ScrollChainTest is DSTestPlus { assertEq(rollup.committedBatches(index), keccak256(header)); return header; } + + /// @dev Prepare 10 batches, each of the first 5 has 2 l1 messages, each of the second 5 has no l1 message. + function _prepareBatchesV7Codec(uint8 version) internal returns (bytes[] memory headers) { + // grant roles + rollup.addProver(address(0)); + rollup.addSequencer(address(0)); + _upgradeToMockBlob(); + + headers = new bytes[](11); + // commit genesis batch + headers[0] = _commitGenesisBatch(); + // commit 5 batches, each has 2 l1 messages + for (uint256 i = 1; i <= 5; ++i) { + headers[i] = _commitBatchV7Codec(version, headers[i - 1]); + } + // commit 5 batches, each has 0 l1 message + for (uint256 i = 6; i <= 10; ++i) { + headers[i] = _commitBatchV7Codec(version, headers[i - 1]); + } + } } diff --git a/src/test/TokenRateLimiter.t.sol b/src/test/TokenRateLimiter.t.sol deleted file mode 100644 index 5eb44b14..00000000 --- a/src/test/TokenRateLimiter.t.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity =0.8.24; - -import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; - -import {TokenRateLimiter} from "../rate-limiter/TokenRateLimiter.sol"; -import {ITokenRateLimiter} from "../rate-limiter/ITokenRateLimiter.sol"; - -contract TokenRateLimiterTest is DSTestPlus { - event UpdateTotalLimit(address indexed token, uint256 oldTotalLimit, uint256 newTotalLimit); - - TokenRateLimiter private limiter; - - function setUp() public { - hevm.warp(86400); - limiter = new TokenRateLimiter(86400); - } - - function testUpdateTotalLimit(address _token, uint104 _newTotalLimit) external { - hevm.assume(_newTotalLimit > 0); - - // not admin, revert - hevm.startPrank(address(1)); - hevm.expectRevert( - "AccessControl: account 0x0000000000000000000000000000000000000001 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000" - ); - limiter.updateTotalLimit(_token, _newTotalLimit); - hevm.stopPrank(); - - // zero revert - hevm.expectRevert(abi.encodeWithSelector(ITokenRateLimiter.TotalLimitIsZero.selector, _token)); - limiter.updateTotalLimit(_token, 0); - - // success - hevm.expectEmit(true, false, false, true); - emit UpdateTotalLimit(_token, 0 ether, _newTotalLimit); - limiter.updateTotalLimit(_token, _newTotalLimit); - (, uint104 _totalLimit, ) = limiter.currentPeriod(_token); - assertEq(_totalLimit, _newTotalLimit); - } - - function testAddUsedAmount(address _token) external { - // non-spender, revert - hevm.startPrank(address(1)); - hevm.expectRevert( - "AccessControl: account 0x0000000000000000000000000000000000000001 is missing role 0x267f05081a073059ae452e6ac77ec189636e43e41051d4c3ec760734b3d173cb" - ); - limiter.addUsedAmount(_token, 0); - hevm.stopPrank(); - - limiter.grantRole(bytes32(0x267f05081a073059ae452e6ac77ec189636e43e41051d4c3ec760734b3d173cb), address(this)); - limiter.updateTotalLimit(_token, 100 ether); - - // exceed total limit on first call - hevm.expectRevert(abi.encodeWithSelector(ITokenRateLimiter.ExceedTotalLimit.selector, _token)); - limiter.addUsedAmount(_token, 100 ether + 1); - _checkTotalCurrentPeriodAmountAmount(_token, 0); - - // exceed total limit on second call - limiter.addUsedAmount(_token, 50 ether); - _checkTotalCurrentPeriodAmountAmount(_token, 50 ether); - hevm.expectRevert(abi.encodeWithSelector(ITokenRateLimiter.ExceedTotalLimit.selector, _token)); - limiter.addUsedAmount(_token, 50 ether + 1); - _checkTotalCurrentPeriodAmountAmount(_token, 50 ether); - - // one period passed - hevm.warp(86400 * 2); - limiter.addUsedAmount(_token, 1 ether); - _checkTotalCurrentPeriodAmountAmount(_token, 1 ether); - - // exceed - hevm.expectRevert(abi.encodeWithSelector(ITokenRateLimiter.ExceedTotalLimit.selector, _token)); - limiter.addUsedAmount(_token, 99 ether + 1); - _checkTotalCurrentPeriodAmountAmount(_token, 1 ether); - } - - function _checkTotalCurrentPeriodAmountAmount(address token, uint256 expected) internal { - (, , uint256 totalAmount) = limiter.currentPeriod(token); - assertEq(totalAmount, expected); - } -} diff --git a/src/test/lido/L1LidoGateway.t.sol b/src/test/lido/L1LidoGateway.t.sol index 5d6b4951..b94ac352 100644 --- a/src/test/lido/L1LidoGateway.t.sol +++ b/src/test/lido/L1LidoGateway.t.sol @@ -330,86 +330,6 @@ contract L1LidoGatewayTest is L1GatewayTestBase { _depositERC20(true, 2, amount, recipient, dataToCall, gasLimit, feePerGas); } - function testDropMessage(uint256 amount, address recipient) public { - hevm.assume(recipient != address(0)); - - amount = bound(amount, 1, l1Token.balanceOf(address(this))); - bytes memory message = abi.encodeCall( - IL2ERC20Gateway.finalizeDepositERC20, - (address(l1Token), address(l2Token), address(this), recipient, amount, new bytes(0)) - ); - gateway.depositERC20AndCall(address(l1Token), recipient, amount, new bytes(0), defaultGasLimit); - - MockScrollMessenger mockMessenger = new MockScrollMessenger(); - MockL1LidoGateway mockGateway = _deployGateway(address(mockMessenger)); - mockGateway.initialize(address(counterpartGateway), address(router), address(mockMessenger)); - mockGateway.initializeV2(address(0), address(0), address(0), address(0)); - - // revert caller is not messenger - hevm.expectRevert(ErrorCallerIsNotMessenger.selector); - mockGateway.onDropMessage(new bytes(0)); - - // revert not in drop context - hevm.expectRevert(ErrorNotInDropMessageContext.selector); - mockMessenger.callTarget(address(mockGateway), abi.encodeCall(mockGateway.onDropMessage, (new bytes(0)))); - - // revert when reentrant - mockMessenger.setXDomainMessageSender(ScrollConstants.DROP_XDOMAIN_MESSAGE_SENDER); - hevm.expectRevert("ReentrancyGuard: reentrant call"); - mockGateway.reentrantCall( - address(mockMessenger), - abi.encodeCall( - mockMessenger.callTarget, - (address(mockGateway), abi.encodeCall(mockGateway.onDropMessage, (message))) - ) - ); - - // revert when invalid selector - hevm.expectRevert("invalid selector"); - mockMessenger.callTarget(address(mockGateway), abi.encodeCall(mockGateway.onDropMessage, (new bytes(4)))); - - // revert when l1 token not supported - hevm.expectRevert(ErrorUnsupportedL1Token.selector); - mockMessenger.callTarget( - address(mockGateway), - abi.encodeCall( - mockGateway.onDropMessage, - ( - abi.encodeCall( - IL2ERC20Gateway.finalizeDepositERC20, - (address(l2Token), address(l2Token), address(this), recipient, amount, new bytes(0)) - ) - ) - ) - ); - - // revert when nonzero msg.value - hevm.expectRevert(ErrorNonZeroMsgValue.selector); - mockMessenger.callTarget{value: 1}( - address(mockGateway), - abi.encodeWithSelector(mockGateway.onDropMessage.selector, message) - ); - - // succeed on drop - // skip message 0 - hevm.startPrank(address(rollup)); - messageQueueV1.popCrossDomainMessage(0, 1, 0x1); - messageQueueV1.finalizePoppedCrossDomainMessage(1); - assertEq(messageQueueV1.nextUnfinalizedQueueIndex(), 1); - assertEq(messageQueueV1.pendingQueueIndex(), 1); - hevm.stopPrank(); - - // should emit RefundERC20 - hevm.expectEmit(true, true, false, true); - emit RefundERC20(address(l1Token), address(this), amount); - - uint256 balance = l1Token.balanceOf(address(this)); - uint256 gatewayBalance = l1Token.balanceOf(address(gateway)); - l1Messenger.dropMessage(address(gateway), address(counterpartGateway), 0, 0, message); - assertEq(gatewayBalance - amount, l1Token.balanceOf(address(gateway))); - assertEq(balance + amount, l1Token.balanceOf(address(this))); - } - function testFinalizeWithdrawERC20( address sender, uint256 amount,