diff --git a/test-foundry/L1ArbitrumExtendedGateway.t.sol b/test-foundry/L1ArbitrumExtendedGateway.t.sol index 0f7007110e..806e9c4c08 100644 --- a/test-foundry/L1ArbitrumExtendedGateway.t.sol +++ b/test-foundry/L1ArbitrumExtendedGateway.t.sol @@ -2,209 +2,64 @@ pragma solidity ^0.8.0; -import "forge-std/Test.sol"; +import "./L1ArbitrumGateway.t.sol"; import "contracts/tokenbridge/ethereum/gateway/L1ArbitrumExtendedGateway.sol"; -import { TestERC20 } from "contracts/tokenbridge/test/TestERC20.sol"; -import { InboxMock } from "contracts/tokenbridge/test/InboxMock.sol"; - -abstract contract L1ArbitrumExtendedGatewayTest is Test { - IL1ArbitrumGateway public l1Gateway; - IERC20 public token; - - address public l2Gateway = makeAddr("l2Gateway"); - address public router = makeAddr("router"); - address public inbox; - address public user = makeAddr("user"); - - // retryable params - uint256 public maxSubmissionCost; - uint256 public maxGas = 1000000000; - uint256 public gasPriceBid = 3; - uint256 public retryableCost; - address public creditBackAddress = makeAddr("creditBackAddress"); - - // fuzzer behaves weirdly when it picks up this address which is used internally for issuing cheatcodes - address internal constant FOUNDRY_CHEATCODE_ADDRESS = - 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; +abstract contract L1ArbitrumExtendedGatewayTest is L1ArbitrumGatewayTest { /* solhint-disable func-name-mixedcase */ function test_encodeWithdrawal(uint256 exitNum, address dest) public { - bytes32 encodedWithdrawal = L1ArbitrumExtendedGateway(address(l1Gateway)).encodeWithdrawal( - exitNum, - dest - ); + bytes32 encodedWithdrawal = + L1ArbitrumExtendedGateway(address(l1Gateway)).encodeWithdrawal(exitNum, dest); bytes32 expectedEncoding = keccak256(abi.encode(exitNum, dest)); assertEq(encodedWithdrawal, expectedEncoding, "Invalid encodeWithdrawal"); } - function test_finalizeInboundTransfer() public virtual { - // fund gateway with tokens being withdrawn - vm.prank(address(l1Gateway)); - TestERC20(address(token)).mint(); - - // snapshot state before - uint256 userBalanceBefore = token.balanceOf(user); - uint256 l1GatewayBalanceBefore = token.balanceOf(address(l1Gateway)); - - // withdrawal params - address from = address(3000); - uint256 withdrawalAmount = 25; - uint256 exitNum = 7; - bytes memory callHookData = ""; - bytes memory data = abi.encode(exitNum, callHookData); - - InboxMock(address(inbox)).setL2ToL1Sender(l2Gateway); - - // trigger withdrawal - vm.prank(address(IInbox(l1Gateway.inbox()).bridge())); - l1Gateway.finalizeInboundTransfer(address(token), from, user, withdrawalAmount, data); - - // check tokens are properly released - uint256 userBalanceAfter = token.balanceOf(user); - assertEq(userBalanceAfter - userBalanceBefore, withdrawalAmount, "Wrong user balance"); - - uint256 l1GatewayBalanceAfter = token.balanceOf(address(l1Gateway)); - assertEq( - l1GatewayBalanceBefore - l1GatewayBalanceAfter, - withdrawalAmount, - "Wrong l1 gateway balance" - ); - } - - function test_finalizeInboundTransfer_revert_NotFromBridge() public { - address notBridge = address(300); - vm.prank(notBridge); - vm.expectRevert("NOT_FROM_BRIDGE"); - l1Gateway.finalizeInboundTransfer(address(token), user, user, 100, ""); - } - - function test_finalizeInboundTransfer_revert_OnlyCounterpartGateway() public { - address notCounterPartGateway = address(400); - InboxMock(address(inbox)).setL2ToL1Sender(notCounterPartGateway); - - // trigger withdrawal - vm.prank(address(IInbox(l1Gateway.inbox()).bridge())); - vm.expectRevert("ONLY_COUNTERPART_GATEWAY"); - l1Gateway.finalizeInboundTransfer(address(token), user, user, 100, ""); - } - - function test_getExternalCall( - uint256 exitNum, - address dest, - bytes memory data - ) public { - (address target, bytes memory extData) = L1ArbitrumExtendedGateway(address(l1Gateway)) - .getExternalCall(exitNum, dest, data); + function test_getExternalCall(uint256 exitNum, address dest, bytes memory data) public { + (address target, bytes memory extData) = + L1ArbitrumExtendedGateway(address(l1Gateway)).getExternalCall(exitNum, dest, data); assertEq(target, dest, "Invalid dest"); assertEq(extData, data, "Invalid data"); bytes32 exitId = keccak256(abi.encode(exitNum, dest)); - (bool isExit, address newTo, bytes memory newData) = L1ArbitrumExtendedGateway( - address(l1Gateway) - ).redirectedExits(exitId); + (bool isExit, address newTo, bytes memory newData) = + L1ArbitrumExtendedGateway(address(l1Gateway)).redirectedExits(exitId); assertEq(isExit, false, "Invalid isExit"); assertEq(newTo, address(0), "Invalid _newTo"); assertEq(newData.length, 0, "Invalid _newData"); } - function test_getExternalCall_Redirected( - uint256 exitNum, - address initialDest, - address newDest - ) public { + function test_getExternalCall_Redirected(uint256 exitNum, address initialDest, address newDest) + public + virtual + { // redirect vm.prank(initialDest); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDest, - newDest, - "", - "" + exitNum, initialDest, newDest, "", "" ); // check getExternalCall returns new destination - (address target, bytes memory extData) = L1ArbitrumExtendedGateway(address(l1Gateway)) - .getExternalCall(exitNum, initialDest, ""); + (address target, bytes memory extData) = + L1ArbitrumExtendedGateway(address(l1Gateway)).getExternalCall(exitNum, initialDest, ""); assertEq(target, newDest, "Invalid dest"); assertEq(extData.length, 0, "Invalid data"); // check exit redirection is properly stored bytes32 exitId = keccak256(abi.encode(exitNum, initialDest)); - (bool isExit, address newTo, bytes memory newData) = L1ArbitrumExtendedGateway( - address(l1Gateway) - ).redirectedExits(exitId); + (bool isExit, address newTo, bytes memory newData) = + L1ArbitrumExtendedGateway(address(l1Gateway)).redirectedExits(exitId); assertEq(isExit, true, "Invalid isExit"); assertEq(newTo, newDest, "Invalid _newTo"); assertEq(newData.length, 0, "Invalid _newData"); } - function test_outboundTransferCustomRefund_revert_ExtraDataDisabled() public { - bytes memory callHookData = abi.encodeWithSignature("doSomething()"); - bytes memory routerEncodedData = buildRouterEncodedData(callHookData); - - vm.prank(router); - vm.expectRevert("EXTRA_DATA_DISABLED"); - l1Gateway.outboundTransferCustomRefund( - address(token), - user, - user, - 400, - 0.1 ether, - 0.01 ether, - routerEncodedData - ); - } - - function test_outboundTransferCustomRefund_revert_L1NotContract() public { - address invalidTokenAddress = address(70); - - vm.prank(router); - vm.expectRevert("L1_NOT_CONTRACT"); - l1Gateway.outboundTransferCustomRefund( - address(invalidTokenAddress), - user, - user, - 400, - 0.1 ether, - 0.01 ether, - buildRouterEncodedData("") - ); - } - - function test_outboundTransferCustomRefund_revert_NotFromRouter() public { - vm.expectRevert("NOT_FROM_ROUTER"); - l1Gateway.outboundTransferCustomRefund( - address(token), - user, - user, - 400, - 0.1 ether, - 0.01 ether, - "" - ); - } - - function test_supportsInterface() public { - bytes4 iface = type(IERC165).interfaceId; - assertEq(l1Gateway.supportsInterface(iface), true, "Interface should be supported"); - - iface = IL1ArbitrumGateway.outboundTransferCustomRefund.selector; - assertEq(l1Gateway.supportsInterface(iface), true, "Interface should be supported"); - - iface = bytes4(0); - assertEq(l1Gateway.supportsInterface(iface), false, "Interface shouldn't be supported"); - - iface = IL1ArbitrumGateway.inbox.selector; - assertEq(l1Gateway.supportsInterface(iface), false, "Interface shouldn't be supported"); - } - - function test_transferExitAndCall_EmptyData_NotRedirected( + function test_transferExitAndCall( uint256 exitNum, address initialDestination, address newDestination - ) public { + ) public virtual { bytes memory newData; bytes memory data; @@ -215,18 +70,13 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { // do it vm.prank(initialDestination); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - newDestination, - newData, - data + exitNum, initialDestination, newDestination, newData, data ); // check exit data is properly updated bytes32 exitId = keccak256(abi.encode(exitNum, initialDestination)); - (bool isExit, address exitTo, bytes memory exitData) = L1ArbitrumExtendedGateway( - address(l1Gateway) - ).redirectedExits(exitId); + (bool isExit, address exitTo, bytes memory exitData) = + L1ArbitrumExtendedGateway(address(l1Gateway)).redirectedExits(exitId); assertEq(isExit, true, "Invalid isExit"); assertEq(exitTo, newDestination, "Invalid exitTo"); assertEq(exitData.length, 0, "Invalid exitData"); @@ -235,35 +85,26 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { function test_transferExitAndCall_EmptyData_Redirected( uint256 exitNum, address initialDestination - ) public { + ) public virtual { bytes memory data; address intermediateDestination = address(new TestExitReceiver()); // transfer exit vm.prank(initialDestination); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - intermediateDestination, - "", - data + exitNum, initialDestination, intermediateDestination, "", data ); address finalDestination = address(new TestExitReceiver()); vm.prank(intermediateDestination); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - finalDestination, - "", - data + exitNum, initialDestination, finalDestination, "", data ); // check exit data is properly updated bytes32 exitId = keccak256(abi.encode(exitNum, initialDestination)); - (bool isExit, address exitTo, bytes memory exitData) = L1ArbitrumExtendedGateway( - address(l1Gateway) - ).redirectedExits(exitId); + (bool isExit, address exitTo, bytes memory exitData) = + L1ArbitrumExtendedGateway(address(l1Gateway)).redirectedExits(exitId); assertEq(isExit, true, "Invalid isExit"); assertEq(exitTo, finalDestination, "Invalid exitTo"); assertEq(exitData.length, 0, "Invalid exitData"); @@ -271,6 +112,7 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { function test_transferExitAndCall_NonEmptyData(uint256 exitNum, address initialDestination) public + virtual { bytes memory newData; bytes memory data = abi.encode("fun()"); @@ -286,18 +128,13 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { // do it vm.prank(initialDestination); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - newDestination, - newData, - data + exitNum, initialDestination, newDestination, newData, data ); // check exit data is properly updated bytes32 exitId = keccak256(abi.encode(exitNum, initialDestination)); - (bool isExit, address exitTo, bytes memory exitData) = L1ArbitrumExtendedGateway( - address(l1Gateway) - ).redirectedExits(exitId); + (bool isExit, address exitTo, bytes memory exitData) = + L1ArbitrumExtendedGateway(address(l1Gateway)).redirectedExits(exitId); assertEq(isExit, true, "Invalid isExit"); assertEq(exitTo, newDestination, "Invalid exitTo"); assertEq(exitData.length, 0, "Invalid exitData"); @@ -306,35 +143,26 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { function test_transferExitAndCall_NonEmptyData_Redirected( uint256 exitNum, address initialDestination - ) public { + ) public virtual { bytes memory data = abi.encode("run()"); address intermediateDestination = address(new TestExitReceiver()); // transfer exit vm.prank(initialDestination); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - intermediateDestination, - "", - data + exitNum, initialDestination, intermediateDestination, "", data ); address finalDestination = address(new TestExitReceiver()); vm.prank(intermediateDestination); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - finalDestination, - "", - data + exitNum, initialDestination, finalDestination, "", data ); // check exit data is properly updated bytes32 exitId = keccak256(abi.encode(exitNum, initialDestination)); - (bool isExit, address exitTo, bytes memory exitData) = L1ArbitrumExtendedGateway( - address(l1Gateway) - ).redirectedExits(exitId); + (bool isExit, address exitTo, bytes memory exitData) = + L1ArbitrumExtendedGateway(address(l1Gateway)).redirectedExits(exitId); assertEq(isExit, true, "Invalid isExit"); assertEq(exitTo, finalDestination, "Invalid exitTo"); assertEq(exitData.length, 0, "Invalid exitData"); @@ -344,11 +172,7 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { address nonSender = address(800); vm.expectRevert("NOT_EXPECTED_SENDER"); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - 4, - nonSender, - address(2), - "", - "" + 4, nonSender, address(2), "", "" ); } @@ -357,61 +181,48 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { vm.prank(address(1)); vm.expectRevert("NO_DATA_ALLOWED"); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - 4, - address(1), - address(2), - nonEmptyData, - "" + 4, address(1), address(2), nonEmptyData, "" ); } - function test_transferExitAndCall_revert_ToNotContract(address initialDestination) public { + function test_transferExitAndCall_revert_ToNotContract(address initialDestination) + public + virtual + { bytes memory data = abi.encode("execute()"); address nonContractNewDestination = address(15); vm.prank(initialDestination); vm.expectRevert("TO_NOT_CONTRACT"); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - 4, - initialDestination, - nonContractNewDestination, - "", - data + 4, initialDestination, nonContractNewDestination, "", data ); } function test_transferExitAndCall_revert_TransferHookFail( uint256 exitNum, address initialDestination - ) public { + ) public virtual { bytes memory data = abi.encode("failIt"); address newDestination = address(new TestExitReceiver()); vm.prank(initialDestination); vm.expectRevert("TRANSFER_HOOK_FAIL"); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - newDestination, - "", - data + exitNum, initialDestination, newDestination, "", data ); } function test_transferExitAndCall_revert_TransferHookFail_Redirected( uint256 exitNum, address initialDestination - ) public { + ) public virtual { bytes memory data = abi.encode("abc"); address intermediateDestination = address(new TestExitReceiver()); vm.prank(initialDestination); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - intermediateDestination, - "", - data + exitNum, initialDestination, intermediateDestination, "", data ); bytes memory failData = abi.encode("failIt"); @@ -420,29 +231,10 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { vm.prank(intermediateDestination); vm.expectRevert("TRANSFER_HOOK_FAIL"); L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( - exitNum, - initialDestination, - finalDestination, - "", - failData + exitNum, initialDestination, finalDestination, "", failData ); } - //// - // Helper functions - //// - function buildRouterEncodedData(bytes memory callHookData) - internal - view - virtual - returns (bytes memory) - { - bytes memory userEncodedData = abi.encode(maxSubmissionCost, callHookData); - bytes memory routerEncodedData = abi.encode(user, userEncodedData); - - return routerEncodedData; - } - ///// /// Event declarations ///// @@ -460,11 +252,11 @@ abstract contract L1ArbitrumExtendedGatewayTest is Test { contract TestExitReceiver is ITradeableExitReceiver { event ExitHookTriggered(address sender, uint256 exitNum, bytes data); - function onExitTransfer( - address sender, - uint256 exitNum, - bytes calldata data - ) external override returns (bool) { + function onExitTransfer(address sender, uint256 exitNum, bytes calldata data) + external + override + returns (bool) + { emit ExitHookTriggered(sender, exitNum, data); return keccak256(data) != keccak256(abi.encode("failIt")); } diff --git a/test-foundry/L1ArbitrumGateway.t.sol b/test-foundry/L1ArbitrumGateway.t.sol new file mode 100644 index 0000000000..8240b4f2a8 --- /dev/null +++ b/test-foundry/L1ArbitrumGateway.t.sol @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "contracts/tokenbridge/ethereum/gateway/L1ArbitrumGateway.sol"; +import {TestERC20} from "contracts/tokenbridge/test/TestERC20.sol"; +import {InboxMock} from "contracts/tokenbridge/test/InboxMock.sol"; + +abstract contract L1ArbitrumGatewayTest is Test { + IL1ArbitrumGateway public l1Gateway; + IERC20 public token; + + address public l2Gateway = makeAddr("l2Gateway"); + address public router = makeAddr("router"); + address public inbox; + address public user = makeAddr("user"); + + // retryable params + uint256 public maxSubmissionCost; + uint256 public maxGas = 1_000_000_000; + uint256 public gasPriceBid = 100_000_000; + uint256 public retryableCost; + address public creditBackAddress = makeAddr("creditBackAddress"); + + // fuzzer behaves weirdly when it picks up this address which is used internally for issuing cheatcodes + address internal constant FOUNDRY_CHEATCODE_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + + /* solhint-disable func-name-mixedcase */ + + function test_finalizeInboundTransfer() public virtual { + // fund gateway with tokens being withdrawn + vm.prank(address(l1Gateway)); + TestERC20(address(token)).mint(); + + // snapshot state before + uint256 userBalanceBefore = token.balanceOf(user); + uint256 l1GatewayBalanceBefore = token.balanceOf(address(l1Gateway)); + + // withdrawal params + address from = address(3000); + uint256 withdrawalAmount = 25; + uint256 exitNum = 7; + bytes memory callHookData = ""; + bytes memory data = abi.encode(exitNum, callHookData); + + InboxMock(address(inbox)).setL2ToL1Sender(l2Gateway); + + // trigger withdrawal + vm.prank(address(IInbox(l1Gateway.inbox()).bridge())); + l1Gateway.finalizeInboundTransfer(address(token), from, user, withdrawalAmount, data); + + // check tokens are properly released + uint256 userBalanceAfter = token.balanceOf(user); + assertEq(userBalanceAfter - userBalanceBefore, withdrawalAmount, "Wrong user balance"); + + uint256 l1GatewayBalanceAfter = token.balanceOf(address(l1Gateway)); + assertEq( + l1GatewayBalanceBefore - l1GatewayBalanceAfter, + withdrawalAmount, + "Wrong l1 gateway balance" + ); + } + + function test_finalizeInboundTransfer_revert_NotFromBridge() public { + address notBridge = address(300); + vm.prank(notBridge); + vm.expectRevert("NOT_FROM_BRIDGE"); + l1Gateway.finalizeInboundTransfer(address(token), user, user, 100, ""); + } + + function test_finalizeInboundTransfer_revert_OnlyCounterpartGateway() public { + address notCounterPartGateway = address(400); + InboxMock(address(inbox)).setL2ToL1Sender(notCounterPartGateway); + + // trigger withdrawal + vm.prank(address(IInbox(l1Gateway.inbox()).bridge())); + vm.expectRevert("ONLY_COUNTERPART_GATEWAY"); + l1Gateway.finalizeInboundTransfer(address(token), user, user, 100, ""); + } + + function test_finalizeInboundTransfer_revert_NoSender() public { + InboxMock(address(inbox)).setL2ToL1Sender(address(0)); + + // trigger withdrawal + vm.prank(address(IInbox(l1Gateway.inbox()).bridge())); + vm.expectRevert("NO_SENDER"); + l1Gateway.finalizeInboundTransfer(address(token), user, user, 100, ""); + } + + function test_getExternalCall() public { + L1ArbitrumGatewayMock mockGateway = new L1ArbitrumGatewayMock(); + + uint256 exitNum = 7; + address initialDestination = makeAddr("initialDestination"); + bytes memory initialData = bytes("1234"); + (address target, bytes memory data) = + mockGateway.getExternalCall(exitNum, initialDestination, initialData); + + assertEq(target, initialDestination, "Wrong target"); + assertEq(data, initialData, "Wrong data"); + } + + function test_getOutboundCalldata() public virtual { + bytes memory outboundCalldata = l1Gateway.getOutboundCalldata({ + _token: address(token), + _from: user, + _to: address(800), + _amount: 355, + _data: abi.encode("doStuff()") + }); + + bytes memory expectedCalldata = abi.encodeWithSelector( + ITokenGateway.finalizeInboundTransfer.selector, + address(token), + user, + address(800), + 355, + abi.encode("", abi.encode("doStuff()")) + ); + + assertEq(outboundCalldata, expectedCalldata, "Invalid outboundCalldata"); + } + + function test_outboundTransfer() public virtual {} + + function test_outboundTransferCustomRefund_revert_ExtraDataDisabled() public { + bytes memory callHookData = abi.encodeWithSignature("doSomething()"); + bytes memory routerEncodedData = buildRouterEncodedData(callHookData); + + vm.prank(router); + vm.expectRevert("EXTRA_DATA_DISABLED"); + l1Gateway.outboundTransferCustomRefund( + address(token), user, user, 400, 0.1 ether, 0.01 ether, routerEncodedData + ); + } + + function test_outboundTransferCustomRefund_revert_L1NotContract() public { + address invalidTokenAddress = address(70); + + vm.prank(router); + vm.expectRevert("L1_NOT_CONTRACT"); + l1Gateway.outboundTransferCustomRefund( + address(invalidTokenAddress), + user, + user, + 400, + 0.1 ether, + 0.01 ether, + buildRouterEncodedData("") + ); + } + + function test_outboundTransferCustomRefund_revert_NotFromRouter() public { + vm.expectRevert("NOT_FROM_ROUTER"); + l1Gateway.outboundTransferCustomRefund( + address(token), user, user, 400, 0.1 ether, 0.01 ether, "" + ); + } + + function test_postUpgradeInit() public { + address proxyAdmin = makeAddr("proxyAdmin"); + vm.store( + address(l1Gateway), + 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103, + bytes32(uint256(uint160(proxyAdmin))) + ); + vm.prank(proxyAdmin); + + L1ArbitrumGateway(address(l1Gateway)).postUpgradeInit(); + } + + function test_postUpgradeInit_revert_NotFromAdmin() public { + address proxyAdmin = makeAddr("proxyAdmin"); + vm.store( + address(l1Gateway), + 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103, + bytes32(uint256(uint160(proxyAdmin))) + ); + + vm.expectRevert("NOT_FROM_ADMIN"); + L1ArbitrumGateway(address(l1Gateway)).postUpgradeInit(); + } + + function test_supportsInterface() public { + bytes4 iface = type(IERC165).interfaceId; + assertEq(l1Gateway.supportsInterface(iface), true, "Interface should be supported"); + + iface = IL1ArbitrumGateway.outboundTransferCustomRefund.selector; + assertEq(l1Gateway.supportsInterface(iface), true, "Interface should be supported"); + + iface = bytes4(0); + assertEq(l1Gateway.supportsInterface(iface), false, "Interface shouldn't be supported"); + + iface = IL1ArbitrumGateway.inbox.selector; + assertEq(l1Gateway.supportsInterface(iface), false, "Interface shouldn't be supported"); + } + + //// + // Helper functions + //// + function buildRouterEncodedData(bytes memory callHookData) + internal + view + virtual + returns (bytes memory) + { + bytes memory userEncodedData = abi.encode(maxSubmissionCost, callHookData); + bytes memory routerEncodedData = abi.encode(user, userEncodedData); + + return routerEncodedData; + } +} + +contract L1ArbitrumGatewayMock is L1ArbitrumGateway { + function calculateL2TokenAddress(address x) + public + view + override(ITokenGateway, TokenGateway) + returns (address) + { + return x; + } +} + +contract MockReentrantInbox { + function createRetryableTicket( + address, + uint256, + uint256, + address, + address, + uint256, + uint256, + bytes memory + ) external payable returns (uint256) { + // re-enter + L1ArbitrumGateway(msg.sender).outboundTransferCustomRefund{value: msg.value}( + address(100), address(100), address(100), 2, 2, 2, bytes("") + ); + } +} + +contract MockReentrantERC20 { + function balanceOf(address) external returns (uint256) { + // re-enter + L1ArbitrumGateway(msg.sender).outboundTransferCustomRefund( + address(100), address(100), address(100), 2, 2, 3, bytes("") + ); + return 5; + } + + function bridgeBurn(address, uint256) external { + // re-enter + L1ArbitrumGateway(msg.sender).outboundTransferCustomRefund( + address(100), address(100), address(100), 2, 2, 3, bytes("") + ); + } +} diff --git a/test-foundry/L1CustomGateway.t.sol b/test-foundry/L1CustomGateway.t.sol index ca4f94048d..9aa6adde10 100644 --- a/test-foundry/L1CustomGateway.t.sol +++ b/test-foundry/L1CustomGateway.t.sol @@ -2,11 +2,18 @@ pragma solidity ^0.8.0; -import { L1ArbitrumExtendedGatewayTest } from "./L1ArbitrumExtendedGateway.t.sol"; -import { L1CustomGateway, IInbox, ITokenGateway, IERC165, IL1ArbitrumGateway, IERC20 } from "contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol"; -import { L2CustomGateway } from "contracts/tokenbridge/arbitrum/gateway/L2CustomGateway.sol"; -import { TestERC20 } from "contracts/tokenbridge/test/TestERC20.sol"; -import { InboxMock } from "contracts/tokenbridge/test/InboxMock.sol"; +import "./L1ArbitrumExtendedGateway.t.sol"; +import { + L1CustomGateway, + IInbox, + ITokenGateway, + IERC165, + IL1ArbitrumGateway, + IERC20 +} from "contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol"; +import {L2CustomGateway} from "contracts/tokenbridge/arbitrum/gateway/L2CustomGateway.sol"; +import {TestERC20} from "contracts/tokenbridge/test/TestERC20.sol"; +import {InboxMock} from "contracts/tokenbridge/test/InboxMock.sol"; contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { // gateway params @@ -43,12 +50,8 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { abi.encode(uint8(0xb1)) ); vm.prank(address(l1Token)); - L1CustomGateway(address(l1Gateway)).registerTokenToL2{ value: retryableCost }( - l2Token, - maxGas, - gasPriceBid, - maxSubmissionCost, - makeAddr("creditBackAddress") + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + l2Token, maxGas, gasPriceBid, maxSubmissionCost, makeAddr("creditBackAddress") ); assertEq(l1Gateway.calculateL2TokenAddress(l1Token), l2Token, "Invalid L2 token address"); @@ -109,47 +112,18 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { function test_forceRegisterTokenToL2_revert_InvalidLength() public virtual { vm.prank(owner); vm.expectRevert("INVALID_LENGTHS"); - L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{ value: retryableCost }( - new address[](1), - new address[](2), - maxGas, - gasPriceBid, - maxSubmissionCost + L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{value: retryableCost}( + new address[](1), new address[](2), maxGas, gasPriceBid, maxSubmissionCost ); } function test_forceRegisterTokenToL2_revert_OnlyOwner() public { vm.expectRevert("ONLY_OWNER"); - L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{ value: retryableCost }( - new address[](1), - new address[](1), - maxGas, - gasPriceBid, - maxSubmissionCost + L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{value: retryableCost}( + new address[](1), new address[](1), maxGas, gasPriceBid, maxSubmissionCost ); } - function test_getOutboundCalldata() public { - bytes memory outboundCalldata = l1Gateway.getOutboundCalldata({ - _token: address(token), - _from: user, - _to: address(800), - _amount: 355, - _data: abi.encode("doStuff()") - }); - - bytes memory expectedCalldata = abi.encodeWithSelector( - ITokenGateway.finalizeInboundTransfer.selector, - address(token), - user, - address(800), - 355, - abi.encode("", abi.encode("doStuff()")) - ); - - assertEq(outboundCalldata, expectedCalldata, "Invalid outboundCalldata"); - } - function test_initialize() public virtual { L1CustomGateway gateway = new L1CustomGateway(); gateway.initialize(l2Gateway, router, inbox, owner); @@ -161,7 +135,23 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { assertEq(gateway.whitelist(), address(0), "Invalid whitelist"); } - function test_outboundTransfer() public virtual { + function test_initialize_revert_BadInbox() public { + L1CustomGateway gateway = new L1CustomGateway(); + address badInbox = address(0); + + vm.expectRevert("BAD_INBOX"); + gateway.initialize(l2Gateway, router, badInbox, owner); + } + + function test_initialize_revert_BadRouter() public { + L1CustomGateway gateway = new L1CustomGateway(); + address badRouter = address(0); + + vm.expectRevert("BAD_ROUTER"); + gateway.initialize(l2Gateway, badRouter, inbox, owner); + } + + function test_outboundTransfer() public virtual override { // snapshot state before uint256 userBalanceBefore = token.balanceOf(user); uint256 l1GatewayBalanceBefore = token.balanceOf(address(l1Gateway)); @@ -172,9 +162,7 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { // register token to gateway vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); vm.prank(address(token)); uint256 seqNum0 = L1CustomGateway(address(l1Gateway)).registerTokenToL2{ @@ -206,13 +194,8 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { // trigger deposit vm.prank(router); - bytes memory seqNum1 = l1Gateway.outboundTransfer{ value: retryableCost }( - address(token), - user, - depositAmount, - maxGas, - gasPriceBid, - routerEncodedData + bytes memory seqNum1 = l1Gateway.outboundTransfer{value: retryableCost}( + address(token), user, depositAmount, maxGas, gasPriceBid, routerEncodedData ); // check tokens are escrowed @@ -241,9 +224,7 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { // register token to gateway vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); vm.prank(address(token)); uint256 seqNum0 = L1CustomGateway(address(l1Gateway)).registerTokenToL2{ @@ -275,7 +256,7 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { // trigger deposit vm.prank(router); - bytes memory seqNum1 = l1Gateway.outboundTransferCustomRefund{ value: retryableCost }( + bytes memory seqNum1 = l1Gateway.outboundTransferCustomRefund{value: retryableCost}( address(token), creditBackAddress, user, @@ -305,12 +286,10 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { // register token to gateway vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); vm.prank(address(token)); - L1CustomGateway(address(l1Gateway)).registerTokenToL2{ value: retryableCost }( + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( makeAddr("tokenL2Address"), maxGas, gasPriceBid, @@ -320,7 +299,7 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { vm.prank(router); vm.expectRevert("ERC20: insufficient allowance"); - l1Gateway.outboundTransferCustomRefund{ value: 1 ether }( + l1Gateway.outboundTransferCustomRefund{value: 1 ether}( address(token), user, user, @@ -331,6 +310,63 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { ); } + function test_outboundTransferCustomRefund_revert_NoL2TokenSet() public virtual { + uint256 tooManyTokens = 500 ether; + + // register token to gateway + vm.mockCall( + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + ); + vm.prank(address(token)); + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + address(0), maxGas, gasPriceBid, maxSubmissionCost, makeAddr("creditBackAddress") + ); + + vm.prank(router); + vm.expectRevert("NO_L2_TOKEN_SET"); + l1Gateway.outboundTransferCustomRefund{value: 1 ether}( + address(token), + user, + user, + tooManyTokens, + 0.1 ether, + 0.01 ether, + buildRouterEncodedData("") + ); + } + + function test_outboundTransferCustomRefund_revert_Reentrancy() public virtual { + // register token to gateway + vm.mockCall( + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + ); + vm.prank(address(token)); + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + makeAddr("tokenL2Address"), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress + ); + + // approve token + uint256 depositAmount = 3; + vm.prank(user); + token.approve(address(l1Gateway), depositAmount); + + // trigger re-entrancy + MockReentrantInbox mockReentrantInbox = new MockReentrantInbox(); + vm.etch(l1Gateway.inbox(), address(mockReentrantInbox).code); + + vm.prank(router); + vm.expectRevert("ReentrancyGuard: reentrant call"); + l1Gateway.outboundTransferCustomRefund{value: retryableCost}( + address(token), + creditBackAddress, + user, + depositAmount, + maxGas, + gasPriceBid, + buildRouterEncodedData("") + ); + } + function test_registerTokenToL2(address l1Token, address l2Token) public virtual { vm.assume(l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS); vm.deal(l1Token, 100 ether); @@ -365,17 +401,12 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { abi.encode(uint8(0xb1)) ); vm.prank(address(l1Token)); - L1CustomGateway(address(l1Gateway)).registerTokenToL2{ value: retryableCost }( - l2Token, - maxGas, - gasPriceBid, - maxSubmissionCost + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + l2Token, maxGas, gasPriceBid, maxSubmissionCost ); assertEq( - L1CustomGateway(address(l1Gateway)).l1ToL2Token(l1Token), - l2Token, - "Invalid L2 token" + L1CustomGateway(address(l1Gateway)).l1ToL2Token(l1Token), l2Token, "Invalid L2 token" ); } @@ -413,71 +444,89 @@ contract L1CustomGatewayTest is L1ArbitrumExtendedGatewayTest { abi.encode(uint8(0xb1)) ); vm.prank(address(l1Token)); - L1CustomGateway(address(l1Gateway)).registerTokenToL2{ value: retryableCost }( - l2Token, - maxGas, - gasPriceBid, - maxSubmissionCost, - creditBackAddress + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + l2Token, maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress ); assertEq( - L1CustomGateway(address(l1Gateway)).l1ToL2Token(l1Token), - l2Token, - "Invalid L2 token" + L1CustomGateway(address(l1Gateway)).l1ToL2Token(l1Token), l2Token, "Invalid L2 token" + ); + } + + function test_registerTokenToL2_UpdateToSameAddress(address l1Token, address l2Token) + public + virtual + { + vm.assume(l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS); + vm.deal(l1Token, 100 ether); + + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(l1Token); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = address(l2Token); + + // register token to gateway + vm.mockCall( + address(l1Token), + abi.encodeWithSignature("isArbitrumEnabled()"), + abi.encode(uint8(0xb1)) + ); + vm.prank(address(l1Token)); + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + l2Token, maxGas, gasPriceBid, maxSubmissionCost + ); + + // re-register + vm.mockCall( + address(l1Token), + abi.encodeWithSignature("isArbitrumEnabled()"), + abi.encode(uint8(0xb1)) + ); + vm.prank(address(l1Token)); + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + l2Token, maxGas, gasPriceBid, maxSubmissionCost + ); + + assertEq( + L1CustomGateway(address(l1Gateway)).l1ToL2Token(l1Token), l2Token, "Invalid L2 token" ); } function test_registerTokenToL2_revert_NotArbEnabled() public virtual { // wrong answer vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xdd)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xdd)) ); vm.prank(address(token)); vm.expectRevert("NOT_ARB_ENABLED"); - L1CustomGateway(address(l1Gateway)).registerTokenToL2{ value: retryableCost }( - address(102), - maxGas, - gasPriceBid, - maxSubmissionCost, - creditBackAddress + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + address(102), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress ); } function test_registerTokenToL2_revert_NoUpdateToDifferentAddress() public virtual { // register token to gateway vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); // set initial address address initialL2TokenAddress = makeAddr("initial"); vm.prank(address(token)); - L1CustomGateway(address(l1Gateway)).registerTokenToL2{ value: retryableCost }( - initialL2TokenAddress, - maxGas, - gasPriceBid, - maxSubmissionCost + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + initialL2TokenAddress, maxGas, gasPriceBid, maxSubmissionCost ); assertEq( - L1CustomGateway(address(l1Gateway)).l1ToL2Token(address(token)), - initialL2TokenAddress + L1CustomGateway(address(l1Gateway)).l1ToL2Token(address(token)), initialL2TokenAddress ); // try to set different one address differentL2TokenAddress = makeAddr("different"); vm.prank(address(token)); vm.expectRevert("NO_UPDATE_TO_DIFFERENT_ADDR"); - L1CustomGateway(address(l1Gateway)).registerTokenToL2{ value: retryableCost }( - differentL2TokenAddress, - maxGas, - gasPriceBid, - maxSubmissionCost + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + differentL2TokenAddress, maxGas, gasPriceBid, maxSubmissionCost ); } diff --git a/test-foundry/L1ERC20Gateway.t.sol b/test-foundry/L1ERC20Gateway.t.sol index c6bfb0f7a5..e24d54a4dc 100644 --- a/test-foundry/L1ERC20Gateway.t.sol +++ b/test-foundry/L1ERC20Gateway.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import { L1ArbitrumExtendedGatewayTest, InboxMock, TestERC20 } from "./L1ArbitrumExtendedGateway.t.sol"; +import "./L1ArbitrumExtendedGateway.t.sol"; import "contracts/tokenbridge/ethereum/gateway/L1ERC20Gateway.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -17,11 +17,7 @@ contract L1ERC20GatewayTest is L1ArbitrumExtendedGatewayTest { l1Gateway = new L1ERC20Gateway(); L1ERC20Gateway(address(l1Gateway)).initialize( - l2Gateway, - router, - inbox, - cloneableProxyHash, - l2BeaconProxyFactory + l2Gateway, router, inbox, cloneableProxyHash, l2BeaconProxyFactory ); token = IERC20(address(new TestERC20())); @@ -33,6 +29,10 @@ contract L1ERC20GatewayTest is L1ArbitrumExtendedGatewayTest { vm.prank(user); TestERC20(address(token)).mint(); vm.deal(router, 100 ether); + + // move some funds to gateway + vm.prank(user); + token.transfer(address(l1Gateway), 100); } /* solhint-disable func-name-mixedcase */ @@ -47,6 +47,22 @@ contract L1ERC20GatewayTest is L1ArbitrumExtendedGatewayTest { assertEq(gateway.whitelist(), address(0), "Invalid whitelist"); } + function test_initialize_revert_BadInbox() public { + L1ERC20Gateway gateway = new L1ERC20Gateway(); + address badInbox = address(0); + + vm.expectRevert("BAD_INBOX"); + gateway.initialize(l2Gateway, router, badInbox, cloneableProxyHash, l2BeaconProxyFactory); + } + + function test_initialize_revert_BadRouter() public { + L1ERC20Gateway gateway = new L1ERC20Gateway(); + address badRouter = address(0); + + vm.expectRevert("BAD_ROUTER"); + gateway.initialize(l2Gateway, badRouter, inbox, cloneableProxyHash, l2BeaconProxyFactory); + } + function test_initialize_revert_InvalidProxyHash() public { L1ERC20Gateway gateway = new L1ERC20Gateway(); bytes32 invalidProxyHash = bytes32(0); @@ -63,7 +79,7 @@ contract L1ERC20GatewayTest is L1ArbitrumExtendedGatewayTest { gateway.initialize(l2Gateway, router, inbox, cloneableProxyHash, invalidBeaconProxyFactory); } - function test_outboundTransfer() public virtual { + function test_outboundTransfer() public virtual override { // snapshot state before uint256 userBalanceBefore = token.balanceOf(user); uint256 l1GatewayBalanceBefore = token.balanceOf(address(l1Gateway)); @@ -98,13 +114,8 @@ contract L1ERC20GatewayTest is L1ArbitrumExtendedGatewayTest { // trigger deposit vm.prank(router); - l1Gateway.outboundTransfer{ value: retryableCost }( - address(token), - user, - depositAmount, - maxGas, - gasPriceBid, - routerEncodedData + l1Gateway.outboundTransfer{value: retryableCost}( + address(token), user, depositAmount, maxGas, gasPriceBid, routerEncodedData ); // check tokens are escrowed @@ -155,14 +166,8 @@ contract L1ERC20GatewayTest is L1ArbitrumExtendedGatewayTest { // trigger deposit vm.prank(router); - l1Gateway.outboundTransferCustomRefund{ value: retryableCost }( - address(token), - refundTo, - user, - depositAmount, - maxGas, - gasPriceBid, - routerEncodedData + l1Gateway.outboundTransferCustomRefund{value: retryableCost}( + address(token), refundTo, user, depositAmount, maxGas, gasPriceBid, routerEncodedData ); // check tokens are escrowed @@ -193,7 +198,30 @@ contract L1ERC20GatewayTest is L1ArbitrumExtendedGatewayTest { ); } - function test_getOutboundCalldata() public { + function test_outboundTransferCustomRefund_revert_Reentrancy() public virtual { + // approve token + uint256 depositAmount = 3; + vm.prank(user); + token.approve(address(l1Gateway), depositAmount); + + // trigger re-entrancy + MockReentrantInbox mockReentrantInbox = new MockReentrantInbox(); + vm.etch(l1Gateway.inbox(), address(mockReentrantInbox).code); + + vm.expectRevert("ReentrancyGuard: reentrant call"); + vm.prank(router); + l1Gateway.outboundTransferCustomRefund{value: retryableCost}( + address(token), + makeAddr("refundTo"), + user, + depositAmount, + maxGas, + gasPriceBid, + buildRouterEncodedData("") + ); + } + + function test_getOutboundCalldata() public override { bytes memory outboundCalldata = l1Gateway.getOutboundCalldata({ _token: address(token), _from: user, diff --git a/test-foundry/L1ForceOnlyReverseCustomGateway.t.sol b/test-foundry/L1ForceOnlyReverseCustomGateway.t.sol new file mode 100644 index 0000000000..1cbf40c2a1 --- /dev/null +++ b/test-foundry/L1ForceOnlyReverseCustomGateway.t.sol @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +import "./L1ReverseCustomGateway.t.sol"; +import {L1ForceOnlyReverseCustomGateway} from + "contracts/tokenbridge/ethereum/gateway/L1ForceOnlyReverseCustomGateway.sol"; +import { + MintableTestCustomTokenL1, + ReverseTestCustomTokenL1 +} from "contracts/tokenbridge/test/TestCustomTokenL1.sol"; +import {ERC20PresetMinterPauser} from + "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; + +contract L1ForceOnlyReverseCustomGatewayTest is L1ReverseCustomGatewayTest { + function setUp() public virtual override { + inbox = address(new InboxMock()); + + l1Gateway = new L1ForceOnlyReverseCustomGateway(); + L1ForceOnlyReverseCustomGateway(address(l1Gateway)).initialize( + l2Gateway, router, inbox, owner + ); + + token = IERC20(address(new TestERC20())); + + maxSubmissionCost = 20; + retryableCost = maxSubmissionCost + gasPriceBid * maxGas; + + // fund user and router + vm.prank(user); + TestERC20(address(token)).mint(); + vm.deal(router, 100 ether); + vm.deal(address(token), 100 ether); + vm.deal(owner, 100 ether); + } + + /* solhint-disable func-name-mixedcase */ + function test_calculateL2TokenAddress(address l1Token, address l2Token) + public + virtual + override + { + vm.assume(l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS); + vm.deal(l1Token, 100 ether); + + // register token to gateway + // register token to gateway + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = l1Token; + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = l2Token; + + vm.prank(owner); + uint256 seqNum0 = L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{ + value: retryableCost + }(l1Tokens, l2Tokens, maxGas, gasPriceBid, maxSubmissionCost); + + assertEq(l1Gateway.calculateL2TokenAddress(l1Token), l2Token, "Invalid L2 token address"); + } + + function test_outboundTransfer() public override { + // fund user with tokens + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); + vm.prank(address(user)); + bridgedToken.mint(); + + // snapshot state before + uint256 userBalanceBefore = bridgedToken.balanceOf(user); + + uint256 amount = 300; + bytes memory callHookData = ""; + bytes memory routerEncodedData = buildRouterEncodedData(callHookData); + + // register token to gateway + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(bridgedToken); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = makeAddr("tokenL2Address"); + + vm.prank(owner); + uint256 seqNum0 = L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{ + value: retryableCost + }(l1Tokens, l2Tokens, maxGas, gasPriceBid, maxSubmissionCost); + + // approve token + vm.prank(user); + bridgedToken.approve(address(l1Gateway), amount); + + // event checkers + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(user, user); + + vm.expectEmit(true, true, true, true); + emit InboxRetryableTicket( + address(l1Gateway), + l2Gateway, + 0, + maxGas, + l1Gateway.getOutboundCalldata(address(bridgedToken), user, user, amount, callHookData) + ); + + vm.expectEmit(true, true, true, true); + emit DepositInitiated(address(bridgedToken), user, user, 1, amount); + + // trigger transfer + vm.prank(router); + bytes memory seqNum1 = l1Gateway.outboundTransfer{value: retryableCost}( + address(bridgedToken), user, amount, maxGas, gasPriceBid, routerEncodedData + ); + + // check tokens are burned + uint256 userBalanceAfter = bridgedToken.balanceOf(user); + assertEq(userBalanceBefore - userBalanceAfter, amount, "Wrong user balance"); + + assertEq(seqNum0, 0, "Invalid seqNum0"); + assertEq(seqNum1, abi.encode(1), "Invalid seqNum1"); + } + + function test_outboundTransferCustomRefund() public override { + // fund user with tokens + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); + vm.prank(address(user)); + bridgedToken.mint(); + + // snapshot state before + uint256 userBalanceBefore = bridgedToken.balanceOf(user); + + uint256 amount = 450; + bytes memory callHookData = ""; + bytes memory routerEncodedData = buildRouterEncodedData(callHookData); + + // register token to gateway + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(bridgedToken); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = makeAddr("tokenL2Address"); + + vm.prank(owner); + uint256 seqNum0 = L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{ + value: retryableCost + }(l1Tokens, l2Tokens, maxGas, gasPriceBid, maxSubmissionCost); + + // approve token + vm.prank(user); + bridgedToken.approve(address(l1Gateway), amount); + + // event checkers + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(creditBackAddress, user); + + vm.expectEmit(true, true, true, true); + emit InboxRetryableTicket( + address(l1Gateway), + l2Gateway, + 0, + maxGas, + l1Gateway.getOutboundCalldata(address(bridgedToken), user, user, amount, callHookData) + ); + + vm.expectEmit(true, true, true, true); + emit DepositInitiated(address(bridgedToken), user, user, 1, amount); + + // trigger deposit + vm.prank(router); + bytes memory seqNum1 = l1Gateway.outboundTransferCustomRefund{value: retryableCost}( + address(bridgedToken), + creditBackAddress, + user, + amount, + maxGas, + gasPriceBid, + routerEncodedData + ); + + // check tokens are escrowed + uint256 userBalanceAfter = bridgedToken.balanceOf(user); + assertEq(userBalanceBefore - userBalanceAfter, amount, "Wrong user balance"); + + assertEq(seqNum0, 0, "Invalid seqNum0"); + assertEq(seqNum1, abi.encode(1), "Invalid seqNum1"); + } + + function test_outboundTransferCustomRefund_revert_InsufficientAllowance() public override { + // fund user with tokens + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); + vm.prank(address(user)); + bridgedToken.mint(); + + uint256 tooManyTokens = 500 ether; + + // register token to gateway + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(bridgedToken); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = makeAddr("tokenL2Address"); + + vm.prank(owner); + uint256 seqNum0 = L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{ + value: retryableCost + }(l1Tokens, l2Tokens, maxGas, gasPriceBid, maxSubmissionCost); + + vm.prank(router); + vm.expectRevert("ERC20: burn amount exceeds balance"); + l1Gateway.outboundTransferCustomRefund{value: 1 ether}( + address(bridgedToken), + user, + user, + tooManyTokens, + 0.1 ether, + 0.01 ether, + buildRouterEncodedData("") + ); + } + + function test_outboundTransferCustomRefund_revert_NoL2TokenSet() public virtual override { + uint256 tooManyTokens = 500 ether; + + // register token to gateway + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(token); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = address(0); + + vm.prank(owner); + L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{value: retryableCost}( + l1Tokens, l2Tokens, maxGas, gasPriceBid, maxSubmissionCost + ); + + vm.prank(router); + vm.expectRevert("NO_L2_TOKEN_SET"); + l1Gateway.outboundTransferCustomRefund{value: 1 ether}( + address(token), + user, + user, + tooManyTokens, + 0.1 ether, + 0.01 ether, + buildRouterEncodedData("") + ); + } + + function test_outboundTransferCustomRefund_revert_Reentrancy() public override { + // fund user with tokens + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); + vm.prank(address(user)); + bridgedToken.mint(); + + // register token to gateway + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(bridgedToken); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = makeAddr("tokenL2Address"); + + vm.prank(owner); + L1CustomGateway(address(l1Gateway)).forceRegisterTokenToL2{value: retryableCost}( + l1Tokens, l2Tokens, maxGas, gasPriceBid, maxSubmissionCost + ); + + // approve token + uint256 amount = 450; + vm.prank(user); + bridgedToken.approve(address(l1Gateway), amount); + + // trigger re-entrancy + MockReentrantERC20 mockReentrantERC20 = new MockReentrantERC20(); + vm.etch(address(bridgedToken), address(mockReentrantERC20).code); + + vm.expectRevert("ReentrancyGuard: reentrant call"); + vm.prank(router); + l1Gateway.outboundTransferCustomRefund{value: retryableCost}( + address(bridgedToken), + creditBackAddress, + user, + amount, + maxGas, + gasPriceBid, + buildRouterEncodedData("") + ); + } + + function test_registerTokenToL2(address, address l2Token) public virtual override { + vm.expectRevert("REGISTER_TOKEN_ON_L2_DISABLED"); + L1CustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + l2Token, maxGas, gasPriceBid, maxSubmissionCost + ); + } + + function test_registerTokenToL2_CustomRefund(address, address) public virtual override { + 0; // N/A + } + + function test_registerTokenToL2_UpdateToSameAddress(address, address) public virtual override { + 0; // N/A + } + + function test_registerTokenToL2_revert_NoUpdateToDifferentAddress() public virtual override { + 0; // N/A + } + + function test_registerTokenToL2_revert_NotArbEnabled() public virtual override { + 0; // N/A + } +} diff --git a/test-foundry/L1OrbitCustomGateway.t.sol b/test-foundry/L1OrbitCustomGateway.t.sol index 20093a8db6..d5f21eac72 100644 --- a/test-foundry/L1OrbitCustomGateway.t.sol +++ b/test-foundry/L1OrbitCustomGateway.t.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.0; -import { L1CustomGatewayTest, IERC20, L2CustomGateway } from "./L1CustomGateway.t.sol"; -import { L1OrbitCustomGateway } from "contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; -import { TestERC20 } from "contracts/tokenbridge/test/TestERC20.sol"; -import { ERC20InboxMock } from "contracts/tokenbridge/test/InboxMock.sol"; +import "./L1CustomGateway.t.sol"; +import {L1OrbitCustomGateway} from "contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20PresetMinterPauser} from + "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; +import {TestERC20} from "contracts/tokenbridge/test/TestERC20.sol"; +import {ERC20InboxMock, IBridge} from "contracts/tokenbridge/test/InboxMock.sol"; contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { ERC20 public nativeToken; @@ -39,10 +40,8 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { /* solhint-disable func-name-mixedcase */ function test_calculateL2TokenAddress(address l1Token, address l2Token) public override { vm.assume( - l1Token != FOUNDRY_CHEATCODE_ADDRESS && - l2Token != FOUNDRY_CHEATCODE_ADDRESS && - l1Token != address(0) && - l1Token != router + l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS + && l1Token != address(0) && l1Token != router ); vm.deal(l1Token, 100 ether); @@ -59,12 +58,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { ); vm.prank(address(l1Token)); L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( - l2Token, - maxGas, - gasPriceBid, - maxSubmissionCost, - creditBackAddress, - nativeTokenTotalFee + l2Token, maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress, nativeTokenTotalFee ); assertEq(l1Gateway.calculateL2TokenAddress(l1Token), l2Token, "Invalid L2 token address"); @@ -109,12 +103,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { // register token to gateway vm.prank(owner); uint256 seqNum = L1OrbitCustomGateway(address(l1Gateway)).forceRegisterTokenToL2( - l1Tokens, - l2Tokens, - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + l1Tokens, l2Tokens, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); ///// checks @@ -151,11 +140,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { vm.prank(owner); vm.expectRevert("NOT_SUPPORTED_IN_ORBIT"); L1OrbitCustomGateway(address(l1Gateway)).forceRegisterTokenToL2( - new address[](1), - new address[](1), - maxGas, - gasPriceBid, - maxSubmissionCost + new address[](1), new address[](1), maxGas, gasPriceBid, maxSubmissionCost ); } @@ -174,9 +159,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); vm.prank(address(token)); uint256 seqNum0 = L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( @@ -220,12 +203,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { // trigger deposit vm.prank(router); bytes memory seqNum1 = l1Gateway.outboundTransfer( - address(token), - user, - depositAmount, - maxGas, - gasPriceBid, - routerEncodedData + address(token), user, depositAmount, maxGas, gasPriceBid, routerEncodedData ); // check tokens are escrowed @@ -258,9 +236,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); vm.prank(address(token)); uint256 seqNum0 = L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( @@ -341,9 +317,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); vm.prank(address(token)); L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( @@ -368,11 +342,55 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { ); } + function test_outboundTransferCustomRefund_revert_NoL2TokenSet() public virtual override { + /// not supported + } + + function test_outboundTransferCustomRefund_revert_Reentrancy() public virtual override { + // register token to gateway + ERC20PresetMinterPauser(address(nativeToken)).mint(address(token), nativeTokenTotalFee); + vm.prank(address(token)); + nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); + + vm.mockCall( + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + ); + vm.prank(address(token)); + L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( + makeAddr("tokenL2Address"), + maxGas, + gasPriceBid, + maxSubmissionCost, + creditBackAddress, + nativeTokenTotalFee + ); + + // approve token + uint256 depositAmount = 5; + vm.prank(user); + token.approve(address(l1Gateway), depositAmount); + + // trigger re-entrancy + MockReentrantERC20 mockReentrantERC20 = new MockReentrantERC20(); + vm.etch(address(token), address(mockReentrantERC20).code); + + vm.expectRevert("ReentrancyGuard: reentrant call"); + vm.prank(router); + l1Gateway.outboundTransferCustomRefund( + address(token), + creditBackAddress, + user, + depositAmount, + maxGas, + gasPriceBid, + buildRouterEncodedData("") + ); + } + function test_registerTokenToL2(address l1Token, address l2Token) public override { vm.assume( - l1Token != FOUNDRY_CHEATCODE_ADDRESS && - l2Token != FOUNDRY_CHEATCODE_ADDRESS && - l1Token != address(0) + l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS + && l1Token != address(0) ); vm.deal(l1Token, 100 ether); @@ -414,11 +432,124 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { ); vm.prank(address(l1Token)); L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( + l2Token, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee + ); + + assertEq( + L1OrbitCustomGateway(address(l1Gateway)).l1ToL2Token(l1Token), l2Token, + "Invalid L2 token" + ); + } + + function test_registerTokenToL2_InboxPrefunded(address l1Token, address l2Token) public { + vm.assume( + l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS + && l1Token != address(0) + ); + vm.deal(l1Token, 100 ether); + + // pre-fund inbox + address inbox = address(l1Gateway.inbox()); + ERC20PresetMinterPauser(address(nativeToken)).mint(inbox, nativeTokenTotalFee); + + // event checkers + vm.expectEmit(true, true, true, true); + emit TokenSet(l1Token, l2Token); + + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(l1Token, l1Token); + + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(l1Token); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = address(l2Token); + vm.expectEmit(true, true, true, true); + emit ERC20InboxRetryableTicket( + address(l1Gateway), + l2Gateway, + 0, maxGas, gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + nativeTokenTotalFee, + abi.encodeWithSelector(L2CustomGateway.registerTokenFromL1.selector, l1Tokens, l2Tokens) + ); + + // register token to gateway + vm.mockCall( + address(l1Token), + abi.encodeWithSignature("isArbitrumEnabled()"), + abi.encode(uint8(0xb1)) + ); + vm.prank(address(l1Token)); + L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( + l2Token, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee + ); + + assertEq( + L1OrbitCustomGateway(address(l1Gateway)).l1ToL2Token(l1Token), + l2Token, + "Invalid L2 token" + ); + } + + function test_registerTokenToL2_InboxPartiallyPrefunded() + public + { + address l1Token = makeAddr("l1Token"); + address l2Token = makeAddr("l2Token"); + vm.deal(l1Token, 100 ether); + + // pre-fund inbox + uint256 prefundAmount = nativeTokenTotalFee - 100; + address inbox = address(l1Gateway.inbox()); + ERC20PresetMinterPauser(address(nativeToken)).mint(inbox, prefundAmount); + + // approve fees + ERC20PresetMinterPauser(address(nativeToken)).mint(address(l1Token), nativeTokenTotalFee); + vm.prank(address(l1Token)); + nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); + + // snapshot + uint256 balanceBefore = nativeToken.balanceOf(address(l1Token)); + + // event checkers + vm.expectEmit(true, true, true, true); + emit TokenSet(l1Token, l2Token); + + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(l1Token, l1Token); + + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(l1Token); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = address(l2Token); + vm.expectEmit(true, true, true, true); + emit ERC20InboxRetryableTicket( + address(l1Gateway), + l2Gateway, + 0, + maxGas, + gasPriceBid, + nativeTokenTotalFee, + abi.encodeWithSelector(L2CustomGateway.registerTokenFromL1.selector, l1Tokens, l2Tokens) + ); + + // register token to gateway + vm.mockCall( + address(l1Token), + abi.encodeWithSignature("isArbitrumEnabled()"), + abi.encode(uint8(0xb1)) + ); + vm.prank(address(l1Token)); + L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( + l2Token, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); assertEq( @@ -426,15 +557,21 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { l2Token, "Invalid L2 token" ); + + // snapshot after + uint256 balanceAfter = nativeToken.balanceOf(address(l1Token)); + assertEq( + balanceBefore - balanceAfter, nativeTokenTotalFee - prefundAmount, "Wrong user balance" + ); } - function test_registerTokenToL2_CustomRefund(address l1Token, address l2Token) public override { + function test_registerTokenToL2_CustomRefund(address l1Token, address l2Token) + public + override + { vm.assume( - l1Token != FOUNDRY_CHEATCODE_ADDRESS && - l2Token != FOUNDRY_CHEATCODE_ADDRESS && - l1Token != address(0) && - l1Token != router && - l1Token != creditBackAddress + l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS + && l1Token != address(0) && l1Token != router && l1Token != creditBackAddress ); vm.deal(l1Token, 100 ether); @@ -476,12 +613,59 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { ); vm.prank(address(l1Token)); L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( + l2Token, maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress, nativeTokenTotalFee + ); + + assertEq( + L1OrbitCustomGateway(address(l1Gateway)).l1ToL2Token(l1Token), l2Token, - maxGas, - gasPriceBid, - maxSubmissionCost, - creditBackAddress, - nativeTokenTotalFee + "Invalid L2 token" + ); + } + + function test_registerTokenToL2_UpdateToSameAddress(address l1Token, address l2Token) + public + virtual + override + { + vm.assume( + l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS + && l1Token != address(0) + ); + vm.deal(l1Token, 100 ether); + + // approve fees + ERC20PresetMinterPauser(address(nativeToken)).mint( + address(l1Token), nativeTokenTotalFee * 4 + ); + vm.prank(address(l1Token)); + nativeToken.approve(address(l1Gateway), nativeTokenTotalFee * 4); + + address[] memory l1Tokens = new address[](1); + l1Tokens[0] = address(l1Token); + address[] memory l2Tokens = new address[](1); + l2Tokens[0] = address(l2Token); + + // register token to gateway + vm.mockCall( + address(l1Token), + abi.encodeWithSignature("isArbitrumEnabled()"), + abi.encode(uint8(0xb1)) + ); + vm.prank(address(l1Token)); + L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( + l2Token, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee + ); + + // re-register + vm.mockCall( + address(l1Token), + abi.encodeWithSignature("isArbitrumEnabled()"), + abi.encode(uint8(0xb1)) + ); + vm.prank(address(l1Token)); + L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( + l2Token, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); assertEq( @@ -494,9 +678,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { function test_registerTokenToL2_revert_NotArbEnabled() public override { // wrong answer vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xdd)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xdd)) ); vm.prank(address(token)); @@ -516,9 +698,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { // register token to gateway vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); // set initial address @@ -526,11 +706,7 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { vm.startPrank(address(token)); nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( - initialL2TokenAddress, - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + initialL2TokenAddress, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); vm.stopPrank(); assertEq( @@ -545,32 +721,21 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { vm.expectRevert("NO_UPDATE_TO_DIFFERENT_ADDR"); L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( - differentL2TokenAddress, - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + differentL2TokenAddress, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); } function test_registerTokenToL2_revert_NotSupportedInOrbit() public { vm.expectRevert("NOT_SUPPORTED_IN_ORBIT"); L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( - address(100), - maxGas, - gasPriceBid, - maxSubmissionCost + address(100), maxGas, gasPriceBid, maxSubmissionCost ); } function test_registerTokenToL2_revert_CustomRefund_NotSupportedInOrbit() public { vm.expectRevert("NOT_SUPPORTED_IN_ORBIT"); L1OrbitCustomGateway(address(l1Gateway)).registerTokenToL2( - address(100), - maxGas, - gasPriceBid, - maxSubmissionCost, - creditBackAddress + address(100), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress ); } @@ -583,11 +748,8 @@ contract L1OrbitCustomGatewayTest is L1CustomGatewayTest { override returns (bytes memory) { - bytes memory userEncodedData = abi.encode( - maxSubmissionCost, - callHookData, - nativeTokenTotalFee - ); + bytes memory userEncodedData = + abi.encode(maxSubmissionCost, callHookData, nativeTokenTotalFee); bytes memory routerEncodedData = abi.encode(user, userEncodedData); return routerEncodedData; diff --git a/test-foundry/L1OrbitERC20Gateway.t.sol b/test-foundry/L1OrbitERC20Gateway.t.sol index ecd7a78291..8de7d21a20 100644 --- a/test-foundry/L1OrbitERC20Gateway.t.sol +++ b/test-foundry/L1OrbitERC20Gateway.t.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.0; -import { L1ERC20GatewayTest } from "./L1ERC20Gateway.t.sol"; +import "./L1ERC20Gateway.t.sol"; import "contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; -import { TestERC20 } from "contracts/tokenbridge/test/TestERC20.sol"; -import { ERC20InboxMock } from "contracts/tokenbridge/test/InboxMock.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20PresetMinterPauser} from + "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; +import {TestERC20} from "contracts/tokenbridge/test/TestERC20.sol"; +import {ERC20InboxMock} from "contracts/tokenbridge/test/InboxMock.sol"; contract L1OrbitERC20GatewayTest is L1ERC20GatewayTest { ERC20 public nativeToken; @@ -21,11 +22,7 @@ contract L1OrbitERC20GatewayTest is L1ERC20GatewayTest { l1Gateway = new L1OrbitERC20Gateway(); L1OrbitERC20Gateway(address(l1Gateway)).initialize( - l2Gateway, - router, - inbox, - cloneableProxyHash, - l2BeaconProxyFactory + l2Gateway, router, inbox, cloneableProxyHash, l2BeaconProxyFactory ); token = IERC20(address(new TestERC20())); @@ -92,12 +89,7 @@ contract L1OrbitERC20GatewayTest is L1ERC20GatewayTest { // trigger deposit vm.prank(router); l1Gateway.outboundTransfer( - address(token), - user, - depositAmount, - maxGas, - gasPriceBid, - routerEncodedData + address(token), user, depositAmount, maxGas, gasPriceBid, routerEncodedData ); // check tokens are escrowed @@ -182,18 +174,197 @@ contract L1OrbitERC20GatewayTest is L1ERC20GatewayTest { ); } + function test_outboundTransferCustomRefund_InboxPrefunded() public { + // retryable params + uint256 depositAmount = 700; + bytes memory callHookData = ""; + bytes memory routerEncodedData = buildRouterEncodedData(callHookData); + + // pre-fund inbox + address inbox = address(l1Gateway.inbox()); + vm.prank(user); + nativeToken.transfer(inbox, nativeTokenTotalFee * 2); + + // snapshot state before + uint256 userBalanceBefore = token.balanceOf(user); + uint256 userNativeTokenBalanceBefore = nativeToken.balanceOf(user); + uint256 l1GatewayBalanceBefore = token.balanceOf(address(l1Gateway)); + + // approve token + vm.prank(user); + token.approve(address(l1Gateway), depositAmount); + + // expect events + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(creditBackAddress, user); + + vm.expectEmit(true, true, true, true); + emit ERC20InboxRetryableTicket( + address(l1Gateway), + l2Gateway, + 0, + maxGas, + gasPriceBid, + nativeTokenTotalFee, + l1Gateway.getOutboundCalldata(address(token), user, user, 700, "") + ); + + vm.expectEmit(true, true, true, true); + emit DepositInitiated(address(token), user, user, 0, depositAmount); + + // trigger deposit + vm.prank(router); + l1Gateway.outboundTransferCustomRefund( + address(token), + creditBackAddress, + user, + depositAmount, + maxGas, + gasPriceBid, + routerEncodedData + ); + + // check tokens are escrowed + uint256 userBalanceAfter = token.balanceOf(user); + assertEq(userBalanceBefore - userBalanceAfter, depositAmount, "Wrong user balance"); + + uint256 userNativeTokenBalanceAfter = nativeToken.balanceOf(user); + assertEq( + userNativeTokenBalanceAfter, + userNativeTokenBalanceBefore, + "Wrong user native token balance" + ); + + uint256 l1GatewayBalanceAfter = token.balanceOf(address(l1Gateway)); + assertEq( + l1GatewayBalanceAfter - l1GatewayBalanceBefore, + depositAmount, + "Wrong l1 gateway balance" + ); + } + + function test_outboundTransferCustomRefund_InboxPartiallyPrefunded() public { + // retryable params + uint256 depositAmount = 700; + bytes memory callHookData = ""; + bytes memory routerEncodedData = buildRouterEncodedData(callHookData); + + // partially pre-fund inbox + uint256 prefundAmount = nativeTokenTotalFee / 3; + address inbox = address(l1Gateway.inbox()); + vm.prank(user); + nativeToken.transfer(inbox, prefundAmount); + + // snapshot state before + uint256 userBalanceBefore = token.balanceOf(user); + uint256 userNativeTokenBalanceBefore = nativeToken.balanceOf(user); + uint256 l1GatewayBalanceBefore = token.balanceOf(address(l1Gateway)); + + // approve fee token + vm.prank(user); + nativeToken.approve(address(l1Gateway), nativeTokenTotalFee - prefundAmount); + + // approve token + vm.prank(user); + token.approve(address(l1Gateway), depositAmount); + + // expect events + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(creditBackAddress, user); + + vm.expectEmit(true, true, true, true); + emit ERC20InboxRetryableTicket( + address(l1Gateway), + l2Gateway, + 0, + maxGas, + gasPriceBid, + nativeTokenTotalFee, + l1Gateway.getOutboundCalldata(address(token), user, user, 700, "") + ); + + vm.expectEmit(true, true, true, true); + emit DepositInitiated(address(token), user, user, 0, depositAmount); + + // trigger deposit + vm.prank(router); + l1Gateway.outboundTransferCustomRefund( + address(token), + creditBackAddress, + user, + depositAmount, + maxGas, + gasPriceBid, + routerEncodedData + ); + + // check tokens are escrowed + uint256 userBalanceAfter = token.balanceOf(user); + assertEq(userBalanceBefore - userBalanceAfter, depositAmount, "Wrong user balance"); + + uint256 userNativeTokenBalanceAfter = nativeToken.balanceOf(user); + assertEq( + userNativeTokenBalanceBefore - userNativeTokenBalanceAfter, + nativeTokenTotalFee - prefundAmount, + "Wrong user native token balance" + ); + + uint256 l1GatewayBalanceAfter = token.balanceOf(address(l1Gateway)); + assertEq( + l1GatewayBalanceAfter - l1GatewayBalanceBefore, + depositAmount, + "Wrong l1 gateway balance" + ); + } + + function test_outboundTransferCustomRefund_revert_NoValue() public { + // trigger deposit + vm.prank(router); + vm.expectRevert("NO_VALUE"); + l1Gateway.outboundTransferCustomRefund{value: 1 ether}( + address(token), creditBackAddress, user, 100, maxGas, gasPriceBid, "" + ); + } + function test_outboundTransferCustomRefund_revert_NotAllowedToBridgeFeeToken() public { // trigger deposit vm.prank(router); vm.expectRevert("NOT_ALLOWED_TO_BRIDGE_FEE_TOKEN"); l1Gateway.outboundTransferCustomRefund( - address(nativeToken), + address(nativeToken), creditBackAddress, user, 100, maxGas, gasPriceBid, "" + ); + } + + function test_outboundTransferCustomRefund_revert_Reentrancy() public override { + // approve fees + vm.prank(user); + nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); + + // approve token + uint256 depositAmount = 3; + vm.prank(user); + token.approve(address(l1Gateway), depositAmount); + + // trigger re-entrancy + MockReentrantERC20 mockReentrantERC20 = new MockReentrantERC20(); + vm.etch(address(token), address(mockReentrantERC20).code); + + vm.expectRevert("ReentrancyGuard: reentrant call"); + vm.prank(router); + l1Gateway.outboundTransferCustomRefund( + address(token), creditBackAddress, user, - 100, + depositAmount, maxGas, gasPriceBid, - "" + buildRouterEncodedData("") ); } @@ -206,11 +377,8 @@ contract L1OrbitERC20GatewayTest is L1ERC20GatewayTest { override returns (bytes memory) { - bytes memory userEncodedData = abi.encode( - maxSubmissionCost, - callHookData, - nativeTokenTotalFee - ); + bytes memory userEncodedData = + abi.encode(maxSubmissionCost, callHookData, nativeTokenTotalFee); bytes memory routerEncodedData = abi.encode(user, userEncodedData); return routerEncodedData; diff --git a/test-foundry/L1OrbitGatewayRouter.t.sol b/test-foundry/L1OrbitGatewayRouter.t.sol index a263ebb543..620941d694 100644 --- a/test-foundry/L1OrbitGatewayRouter.t.sol +++ b/test-foundry/L1OrbitGatewayRouter.t.sol @@ -2,15 +2,16 @@ pragma solidity ^0.8.0; -import { L1GatewayRouterTest } from "./L1GatewayRouter.t.sol"; -import { ERC20InboxMock } from "contracts/tokenbridge/test/InboxMock.sol"; -import { L1OrbitERC20Gateway } from "contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol"; -import { L1OrbitGatewayRouter } from "contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol"; -import { L2GatewayRouter } from "contracts/tokenbridge/arbitrum/gateway/L2GatewayRouter.sol"; -import { L1GatewayRouter } from "contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol"; -import { L1OrbitCustomGateway } from "contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; +import {L1GatewayRouterTest} from "./L1GatewayRouter.t.sol"; +import {ERC20InboxMock} from "contracts/tokenbridge/test/InboxMock.sol"; +import {L1OrbitERC20Gateway} from "contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol"; +import {L1OrbitGatewayRouter} from "contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol"; +import {L2GatewayRouter} from "contracts/tokenbridge/arbitrum/gateway/L2GatewayRouter.sol"; +import {L1GatewayRouter} from "contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol"; +import {L1OrbitCustomGateway} from "contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20PresetMinterPauser} from + "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; import "forge-std/console.sol"; @@ -52,12 +53,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.startPrank(owner); nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); l1OrbitRouter.setGateways( - tokens, - gateways, - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + tokens, gateways, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); address gateway = router.getGateway(token); @@ -75,12 +71,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.startPrank(owner); nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); l1OrbitRouter.setGateways( - tokens, - gateways, - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + tokens, gateways, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); address gateway = router.getGateway(token); @@ -101,17 +92,13 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.startPrank(owner); nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); l1OrbitRouter.setDefaultGateway( - address(defaultGateway), - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + address(defaultGateway), maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); vm.stopPrank(); // create token ERC20PresetMinterPauser token = new ERC20PresetMinterPauser("X", "Y"); - token.mint(user, 10000); + token.mint(user, 10_000); // snapshot state before uint256 userBalanceBefore = token.balanceOf(user); @@ -145,9 +132,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { uint256 l1GatewayBalanceAfter = token.balanceOf(address(defaultGateway)); assertEq( - l1GatewayBalanceAfter - l1GatewayBalanceBefore, - amount, - "Wrong defaultGateway balance" + l1GatewayBalanceAfter - l1GatewayBalanceBefore, amount, "Wrong defaultGateway balance" ); uint256 userNativeTokenBalanceAfter = nativeToken.balanceOf(user); @@ -172,11 +157,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.startPrank(owner); nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); l1OrbitRouter.setDefaultGateway( - address(defaultGateway), - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + address(defaultGateway), maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); vm.stopPrank(); @@ -200,17 +181,13 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.startPrank(owner); nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); l1OrbitRouter.setDefaultGateway( - address(defaultGateway), - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + address(defaultGateway), maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); vm.stopPrank(); // create token ERC20PresetMinterPauser token = new ERC20PresetMinterPauser("X", "Y"); - token.mint(user, 10000); + token.mint(user, 10_000); // snapshot state before uint256 userBalanceBefore = token.balanceOf(user); @@ -238,13 +215,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { /// deposit it vm.prank(user); l1Router.outboundTransferCustomRefund( - address(token), - refundTo, - to, - amount, - maxGas, - gasPriceBid, - userEncodedData + address(token), refundTo, to, amount, maxGas, gasPriceBid, userEncodedData ); // check tokens are escrowed @@ -253,9 +224,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { uint256 l1GatewayBalanceAfter = token.balanceOf(address(defaultGateway)); assertEq( - l1GatewayBalanceAfter - l1GatewayBalanceBefore, - amount, - "Wrong defaultGateway balance" + l1GatewayBalanceAfter - l1GatewayBalanceBefore, amount, "Wrong defaultGateway balance" ); uint256 userNativeTokenBalanceAfter = nativeToken.balanceOf(user); @@ -280,11 +249,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.startPrank(owner); nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); l1OrbitRouter.setDefaultGateway( - address(defaultGateway), - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + address(defaultGateway), maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); vm.stopPrank(); @@ -292,13 +257,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.prank(user); vm.expectRevert("NOT_ALLOWED_TO_BRIDGE_FEE_TOKEN"); l1Router.outboundTransferCustomRefund( - address(nativeToken), - user, - user, - 100, - maxGas, - gasPriceBid, - "" + address(nativeToken), user, user, 100, maxGas, gasPriceBid, "" ); } @@ -336,8 +295,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { gasPriceBid, nativeTokenTotalFee, abi.encodeWithSelector( - L2GatewayRouter.setDefaultGateway.selector, - newDefaultGatewayCounterpart + L2GatewayRouter.setDefaultGateway.selector, newDefaultGatewayCounterpart ) ); @@ -361,12 +319,96 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { assertEq(seqNum, 0, "Invalid seqNum"); } - function test_setDefaultGateway_AddressZero() public override { - address newL1DefaultGateway = address(0); + function test_setDefaultGateway_InboxPrefunded() public { + L1OrbitERC20Gateway newL1DefaultGateway = new L1OrbitERC20Gateway(); + address newDefaultGatewayCounterpart = makeAddr("newDefaultGatewayCounterpart"); + newL1DefaultGateway.initialize( + newDefaultGatewayCounterpart, + address(l1OrbitRouter), + inbox, + 0x0000000000000000000000000000000000000000000000000000000000000001, + makeAddr("l2BeaconProxyFactory") + ); + + // prefund inbox + vm.prank(owner); + nativeToken.transfer(address(inbox), nativeTokenTotalFee * 2); + + // snapshot balance + uint256 ownerNativeTokenBalance = nativeToken.balanceOf(owner); + + // event checkers + vm.expectEmit(true, true, true, true); + emit DefaultGatewayUpdated(address(newL1DefaultGateway)); + + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(owner, owner); + + vm.expectEmit(true, true, true, true); + emit ERC20InboxRetryableTicket( + address(l1OrbitRouter), + counterpartGateway, + 0, + maxGas, + gasPriceBid, + nativeTokenTotalFee, + abi.encodeWithSelector( + L2GatewayRouter.setDefaultGateway.selector, newDefaultGatewayCounterpart + ) + ); + + // set it + vm.prank(owner); + uint256 seqNum = l1OrbitRouter.setDefaultGateway( + address(newL1DefaultGateway), + maxGas, + gasPriceBid, + maxSubmissionCost, + nativeTokenTotalFee + ); + + /// checks + assertEq( + l1OrbitRouter.defaultGateway(), + address(newL1DefaultGateway), + "Invalid newL1DefaultGateway" + ); + + assertEq(seqNum, 0, "Invalid seqNum"); + + uint256 ownerNativeTokenBalanceAfter = nativeToken.balanceOf(owner); + assertEq( + ownerNativeTokenBalance, + ownerNativeTokenBalanceAfter, + "Wrong owner native token balance" + ); + } + + function test_setDefaultGateway_InboxPartiallyPrefunded() public { + L1OrbitERC20Gateway newL1DefaultGateway = new L1OrbitERC20Gateway(); + address newDefaultGatewayCounterpart = makeAddr("newDefaultGatewayCounterpart"); + newL1DefaultGateway.initialize( + newDefaultGatewayCounterpart, + address(l1OrbitRouter), + inbox, + 0x0000000000000000000000000000000000000000000000000000000000000001, + makeAddr("l2BeaconProxyFactory") + ); + + // prefund inbox + uint256 prefundAmount = nativeTokenTotalFee / 2 + 1; + vm.prank(owner); + nativeToken.transfer(address(inbox), prefundAmount); + + // snapshot balance + uint256 ownerNativeTokenBalance = nativeToken.balanceOf(owner); // approve fees vm.prank(owner); - nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); + nativeToken.approve(address(l1OrbitRouter), prefundAmount); // event checkers vm.expectEmit(true, true, true, true); @@ -386,13 +428,15 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { maxGas, gasPriceBid, nativeTokenTotalFee, - abi.encodeWithSelector(L2GatewayRouter.setDefaultGateway.selector, address(0)) + abi.encodeWithSelector( + L2GatewayRouter.setDefaultGateway.selector, newDefaultGatewayCounterpart + ) ); // set it vm.prank(owner); uint256 seqNum = l1OrbitRouter.setDefaultGateway( - newL1DefaultGateway, + address(newL1DefaultGateway), maxGas, gasPriceBid, maxSubmissionCost, @@ -407,16 +451,64 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { ); assertEq(seqNum, 0, "Invalid seqNum"); + + uint256 ownerNativeTokenBalanceAfter = nativeToken.balanceOf(owner); + assertEq( + ownerNativeTokenBalance - ownerNativeTokenBalanceAfter, + nativeTokenTotalFee - prefundAmount, + "Wrong owner native token balance" + ); } - function test_setDefaultGateway_revert_NotSupportedInOrbit() public { + function test_setDefaultGateway_AddressZero() public override { + address newL1DefaultGateway = address(0); + + // approve fees vm.prank(owner); - vm.expectRevert("NOT_SUPPORTED_IN_ORBIT"); - l1OrbitRouter.setDefaultGateway{ value: retryableCost }( - address(5), + nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); + + // event checkers + vm.expectEmit(true, true, true, true); + emit DefaultGatewayUpdated(address(newL1DefaultGateway)); + + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(owner, owner); + + vm.expectEmit(true, true, true, true); + emit ERC20InboxRetryableTicket( + address(l1OrbitRouter), + counterpartGateway, + 0, maxGas, gasPriceBid, - maxSubmissionCost + nativeTokenTotalFee, + abi.encodeWithSelector(L2GatewayRouter.setDefaultGateway.selector, address(0)) + ); + + // set it + vm.prank(owner); + uint256 seqNum = l1OrbitRouter.setDefaultGateway( + newL1DefaultGateway, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee + ); + + /// checks + assertEq( + l1OrbitRouter.defaultGateway(), + address(newL1DefaultGateway), + "Invalid newL1DefaultGateway" + ); + + assertEq(seqNum, 0, "Invalid seqNum"); + } + + function test_setDefaultGateway_revert_NotSupportedInOrbit() public { + vm.prank(owner); + vm.expectRevert("NOT_SUPPORTED_IN_ORBIT"); + l1OrbitRouter.setDefaultGateway{value: retryableCost}( + address(5), maxGas, gasPriceBid, maxSubmissionCost ); } @@ -432,8 +524,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { // approve fees ERC20PresetMinterPauser(address(nativeToken)).mint( - address(customToken), - nativeTokenTotalFee + address(customToken), nativeTokenTotalFee ); vm.prank(address(customToken)); nativeToken.approve(address(customGateway), nativeTokenTotalFee); @@ -455,8 +546,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { ); ERC20PresetMinterPauser(address(nativeToken)).mint( - address(customToken), - nativeTokenTotalFee + address(customToken), nativeTokenTotalFee ); // snapshot state before @@ -494,11 +584,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { // set gateway vm.prank(address(customToken)); uint256 seqNum = l1OrbitRouter.setGateway( - address(customGateway), - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + address(customGateway), maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); ///// checks @@ -531,8 +617,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { // register token to gateway ERC20PresetMinterPauser(address(nativeToken)).mint( - address(customToken), - nativeTokenTotalFee + address(customToken), nativeTokenTotalFee ); vm.prank(address(customToken)); nativeToken.approve(address(customGateway), nativeTokenTotalFee); @@ -554,8 +639,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { // approve fees ERC20PresetMinterPauser(address(nativeToken)).mint( - address(customToken), - nativeTokenTotalFee + address(customToken), nativeTokenTotalFee ); vm.prank(address(customToken)); nativeToken.approve(address(l1OrbitRouter), nativeTokenTotalFee); @@ -612,10 +696,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { address initialGateway = address(new L1OrbitCustomGateway()); address l2Counterpart = makeAddr("l2Counterpart"); L1OrbitCustomGateway(initialGateway).initialize( - l2Counterpart, - address(l1Router), - address(inbox), - owner + l2Counterpart, address(l1Router), address(inbox), owner ); // create token @@ -628,9 +709,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { nativeToken.approve(address(initialGateway), nativeTokenTotalFee); vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); vm.prank(token); L1OrbitCustomGateway(initialGateway).registerTokenToL2( @@ -685,12 +764,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.prank(nonArbEnabledToken); vm.expectRevert("NOT_ARB_ENABLED"); l1OrbitRouter.setGateway( - makeAddr("gateway"), - 100000, - 3, - 200, - makeAddr("creditback"), - nativeTokenTotalFee + makeAddr("gateway"), 100_000, 3, 200, makeAddr("creditback"), nativeTokenTotalFee ); } @@ -721,11 +795,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { function test_setGateway_revert_CustomCreaditbackNotSupportedInOrbit() public { vm.expectRevert("NOT_SUPPORTED_IN_ORBIT"); l1OrbitRouter.setGateway( - address(103), - maxGas, - gasPriceBid, - maxSubmissionCost, - creditBackAddress + address(103), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress ); } @@ -764,16 +834,11 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { /// init all for (uint256 i = 0; i < 2; i++) { L1OrbitCustomGateway(gateways[i]).initialize( - l2Counterpart, - address(l1Router), - address(inbox), - owner + l2Counterpart, address(l1Router), address(inbox), owner ); vm.mockCall( - tokens[i], - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + tokens[i], abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); // register tokens to gateways @@ -826,12 +891,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { /// set gateways vm.prank(owner); uint256 seqNum = l1OrbitRouter.setGateways( - tokens, - gateways, - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + tokens, gateways, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); ///// checks @@ -846,10 +906,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { address initialGateway = address(new L1OrbitCustomGateway()); address l2Counterpart = makeAddr("l2Counterpart"); L1OrbitCustomGateway(initialGateway).initialize( - l2Counterpart, - address(l1Router), - address(inbox), - owner + l2Counterpart, address(l1Router), address(inbox), owner ); // create token @@ -862,9 +919,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { nativeToken.approve(initialGateway, nativeTokenTotalFee); vm.mockCall( - address(token), - abi.encodeWithSignature("isArbitrumEnabled()"), - abi.encode(uint8(0xb1)) + address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) ); vm.prank(token); L1OrbitCustomGateway(initialGateway).registerTokenToL2( @@ -927,12 +982,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { /// set gateways vm.prank(owner); uint256 seqNum = l1OrbitRouter.setGateways( - _tokenArr, - _gatewayArr, - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + _tokenArr, _gatewayArr, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); ///// checks @@ -952,12 +1002,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.prank(owner); vm.expectRevert("WRONG_LENGTH"); l1OrbitRouter.setGateways( - tokens, - gateways, - maxGas, - gasPriceBid, - maxSubmissionCost, - nativeTokenTotalFee + tokens, gateways, maxGas, gasPriceBid, maxSubmissionCost, nativeTokenTotalFee ); } @@ -965,11 +1010,7 @@ contract L1OrbitGatewayRouterTest is L1GatewayRouterTest { vm.prank(owner); vm.expectRevert("NOT_SUPPORTED_IN_ORBIT"); l1OrbitRouter.setGateways( - new address[](2), - new address[](2), - maxGas, - gasPriceBid, - maxSubmissionCost + new address[](2), new address[](2), maxGas, gasPriceBid, maxSubmissionCost ); } diff --git a/test-foundry/L1OrbitReverseCustomGateway.t.sol b/test-foundry/L1OrbitReverseCustomGateway.t.sol index 2300b1fb0d..820574ca57 100644 --- a/test-foundry/L1OrbitReverseCustomGateway.t.sol +++ b/test-foundry/L1OrbitReverseCustomGateway.t.sol @@ -2,10 +2,21 @@ pragma solidity ^0.8.0; -import { L1OrbitCustomGatewayTest, ERC20InboxMock, TestERC20, IERC20, ERC20, ERC20PresetMinterPauser } from "./L1OrbitCustomGateway.t.sol"; -import { L1OrbitReverseCustomGateway } from "contracts/tokenbridge/ethereum/gateway/L1OrbitReverseCustomGateway.sol"; -import { MintableTestCustomTokenL1, ReverseTestCustomTokenL1 } from "contracts/tokenbridge/test/TestCustomTokenL1.sol"; -import { IInbox } from "contracts/tokenbridge/ethereum/L1ArbitrumMessenger.sol"; +import { + L1OrbitCustomGatewayTest, + ERC20InboxMock, + TestERC20, + IERC20, + ERC20, + ERC20PresetMinterPauser +} from "./L1OrbitCustomGateway.t.sol"; +import {L1OrbitReverseCustomGateway} from + "contracts/tokenbridge/ethereum/gateway/L1OrbitReverseCustomGateway.sol"; +import { + MintableTestCustomTokenL1, + ReverseTestCustomTokenL1 +} from "contracts/tokenbridge/test/TestCustomTokenL1.sol"; +import {IInbox} from "contracts/tokenbridge/ethereum/L1ArbitrumMessenger.sol"; contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { function setUp() public override { @@ -34,10 +45,8 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { /* solhint-disable func-name-mixedcase */ function test_finalizeInboundTransfer() public override { // fund gateway with bridged tokens - MintableTestCustomTokenL1 bridgedToken = new MintableTestCustomTokenL1( - address(l1Gateway), - router - ); + MintableTestCustomTokenL1 bridgedToken = + new MintableTestCustomTokenL1(address(l1Gateway), router); vm.prank(address(l1Gateway)); bridgedToken.mint(); @@ -56,11 +65,7 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { // trigger deposit vm.prank(address(IInbox(l1Gateway.inbox()).bridge())); L1OrbitReverseCustomGateway(address(l1Gateway)).finalizeInboundTransfer( - address(bridgedToken), - from, - user, - amount, - data + address(bridgedToken), from, user, amount, data ); // check tokens are minted @@ -70,10 +75,8 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { function test_outboundTransfer() public override { // fund user with tokens - MintableTestCustomTokenL1 bridgedToken = new ReverseTestCustomTokenL1( - address(l1Gateway), - router - ); + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); vm.prank(address(user)); bridgedToken.mint(); @@ -86,8 +89,7 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { // approve fees ERC20PresetMinterPauser(address(nativeToken)).mint( - address(bridgedToken), - nativeTokenTotalFee + address(bridgedToken), nativeTokenTotalFee ); vm.prank(address(bridgedToken)); nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); @@ -141,12 +143,7 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { // trigger transfer vm.prank(router); bytes memory seqNum1 = L1OrbitReverseCustomGateway(address(l1Gateway)).outboundTransfer( - address(bridgedToken), - user, - amount, - maxGas, - gasPriceBid, - routerEncodedData + address(bridgedToken), user, amount, maxGas, gasPriceBid, routerEncodedData ); // check tokens are burned @@ -159,10 +156,8 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { function test_outboundTransferCustomRefund() public override { // fund user with tokens - MintableTestCustomTokenL1 bridgedToken = new ReverseTestCustomTokenL1( - address(l1Gateway), - router - ); + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); vm.prank(address(user)); bridgedToken.mint(); @@ -175,8 +170,7 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { // approve fees ERC20PresetMinterPauser(address(nativeToken)).mint( - address(bridgedToken), - nativeTokenTotalFee + address(bridgedToken), nativeTokenTotalFee ); vm.prank(address(bridgedToken)); nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); @@ -231,14 +225,14 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { vm.prank(router); bytes memory seqNum1 = L1OrbitReverseCustomGateway(address(l1Gateway)) .outboundTransferCustomRefund( - address(bridgedToken), - creditBackAddress, - user, - amount, - maxGas, - gasPriceBid, - routerEncodedData - ); + address(bridgedToken), + creditBackAddress, + user, + amount, + maxGas, + gasPriceBid, + routerEncodedData + ); // check tokens are escrowed uint256 userBalanceAfter = bridgedToken.balanceOf(user); @@ -250,10 +244,8 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { function test_outboundTransferCustomRefund_revert_InsufficientAllowance() public override { // fund user with tokens - MintableTestCustomTokenL1 bridgedToken = new ReverseTestCustomTokenL1( - address(l1Gateway), - router - ); + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); vm.prank(address(user)); bridgedToken.mint(); @@ -261,8 +253,7 @@ contract L1OrbitReverseCustomGatewayTest is L1OrbitCustomGatewayTest { // approve fees ERC20PresetMinterPauser(address(nativeToken)).mint( - address(bridgedToken), - nativeTokenTotalFee + address(bridgedToken), nativeTokenTotalFee ); vm.prank(address(bridgedToken)); nativeToken.approve(address(l1Gateway), nativeTokenTotalFee); diff --git a/test-foundry/L1ReverseCustomGateway.t.sol b/test-foundry/L1ReverseCustomGateway.t.sol index 30dce5bba1..311bac5905 100644 --- a/test-foundry/L1ReverseCustomGateway.t.sol +++ b/test-foundry/L1ReverseCustomGateway.t.sol @@ -2,9 +2,15 @@ pragma solidity ^0.8.0; -import { L1CustomGatewayTest, InboxMock, IERC20, IInbox, TestERC20 } from "./L1CustomGateway.t.sol"; -import { L1ReverseCustomGateway } from "contracts/tokenbridge/ethereum/gateway/L1ReverseCustomGateway.sol"; -import { MintableTestCustomTokenL1, ReverseTestCustomTokenL1 } from "contracts/tokenbridge/test/TestCustomTokenL1.sol"; +import "./L1CustomGateway.t.sol"; +import {L1ReverseCustomGateway} from + "contracts/tokenbridge/ethereum/gateway/L1ReverseCustomGateway.sol"; +import { + MintableTestCustomTokenL1, + ReverseTestCustomTokenL1 +} from "contracts/tokenbridge/test/TestCustomTokenL1.sol"; +import {ERC20PresetMinterPauser} from + "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { function setUp() public virtual override { @@ -27,12 +33,10 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { } /* solhint-disable func-name-mixedcase */ - function test_finalizeInboundTransfer() public override { + function test_finalizeInboundTransfer() public virtual override { // fund gateway with bridged tokens - MintableTestCustomTokenL1 bridgedToken = new MintableTestCustomTokenL1( - address(l1Gateway), - router - ); + MintableTestCustomTokenL1 bridgedToken = + new MintableTestCustomTokenL1(address(l1Gateway), router); vm.prank(address(l1Gateway)); bridgedToken.mint(); @@ -57,12 +61,10 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { assertEq(userBalanceAfter - userBalanceBefore, amount, "Wrong user balance"); } - function test_outboundTransfer() public override { + function test_outboundTransfer() public virtual override { // fund user with tokens - MintableTestCustomTokenL1 bridgedToken = new ReverseTestCustomTokenL1( - address(l1Gateway), - router - ); + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); vm.prank(address(user)); bridgedToken.mint(); @@ -110,13 +112,8 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { // trigger transfer vm.prank(router); - bytes memory seqNum1 = l1Gateway.outboundTransfer{ value: retryableCost }( - address(bridgedToken), - user, - amount, - maxGas, - gasPriceBid, - routerEncodedData + bytes memory seqNum1 = l1Gateway.outboundTransfer{value: retryableCost}( + address(bridgedToken), user, amount, maxGas, gasPriceBid, routerEncodedData ); // check tokens are burned @@ -127,12 +124,10 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { assertEq(seqNum1, abi.encode(1), "Invalid seqNum1"); } - function test_outboundTransferCustomRefund() public override { + function test_outboundTransferCustomRefund() public virtual override { // fund user with tokens - MintableTestCustomTokenL1 bridgedToken = new ReverseTestCustomTokenL1( - address(l1Gateway), - router - ); + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); vm.prank(address(user)); bridgedToken.mint(); @@ -180,7 +175,7 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { // trigger deposit vm.prank(router); - bytes memory seqNum1 = l1Gateway.outboundTransferCustomRefund{ value: retryableCost }( + bytes memory seqNum1 = l1Gateway.outboundTransferCustomRefund{value: retryableCost}( address(bridgedToken), creditBackAddress, user, @@ -198,12 +193,14 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { assertEq(seqNum1, abi.encode(1), "Invalid seqNum1"); } - function test_outboundTransferCustomRefund_revert_InsufficientAllowance() public override { + function test_outboundTransferCustomRefund_revert_InsufficientAllowance() + public + virtual + override + { // fund user with tokens - MintableTestCustomTokenL1 bridgedToken = new ReverseTestCustomTokenL1( - address(l1Gateway), - router - ); + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); vm.prank(address(user)); bridgedToken.mint(); @@ -217,7 +214,7 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { ); vm.deal(address(bridgedToken), 100 ether); vm.prank(address(bridgedToken)); - L1ReverseCustomGateway(address(l1Gateway)).registerTokenToL2{ value: retryableCost }( + L1ReverseCustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( makeAddr("tokenL2Address"), maxGas, gasPriceBid, @@ -227,7 +224,7 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { vm.prank(router); vm.expectRevert("ERC20: burn amount exceeds balance"); - l1Gateway.outboundTransferCustomRefund{ value: 1 ether }( + l1Gateway.outboundTransferCustomRefund{value: 1 ether}( address(bridgedToken), user, user, @@ -237,4 +234,45 @@ contract L1ReverseCustomGatewayTest is L1CustomGatewayTest { buildRouterEncodedData("") ); } + + function test_outboundTransferCustomRefund_revert_Reentrancy() public virtual override { + // fund user with tokens + MintableTestCustomTokenL1 bridgedToken = + new ReverseTestCustomTokenL1(address(l1Gateway), router); + vm.prank(address(user)); + bridgedToken.mint(); + + // register token to gateway + vm.mockCall( + address(bridgedToken), + abi.encodeWithSignature("isArbitrumEnabled()"), + abi.encode(uint8(0xb1)) + ); + vm.deal(address(bridgedToken), 100 ether); + vm.prank(address(bridgedToken)); + L1ReverseCustomGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + makeAddr("tokenL2Address"), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress + ); + + // approve token + uint256 amount = 450; + vm.prank(user); + bridgedToken.approve(address(l1Gateway), amount); + + // trigger re-entrancy + MockReentrantERC20 mockReentrantERC20 = new MockReentrantERC20(); + vm.etch(address(bridgedToken), address(mockReentrantERC20).code); + + vm.expectRevert("ReentrancyGuard: reentrant call"); + vm.prank(router); + l1Gateway.outboundTransferCustomRefund{value: retryableCost}( + address(bridgedToken), + creditBackAddress, + user, + amount, + maxGas, + gasPriceBid, + buildRouterEncodedData("") + ); + } } diff --git a/test-foundry/L1WethGateway.t.sol b/test-foundry/L1WethGateway.t.sol new file mode 100644 index 0000000000..b7ff776110 --- /dev/null +++ b/test-foundry/L1WethGateway.t.sol @@ -0,0 +1,748 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +import "./L1ArbitrumExtendedGateway.t.sol"; +import { + L1WethGateway, + IInbox, + ITokenGateway, + IERC165, + IL1ArbitrumGateway, + IERC20 +} from "contracts/tokenbridge/ethereum/gateway/L1WethGateway.sol"; +import {L2CustomGateway} from "contracts/tokenbridge/arbitrum/gateway/L2CustomGateway.sol"; +import {TestERC20} from "contracts/tokenbridge/test/TestERC20.sol"; +import {InboxMock} from "contracts/tokenbridge/test/InboxMock.sol"; +import {TestWETH9} from "contracts/tokenbridge/test/TestWETH9.sol"; + +contract L1WethGatewayTest is L1ArbitrumExtendedGatewayTest { + // gateway params + address public owner = makeAddr("owner"); + + address public L1_WETH = address(new TestWETH9("weth", "weth")); + address public L2_WETH = makeAddr("L2_WETH"); + + function setUp() public virtual { + inbox = address(new InboxMock()); + + l1Gateway = new L1WethGateway(); + L1WethGateway(payable(address(l1Gateway))).initialize( + l2Gateway, router, inbox, L1_WETH, L2_WETH + ); + + maxSubmissionCost = 20; + retryableCost = maxSubmissionCost + gasPriceBid * maxGas; + + vm.deal(router, 100 ether); + } + + /* solhint-disable func-name-mixedcase */ + + function test_finalizeInboundTransfer() public override { + // fund gateway with tokens being withdrawn + uint256 withdrawalAmount = 25 ether; + vm.deal(address(l1Gateway), withdrawalAmount); + + // snapshot state before + uint256 userBalanceBefore = ERC20(L1_WETH).balanceOf(user); + uint256 l1GatewayBalanceBefore = address(l1Gateway).balance; + + // withdrawal params + address from = address(3000); + uint256 exitNum = 7; + bytes memory callHookData = ""; + bytes memory data = abi.encode(exitNum, callHookData); + + InboxMock(address(inbox)).setL2ToL1Sender(l2Gateway); + + // trigger withdrawal + vm.prank(address(IInbox(l1Gateway.inbox()).bridge())); + l1Gateway.finalizeInboundTransfer(L1_WETH, from, user, withdrawalAmount, data); + + // check tokens are properly released + uint256 userBalanceAfter = ERC20(L1_WETH).balanceOf(user); + assertEq(userBalanceAfter - userBalanceBefore, withdrawalAmount, "Wrong user balance"); + + uint256 l1GatewayBalanceAfter = address(l1Gateway).balance; + assertEq( + l1GatewayBalanceBefore - l1GatewayBalanceAfter, + withdrawalAmount, + "Wrong l1 gateway balance" + ); + } + + function test_getExternalCall_Redirected(uint256 exitNum, address initialDest, address newDest) + public + override + { + // N/A + } + + function test_transferExitAndCall(uint256, address, address) public override { + // do it + address initialDestination = makeAddr("initialDestination"); + vm.prank(initialDestination); + vm.expectRevert("TRADABLE_EXIT_TEMP_DISABLED"); + L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( + 1, initialDestination, makeAddr("newDestination"), bytes(""), bytes("") + ); + } + + function test_transferExitAndCall_EmptyData_Redirected( + uint256 exitNum, + address initialDestination + ) public override { + // N/A + } + + function test_transferExitAndCall_NonEmptyData(uint256 exitNum, address initialDestination) + public + override + { + bytes memory data = abi.encode("fun()"); + vm.prank(initialDestination); + vm.expectRevert("TRADABLE_EXIT_TEMP_DISABLED"); + L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( + exitNum, initialDestination, makeAddr("newDestination"), bytes(""), data + ); + } + + function test_transferExitAndCall_NonEmptyData_Redirected(uint256, address) public override { + // N/A + } + + function test_transferExitAndCall_revert_ToNotContract(address initialDestination) + public + override + { + bytes memory data = abi.encode("execute()"); + address nonContractNewDestination = address(15); + + vm.prank(initialDestination); + vm.expectRevert("TRADABLE_EXIT_TEMP_DISABLED"); + L1ArbitrumExtendedGateway(address(l1Gateway)).transferExitAndCall( + 4, initialDestination, nonContractNewDestination, "", data + ); + } + + function test_transferExitAndCall_revert_TransferHookFail(uint256, address) public override { + // N/A + } + + function test_transferExitAndCall_revert_TransferHookFail_Redirected(uint256, address) + public + override + { + // N/A + } + + function test_calculateL2TokenAddress() public virtual { + assertEq(l1Gateway.calculateL2TokenAddress(L1_WETH), L2_WETH, "Invalid L2 token address"); + } + + function test_calculateL2TokenAddress(address l1Token) public virtual { + vm.assume(l1Token != L1_WETH); + assertEq(l1Gateway.calculateL2TokenAddress(l1Token), address(0), "Invalid L2 token address"); + } + + // function test_forceRegisterTokenToL2() public virtual { + // address[] memory l1Tokens = new address[](2); + // l1Tokens[0] = makeAddr("l1Token1"); + // l1Tokens[1] = makeAddr("l1Token2"); + // address[] memory l2Tokens = new address[](2); + // l2Tokens[0] = makeAddr("l2Token1"); + // l2Tokens[1] = makeAddr("l2Token2"); + + // // expect events + // vm.expectEmit(true, true, true, true); + // emit TokenSet(l1Tokens[0], l2Tokens[0]); + + // vm.expectEmit(true, true, true, true); + // emit TokenSet(l1Tokens[1], l2Tokens[1]); + + // vm.expectEmit(true, true, true, true); + // emit TicketData(maxSubmissionCost); + + // vm.expectEmit(true, true, true, true); + // emit RefundAddresses(owner, owner); + + // vm.expectEmit(true, true, true, true); + // emit InboxRetryableTicket( + // address(l1Gateway), + // l2Gateway, + // 0, + // maxGas, + // abi.encodeWithSelector(L2CustomGateway.registerTokenFromL1.selector, l1Tokens, l2Tokens) + // ); + + // // register token to gateway + // vm.prank(owner); + // uint256 seqNum = L1WethGateway(address(l1Gateway)).forceRegisterTokenToL2{ + // value: retryableCost + // }(l1Tokens, l2Tokens, maxGas, gasPriceBid, maxSubmissionCost); + + // ///// checks + // assertEq( + // L1WethGateway(address(l1Gateway)).l1ToL2Token(l1Tokens[0]), + // l2Tokens[0], + // "Invalid L2 token" + // ); + + // assertEq( + // L1WethGateway(address(l1Gateway)).l1ToL2Token(l1Tokens[1]), + // l2Tokens[1], + // "Invalid L2 token" + // ); + + // assertEq(seqNum, 0, "Invalid seqNum"); + // } + + // function test_forceRegisterTokenToL2_revert_InvalidLength() public virtual { + // vm.prank(owner); + // vm.expectRevert("INVALID_LENGTHS"); + // L1WethGateway(address(l1Gateway)).forceRegisterTokenToL2{value: retryableCost}( + // new address[](1), new address[](2), maxGas, gasPriceBid, maxSubmissionCost + // ); + // } + + // function test_forceRegisterTokenToL2_revert_OnlyOwner() public { + // vm.expectRevert("ONLY_OWNER"); + // L1WethGateway(address(l1Gateway)).forceRegisterTokenToL2{value: retryableCost}( + // new address[](1), new address[](1), maxGas, gasPriceBid, maxSubmissionCost + // ); + // } + + // function test_getOutboundCalldata() public { + // bytes memory outboundCalldata = l1Gateway.getOutboundCalldata({ + // _token: address(token), + // _from: user, + // _to: address(800), + // _amount: 355, + // _data: abi.encode("doStuff()") + // }); + + // bytes memory expectedCalldata = abi.encodeWithSelector( + // ITokenGateway.finalizeInboundTransfer.selector, + // address(token), + // user, + // address(800), + // 355, + // abi.encode("", abi.encode("doStuff()")) + // ); + + // assertEq(outboundCalldata, expectedCalldata, "Invalid outboundCalldata"); + // } + + function test_initialize() public { + L1WethGateway gateway = new L1WethGateway(); + gateway.initialize(l2Gateway, router, inbox, L1_WETH, L2_WETH); + + assertEq(gateway.counterpartGateway(), l2Gateway, "Invalid counterpartGateway"); + assertEq(gateway.router(), router, "Invalid router"); + assertEq(gateway.inbox(), inbox, "Invalid inbox"); + assertEq(gateway.l1Weth(), L1_WETH, "Invalid L1_WETH"); + assertEq(gateway.l2Weth(), L2_WETH, "Invalid L2_WETH"); + } + + function test_initialize_revert_InvalidL1Weth() public { + L1WethGateway gateway = new L1WethGateway(); + vm.expectRevert("INVALID_L1WETH"); + gateway.initialize(l2Gateway, router, inbox, address(0), L2_WETH); + } + + function test_initialize_revert_InvalidL2Weth() public { + L1WethGateway gateway = new L1WethGateway(); + vm.expectRevert("INVALID_L2WETH"); + gateway.initialize(l2Gateway, router, inbox, L1_WETH, address(0)); + } + + function test_outboundTransferCustomRefund() public virtual { + uint256 depositAmountInToken = 2 ether; + uint256 depositAmountInEth = 4 ether; + uint256 depositAmount = depositAmountInToken + depositAmountInEth; + + // snapshot state before + vm.deal(user, depositAmount * 2); + vm.prank(user); + TestWETH9(L1_WETH).deposit{value: depositAmountInToken}(); + uint256 userBalanceBefore = user.balance; + uint256 userWethBalanceBefore = ERC20(L1_WETH).balanceOf(user); + uint256 bridgeBalanceBefore = address(IInbox(l1Gateway.inbox()).bridge()).balance; + + { + // approve token + vm.prank(user); + ERC20(L1_WETH).approve(address(l1Gateway), depositAmountInToken); + + // event checkers + vm.expectEmit(true, true, true, true); + emit TicketData(maxSubmissionCost); + + vm.expectEmit(true, true, true, true); + emit RefundAddresses(creditBackAddress, user); + } + + vm.expectEmit(true, true, true, true); + emit InboxRetryableTicket( + address(l1Gateway), + l2Gateway, + depositAmountInToken, + maxGas, + l1Gateway.getOutboundCalldata(address(L1_WETH), user, user, depositAmountInToken, "") + ); + + vm.expectEmit(true, true, true, true); + emit DepositInitiated(address(L1_WETH), user, user, 0, depositAmountInToken); + + // trigger deposit + vm.prank(router); + bytes memory seqNum0 = l1Gateway.outboundTransferCustomRefund{ + value: retryableCost + depositAmountInEth + }( + L1_WETH, + creditBackAddress, + user, + depositAmountInToken, + maxGas, + gasPriceBid, + buildRouterEncodedData("") + ); + + // check tokens are escrowed + uint256 userBalanceAfter = user.balance; + assertLe(userBalanceBefore - userBalanceAfter, depositAmountInEth, "Wrong user ETH balance"); + + uint256 userWethBalanceAfter = ERC20(L1_WETH).balanceOf(user); + assertEq( + userWethBalanceBefore - userWethBalanceAfter, + depositAmountInToken, + "Wrong user WETH balance" + ); + + uint256 bridgeBalanceAfter = address(IInbox(l1Gateway.inbox()).bridge()).balance; + assertGe(bridgeBalanceAfter - bridgeBalanceBefore, depositAmount, "Wrong bridge balance"); + + assertEq(seqNum0, abi.encode(0), "Invalid seqNum0"); + } + + // function test_outboundTransfer() public virtual { + // // snapshot state before + // uint256 userBalanceBefore = token.balanceOf(user); + // uint256 l1GatewayBalanceBefore = token.balanceOf(address(l1Gateway)); + + // uint256 depositAmount = 300; + // bytes memory callHookData = ""; + // bytes memory routerEncodedData = buildRouterEncodedData(callHookData); + + // // register token to gateway + // vm.mockCall( + // address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(token)); + // uint256 seqNum0 = L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // makeAddr("tokenL2Address"), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress + // ); + + // // approve token + // vm.prank(user); + // token.approve(address(l1Gateway), depositAmount); + + // // event checkers + // vm.expectEmit(true, true, true, true); + // emit TicketData(maxSubmissionCost); + + // vm.expectEmit(true, true, true, true); + // emit RefundAddresses(user, user); + + // vm.expectEmit(true, true, true, true); + // emit InboxRetryableTicket( + // address(l1Gateway), + // l2Gateway, + // 0, + // maxGas, + // l1Gateway.getOutboundCalldata(address(token), user, user, depositAmount, callHookData) + // ); + + // vm.expectEmit(true, true, true, true); + // emit DepositInitiated(address(token), user, user, 1, depositAmount); + + // // trigger deposit + // vm.prank(router); + // bytes memory seqNum1 = l1Gateway.outboundTransfer{value: retryableCost}( + // address(token), user, depositAmount, maxGas, gasPriceBid, routerEncodedData + // ); + + // // check tokens are escrowed + // uint256 userBalanceAfter = token.balanceOf(user); + // assertEq(userBalanceBefore - userBalanceAfter, depositAmount, "Wrong user balance"); + + // uint256 l1GatewayBalanceAfter = token.balanceOf(address(l1Gateway)); + // assertEq( + // l1GatewayBalanceAfter - l1GatewayBalanceBefore, + // depositAmount, + // "Wrong l1 gateway balance" + // ); + + // assertEq(seqNum0, 0, "Invalid seqNum0"); + // assertEq(seqNum1, abi.encode(1), "Invalid seqNum1"); + // } + + // function test_outboundTransferCustomRefund() public virtual { + // // snapshot state before + // uint256 userBalanceBefore = token.balanceOf(user); + // uint256 l1GatewayBalanceBefore = token.balanceOf(address(l1Gateway)); + + // uint256 depositAmount = 450; + // bytes memory callHookData = ""; + // bytes memory routerEncodedData = buildRouterEncodedData(callHookData); + + // // register token to gateway + // vm.mockCall( + // address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(token)); + // uint256 seqNum0 = L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // makeAddr("tokenL2Address"), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress + // ); + + // // approve token + // vm.prank(user); + // token.approve(address(l1Gateway), depositAmount); + + // // event checkers + // vm.expectEmit(true, true, true, true); + // emit TicketData(maxSubmissionCost); + + // vm.expectEmit(true, true, true, true); + // emit RefundAddresses(creditBackAddress, user); + + // vm.expectEmit(true, true, true, true); + // emit InboxRetryableTicket( + // address(l1Gateway), + // l2Gateway, + // 0, + // maxGas, + // l1Gateway.getOutboundCalldata(address(token), user, user, depositAmount, callHookData) + // ); + + // vm.expectEmit(true, true, true, true); + // emit DepositInitiated(address(token), user, user, 1, depositAmount); + + // // trigger deposit + // vm.prank(router); + // bytes memory seqNum1 = l1Gateway.outboundTransferCustomRefund{value: retryableCost}( + // address(token), + // creditBackAddress, + // user, + // depositAmount, + // maxGas, + // gasPriceBid, + // routerEncodedData + // ); + + // // check tokens are escrowed + // uint256 userBalanceAfter = token.balanceOf(user); + // assertEq(userBalanceBefore - userBalanceAfter, depositAmount, "Wrong user balance"); + + // uint256 l1GatewayBalanceAfter = token.balanceOf(address(l1Gateway)); + // assertEq( + // l1GatewayBalanceAfter - l1GatewayBalanceBefore, + // depositAmount, + // "Wrong l1 gateway balance" + // ); + + // assertEq(seqNum0, 0, "Invalid seqNum0"); + // assertEq(seqNum1, abi.encode(1), "Invalid seqNum1"); + // } + + // function test_outboundTransferCustomRefund_revert_InsufficientAllowance() public virtual { + // uint256 tooManyTokens = 500 ether; + + // // register token to gateway + // vm.mockCall( + // address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(token)); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // makeAddr("tokenL2Address"), + // maxGas, + // gasPriceBid, + // maxSubmissionCost, + // makeAddr("creditBackAddress") + // ); + + // vm.prank(router); + // vm.expectRevert("ERC20: insufficient allowance"); + // l1Gateway.outboundTransferCustomRefund{value: 1 ether}( + // address(token), + // user, + // user, + // tooManyTokens, + // 0.1 ether, + // 0.01 ether, + // buildRouterEncodedData("") + // ); + // } + + // function test_outboundTransferCustomRefund_revert_NoL2TokenSet() public virtual { + // uint256 tooManyTokens = 500 ether; + + // // register token to gateway + // vm.mockCall( + // address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(token)); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // address(0), maxGas, gasPriceBid, maxSubmissionCost, makeAddr("creditBackAddress") + // ); + + // vm.prank(router); + // vm.expectRevert("NO_L2_TOKEN_SET"); + // l1Gateway.outboundTransferCustomRefund{value: 1 ether}( + // address(token), + // user, + // user, + // tooManyTokens, + // 0.1 ether, + // 0.01 ether, + // buildRouterEncodedData("") + // ); + // } + + // function test_outboundTransferCustomRefund_revert_Reentrancy() public virtual { + // // register token to gateway + // vm.mockCall( + // address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(token)); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // makeAddr("tokenL2Address"), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress + // ); + + // // approve token + // uint256 depositAmount = 3; + // vm.prank(user); + // token.approve(address(l1Gateway), depositAmount); + + // // trigger re-entrancy + // MockReentrantInbox mockReentrantInbox = new MockReentrantInbox(); + // vm.etch(l1Gateway.inbox(), address(mockReentrantInbox).code); + + // vm.prank(router); + // vm.expectRevert("ReentrancyGuard: reentrant call"); + // l1Gateway.outboundTransferCustomRefund{value: retryableCost}( + // address(token), + // creditBackAddress, + // user, + // depositAmount, + // maxGas, + // gasPriceBid, + // buildRouterEncodedData("") + // ); + // } + + // function test_registerTokenToL2(address l1Token, address l2Token) public virtual { + // vm.assume(l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS); + // vm.deal(l1Token, 100 ether); + + // // event checkers + // vm.expectEmit(true, true, true, true); + // emit TokenSet(l1Token, l2Token); + + // vm.expectEmit(true, true, true, true); + // emit TicketData(maxSubmissionCost); + + // vm.expectEmit(true, true, true, true); + // emit RefundAddresses(l1Token, l1Token); + + // address[] memory l1Tokens = new address[](1); + // l1Tokens[0] = address(l1Token); + // address[] memory l2Tokens = new address[](1); + // l2Tokens[0] = address(l2Token); + // vm.expectEmit(true, true, true, true); + // emit InboxRetryableTicket( + // address(l1Gateway), + // l2Gateway, + // 0, + // maxGas, + // abi.encodeWithSelector(L2CustomGateway.registerTokenFromL1.selector, l1Tokens, l2Tokens) + // ); + + // // register token to gateway + // vm.mockCall( + // address(l1Token), + // abi.encodeWithSignature("isArbitrumEnabled()"), + // abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(l1Token)); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // l2Token, maxGas, gasPriceBid, maxSubmissionCost + // ); + + // assertEq( + // L1WethGateway(address(l1Gateway)).l1ToL2Token(l1Token), l2Token, "Invalid L2 token" + // ); + // } + + // function test_registerTokenToL2_CustomRefund(address l1Token, address l2Token) public virtual { + // vm.assume(l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS); + // vm.deal(l1Token, 100 ether); + + // // event checkers + // vm.expectEmit(true, true, true, true); + // emit TokenSet(l1Token, l2Token); + + // vm.expectEmit(true, true, true, true); + // emit TicketData(maxSubmissionCost); + + // vm.expectEmit(true, true, true, true); + // emit RefundAddresses(creditBackAddress, creditBackAddress); + + // address[] memory l1Tokens = new address[](1); + // l1Tokens[0] = address(l1Token); + // address[] memory l2Tokens = new address[](1); + // l2Tokens[0] = address(l2Token); + // vm.expectEmit(true, true, true, true); + // emit InboxRetryableTicket( + // address(l1Gateway), + // l2Gateway, + // 0, + // maxGas, + // abi.encodeWithSelector(L2CustomGateway.registerTokenFromL1.selector, l1Tokens, l2Tokens) + // ); + + // // register token to gateway + // vm.mockCall( + // address(l1Token), + // abi.encodeWithSignature("isArbitrumEnabled()"), + // abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(l1Token)); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // l2Token, maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress + // ); + + // assertEq( + // L1WethGateway(address(l1Gateway)).l1ToL2Token(l1Token), l2Token, "Invalid L2 token" + // ); + // } + + // function test_registerTokenToL2_UpdateToSameAddress(address l1Token, address l2Token) + // public + // virtual + // { + // vm.assume(l1Token != FOUNDRY_CHEATCODE_ADDRESS && l2Token != FOUNDRY_CHEATCODE_ADDRESS); + // vm.deal(l1Token, 100 ether); + + // address[] memory l1Tokens = new address[](1); + // l1Tokens[0] = address(l1Token); + // address[] memory l2Tokens = new address[](1); + // l2Tokens[0] = address(l2Token); + + // // register token to gateway + // vm.mockCall( + // address(l1Token), + // abi.encodeWithSignature("isArbitrumEnabled()"), + // abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(l1Token)); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // l2Token, maxGas, gasPriceBid, maxSubmissionCost + // ); + + // // re-register + // vm.mockCall( + // address(l1Token), + // abi.encodeWithSignature("isArbitrumEnabled()"), + // abi.encode(uint8(0xb1)) + // ); + // vm.prank(address(l1Token)); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // l2Token, maxGas, gasPriceBid, maxSubmissionCost + // ); + + // assertEq( + // L1WethGateway(address(l1Gateway)).l1ToL2Token(l1Token), l2Token, "Invalid L2 token" + // ); + // } + + // function test_registerTokenToL2_revert_NotArbEnabled() public virtual { + // // wrong answer + // vm.mockCall( + // address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xdd)) + // ); + + // vm.prank(address(token)); + // vm.expectRevert("NOT_ARB_ENABLED"); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // address(102), maxGas, gasPriceBid, maxSubmissionCost, creditBackAddress + // ); + // } + + // function test_registerTokenToL2_revert_NoUpdateToDifferentAddress() public virtual { + // // register token to gateway + // vm.mockCall( + // address(token), abi.encodeWithSignature("isArbitrumEnabled()"), abi.encode(uint8(0xb1)) + // ); + + // // set initial address + // address initialL2TokenAddress = makeAddr("initial"); + // vm.prank(address(token)); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // initialL2TokenAddress, maxGas, gasPriceBid, maxSubmissionCost + // ); + // assertEq( + // L1WethGateway(address(l1Gateway)).l1ToL2Token(address(token)), initialL2TokenAddress + // ); + + // // try to set different one + // address differentL2TokenAddress = makeAddr("different"); + // vm.prank(address(token)); + // vm.expectRevert("NO_UPDATE_TO_DIFFERENT_ADDR"); + // L1WethGateway(address(l1Gateway)).registerTokenToL2{value: retryableCost}( + // differentL2TokenAddress, maxGas, gasPriceBid, maxSubmissionCost + // ); + // } + + // function test_setOwner(address newOwner) public { + // vm.assume(newOwner != address(0)); + + // vm.prank(owner); + // L1WethGateway(address(l1Gateway)).setOwner(newOwner); + + // assertEq(L1WethGateway(address(l1Gateway)).owner(), newOwner, "Invalid owner"); + // } + + // function test_setOwner_revert_InvalidOwner() public { + // address invalidOwner = address(0); + + // vm.prank(owner); + // vm.expectRevert("INVALID_OWNER"); + // L1WethGateway(address(l1Gateway)).setOwner(invalidOwner); + // } + + // function test_setOwner_revert_OnlyOwner() public { + // address nonOwner = address(250); + + // vm.prank(nonOwner); + // vm.expectRevert("ONLY_OWNER"); + // L1WethGateway(address(l1Gateway)).setOwner(address(300)); + // } + + //// + // Event declarations + //// + event TokenSet(address indexed l1Address, address indexed l2Address); + + event DepositInitiated( + address l1Token, + address indexed _from, + address indexed _to, + uint256 indexed _sequenceNumber, + uint256 _amount + ); + event TicketData(uint256 maxSubmissionCost); + event RefundAddresses(address excessFeeRefundAddress, address callValueRefundAddress); + event InboxRetryableTicket(address from, address to, uint256 value, uint256 maxGas, bytes data); +} diff --git a/test-mutation/gambitTester.ts b/test-mutation/gambitTester.ts index f99fa251d7..9450abb1ef 100644 --- a/test-mutation/gambitTester.ts +++ b/test-mutation/gambitTester.ts @@ -13,7 +13,7 @@ const TEST_TIMES = [ 'remappings.txt', 'test-foundry', ] -const MAX_TASKS = os.cpus().length - 1 +const MAX_TASKS = Math.max(1, os.cpus().length - 1) const execAsync = promisify(exec) const symlink = promisify(fs.symlink) @@ -123,7 +123,7 @@ async function _testMutant(mutant: Mutant): Promise { try { await execAsync(`forge build --root ${testDirectory}`) await execAsync( - `forge test --fail-fast --gas-limit 30000000 --root ${testDirectory}` + `forge test --fail-fast --no-match-contract AtomicTokenBridgeCreatorTest --root ${testDirectory}` ) mutantStatus = MutantStatus.SURVIVED } catch (error) {