From 193f4704dd5fae12d09ef5be4582d844bf9618b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CF=87=C2=B2?= <88190723+ChiTimesChi@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:22:47 +0100 Subject: [PATCH] Feat: fast bridge router v2 (#330) * feat: copy FastBridgeRouter, scaffold new error * test: establish template for FBR v2 tests * test: add token tests for FBR V2 * feat: decode originSender in FastBridgeRouterV2 * refactor: deduplicate origin query in tests * refactor: deduplicate V2 tests * test: add native ETH tests for FBR v2 * refactor: EOA fallback * chore: better docs for `_getOriginSender` * feat: FBRv2 deploy, configure scripts * build: update network-specific options * deploy: FBR v2 * test: add cases for deadline/minAmountOut * fix: check deadline/amountOut for no-swap cases * deploy: updated FB RouterV2 * build: add helper scripts --- contracts/rfq/FastBridgeRouterV2.sol | 141 +++ deployments/arbitrum/FastBridgeRouterV2.json | 396 ++++++ deployments/base/FastBridgeRouterV2.json | 396 ++++++ deployments/blast/FastBridgeRouterV2.json | 396 ++++++ deployments/bsc/FastBridgeRouterV2.json | 396 ++++++ deployments/linea/FastBridgeRouterV2.json | 396 ++++++ deployments/mainnet/FastBridgeRouterV2.json | 396 ++++++ deployments/optimism/FastBridgeRouterV2.json | 396 ++++++ deployments/scroll/FastBridgeRouterV2.json | 396 ++++++ script/configs/Create2Factory.salts.json | 5 + script/networks.json | 4 - script/rfq/BrickFastBridgeRouter.s.sol | 33 + ....sol => ConfigureFastBridgeRouterV2.s.sol} | 24 +- script/rfq/DeployFastBridgeRouterV2.s.sol | 20 + script/rfq/rfq-cmd.sh | 29 + script/rfq/rfq-run.sh | 19 + test/mocks/MockSenderContract.sol | 10 + test/rfq/FBRTest.sol | 17 +- test/rfq/FastBridgeRouter.Native.t.sol | 257 ++-- test/rfq/FastBridgeRouter.QuoterV1.t.sol | 2 +- test/rfq/FastBridgeRouter.t.sol | 93 +- .../FastBridgeRouterV2.Native.QuoterV1.t.sol | 28 + .../FastBridgeRouterV2.Native.QuoterV2.t.sol | 32 + test/rfq/FastBridgeRouterV2.Native.t.sol | 1122 +++++++++++++++++ test/rfq/FastBridgeRouterV2.QuoterV1.t.sol | 29 + test/rfq/FastBridgeRouterV2.QuoterV2.t.sol | 33 + test/rfq/FastBridgeRouterV2.t.sol | 299 +++++ 27 files changed, 5105 insertions(+), 260 deletions(-) create mode 100644 contracts/rfq/FastBridgeRouterV2.sol create mode 100644 deployments/arbitrum/FastBridgeRouterV2.json create mode 100644 deployments/base/FastBridgeRouterV2.json create mode 100644 deployments/blast/FastBridgeRouterV2.json create mode 100644 deployments/bsc/FastBridgeRouterV2.json create mode 100644 deployments/linea/FastBridgeRouterV2.json create mode 100644 deployments/mainnet/FastBridgeRouterV2.json create mode 100644 deployments/optimism/FastBridgeRouterV2.json create mode 100644 deployments/scroll/FastBridgeRouterV2.json create mode 100644 script/rfq/BrickFastBridgeRouter.s.sol rename script/rfq/{ConfigureFastBridgeRouter.t.sol => ConfigureFastBridgeRouterV2.s.sol} (61%) create mode 100644 script/rfq/DeployFastBridgeRouterV2.s.sol create mode 100755 script/rfq/rfq-cmd.sh create mode 100755 script/rfq/rfq-run.sh create mode 100644 test/mocks/MockSenderContract.sol create mode 100644 test/rfq/FastBridgeRouterV2.Native.QuoterV1.t.sol create mode 100644 test/rfq/FastBridgeRouterV2.Native.QuoterV2.t.sol create mode 100644 test/rfq/FastBridgeRouterV2.Native.t.sol create mode 100644 test/rfq/FastBridgeRouterV2.QuoterV1.t.sol create mode 100644 test/rfq/FastBridgeRouterV2.QuoterV2.t.sol create mode 100644 test/rfq/FastBridgeRouterV2.t.sol diff --git a/contracts/rfq/FastBridgeRouterV2.sol b/contracts/rfq/FastBridgeRouterV2.sol new file mode 100644 index 000000000..48ee0ecd0 --- /dev/null +++ b/contracts/rfq/FastBridgeRouterV2.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {DefaultRouter, DeadlineExceeded, InsufficientOutputAmount} from "../router/DefaultRouter.sol"; +import {UniversalTokenLib} from "../router/libs/UniversalToken.sol"; +import {ActionLib, LimitedToken} from "../router/libs/Structs.sol"; +import {IFastBridge} from "./interfaces/IFastBridge.sol"; +import {IFastBridgeRouter, SwapQuery} from "./interfaces/IFastBridgeRouter.sol"; +import {ISwapQuoter} from "./interfaces/ISwapQuoter.sol"; + +import {Ownable} from "@openzeppelin/contracts-4.5.0/access/Ownable.sol"; + +contract FastBridgeRouterV2 is DefaultRouter, Ownable, IFastBridgeRouter { + using UniversalTokenLib for address; + + error FastBridgeRouterV2__OriginSenderNotSpecified(); + + /// @notice Emitted when the swap quoter is set. + /// @param newSwapQuoter The new swap quoter. + event SwapQuoterSet(address newSwapQuoter); + + /// @notice Emitted when the new FastBridge contract is set. + /// @param newFastBridge The new FastBridge contract. + event FastBridgeSet(address newFastBridge); + + /// @inheritdoc IFastBridgeRouter + bytes1 public constant GAS_REBATE_FLAG = 0x2A; + + /// @inheritdoc IFastBridgeRouter + address public fastBridge; + /// @inheritdoc IFastBridgeRouter + address public swapQuoter; + + constructor(address owner_) { + transferOwnership(owner_); + } + + /// @inheritdoc IFastBridgeRouter + function setFastBridge(address fastBridge_) external onlyOwner { + fastBridge = fastBridge_; + emit FastBridgeSet(fastBridge_); + } + + /// @inheritdoc IFastBridgeRouter + function setSwapQuoter(address swapQuoter_) external onlyOwner { + swapQuoter = swapQuoter_; + emit SwapQuoterSet(swapQuoter_); + } + + /// @inheritdoc IFastBridgeRouter + function bridge( + address recipient, + uint256 chainId, + address token, + uint256 amount, + SwapQuery memory originQuery, + SwapQuery memory destQuery + ) external payable { + address originSender = _getOriginSender(destQuery.rawParams); + if (originSender == address(0)) { + revert FastBridgeRouterV2__OriginSenderNotSpecified(); + } + if (originQuery.hasAdapter()) { + // Perform a swap using the swap adapter, set this contract as recipient + (token, amount) = _doSwap(address(this), token, amount, originQuery); + } else { + // Otherwise, pull the token from the user to this contract + // We still need to perform the deadline and amountOut checks + // solhint-disable-next-line not-rely-on-time + if (block.timestamp > originQuery.deadline) { + revert DeadlineExceeded(); + } + if (amount < originQuery.minAmountOut) { + revert InsufficientOutputAmount(); + } + amount = _pullToken(address(this), token, amount); + } + IFastBridge.BridgeParams memory params = IFastBridge.BridgeParams({ + dstChainId: uint32(chainId), + sender: originSender, + to: recipient, + originToken: token, + destToken: destQuery.tokenOut, + originAmount: amount, + destAmount: destQuery.minAmountOut, + sendChainGas: _chainGasRequested(destQuery.rawParams), + deadline: destQuery.deadline + }); + token.universalApproveInfinity(fastBridge, amount); + uint256 msgValue = token == UniversalTokenLib.ETH_ADDRESS ? amount : 0; + IFastBridge(fastBridge).bridge{value: msgValue}(params); + } + + /// @inheritdoc IFastBridgeRouter + function getOriginAmountOut( + address tokenIn, + address[] memory rfqTokens, + uint256 amountIn + ) external view returns (SwapQuery[] memory originQueries) { + uint256 len = rfqTokens.length; + originQueries = new SwapQuery[](len); + for (uint256 i = 0; i < len; ++i) { + originQueries[i] = ISwapQuoter(swapQuoter).getAmountOut( + LimitedToken({actionMask: ActionLib.allActions(), token: tokenIn}), + rfqTokens[i], + amountIn + ); + // Adjust the Adapter address if it exists + if (originQueries[i].hasAdapter()) { + originQueries[i].routerAdapter = address(this); + } + } + } + + /// @dev Retrieves the origin sender from the raw params. + /// Note: falls back to msg.sender if origin sender is not specified in the raw params, but + /// msg.sender is an EOA. + function _getOriginSender(bytes memory rawParams) internal view returns (address originSender) { + // Origin sender (if present) is encoded as 20 bytes following the rebate flag + if (rawParams.length >= 21) { + // The easiest way to read from memory is to use assembly + // solhint-disable-next-line no-inline-assembly + assembly { + // Skip the rawParams.length (32 bytes) and the rebate flag (1 byte) + originSender := mload(add(rawParams, 33)) + // The address is in the highest 160 bits. Shift right by 96 to get it in the lowest 160 bits + originSender := shr(96, originSender) + } + } + if (originSender == address(0) && msg.sender.code.length == 0) { + // Fall back to msg.sender if it is an EOA. This maintains backward compatibility + // for cases where we can safely assume that the origin sender is the same as msg.sender. + originSender = msg.sender; + } + } + + /// @dev Checks if the explicit instruction to send gas to the destination chain was provided. + function _chainGasRequested(bytes memory rawParams) internal pure returns (bool) { + return rawParams.length > 0 && rawParams[0] == GAS_REBATE_FLAG; + } +} diff --git a/deployments/arbitrum/FastBridgeRouterV2.json b/deployments/arbitrum/FastBridgeRouterV2.json new file mode 100644 index 000000000..6c040224c --- /dev/null +++ b/deployments/arbitrum/FastBridgeRouterV2.json @@ -0,0 +1,396 @@ +{ + "address": "0x00cD000000003f7F682BE4813200893d4e690000", + "constructorArgs": "0x0000000000000000000000000fea3e5840334fc758a3decf14546bfdfbef5cd3", + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "GAS_REBATE_FLAG", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes1", + "internalType": "bytes1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "adapterSwap", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "destQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "fastBridge", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getOriginAmountOut", + "inputs": [ + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "rfqTokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "originQueries", + "type": "tuple[]", + "internalType": "struct SwapQuery[]", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastBridge", + "inputs": [ + { + "name": "fastBridge_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSwapQuoter", + "inputs": [ + { + "name": "swapQuoter_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapQuoter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FastBridgeSet", + "inputs": [ + { + "name": "newFastBridge", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapQuoterSet", + "inputs": [ + { + "name": "newSwapQuoter", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "FastBridgeRouterV2__OriginSenderNotSpecified", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MsgValueIncorrect", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "TokenAddressMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotContract", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotETH", + "inputs": [] + }, + { + "type": "error", + "name": "TokensIdentical", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/deployments/base/FastBridgeRouterV2.json b/deployments/base/FastBridgeRouterV2.json new file mode 100644 index 000000000..6c040224c --- /dev/null +++ b/deployments/base/FastBridgeRouterV2.json @@ -0,0 +1,396 @@ +{ + "address": "0x00cD000000003f7F682BE4813200893d4e690000", + "constructorArgs": "0x0000000000000000000000000fea3e5840334fc758a3decf14546bfdfbef5cd3", + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "GAS_REBATE_FLAG", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes1", + "internalType": "bytes1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "adapterSwap", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "destQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "fastBridge", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getOriginAmountOut", + "inputs": [ + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "rfqTokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "originQueries", + "type": "tuple[]", + "internalType": "struct SwapQuery[]", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastBridge", + "inputs": [ + { + "name": "fastBridge_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSwapQuoter", + "inputs": [ + { + "name": "swapQuoter_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapQuoter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FastBridgeSet", + "inputs": [ + { + "name": "newFastBridge", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapQuoterSet", + "inputs": [ + { + "name": "newSwapQuoter", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "FastBridgeRouterV2__OriginSenderNotSpecified", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MsgValueIncorrect", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "TokenAddressMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotContract", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotETH", + "inputs": [] + }, + { + "type": "error", + "name": "TokensIdentical", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/deployments/blast/FastBridgeRouterV2.json b/deployments/blast/FastBridgeRouterV2.json new file mode 100644 index 000000000..6c040224c --- /dev/null +++ b/deployments/blast/FastBridgeRouterV2.json @@ -0,0 +1,396 @@ +{ + "address": "0x00cD000000003f7F682BE4813200893d4e690000", + "constructorArgs": "0x0000000000000000000000000fea3e5840334fc758a3decf14546bfdfbef5cd3", + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "GAS_REBATE_FLAG", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes1", + "internalType": "bytes1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "adapterSwap", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "destQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "fastBridge", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getOriginAmountOut", + "inputs": [ + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "rfqTokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "originQueries", + "type": "tuple[]", + "internalType": "struct SwapQuery[]", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastBridge", + "inputs": [ + { + "name": "fastBridge_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSwapQuoter", + "inputs": [ + { + "name": "swapQuoter_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapQuoter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FastBridgeSet", + "inputs": [ + { + "name": "newFastBridge", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapQuoterSet", + "inputs": [ + { + "name": "newSwapQuoter", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "FastBridgeRouterV2__OriginSenderNotSpecified", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MsgValueIncorrect", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "TokenAddressMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotContract", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotETH", + "inputs": [] + }, + { + "type": "error", + "name": "TokensIdentical", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/deployments/bsc/FastBridgeRouterV2.json b/deployments/bsc/FastBridgeRouterV2.json new file mode 100644 index 000000000..6c040224c --- /dev/null +++ b/deployments/bsc/FastBridgeRouterV2.json @@ -0,0 +1,396 @@ +{ + "address": "0x00cD000000003f7F682BE4813200893d4e690000", + "constructorArgs": "0x0000000000000000000000000fea3e5840334fc758a3decf14546bfdfbef5cd3", + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "GAS_REBATE_FLAG", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes1", + "internalType": "bytes1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "adapterSwap", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "destQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "fastBridge", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getOriginAmountOut", + "inputs": [ + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "rfqTokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "originQueries", + "type": "tuple[]", + "internalType": "struct SwapQuery[]", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastBridge", + "inputs": [ + { + "name": "fastBridge_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSwapQuoter", + "inputs": [ + { + "name": "swapQuoter_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapQuoter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FastBridgeSet", + "inputs": [ + { + "name": "newFastBridge", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapQuoterSet", + "inputs": [ + { + "name": "newSwapQuoter", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "FastBridgeRouterV2__OriginSenderNotSpecified", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MsgValueIncorrect", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "TokenAddressMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotContract", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotETH", + "inputs": [] + }, + { + "type": "error", + "name": "TokensIdentical", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/deployments/linea/FastBridgeRouterV2.json b/deployments/linea/FastBridgeRouterV2.json new file mode 100644 index 000000000..6c040224c --- /dev/null +++ b/deployments/linea/FastBridgeRouterV2.json @@ -0,0 +1,396 @@ +{ + "address": "0x00cD000000003f7F682BE4813200893d4e690000", + "constructorArgs": "0x0000000000000000000000000fea3e5840334fc758a3decf14546bfdfbef5cd3", + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "GAS_REBATE_FLAG", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes1", + "internalType": "bytes1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "adapterSwap", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "destQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "fastBridge", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getOriginAmountOut", + "inputs": [ + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "rfqTokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "originQueries", + "type": "tuple[]", + "internalType": "struct SwapQuery[]", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastBridge", + "inputs": [ + { + "name": "fastBridge_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSwapQuoter", + "inputs": [ + { + "name": "swapQuoter_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapQuoter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FastBridgeSet", + "inputs": [ + { + "name": "newFastBridge", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapQuoterSet", + "inputs": [ + { + "name": "newSwapQuoter", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "FastBridgeRouterV2__OriginSenderNotSpecified", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MsgValueIncorrect", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "TokenAddressMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotContract", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotETH", + "inputs": [] + }, + { + "type": "error", + "name": "TokensIdentical", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/deployments/mainnet/FastBridgeRouterV2.json b/deployments/mainnet/FastBridgeRouterV2.json new file mode 100644 index 000000000..6c040224c --- /dev/null +++ b/deployments/mainnet/FastBridgeRouterV2.json @@ -0,0 +1,396 @@ +{ + "address": "0x00cD000000003f7F682BE4813200893d4e690000", + "constructorArgs": "0x0000000000000000000000000fea3e5840334fc758a3decf14546bfdfbef5cd3", + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "GAS_REBATE_FLAG", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes1", + "internalType": "bytes1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "adapterSwap", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "destQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "fastBridge", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getOriginAmountOut", + "inputs": [ + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "rfqTokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "originQueries", + "type": "tuple[]", + "internalType": "struct SwapQuery[]", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastBridge", + "inputs": [ + { + "name": "fastBridge_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSwapQuoter", + "inputs": [ + { + "name": "swapQuoter_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapQuoter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FastBridgeSet", + "inputs": [ + { + "name": "newFastBridge", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapQuoterSet", + "inputs": [ + { + "name": "newSwapQuoter", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "FastBridgeRouterV2__OriginSenderNotSpecified", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MsgValueIncorrect", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "TokenAddressMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotContract", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotETH", + "inputs": [] + }, + { + "type": "error", + "name": "TokensIdentical", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/deployments/optimism/FastBridgeRouterV2.json b/deployments/optimism/FastBridgeRouterV2.json new file mode 100644 index 000000000..6c040224c --- /dev/null +++ b/deployments/optimism/FastBridgeRouterV2.json @@ -0,0 +1,396 @@ +{ + "address": "0x00cD000000003f7F682BE4813200893d4e690000", + "constructorArgs": "0x0000000000000000000000000fea3e5840334fc758a3decf14546bfdfbef5cd3", + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "GAS_REBATE_FLAG", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes1", + "internalType": "bytes1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "adapterSwap", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "destQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "fastBridge", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getOriginAmountOut", + "inputs": [ + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "rfqTokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "originQueries", + "type": "tuple[]", + "internalType": "struct SwapQuery[]", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastBridge", + "inputs": [ + { + "name": "fastBridge_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSwapQuoter", + "inputs": [ + { + "name": "swapQuoter_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapQuoter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FastBridgeSet", + "inputs": [ + { + "name": "newFastBridge", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapQuoterSet", + "inputs": [ + { + "name": "newSwapQuoter", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "FastBridgeRouterV2__OriginSenderNotSpecified", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MsgValueIncorrect", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "TokenAddressMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotContract", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotETH", + "inputs": [] + }, + { + "type": "error", + "name": "TokensIdentical", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/deployments/scroll/FastBridgeRouterV2.json b/deployments/scroll/FastBridgeRouterV2.json new file mode 100644 index 000000000..6c040224c --- /dev/null +++ b/deployments/scroll/FastBridgeRouterV2.json @@ -0,0 +1,396 @@ +{ + "address": "0x00cD000000003f7F682BE4813200893d4e690000", + "constructorArgs": "0x0000000000000000000000000fea3e5840334fc758a3decf14546bfdfbef5cd3", + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "GAS_REBATE_FLAG", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes1", + "internalType": "bytes1" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "adapterSwap", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "destQuery", + "type": "tuple", + "internalType": "struct SwapQuery", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "fastBridge", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getOriginAmountOut", + "inputs": [ + { + "name": "tokenIn", + "type": "address", + "internalType": "address" + }, + { + "name": "rfqTokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "originQueries", + "type": "tuple[]", + "internalType": "struct SwapQuery[]", + "components": [ + { + "name": "routerAdapter", + "type": "address", + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "internalType": "address" + }, + { + "name": "minAmountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rawParams", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastBridge", + "inputs": [ + { + "name": "fastBridge_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setSwapQuoter", + "inputs": [ + { + "name": "swapQuoter_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapQuoter", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FastBridgeSet", + "inputs": [ + { + "name": "newFastBridge", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapQuoterSet", + "inputs": [ + { + "name": "newSwapQuoter", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "DeadlineExceeded", + "inputs": [] + }, + { + "type": "error", + "name": "FastBridgeRouterV2__OriginSenderNotSpecified", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MsgValueIncorrect", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "TokenAddressMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotContract", + "inputs": [] + }, + { + "type": "error", + "name": "TokenNotETH", + "inputs": [] + }, + { + "type": "error", + "name": "TokensIdentical", + "inputs": [] + } + ] +} \ No newline at end of file diff --git a/script/configs/Create2Factory.salts.json b/script/configs/Create2Factory.salts.json index 6a67b0080..74afec273 100644 --- a/script/configs/Create2Factory.salts.json +++ b/script/configs/Create2Factory.salts.json @@ -9,6 +9,11 @@ "predictedAddress": "0x0000000000489d89D2B233D3375C045dfD05745F", "salt": "0x0000000000000000000000000000000000000000a887a859fa44720198162f0e" }, + "FastBridgeRouterV2": { + "initCodeHash": "0x27f5c13b6e4bed194d26ed65fa860d6af815eff7e634fee55ac9c039ce5105b7", + "predictedAddress": "0x00cD000000003f7F682BE4813200893d4e690000", + "salt": "0x0000000000000000000000000000000000000000d03957700745a831060da626" + }, "SynapseRouter": { "initCodeHash": "0x4e3652671a68c0263a6844cbfabcab93d1a951bdc4b7983183b99f74308383e0", "predictedAddress": "0x0000000000365b1d5B142732CF4d33BcddED21Fc", diff --git a/script/networks.json b/script/networks.json index 86d15c107..b4c207f48 100644 --- a/script/networks.json +++ b/script/networks.json @@ -1,9 +1,6 @@ { "forgeOptions": { - "arbitrum": "--with-gas-price 100000000", "aurora": "--legacy --slow", - "base": "--with-gas-price 100000000", - "blast": "--with-gas-price 100000000", "boba": "--legacy --skip-simulation --slow", "canto": "--legacy --slow", "cronos": "--slow", @@ -14,7 +11,6 @@ "metis": "--legacy", "moonbeam": "--skip-simulation --slow", "moonriver": "--skip-simulation --slow", - "optimism": "--with-gas-price 1000000", "polygon": "--slow" } } diff --git a/script/rfq/BrickFastBridgeRouter.s.sol b/script/rfq/BrickFastBridgeRouter.s.sol new file mode 100644 index 000000000..19ea7d7b0 --- /dev/null +++ b/script/rfq/BrickFastBridgeRouter.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {FastBridgeRouter} from "../../contracts/rfq/FastBridgeRouter.sol"; + +import {BasicSynapseScript} from "../templates/BasicSynapse.s.sol"; + +contract BrickFastBridgeRouter is BasicSynapseScript { + FastBridgeRouter public router; + + function setUp() internal override { + super.setUp(); + address payable routerDeployment = payable(getDeploymentAddress("FastBridgeRouter")); + router = FastBridgeRouter(routerDeployment); + } + + function run() external { + // Setup the BasicSynapseScript + setUp(); + vm.startBroadcast(); + brickFastBridgeRouter(); + vm.stopBroadcast(); + } + + function brickFastBridgeRouter() internal { + if (router.fastBridge() != address(0)) { + router.setFastBridge(address(0)); + printLog(string.concat(unicode"✅ Fast bridge set to zero")); + } else { + printLog(string.concat(unicode"🟡 Skipping: Fast bridge is already set to zero")); + } + } +} diff --git a/script/rfq/ConfigureFastBridgeRouter.t.sol b/script/rfq/ConfigureFastBridgeRouterV2.s.sol similarity index 61% rename from script/rfq/ConfigureFastBridgeRouter.t.sol rename to script/rfq/ConfigureFastBridgeRouterV2.s.sol index 75347e8d8..e20d1b1d5 100644 --- a/script/rfq/ConfigureFastBridgeRouter.t.sol +++ b/script/rfq/ConfigureFastBridgeRouterV2.s.sol @@ -1,40 +1,38 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {FastBridgeRouter} from "../../contracts/rfq/FastBridgeRouter.sol"; +import {FastBridgeRouterV2} from "../../contracts/rfq/FastBridgeRouterV2.sol"; import {console2, BasicSynapseScript} from "../templates/BasicSynapse.s.sol"; -contract ConfigureFastBridgeRouter is BasicSynapseScript { - string public constant FAST_BRIDGE_ROUTER = "FastBridgeRouter"; - - FastBridgeRouter public fastBridgeRouter; +contract ConfigureFastBridgeRouterV2 is BasicSynapseScript { + FastBridgeRouterV2 public router; function setUp() internal override { super.setUp(); - address payable routerDeployment = payable(getDeploymentAddress(FAST_BRIDGE_ROUTER)); - fastBridgeRouter = FastBridgeRouter(routerDeployment); + address payable routerDeployment = payable(getDeploymentAddress("FastBridgeRouterV2")); + router = FastBridgeRouterV2(routerDeployment); } function run() external { // Setup the BasicSynapseScript setUp(); vm.startBroadcast(); - configureFastBridgeRouter(); + configureFastBridgeRouterV2(); vm.stopBroadcast(); } - function configureFastBridgeRouter() internal { + function configureFastBridgeRouterV2() internal { address fastBridge = getDeploymentAddress("FastBridge"); - if (fastBridgeRouter.fastBridge() != fastBridge) { - fastBridgeRouter.setFastBridge(fastBridge); + if (router.fastBridge() != fastBridge) { + router.setFastBridge(fastBridge); printLog(string.concat(unicode"✅ Fast bridge set to ", vm.toString(fastBridge))); } else { printLog(string.concat(unicode"🟡 Skipping: Fast bridge is already set to ", vm.toString(fastBridge))); } address swapQuoter = getDeploymentAddress("SwapQuoterV2"); - if (fastBridgeRouter.swapQuoter() != swapQuoter) { - fastBridgeRouter.setSwapQuoter(swapQuoter); + if (router.swapQuoter() != swapQuoter) { + router.setSwapQuoter(swapQuoter); printLog(string.concat(unicode"✅ SwapQuoter set to ", vm.toString(swapQuoter))); } else { printLog(string.concat(unicode"🟡 Skipping: SwapQuoter is already set to ", vm.toString(swapQuoter))); diff --git a/script/rfq/DeployFastBridgeRouterV2.s.sol b/script/rfq/DeployFastBridgeRouterV2.s.sol new file mode 100644 index 000000000..201d8af1a --- /dev/null +++ b/script/rfq/DeployFastBridgeRouterV2.s.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {BasicSynapseScript} from "../templates/BasicSynapse.s.sol"; + +contract DeployFastBridgeRouter is BasicSynapseScript { + function run() external { + // Setup the BasicSynapseScript + setUp(); + vm.startBroadcast(); + // Use `deployCreate2` as callback to deploy the contract with CREATE2 + // This will load deployment salt from the pre-saved list, if there's an entry for the contract + deployAndSave({ + contractName: "FastBridgeRouterV2", + constructorArgs: abi.encode(msg.sender), + deployCode: deployCreate2 + }); + vm.stopBroadcast(); + } +} diff --git a/script/rfq/rfq-cmd.sh b/script/rfq/rfq-cmd.sh new file mode 100755 index 000000000..46c9a7d80 --- /dev/null +++ b/script/rfq/rfq-cmd.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# This script runs an RFQ script for all chains with FastBridge deployment +# Usage: ./script/rfq-cmd.sh "" +# - chain will be run for all RFQ chains + +# Colors +RED="\033[0;31m" +NC="\033[0m" # No Color + +command=$1 +# Get the rest of the args +shift 1 +# Check that all required args exist +if [ -z "$command" ]; then + echo -e "${RED}Usage: ./script/rfq-cmd.sh ${NC}" + exit 1 +fi + +# Find all FastBridge.json files in ./deployments +fastBridgeDeployments=$(find ./deployments -name FastBridge.json) +# Extract chain name from the list of filenames, sort alphabetically +chainNames=$(echo "$fastBridgeDeployments" | sed 's/.*\/\(.*\)\/FastBridge.json/\1/' | sort) +# Print the comma separated list of chain aliases, don't put a comma after the last one +chainNamesFormatted=$(echo "$chainNames" | sed ':a;N;$!ba;s/\n/, /g') +echo "Running $command for chains: [$chainNamesFormatted]" + +for chainName in $chainNames; do + $command "$chainName" "$@" +done diff --git a/script/rfq/rfq-run.sh b/script/rfq/rfq-run.sh new file mode 100755 index 000000000..d52ba403f --- /dev/null +++ b/script/rfq/rfq-run.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# This script runs an RFQ script for all chains with FastBridge deployment +# Usage: ./script/rfq-run.sh pathToScript +# - ./script/run.sh pathToScript chain will be run for all RFQ chains + +# Colors +RED="\033[0;31m" +NC="\033[0m" # No Color + +scriptFN=$1 +# Get the rest of the args +shift 1 +# Check that all required args exist +if [ -z "$scriptFN" ]; then + echo -e "${RED}Usage: ./script/rfq-run.sh pathToScript ${NC}" + exit 1 +fi + +./script/rfq/rfq-cmd.sh "./script/run.sh $scriptFN" "$@" diff --git a/test/mocks/MockSenderContract.sol b/test/mocks/MockSenderContract.sol new file mode 100644 index 000000000..d97efcdf0 --- /dev/null +++ b/test/mocks/MockSenderContract.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Address} from "@openzeppelin/contracts-4.5.0/utils/Address.sol"; + +contract MockSenderContract { + function doCall(address target, bytes memory data) external payable { + Address.functionCallWithValue(target, data, msg.value); + } +} diff --git a/test/rfq/FBRTest.sol b/test/rfq/FBRTest.sol index a2843c985..686fd66ac 100644 --- a/test/rfq/FBRTest.sol +++ b/test/rfq/FBRTest.sol @@ -10,10 +10,11 @@ import {MockDefaultPool} from "../mocks/MockDefaultPool.sol"; import {Test} from "forge-std/Test.sol"; abstract contract FBRTest is Test { - uint256 constant RFQ_DEADLINE = 12 hours; - address constant TOKEN_OUT = address(1337); - uint256 constant FIXED_FEE = 0.01 ether; - uint32 constant DST_CHAIN_ID = 420; + uint8 public constant REBATE_FLAG = 42; + uint256 public constant RFQ_DEADLINE = 12 hours; + address public constant TOKEN_OUT = address(1337); + uint256 public constant FIXED_FEE = 0.01 ether; + uint32 public constant DST_CHAIN_ID = 420; FastBridgeRouter public router; MockFastBridge public fastBridge; @@ -29,11 +30,15 @@ abstract contract FBRTest is Test { recipient = makeAddr("Recipient"); user = makeAddr("User"); fastBridge = new MockFastBridge(); - router = new FastBridgeRouter(owner); + router = FastBridgeRouter(deployRouter()); vm.prank(owner); router.setFastBridge(address(fastBridge)); } + function deployRouter() public virtual returns (address payable) { + return payable(new FastBridgeRouter(owner)); + } + function test_constructor() public { assertEq(address(router.fastBridge()), address(fastBridge)); assertEq(router.owner(), owner); @@ -75,7 +80,7 @@ abstract contract FBRTest is Test { tokenOut: TOKEN_OUT, minAmountOut: amount - FIXED_FEE, deadline: block.timestamp + RFQ_DEADLINE, - rawParams: abi.encodePacked(uint8(42)) + rawParams: abi.encodePacked(REBATE_FLAG) }); } diff --git a/test/rfq/FastBridgeRouter.Native.t.sol b/test/rfq/FastBridgeRouter.Native.t.sol index f0f71b7b6..7688d982e 100644 --- a/test/rfq/FastBridgeRouter.Native.t.sol +++ b/test/rfq/FastBridgeRouter.Native.t.sol @@ -16,7 +16,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { MockERC20 public token; MockWETH public weth; - function setUp() public override { + function setUp() public virtual override { super.setUp(); token = new MockERC20("TKN", 18); weth = new MockWETH(); @@ -28,34 +28,70 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Mint some tokens to the pool token.mint(address(pool), 100 ether); weth.mint(address(pool), 120 ether); - // Mint some tokens to the user - token.mint(user, 10 ether); - weth.mint(user, 10 ether); - deal(user, 10 ether); - // Approve the Router to spend the user's tokens - vm.prank(user); + prepareAccount(user); + setUpSwapQuoter(); + } + + function prepareAccount(address account) public { + token.mint(account, 10 ether); + weth.mint(account, 10 ether); + deal(account, 10 ether); + vm.prank(account); token.approve(address(router), 10 ether); - vm.prank(user); + vm.prank(account); weth.approve(address(router), 10 ether); - - setUpSwapQuoter(); } function setUpSwapQuoter() internal virtual; - // ═══════════════════════════════════════════ TESTS: START FROM ETH ═══════════════════════════════════════════════ - - // Start from ETH, use ETH for RFQ - function test_bridge_eth_noOriginSwap_noGasRebate() public { - uint256 amount = 1 ether; - // No swap on origin chain - SwapQuery memory originQuery = SwapQuery({ + function getOriginQueryNoSwap(address tokenOut, uint256 amount) + internal + view + returns (SwapQuery memory originQuery) + { + originQuery = SwapQuery({ routerAdapter: address(0), - tokenOut: ETH, + tokenOut: tokenOut, minAmountOut: amount, deadline: block.timestamp, rawParams: "" }); + } + + function getOriginQueryWithSwap(address tokenOut, uint256 amount) + internal + view + returns (SwapQuery memory originQuery) + { + originQuery = SwapQuery({ + routerAdapter: address(router), + tokenOut: tokenOut, + minAmountOut: amount, + deadline: block.timestamp, + rawParams: tokenOut == address(token) ? getOriginSwapParams(1, 0) : getOriginSwapParams(0, 1) + }); + } + + function getOriginQueryWithHandleETH(address tokenOut, uint256 amount) + internal + view + returns (SwapQuery memory originQuery) + { + originQuery = SwapQuery({ + routerAdapter: address(router), + tokenOut: tokenOut, + minAmountOut: amount, + deadline: block.timestamp, + rawParams: getOriginHandleETHParams() + }); + } + + // ═══════════════════════════════════════════ TESTS: START FROM ETH ═══════════════════════════════════════════════ + + // Start from ETH, use ETH for RFQ + function test_bridge_eth_noOriginSwap_noGasRebate() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(ETH, amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: ETH, originAmount: amount, @@ -80,14 +116,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from ETH, use ETH for RFQ (with gas rebate) function test_bridge_eth_noOriginSwap_withGasRebate() public { uint256 amount = 1 ether; - // No swap on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: ETH, - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(ETH, amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: ETH, originAmount: amount, @@ -112,14 +141,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from ETH, use WETH for RFQ function test_bridge_eth_withOriginWrap_noGasRebate() public { uint256 amount = 1 ether; - // Wrap ETH on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(weth), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginHandleETHParams() - }); + SwapQuery memory originQuery = getOriginQueryWithHandleETH(address(weth), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(weth), originAmount: amount, @@ -144,14 +166,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from ETH, use WETH for RFQ (with gas rebate) function test_bridge_eth_withOriginWrap_withGasRebate() public { uint256 amount = 1 ether; - // Wrap ETH on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(weth), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginHandleETHParams() - }); + SwapQuery memory originQuery = getOriginQueryWithHandleETH(address(weth), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(weth), originAmount: amount, @@ -177,14 +192,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_eth_withOriginSwap_noGasRebate() public { uint256 amountBeforeSwap = 1 ether; uint256 amount = pool.calculateSwap(1, 0, amountBeforeSwap); - // Swap ETH on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(token), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(1, 0) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(token), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token), originAmount: amount, @@ -210,14 +218,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_eth_withOriginSwap_withGasRebate() public { uint256 amountBeforeSwap = 1 ether; uint256 amount = pool.calculateSwap(1, 0, amountBeforeSwap); - // Swap ETH on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(token), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(1, 0) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(token), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token), originAmount: amount, @@ -244,14 +245,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from WETH, use WETH for RFQ function test_bridge_weth_noOriginSwap_noGasRebate() public { uint256 amount = 1 ether; - // No swap on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: address(weth), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(address(weth), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(weth), originAmount: amount, @@ -276,14 +270,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from WETH, use WETH for RFQ (with gas rebate) function test_bridge_weth_noOriginSwap_withGasRebate() public { uint256 amount = 1 ether; - // No swap on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: address(weth), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(address(weth), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(weth), originAmount: amount, @@ -308,14 +295,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from WETH, use ETH for RFQ function test_bridge_weth_withOriginUnwrap_noGasRebate() public { uint256 amount = 1 ether; - // Unwrap WETH on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: ETH, - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginHandleETHParams() - }); + SwapQuery memory originQuery = getOriginQueryWithHandleETH(ETH, amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: ETH, originAmount: amount, @@ -340,14 +320,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from WETH, use ETH for RFQ (with gas rebate) function test_bridge_weth_withOriginUnwrap_withGasRebate() public { uint256 amount = 1 ether; - // Unwrap WETH on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: ETH, - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginHandleETHParams() - }); + SwapQuery memory originQuery = getOriginQueryWithHandleETH(ETH, amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: ETH, originAmount: amount, @@ -373,14 +346,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_weth_withOriginSwap_noGasRebate() public { uint256 amountBeforeSwap = 1 ether; uint256 amount = pool.calculateSwap(1, 0, amountBeforeSwap); - // Swap WETH on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(token), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(1, 0) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(token), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token), originAmount: amount, @@ -406,14 +372,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_weth_withOriginSwap_withGasRebate() public { uint256 amountBeforeSwap = 1 ether; uint256 amount = pool.calculateSwap(1, 0, amountBeforeSwap); - // Swap WETH on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(token), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(1, 0) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(token), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token), originAmount: amount, @@ -440,14 +399,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from paired token, use paired token for RFQ function test_bridge_token_noOriginSwap_noGasRebate() public { uint256 amount = 1 ether; - // No swap on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: address(token), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(address(token), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token), originAmount: amount, @@ -472,14 +424,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { // Start from paired token, use paired token for RFQ (with gas rebate) function test_bridge_token_noOriginSwap_withGasRebate() public { uint256 amount = 1 ether; - // No swap on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: address(token), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(address(token), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token), originAmount: amount, @@ -505,14 +450,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_token_withOriginSwap_noGasRebate() public { uint256 amountBeforeSwap = 1 ether; uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); - // Swap token on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(weth), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(0, 1) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(weth), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(weth), originAmount: amount, @@ -538,14 +476,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_token_withOriginSwap_withGasRebate() public { uint256 amountBeforeSwap = 1 ether; uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); - // Swap token on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(weth), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(0, 1) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(weth), amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(weth), originAmount: amount, @@ -571,14 +502,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_token_withOriginSwapUnwrap_noGasRebate() public { uint256 amountBeforeSwap = 1 ether; uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); - // Swap & unwrap token on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: ETH, - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(0, 1) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(ETH, amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: ETH, originAmount: amount, @@ -604,14 +528,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_token_withOriginSwapUnwrap_withGasRebate() public { uint256 amountBeforeSwap = 1 ether; uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); - // Swap & unwrap token on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: ETH, - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(0, 1) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(ETH, amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: ETH, originAmount: amount, @@ -637,13 +554,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_revert_msgValueWithERC20() public { uint256 amount = 1 ether; - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: address(token), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(address(token), amount); vm.expectRevert(TokenNotETH.selector); vm.prank(user); router.bridge{value: 1 ether}({ @@ -658,13 +569,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_revert_msgValueZeroWithETH() public { uint256 amount = 1 ether; - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: ETH, - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(ETH, amount); vm.expectRevert(TokenNotContract.selector); vm.prank(user); router.bridge({ @@ -679,13 +584,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_revert_msgValueLowerWithETH() public { uint256 amount = 1 ether; - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: ETH, - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(ETH, amount); vm.expectRevert(MsgValueIncorrect.selector); vm.prank(user); router.bridge{value: amount - 1}({ @@ -700,13 +599,7 @@ abstract contract FastBridgeRouterNativeTest is FBRTest { function test_bridge_revert_msgValueHigherWithETH() public { uint256 amount = 1 ether; - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: ETH, - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(ETH, amount); vm.expectRevert(MsgValueIncorrect.selector); vm.prank(user); router.bridge{value: amount + 1}({ diff --git a/test/rfq/FastBridgeRouter.QuoterV1.t.sol b/test/rfq/FastBridgeRouter.QuoterV1.t.sol index b8cdc8330..9f6c5ef87 100644 --- a/test/rfq/FastBridgeRouter.QuoterV1.t.sol +++ b/test/rfq/FastBridgeRouter.QuoterV1.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.17; import {FastBridgeRouterTest} from "./FastBridgeRouter.t.sol"; import {ISwapQuoterV1} from "../interfaces/ISwapQuoterV1.sol"; -contract FastBridgeRouterNativeTest is FastBridgeRouterTest { +contract FastBridgeRouterQuoterV1Test is FastBridgeRouterTest { function setUpSwapQuoter() internal override { // SwapQuoter V1 is solidity 0.6.12, so we use the cheatcode to deploy it // new SwapQuoter(synapseRouter, weth, owner) diff --git a/test/rfq/FastBridgeRouter.t.sol b/test/rfq/FastBridgeRouter.t.sol index 1351fa3d2..843e52639 100644 --- a/test/rfq/FastBridgeRouter.t.sol +++ b/test/rfq/FastBridgeRouter.t.sol @@ -16,7 +16,7 @@ abstract contract FastBridgeRouterTest is FBRTest { event FastBridgeSet(address newFastBridge); event SwapQuoterSet(address newSwapQuoter); - function setUp() public override { + function setUp() public virtual override { super.setUp(); token0 = new MockERC20("T0", 18); token1 = new MockERC20("T1", 18); @@ -28,20 +28,41 @@ abstract contract FastBridgeRouterTest is FBRTest { // Mint some tokens to the pool token0.mint(address(pool), 100 ether); token1.mint(address(pool), 120 ether); - // Mint some tokens to the user - token0.mint(user, 10 ether); - token1.mint(user, 10 ether); - // Approve the Router to spend the user's tokens - vm.prank(user); + prepareAccount(user); + setUpSwapQuoter(); + } + + function prepareAccount(address account) public { + token0.mint(account, 10 ether); + token1.mint(account, 10 ether); + vm.prank(account); token0.approve(address(router), 10 ether); - vm.prank(user); + vm.prank(account); token1.approve(address(router), 10 ether); - - setUpSwapQuoter(); } function setUpSwapQuoter() internal virtual; + function getOriginQueryNoSwap(uint256 amount) public view returns (SwapQuery memory originQuery) { + originQuery = SwapQuery({ + routerAdapter: address(0), + tokenOut: address(token0), + minAmountOut: amount, + deadline: block.timestamp, + rawParams: "" + }); + } + + function getOriginQueryWithSwap(uint256 amount) public view returns (SwapQuery memory originQuery) { + originQuery = SwapQuery({ + routerAdapter: address(router), + tokenOut: address(token1), + minAmountOut: amount, + deadline: block.timestamp, + rawParams: getOriginSwapParams(0, 1) + }); + } + // ══════════════════════════════════════════ TESTS: SET FAST BRIDGE ═══════════════════════════════════════════════ function test_setFastBridge_setsFastBridge() public { @@ -92,16 +113,9 @@ abstract contract FastBridgeRouterTest is FBRTest { // ═══════════════════════════════════════════════ TESTS: BRIDGE ═══════════════════════════════════════════════════ - function test_bridge_noOriginSwap_noGasRebate() public { + function test_bridge_noOriginSwap_noGasRebate_senderEOA() public { uint256 amount = 1 ether; - // No swap on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: address(token0), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token0), originAmount: amount, @@ -123,16 +137,9 @@ abstract contract FastBridgeRouterTest is FBRTest { }); } - function test_bridge_noOriginSwap_withGasRebate() public { + function test_bridge_noOriginSwap_withGasRebate_senderEOA() public { uint256 amount = 1 ether; - // No swap on origin chain - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(0), - tokenOut: address(token0), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: "" - }); + SwapQuery memory originQuery = getOriginQueryNoSwap(amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token0), originAmount: amount, @@ -154,17 +161,10 @@ abstract contract FastBridgeRouterTest is FBRTest { }); } - function test_bridge_withOriginSwap_noGasRebate() public { + function test_bridge_withOriginSwap_noGasRebate_senderEOA() public { uint256 amountBeforeSwap = 1 ether; - // T0 -> T1 swap on origin chain uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(token1), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(0, 1) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token1), originAmount: amount, @@ -186,17 +186,10 @@ abstract contract FastBridgeRouterTest is FBRTest { }); } - function test_bridge_withOriginSwap_withGasRebate() public { + function test_bridge_withOriginSwap_withGasRebate_senderEOA() public { uint256 amountBeforeSwap = 1 ether; - // T0 -> T1 swap on origin chain uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(token1), - minAmountOut: amount, - deadline: block.timestamp, - rawParams: getOriginSwapParams(0, 1) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(amount); IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ originToken: address(token1), originAmount: amount, @@ -222,7 +215,6 @@ abstract contract FastBridgeRouterTest is FBRTest { function test_bridge_revert_originSwap_deadlineExceeded() public { uint256 amountBeforeSwap = 1 ether; - // T0 -> T1 swap on origin chain uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); SwapQuery memory originQuery = SwapQuery({ routerAdapter: address(router), @@ -245,15 +237,8 @@ abstract contract FastBridgeRouterTest is FBRTest { function test_bridge_revert_originSwap_minAmountOutNotMet() public { uint256 amountBeforeSwap = 1 ether; - // T0 -> T1 swap on origin chain uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); - SwapQuery memory originQuery = SwapQuery({ - routerAdapter: address(router), - tokenOut: address(token1), - minAmountOut: amount + 1, - deadline: block.timestamp, - rawParams: getOriginSwapParams(0, 1) - }); + SwapQuery memory originQuery = getOriginQueryWithSwap(amount + 1); vm.expectRevert(InsufficientOutputAmount.selector); vm.prank(user); router.bridge({ diff --git a/test/rfq/FastBridgeRouterV2.Native.QuoterV1.t.sol b/test/rfq/FastBridgeRouterV2.Native.QuoterV1.t.sol new file mode 100644 index 000000000..7e7c92872 --- /dev/null +++ b/test/rfq/FastBridgeRouterV2.Native.QuoterV1.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {FastBridgeRouterV2NativeTest} from "./FastBridgeRouterV2.Native.t.sol"; +import {ISwapQuoterV1} from "../interfaces/ISwapQuoterV1.sol"; + +contract FastBridgeRouterV2NativeQuoterV1Test is FastBridgeRouterV2NativeTest { + function setUpSwapQuoter() internal override { + // SwapQuoter V1 is solidity 0.6.12, so we use the cheatcode to deploy it + // new SwapQuoter(synapseRouter, weth, owner) + bytes memory constructorArgs = abi.encode( + // Existing SwapQuoter will be always pointing towards another router + address(1), // synapseRouter + address(weth), // weth + address(this) // owner + ); + address swapQuoter = deployCode("SwapQuoter.sol", constructorArgs); + addPool(swapQuoter); + vm.prank(owner); + router.setSwapQuoter(swapQuoter); + } + + function addPool(address swapQuoter) internal { + address[] memory pools = new address[](1); + pools[0] = address(pool); + ISwapQuoterV1(swapQuoter).addPools(pools); + } +} diff --git a/test/rfq/FastBridgeRouterV2.Native.QuoterV2.t.sol b/test/rfq/FastBridgeRouterV2.Native.QuoterV2.t.sol new file mode 100644 index 000000000..463bcea6a --- /dev/null +++ b/test/rfq/FastBridgeRouterV2.Native.QuoterV2.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {FastBridgeRouterV2NativeTest} from "./FastBridgeRouterV2.Native.t.sol"; +import {DefaultPoolCalc} from "../../contracts/router/quoter/DefaultPoolCalc.sol"; +import {SwapQuoterV2} from "../../contracts/router/quoter/SwapQuoterV2.sol"; + +contract FastBridgeRouterV2NativeQuoterV2Test is FastBridgeRouterV2NativeTest { + function setUpSwapQuoter() internal override { + DefaultPoolCalc poolCalc = new DefaultPoolCalc(); + SwapQuoterV2 quoter = new SwapQuoterV2({ + // Existing SwapQuoter will be always pointing towards another router + synapseRouter_: address(1), + defaultPoolCalc_: address(poolCalc), + weth_: address(weth), + owner_: address(this) + }); + addPool(quoter); + vm.prank(owner); + router.setSwapQuoter(address(quoter)); + } + + function addPool(SwapQuoterV2 quoter) internal { + SwapQuoterV2.BridgePool[] memory pools = new SwapQuoterV2.BridgePool[](1); + pools[0] = SwapQuoterV2.BridgePool({ + bridgeToken: address(0), + poolType: SwapQuoterV2.PoolType.Default, + pool: address(pool) + }); + quoter.addPools(pools); + } +} diff --git a/test/rfq/FastBridgeRouterV2.Native.t.sol b/test/rfq/FastBridgeRouterV2.Native.t.sol new file mode 100644 index 000000000..3927feafb --- /dev/null +++ b/test/rfq/FastBridgeRouterV2.Native.t.sol @@ -0,0 +1,1122 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {FastBridgeRouterV2} from "../../contracts/rfq/FastBridgeRouterV2.sol"; +import {DeadlineExceeded, InsufficientOutputAmount} from "../../contracts/router/DefaultRouter.sol"; + +import {MockSenderContract} from "../mocks/MockSenderContract.sol"; + +import {FastBridgeRouterNativeTest, IFastBridge, SwapQuery} from "./FastBridgeRouter.Native.t.sol"; + +// solhint-disable not-rely-on-time +// solhint-disable ordering +// solhint-disable func-name-mixedcase +abstract contract FastBridgeRouterV2NativeTest is FastBridgeRouterNativeTest { + address public externalContract; + + function setUp() public virtual override { + super.setUp(); + externalContract = address(new MockSenderContract()); + prepareAccount(externalContract); + } + + function deployRouter() public virtual override returns (address payable) { + return payable(new FastBridgeRouterV2(owner)); + } + + function getDestQueryNoRebateWithOriginSender(uint256 amount, address originSender) + public + view + returns (SwapQuery memory destQuery) + { + destQuery = SwapQuery({ + routerAdapter: address(0), + tokenOut: TOKEN_OUT, + minAmountOut: amount - FIXED_FEE, + deadline: block.timestamp + RFQ_DEADLINE, + rawParams: abi.encodePacked(uint8(0), originSender) + }); + } + + function getDestQueryWithRebateWithOriginSender(uint256 amount, address originSender) + public + view + returns (SwapQuery memory destQuery) + { + destQuery = SwapQuery({ + routerAdapter: address(0), + tokenOut: TOKEN_OUT, + minAmountOut: amount - FIXED_FEE, + deadline: block.timestamp + RFQ_DEADLINE, + rawParams: abi.encodePacked(REBATE_FLAG, originSender) + }); + } + + function expectRevertOriginSenderNotSpecified() public { + vm.expectRevert(FastBridgeRouterV2.FastBridgeRouterV2__OriginSenderNotSpecified.selector); + } + + // ════════════════════════════ TESTS: START FROM ETH (EOA, ORIGIN SENDER PROVIDED) ════════════════════════════════ + + // Start from ETH, use ETH for RFQ + function check_bridge_eth_noOriginSwap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(ETH, amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: ETH, + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: amount, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge{value: amount}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + // Start from ETH, use WETH for RFQ + function check_bridge_eth_withOriginWrap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryWithHandleETH(address(weth), amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: address(weth), + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: 0, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge{value: amount}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + // Start from ETH, use paired token for RFQ + function check_bridge_eth_withOriginSwap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(1, 0, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(token), amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: address(token), + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: 0, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge{value: amountBeforeSwap}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + function test_bridge_eth_noOriginSwap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_eth_noOriginSwap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_eth_noOriginSwap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_eth_noOriginSwap({caller: user, originSender: user, gasRebate: true}); + } + + function test_bridge_eth_withOriginWrap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_eth_withOriginWrap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_eth_withOriginWrap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_eth_withOriginWrap({caller: user, originSender: user, gasRebate: true}); + } + + function test_bridge_eth_withOriginSwap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_eth_withOriginSwap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_eth_withOriginSwap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_eth_withOriginSwap({caller: user, originSender: user, gasRebate: true}); + } + + // Note: Calls from EOA with origin sender set to zero address should succeed + function test_bridge_eth_noOriginSwap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_eth_noOriginSwap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_eth_noOriginSwap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_eth_noOriginSwap({caller: user, originSender: address(0), gasRebate: true}); + } + + function test_bridge_eth_withOriginWrap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_eth_withOriginWrap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_eth_withOriginWrap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_eth_withOriginWrap({caller: user, originSender: address(0), gasRebate: true}); + } + + function test_bridge_eth_withOriginSwap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_eth_withOriginSwap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_eth_withOriginSwap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_eth_withOriginSwap({caller: user, originSender: address(0), gasRebate: true}); + } + + // ═══════════════════════════ TESTS: START FROM WETH (EOA, ORIGIN SENDER PROVIDED) ════════════════════════════════ + + // Start from WETH, use WETH for RFQ + function check_bridge_weth_noOriginSwap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(address(weth), amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: address(weth), + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: 0, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amount, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + // Start from WETH, use ETH for RFQ + function check_bridge_weth_withOriginUnwrap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryWithHandleETH(ETH, amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: ETH, + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: amount, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amount, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + // Start from WETH, use paired token for RFQ + function check_bridge_weth_withOriginSwap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(1, 0, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(token), amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: address(token), + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: 0, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + function test_bridge_weth_noOriginSwap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_weth_noOriginSwap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_weth_noOriginSwap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_weth_noOriginSwap({caller: user, originSender: user, gasRebate: true}); + } + + function test_bridge_weth_withOriginUnwrap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_weth_withOriginUnwrap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_weth_withOriginUnwrap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_weth_withOriginUnwrap({caller: user, originSender: user, gasRebate: true}); + } + + function test_bridge_weth_withOriginSwap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_weth_withOriginSwap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_weth_withOriginSwap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_weth_withOriginSwap({caller: user, originSender: user, gasRebate: true}); + } + + // Note: Calls from EOA with origin sender set to zero address should succeed + function test_bridge_weth_noOriginSwap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_weth_noOriginSwap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_weth_noOriginSwap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_weth_noOriginSwap({caller: user, originSender: address(0), gasRebate: true}); + } + + function test_bridge_weth_withOriginUnwrap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_weth_withOriginUnwrap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_weth_withOriginUnwrap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_weth_withOriginUnwrap({caller: user, originSender: address(0), gasRebate: true}); + } + + function test_bridge_weth_withOriginSwap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_weth_withOriginSwap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_weth_withOriginSwap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_weth_withOriginSwap({caller: user, originSender: address(0), gasRebate: true}); + } + + // ═══════════════════════ TESTS: START FROM PAIRED TOKEN (EOA, ORIGIN SENDER PROVIDED) ════════════════════════════ + + // Start from paired token, use paired token for RFQ + function check_bridge_token_noOriginSwap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(address(token), amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: address(token), + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: 0, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amount, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + // Start from paired token, use WETH for RFQ + function check_bridge_token_withOriginSwap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(weth), amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: address(weth), + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: 0, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + // Start from paired token, use ETH for RFQ + function check_bridge_token_withOriginSwapUnwrap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(ETH, amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: ETH, + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: amount, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + function test_bridge_token_noOriginSwap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_token_noOriginSwap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_token_noOriginSwap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_token_noOriginSwap({caller: user, originSender: user, gasRebate: true}); + } + + function test_bridge_token_withOriginSwap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_token_withOriginSwap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_token_withOriginSwap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_token_withOriginSwap({caller: user, originSender: user, gasRebate: true}); + } + + function test_bridge_token_withOriginSwapUnwrap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_token_withOriginSwapUnwrap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_token_withOriginSwapUnwrap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_token_withOriginSwapUnwrap({caller: user, originSender: user, gasRebate: true}); + } + + // Note: Calls from EOA with origin sender set to zero address should succeed + function test_bridge_token_noOriginSwap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_token_noOriginSwap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_token_noOriginSwap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_token_noOriginSwap({caller: user, originSender: address(0), gasRebate: true}); + } + + function test_bridge_token_withOriginSwap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_token_withOriginSwap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_token_withOriginSwap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_token_withOriginSwap({caller: user, originSender: address(0), gasRebate: true}); + } + + function test_bridge_token_withOriginSwapUnwrap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_token_withOriginSwapUnwrap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_token_withOriginSwapUnwrap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_token_withOriginSwapUnwrap({caller: user, originSender: address(0), gasRebate: true}); + } + + // ═════════════════════════ TESTS: START FROM ETH (CONTRACT, ORIGIN SENDER PROVIDED) ══════════════════════════════ + + function test_bridge_eth_noOriginSwap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_eth_noOriginSwap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_eth_noOriginSwap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_eth_noOriginSwap({caller: externalContract, originSender: user, gasRebate: true}); + } + + function test_bridge_eth_withOriginWrap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_eth_withOriginWrap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_eth_withOriginWrap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_eth_withOriginWrap({caller: externalContract, originSender: user, gasRebate: true}); + } + + function test_bridge_eth_withOriginSwap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_eth_withOriginSwap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_eth_withOriginSwap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_eth_withOriginSwap({caller: externalContract, originSender: user, gasRebate: true}); + } + + // ═════════════════════════ TESTS: START FROM WETH (CONTRACT, ORIGIN SENDER PROVIDED) ═════════════════════════════ + + function test_bridge_weth_noOriginSwap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_weth_noOriginSwap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_weth_noOriginSwap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_weth_noOriginSwap({caller: externalContract, originSender: user, gasRebate: true}); + } + + function test_bridge_weth_withOriginUnwrap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_weth_withOriginUnwrap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_weth_withOriginUnwrap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_weth_withOriginUnwrap({caller: externalContract, originSender: user, gasRebate: true}); + } + + function test_bridge_weth_withOriginSwap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_weth_withOriginSwap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_weth_withOriginSwap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_weth_withOriginSwap({caller: externalContract, originSender: user, gasRebate: true}); + } + + // ═════════════════════ TESTS: START FROM PAIRED TOKEN (CONTRACT, ORIGIN SENDER PROVIDED) ═════════════════════════ + + function test_bridge_token_noOriginSwap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_token_noOriginSwap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_token_noOriginSwap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_token_noOriginSwap({caller: externalContract, originSender: user, gasRebate: true}); + } + + function test_bridge_token_withOriginSwap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_token_withOriginSwap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_token_withOriginSwap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_token_withOriginSwap({caller: externalContract, originSender: user, gasRebate: true}); + } + + function test_bridge_token_withOriginSwapUnwrap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_token_withOriginSwapUnwrap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_token_withOriginSwapUnwrap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_token_withOriginSwapUnwrap({caller: externalContract, originSender: user, gasRebate: true}); + } + + // ═══════════════════════ TESTS: START FROM ETH (CONTRACT, ORIGIN SENDER NOT PROVIDED) ════════════════════════════ + + function check_bridge_eth_noOriginSwap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amount = 1 ether; + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge{value: amount}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: getOriginQueryNoSwap(ETH, amount), + destQuery: destQuery + }); + } + + function check_bridge_eth_withOriginWrap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amount = 1 ether; + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge{value: amount}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: getOriginQueryWithHandleETH(address(weth), amount), + destQuery: destQuery + }); + } + + function check_bridge_eth_withOriginSwap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(1, 0, amountBeforeSwap); + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge{value: amountBeforeSwap}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amountBeforeSwap, + originQuery: getOriginQueryWithSwap(address(token), amount), + destQuery: destQuery + }); + } + + function test_bridge_eth_noOriginSwap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_eth_noOriginSwap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_eth_noOriginSwap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_eth_noOriginSwap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_eth_noOriginSwap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + function test_bridge_eth_withOriginWrap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_eth_withOriginWrap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_eth_withOriginWrap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_eth_withOriginWrap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_eth_withOriginWrap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + function test_bridge_eth_withOriginSwap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_eth_withOriginSwap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_eth_withOriginSwap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_eth_withOriginSwap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_eth_withOriginSwap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + // ═══════════════════════ TESTS: START FROM WETH (CONTRACT, ORIGIN SENDER NOT PROVIDED) ═══════════════════════════ + + function check_bridge_weth_noOriginSwap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amount = 1 ether; + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amount, + originQuery: getOriginQueryNoSwap(address(weth), amount), + destQuery: destQuery + }); + } + + function check_bridge_weth_withOriginUnwrap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amount = 1 ether; + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amount, + originQuery: getOriginQueryWithHandleETH(ETH, amount), + destQuery: destQuery + }); + } + + function check_bridge_weth_withOriginSwap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amount = 1 ether; + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amount, + originQuery: getOriginQueryWithSwap(address(token), amount), + destQuery: destQuery + }); + } + + function test_bridge_weth_noOriginSwap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_weth_noOriginSwap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_weth_noOriginSwap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_weth_noOriginSwap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_weth_noOriginSwap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + function test_bridge_weth_withOriginUnwrap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_weth_withOriginUnwrap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_weth_withOriginUnwrap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_weth_withOriginUnwrap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_weth_withOriginUnwrap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + function test_bridge_weth_withOriginSwap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_weth_withOriginSwap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_weth_withOriginSwap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_weth_withOriginSwap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_weth_withOriginSwap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + // ═══════════════════ TESTS: START FROM PAIRED TOKEN (CONTRACT, ORIGIN SENDER NOT PROVIDED) ═══════════════════════ + + function check_bridge_token_noOriginSwap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amount = 1 ether; + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amount, + originQuery: getOriginQueryNoSwap(address(token), amount), + destQuery: destQuery + }); + } + + function check_bridge_token_withOriginSwap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amount, + originQuery: getOriginQueryWithSwap(address(weth), amount), + destQuery: destQuery + }); + } + + function check_bridge_token_withOriginSwapUnwrap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amount, + originQuery: getOriginQueryWithSwap(ETH, amount), + destQuery: destQuery + }); + } + + function test_bridge_token_noOriginSwap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_token_noOriginSwap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_token_noOriginSwap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_token_noOriginSwap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_token_noOriginSwap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + function test_bridge_token_withOriginSwap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_token_withOriginSwap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_token_withOriginSwap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_token_withOriginSwap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_token_withOriginSwap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + function test_bridge_token_withOriginSwapUnwrap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_token_withOriginSwapUnwrap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_token_withOriginSwapUnwrap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_token_withOriginSwapUnwrap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_token_withOriginSwapUnwrap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + // ═══════════════════════════════════════════════ MISC REVERTS ════════════════════════════════════════════════════ + + function test_bridge_eth_noOriginSwap_revert_deadlineExceeded() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(ETH, amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge{value: amount}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_eth_withOriginWrap_revert_deadlineExceeded() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryWithHandleETH(address(weth), amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge{value: amount}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_eth_withOriginSwap_revert_deadlineExceeded() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(weth), amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge{value: amountBeforeSwap}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_weth_noOriginSwap_revert_deadlineExceeded() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(address(weth), amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_weth_withOriginUnwrap_revert_deadlineExceeded() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryWithHandleETH(ETH, amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_weth_withOriginSwap_revert_deadlineExceeded() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(weth), amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_token_noOriginSwap_revert_deadlineExceeded() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(address(token), amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_token_withOriginSwap_revert_deadlineExceeded() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(weth), amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_token_withOriginSwapUnwrap_revert_deadlineExceeded() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(ETH, amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_eth_noOriginSwap_revert_insufficientOutputAmount() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(ETH, amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge{value: amount}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_eth_withOriginWrap_revert_insufficientOutputAmount() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryWithHandleETH(address(weth), amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge{value: amount}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_eth_withOriginSwap_revert_insufficientOutputAmount() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(token), amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge{value: amountBeforeSwap}({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: ETH, + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_weth_noOriginSwap_revert_insufficientOutputAmount() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(address(weth), amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_weth_withOriginUnwrap_revert_insufficientOutputAmount() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryWithHandleETH(ETH, amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_weth_withOriginSwap_revert_insufficientOutputAmount() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(token), amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(weth), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_token_noOriginSwap_revert_insufficientOutputAmount() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(address(token), amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_token_withOriginSwap_revert_insufficientOutputAmount() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(address(weth), amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_token_withOriginSwapUnwrap_revert_insufficientOutputAmount() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(ETH, amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } +} diff --git a/test/rfq/FastBridgeRouterV2.QuoterV1.t.sol b/test/rfq/FastBridgeRouterV2.QuoterV1.t.sol new file mode 100644 index 000000000..519645f61 --- /dev/null +++ b/test/rfq/FastBridgeRouterV2.QuoterV1.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {FastBridgeRouterV2Test} from "./FastBridgeRouterV2.t.sol"; +import {ISwapQuoterV1} from "../interfaces/ISwapQuoterV1.sol"; + +contract FastBridgeRouterV2QuoterV1Test is FastBridgeRouterV2Test { + function setUpSwapQuoter() internal override { + // SwapQuoter V1 is solidity 0.6.12, so we use the cheatcode to deploy it + // new SwapQuoter(synapseRouter, weth, owner) + bytes memory constructorArgs = abi.encode( + // Existing SwapQuoter will be always pointing towards another router + address(1), // synapseRouter + // We don't care about WETH in this test + address(2), // weth + address(this) // owner + ); + address swapQuoter = deployCode("SwapQuoter.sol", constructorArgs); + addPool(swapQuoter); + vm.prank(owner); + router.setSwapQuoter(swapQuoter); + } + + function addPool(address swapQuoter) internal { + address[] memory pools = new address[](1); + pools[0] = address(pool); + ISwapQuoterV1(swapQuoter).addPools(pools); + } +} diff --git a/test/rfq/FastBridgeRouterV2.QuoterV2.t.sol b/test/rfq/FastBridgeRouterV2.QuoterV2.t.sol new file mode 100644 index 000000000..57f61cf07 --- /dev/null +++ b/test/rfq/FastBridgeRouterV2.QuoterV2.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {FastBridgeRouterV2Test} from "./FastBridgeRouterV2.t.sol"; +import {DefaultPoolCalc} from "../../contracts/router/quoter/DefaultPoolCalc.sol"; +import {SwapQuoterV2} from "../../contracts/router/quoter/SwapQuoterV2.sol"; + +contract FastBridgeRouterV2QuoterV2Test is FastBridgeRouterV2Test { + function setUpSwapQuoter() internal override { + DefaultPoolCalc poolCalc = new DefaultPoolCalc(); + SwapQuoterV2 quoter = new SwapQuoterV2({ + // Existing SwapQuoter will be always pointing towards another router + synapseRouter_: address(1), + defaultPoolCalc_: address(poolCalc), + // We don't care about WETH in this test + weth_: address(2), + owner_: address(this) + }); + addPool(quoter); + vm.prank(owner); + router.setSwapQuoter(address(quoter)); + } + + function addPool(SwapQuoterV2 quoter) internal { + SwapQuoterV2.BridgePool[] memory pools = new SwapQuoterV2.BridgePool[](1); + pools[0] = SwapQuoterV2.BridgePool({ + bridgeToken: address(0), + poolType: SwapQuoterV2.PoolType.Default, + pool: address(pool) + }); + quoter.addPools(pools); + } +} diff --git a/test/rfq/FastBridgeRouterV2.t.sol b/test/rfq/FastBridgeRouterV2.t.sol new file mode 100644 index 000000000..794099f82 --- /dev/null +++ b/test/rfq/FastBridgeRouterV2.t.sol @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {DeadlineExceeded, InsufficientOutputAmount} from "../../contracts/router/DefaultRouter.sol"; +import {FastBridgeRouterV2} from "../../contracts/rfq/FastBridgeRouterV2.sol"; + +import {MockSenderContract} from "../mocks/MockSenderContract.sol"; + +import {FastBridgeRouterTest, IFastBridge, SwapQuery} from "./FastBridgeRouter.t.sol"; + +// solhint-disable not-rely-on-time +// solhint-disable ordering +// solhint-disable func-name-mixedcase +abstract contract FastBridgeRouterV2Test is FastBridgeRouterTest { + address public externalContract; + + function setUp() public virtual override { + super.setUp(); + externalContract = address(new MockSenderContract()); + prepareAccount(externalContract); + } + + function deployRouter() public virtual override returns (address payable) { + return payable(new FastBridgeRouterV2(owner)); + } + + function getDestQueryNoRebateWithOriginSender(uint256 amount, address originSender) + public + view + returns (SwapQuery memory destQuery) + { + destQuery = SwapQuery({ + routerAdapter: address(0), + tokenOut: TOKEN_OUT, + minAmountOut: amount - FIXED_FEE, + deadline: block.timestamp + RFQ_DEADLINE, + rawParams: abi.encodePacked(uint8(0), originSender) + }); + } + + function getDestQueryWithRebateWithOriginSender(uint256 amount, address originSender) + public + view + returns (SwapQuery memory destQuery) + { + destQuery = SwapQuery({ + routerAdapter: address(0), + tokenOut: TOKEN_OUT, + minAmountOut: amount - FIXED_FEE, + deadline: block.timestamp + RFQ_DEADLINE, + rawParams: abi.encodePacked(REBATE_FLAG, originSender) + }); + } + + function expectRevertOriginSenderNotSpecified() public { + vm.expectRevert(FastBridgeRouterV2.FastBridgeRouterV2__OriginSenderNotSpecified.selector); + } + + // ════════════════════════════════ TESTS: BRIDGE (EOA, ORIGIN SENDER PROVIDED) ════════════════════════════════════ + + function check_bridge_noOriginSwap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: address(token0), + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: 0, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token0), + amount: amount, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + function check_bridge_withOriginSwap( + address caller, + address originSender, + bool gasRebate + ) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(amount); + IFastBridge.BridgeParams memory expectedParams = getExpectedBridgeParams({ + originToken: address(token1), + originAmount: amount, + sendChainGas: gasRebate + }); + vm.expectCall({ + callee: address(fastBridge), + msgValue: 0, + data: abi.encodeCall(IFastBridge.bridge, (expectedParams)) + }); + vm.prank(caller); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token0), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: gasRebate + ? getDestQueryWithRebateWithOriginSender(amount, originSender) + : getDestQueryNoRebateWithOriginSender(amount, originSender) + }); + } + + function test_bridge_noOriginSwap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_noOriginSwap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_noOriginSwap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_noOriginSwap({caller: user, originSender: user, gasRebate: true}); + } + + function test_bridge_withOriginSwap_noGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_withOriginSwap({caller: user, originSender: user, gasRebate: false}); + } + + function test_bridge_withOriginSwap_withGasRebate_senderEOA_withOriginSenderSet() public { + check_bridge_withOriginSwap({caller: user, originSender: user, gasRebate: true}); + } + + // Note: Calls from EOA with origin sender set to zero address should succeed + function test_bridge_noOriginSwap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_noOriginSwap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_noOriginSwap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_noOriginSwap({caller: user, originSender: address(0), gasRebate: true}); + } + + function test_bridge_withOriginSwap_noGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_withOriginSwap({caller: user, originSender: address(0), gasRebate: false}); + } + + function test_bridge_withOriginSwap_withGasRebate_senderEOA_withOriginSenderZero() public { + check_bridge_withOriginSwap({caller: user, originSender: address(0), gasRebate: true}); + } + + // ═════════════════════════════ TESTS: BRIDGE (CONTRACT, ORIGIN SENDER PROVIDED) ══════════════════════════════════ + + function test_bridge_noOriginSwap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_noOriginSwap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_noOriginSwap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_noOriginSwap({caller: externalContract, originSender: user, gasRebate: true}); + } + + function test_bridge_withOriginSwap_noGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_withOriginSwap({caller: externalContract, originSender: user, gasRebate: false}); + } + + function test_bridge_withOriginSwap_withGasRebate_senderContract_withOriginSenderSet() public { + check_bridge_withOriginSwap({caller: externalContract, originSender: user, gasRebate: true}); + } + + // ═══════════════════════════ TESTS: BRIDGE (CONTRACT, ORIGIN SENDER NOT PROVIDED) ════════════════════════════════ + + function check_bridge_noOriginSwap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(amount); + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token0), + amount: amount, + originQuery: originQuery, + destQuery: destQuery + }); + } + + function check_bridge_withOriginSwap_senderContract_revert(SwapQuery memory destQuery) public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(amount); + expectRevertOriginSenderNotSpecified(); + vm.prank(externalContract); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token0), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: destQuery + }); + } + + function test_bridge_noOriginSwap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_noOriginSwap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_noOriginSwap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_noOriginSwap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_noOriginSwap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + function test_bridge_withOriginSwap_senderContract_reverts() public { + // Revert when origin sender is not encoded into destQuery + check_bridge_withOriginSwap_senderContract_revert({destQuery: getDestQueryNoRebate(1 ether)}); + check_bridge_withOriginSwap_senderContract_revert({destQuery: getDestQueryWithRebate(1 ether)}); + // Revert when empty origin sender is encoded into destQuery + check_bridge_withOriginSwap_senderContract_revert({ + destQuery: getDestQueryNoRebateWithOriginSender(1 ether, address(0)) + }); + check_bridge_withOriginSwap_senderContract_revert({ + destQuery: getDestQueryWithRebateWithOriginSender(1 ether, address(0)) + }); + } + + // ═══════════════════════════════════════════════ MISC REVERTS ════════════════════════════════════════════════════ + + function test_bridge_noOriginSwap_revert_deadlineExceeded() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token0), + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_withOriginSwap_revert_deadlineExceeded() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(amount); + originQuery.deadline = block.timestamp - 1; + vm.expectRevert(DeadlineExceeded.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token0), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_noOriginSwap_revert_insufficientOutputAmount() public { + uint256 amount = 1 ether; + SwapQuery memory originQuery = getOriginQueryNoSwap(amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token0), + amount: amount, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } + + function test_bridge_withOriginSwap_revert_insufficientOutputAmount() public { + uint256 amountBeforeSwap = 1 ether; + uint256 amount = pool.calculateSwap(0, 1, amountBeforeSwap); + SwapQuery memory originQuery = getOriginQueryWithSwap(amount); + originQuery.minAmountOut = amount + 1; + vm.expectRevert(InsufficientOutputAmount.selector); + vm.prank(user); + router.bridge({ + recipient: recipient, + chainId: DST_CHAIN_ID, + token: address(token0), + amount: amountBeforeSwap, + originQuery: originQuery, + destQuery: getDestQueryNoRebate(amount) + }); + } +}