packages/contracts-rfq/contracts/legacy/router/DefaultRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {DefaultAdapter} from "./adapters/DefaultAdapter.sol";
import {IRouterAdapter} from "./interfaces/IRouterAdapter.sol";
import {DeadlineExceeded, InsufficientOutputAmount, MsgValueIncorrect, TokenNotETH} from "./libs/Errors.sol";
import {Action, DefaultParams, SwapQuery} from "./libs/Structs.sol";
import {UniversalTokenLib} from "./libs/UniversalToken.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts-4.5.0/token/ERC20/utils/SafeERC20.sol";
/// @title DefaultRouter
/// @notice Base contract for all Synapse Routers, that is able to natively work with Default Pools
/// due to the fact that it inherits from DefaultAdapter.
abstract contract DefaultRouter is DefaultAdapter {
using SafeERC20 for IERC20;
using UniversalTokenLib for address;
/// @dev Performs a "swap from tokenIn" following instructions from `query`.
/// `query` will include the router adapter to use, and the exact type of "tokenIn -> tokenOut swap"
/// should be encoded in `query.rawParams`.
function _doSwap(
address recipient,
address tokenIn,
uint256 amountIn,
SwapQuery memory query
) internal returns (address tokenOut, uint256 amountOut) {
// First, check the deadline for the swap
// solhint-disable-next-line not-rely-on-time
if (block.timestamp > query.deadline) revert DeadlineExceeded();
// Pull initial token from the user to specified router adapter
amountIn = _pullToken(query.routerAdapter, tokenIn, amountIn);
tokenOut = query.tokenOut;
address routerAdapter = query.routerAdapter;
if (routerAdapter == address(this)) {
// If the router adapter is this contract, we can perform the swap directly and trust the result
amountOut = _adapterSwap(recipient, tokenIn, amountIn, tokenOut, query.rawParams);
} else {
// Otherwise, we need to call the router adapter. Adapters are permissionless, so we verify the result
// Record tokenOut balance before swap
amountOut = tokenOut.universalBalanceOf(recipient);
IRouterAdapter(routerAdapter).adapterSwap{value: msg.value}({
recipient: recipient,
tokenIn: tokenIn,
amountIn: amountIn,
tokenOut: tokenOut,
rawParams: query.rawParams
});
// Use the difference between the recorded balance and the current balance as the amountOut
amountOut = tokenOut.universalBalanceOf(recipient) - amountOut;
}
// Finally, check that the recipient received at least as much as they wanted
if (amountOut < query.minAmountOut) revert InsufficientOutputAmount();
}
/// @dev Pulls a requested token from the user to the requested recipient.
/// Or, if msg.value was provided, check that ETH_ADDRESS was used and msg.value is correct.
function _pullToken(
address recipient,
address token,
uint256 amount
) internal returns (uint256 amountPulled) {
if (msg.value == 0) {
token.assertIsContract();
// Record token balance before transfer
amountPulled = IERC20(token).balanceOf(recipient);
// Token needs to be pulled only if msg.value is zero
// This way user can specify WETH as the origin asset
IERC20(token).safeTransferFrom(msg.sender, recipient, amount);
// Use the difference between the recorded balance and the current balance as the amountPulled
amountPulled = IERC20(token).balanceOf(recipient) - amountPulled;
} else {
// Otherwise, we need to check that ETH was specified
if (token != UniversalTokenLib.ETH_ADDRESS) revert TokenNotETH();
// And that amount matches msg.value
if (amount != msg.value) revert MsgValueIncorrect();
// We will forward msg.value in the external call later, if recipient is not this contract
amountPulled = msg.value;
}
}
}