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/src/L1/IL1ScrollMessenger.sol b/src/L1/IL1ScrollMessenger.sol index 4a8f2b9f..dac1c8ce 100644 --- a/src/L1/IL1ScrollMessenger.sol +++ b/src/L1/IL1ScrollMessenger.sol @@ -62,18 +62,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..15bde04a 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,6 +76,8 @@ contract L1ScrollMessenger is ScrollMessengerBase, IL1ScrollMessenger { mapping(bytes32 => bool) public isL2MessageExecuted; /// @notice Mapping from L1 message hash to drop status. + /// @custom:deprecated This is no longer used. + // slither-disable-next-line uninitialized-state mapping(bytes32 => bool) public isL1MessageDropped; /// @dev The storage slot used as Rollup contract, which is deprecated now. @@ -281,64 +281,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 * ************************/ 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/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/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/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/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..74aaad35 100644 --- a/src/test/L1ScrollMessengerTest.t.sol +++ b/src/test/L1ScrollMessengerTest.t.sol @@ -237,8 +237,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 +273,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/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,