From e5e34f1f68cab210cd3aa837b9a3fb3a89e9ce46 Mon Sep 17 00:00:00 2001 From: Carter Carlson Date: Fri, 16 Feb 2024 16:16:01 -0800 Subject: [PATCH] refactor: interfaces --- .../periphery/FraxswapRouterMultihop.sol | 202 +++--------------- .../periphery/interfaces/IFraxswapRouter.sol | 18 ++ .../interfaces/IFraxswapRouterMultihop.sol | 68 +++++- .../periphery/interfaces/IPoolInterface.sol | 64 ++++++ 4 files changed, 170 insertions(+), 182 deletions(-) create mode 100644 src/contracts/periphery/interfaces/IFraxswapRouter.sol create mode 100644 src/contracts/periphery/interfaces/IPoolInterface.sol diff --git a/src/contracts/periphery/FraxswapRouterMultihop.sol b/src/contracts/periphery/FraxswapRouterMultihop.sol index ddaaede..01111ee 100644 --- a/src/contracts/periphery/FraxswapRouterMultihop.sol +++ b/src/contracts/periphery/FraxswapRouterMultihop.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.0; +pragma solidity ^0.8.0; pragma abicoder v2; // ==================================================================== @@ -13,6 +13,7 @@ pragma abicoder v2; // ===================== Fraxswap Router Multihop ===================== // ==================================================================== +import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import { SafeCast } from "@uniswap/v3-core/contracts/libraries/SafeCast.sol"; @@ -22,10 +23,13 @@ import { IUniswapV3SwapCallback } from "@uniswap/v3-core/contracts/interfaces/ca import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import { IUniswapV2Pair } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; +import { IPoolInterface } from "./interfaces/IPoolInterface.sol"; +import { IFraxswapRouterMultihop } from "./interfaces/IFraxswapRouterMultihop.sol"; + /// @title Fraxswap Router Multihop /// @dev Router for swapping across the majority of the FRAX liquidity /// @author Frax Finance (https://github.com/FraxFinance) -contract FraxswapRouterMultihop is ReentrancyGuard { +contract FraxswapRouterMultihop is ReentrancyGuard, IFraxswapRouterMultihop { using SafeCast for uint256; using SafeCast for int256; @@ -96,8 +100,9 @@ contract FraxswapRouterMultihop is ReentrancyGuard { } bool outputETH = params.tokenOut == address(0); // save gas uint256 initialBalance; - if (!CHECK_AMOUNTOUT_IN_ROUTER) + if (!CHECK_AMOUNTOUT_IN_ROUTER) { initialBalance = outputETH ? params.recipient.balance : IERC20(params.tokenOut).balanceOf(params.recipient); + } for (uint256 i; i < route.nextHops.length; ++i) { FraxswapRoute memory nextRoute = i == 0 ? firstRoute : abi.decode(route.nextHops[i], (FraxswapRoute)); executeAllHops(route, nextRoute, params.recipient); @@ -119,10 +124,11 @@ contract FraxswapRouterMultihop is ReentrancyGuard { TransferHelper.safeTransfer(params.tokenOut, params.recipient, amountOut); } } - if (!CHECK_AMOUNTOUT_IN_ROUTER) + if (!CHECK_AMOUNTOUT_IN_ROUTER) { amountOut = outputETH ? params.recipient.balance : IERC20(params.tokenOut).balanceOf(params.recipient) - initialBalance; + } // Check output amounts (IMPORTANT CHECK) require(amountOut >= params.amountOutMinimum, "FSR:IO"); // Insufficient output @@ -165,11 +171,11 @@ contract FraxswapRouterMultihop is ReentrancyGuard { bool zeroForOne = (step.extraParam2 == 0 && prevRoute.tokenOut < step.tokenOut) || step.extraParam2 == 1; if (step.swapType == 0) { // Execute virtual orders for Fraxswap - PoolInterface(step.pool).executeVirtualOrders(block.timestamp); + IPoolInterface(step.pool).executeVirtualOrders(block.timestamp); } if (step.extraParam1 == 1) { // Fraxswap V2 has getAmountOut in the pair (different fees) - amountOut = PoolInterface(step.pool).getAmountOut(amountIn, prevRoute.tokenOut); + amountOut = IPoolInterface(step.pool).getAmountOut(amountIn, prevRoute.tokenOut); } else { // use the reserves and helper function for Uniswap V2 and Fraxswap V1 (uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(step.pool).getReserves(); @@ -202,12 +208,12 @@ contract FraxswapRouterMultihop is ReentrancyGuard { } else if (step.swapType == 3) { // Curve exchange V2 TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); - PoolInterface(step.pool).exchange(step.extraParam1, step.extraParam2, amountIn, 0); + IPoolInterface(step.pool).exchange(step.extraParam1, step.extraParam2, amountIn, 0); amountOut = IERC20(step.tokenOut).balanceOf(address(this)); } else if (step.swapType == 4) { // Curve exchange, with returns (no ETH) TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); - amountOut = PoolInterface(step.pool).exchange( + amountOut = IPoolInterface(step.pool).exchange( int128(int256(step.extraParam1)), int128(int256(step.extraParam2)), amountIn, @@ -216,7 +222,7 @@ contract FraxswapRouterMultihop is ReentrancyGuard { } else if (step.swapType == 5) { // Curve exchange_underlying TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); - amountOut = PoolInterface(step.pool).exchange_underlying( + amountOut = IPoolInterface(step.pool).exchange_underlying( int128(int256(step.extraParam1)), int128(int256(step.extraParam2)), amountIn, @@ -225,7 +231,7 @@ contract FraxswapRouterMultihop is ReentrancyGuard { } else if (step.swapType == 6) { // Saddle TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); - amountOut = PoolInterface(step.pool).swap( + amountOut = IPoolInterface(step.pool).swap( uint8(step.extraParam1), uint8(step.extraParam2), amountIn, @@ -236,14 +242,14 @@ contract FraxswapRouterMultihop is ReentrancyGuard { // FPIController TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); if (prevRoute.tokenOut == FRAX) { - amountOut = PoolInterface(step.pool).mintFPI(amountIn, 0); + amountOut = IPoolInterface(step.pool).mintFPI(amountIn, 0); } else { - amountOut = PoolInterface(step.pool).redeemFPI(amountIn, 0); + amountOut = IPoolInterface(step.pool).redeemFPI(amountIn, 0); } } else if (step.swapType == 8) { // ERC4626/Fraxlend deposit TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); - amountOut = PoolInterface(step.pool).deposit( + amountOut = IPoolInterface(step.pool).deposit( amountIn, step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this) ); @@ -253,7 +259,7 @@ contract FraxswapRouterMultihop is ReentrancyGuard { // Unwrap WETH WETH9.withdraw(amountIn); } - PoolInterface(step.pool).submitAndGive{ value: amountIn }( + IPoolInterface(step.pool).submitAndGive{ value: amountIn }( step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this) ); amountOut = amountIn; // exchange 1 for 1 @@ -279,7 +285,7 @@ contract FraxswapRouterMultihop is ReentrancyGuard { } else { TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); } - amountOut = PoolInterface(step.pool).exchange{ value: value }( + amountOut = IPoolInterface(step.pool).exchange{ value: value }( int128(int256(step.extraParam1)), int128(int256(step.extraParam2)), amountIn, @@ -295,7 +301,7 @@ contract FraxswapRouterMultihop is ReentrancyGuard { // this pool is funded by router TransferHelper.safeTransfer(prevRoute.tokenOut, step.pool, amountIn); } - amountOut = PoolInterface(step.pool).exchange_received( + amountOut = IPoolInterface(step.pool).exchange_received( int128(int256(step.extraParam1)), int128(int256(step.extraParam2)), amountIn, @@ -304,7 +310,7 @@ contract FraxswapRouterMultihop is ReentrancyGuard { ); } else if (step.swapType == 13) { // Redeem ERC4626 - amountOut = PoolInterface(step.pool).redeem( + amountOut = IPoolInterface(step.pool).redeem( amountIn, step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this), address(this) @@ -312,7 +318,7 @@ contract FraxswapRouterMultihop is ReentrancyGuard { } else if (step.swapType == 14) { // Curve exchange, with returns + receiver (no ETH) TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); - amountOut = PoolInterface(step.pool).exchange( + amountOut = IPoolInterface(step.pool).exchange( int128(int256(step.extraParam1)), int128(int256(step.extraParam2)), amountIn, @@ -466,164 +472,4 @@ contract FraxswapRouterMultihop is ReentrancyGuard { uint256 denominator = reserveIn * 1000 + amountInWithFee; amountOut = numerator / denominator; } - - /// --------------------------- - /// --------- Structs --------- - /// --------------------------- - - /// @notice Input to the swap function - /// @dev contains the top level info for the swap - /// @param tokenIn input token address - /// @param amountIn input token amount - /// @param tokenOut output token address - /// @param amountOutMinimum output token minimum amount (reverts if lower) - /// @param recipient recipient of the output token - /// @param deadline deadline for this swap transaction (reverts if exceeded) - /// @param v v value for permit signature if supported - /// @param r r value for permit signature if supported - /// @param s s value for permit signature if supported - /// @param route byte encoded - struct FraxswapParams { - address tokenIn; - uint256 amountIn; - address tokenOut; - uint256 amountOutMinimum; - address recipient; - uint256 deadline; - bool approveMax; - uint8 v; - bytes32 r; - bytes32 s; - bytes route; - } - - /// @notice A hop along the swap route - /// @dev a series of swaps (steps) containing the same input token and output token - /// @param tokenOut output token address - /// @param amountOut output token amount - /// @param percentOfHop amount of output tokens from the previous hop going into this hop - /// @param steps list of swaps from the same input token to the same output token - /// @param nextHops next hops from this one, the next hops' input token is the tokenOut - struct FraxswapRoute { - address tokenOut; - uint256 amountOut; - uint256 percentOfHop; - bytes[] steps; - bytes[] nextHops; - } - - /// @notice A swap step in a specific route. routes can have multiple swap steps - /// @dev a single swap to a token from the token of the previous hop in the route - /// @param swapType type of the swap performed (Uniswap V2, Fraxswap,Curve, etc) - /// @param directFundNextPool 0 = funds go via router, 1 = fund are sent directly to pool - /// @param directFundThisPool 0 = funds go via router, 1 = fund are sent directly to pool - /// @param tokenOut the target token of the step. output token address - /// @param pool address of the pool to use in the step - /// @param extraParam1 extra data used in the step - /// @param extraParam2 extra data used in the step - /// @param percentOfHop percentage of all of the steps in this route (hop) - struct FraxswapStepData { - uint8 swapType; - uint8 directFundNextPool; - uint8 directFundThisPool; - address tokenOut; - address pool; - uint256 extraParam1; - uint256 extraParam2; - uint256 percentOfHop; - } - - /// @notice struct for Uniswap V3 callback - /// @param tokenIn address of input token - /// @param directFundThisPool this pool already been funded - struct SwapCallbackData { - address tokenIn; - bool directFundThisPool; - } - - /// --------------------------- - /// --------- Events ---------- - /// --------------------------- - - /// @notice Routing event emitted every swap - /// @param tokenIn address of the original input token - /// @param tokenOut address of the final output token - /// @param amountIn input amount for original input token - /// @param amountOut output amount for the final output token - event Routed(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut); -} - -// Interface for ERC20 Permit -interface IERC20Permit is IERC20 { - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; -} - -// Interface used to call pool specific functions -interface PoolInterface { - // Fraxswap - function executeVirtualOrders(uint256 blockNumber) external; - - function getAmountOut(uint256 amountIn, address tokenIn) external view returns (uint256); - - // Fraxlend - function deposit(uint256 userId, address userAddress) external returns (uint256 _sharesReceived); - - // FrxETHMinter - function submitAndGive(address recipient) external payable; - - // Curve - function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external; - - function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external payable returns (uint256); - - function exchange( - int128 i, - int128 j, - uint256 dx, - uint256 min_dy, - address receiver - ) external payable returns (uint256); - - function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256); - - function exchange_received( - int128 i, - int128 j, - uint256 dx, - uint256 min_dy, - address receiver - ) external returns (uint256); - - // Saddle - function swap( - uint8 tokenIndexFrom, - uint8 tokenIndexTo, - uint256 dx, - uint256 minDy, - uint256 deadline - ) external returns (uint256); - - //FPI Mint/Redeem - function mintFPI(uint256 frax_in, uint256 min_fpi_out) external returns (uint256 fpi_out); - - function redeemFPI(uint256 fpi_in, uint256 min_frax_out) external returns (uint256 frax_out); - - // ERC4626 redeem - function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); - - // generic swap wrapper - function swap( - address tokenIn, - address tokenOut, - uint256 amountIn, - address target - ) external returns (uint256 amountOut); } diff --git a/src/contracts/periphery/interfaces/IFraxswapRouter.sol b/src/contracts/periphery/interfaces/IFraxswapRouter.sol new file mode 100644 index 0000000..cac738d --- /dev/null +++ b/src/contracts/periphery/interfaces/IFraxswapRouter.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; + +interface IFraxswapRouter is IUniswapV2Router02 { + function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB); + function getAmountsOut(uint256 amountIn, address[] memory path) external view returns (uint256[] memory amounts); + function getAmountsIn(uint256 amountOut, address[] memory path) external view returns (uint256[] memory amounts); + function getAmountsOutWithTwamm( + uint256 amountIn, + address[] memory path + ) external returns (uint256[] memory amounts); + function getAmountsInWithTwamm( + uint256 amountOut, + address[] memory path + ) external returns (uint256[] memory amounts); +} diff --git a/src/contracts/periphery/interfaces/IFraxswapRouterMultihop.sol b/src/contracts/periphery/interfaces/IFraxswapRouterMultihop.sol index 41438ee..b54192e 100644 --- a/src/contracts/periphery/interfaces/IFraxswapRouterMultihop.sol +++ b/src/contracts/periphery/interfaces/IFraxswapRouterMultihop.sol @@ -2,6 +2,18 @@ pragma solidity ^0.8.0; interface IFraxswapRouterMultihop { + /// @notice Input to the swap function + /// @dev contains the top level info for the swap + /// @param tokenIn input token address + /// @param amountIn input token amount + /// @param tokenOut output token address + /// @param amountOutMinimum output token minimum amount (reverts if lower) + /// @param recipient recipient of the output token + /// @param deadline deadline for this swap transaction (reverts if exceeded) + /// @param v v value for permit signature if supported + /// @param r r value for permit signature if supported + /// @param s s value for permit signature if supported + /// @param route byte encoded struct FraxswapParams { address tokenIn; uint256 amountIn; @@ -16,6 +28,57 @@ interface IFraxswapRouterMultihop { bytes route; } + /// @notice A hop along the swap route + /// @dev a series of swaps (steps) containing the same input token and output token + /// @param tokenOut output token address + /// @param amountOut output token amount + /// @param percentOfHop amount of output tokens from the previous hop going into this hop + /// @param steps list of swaps from the same input token to the same output token + /// @param nextHops next hops from this one, the next hops' input token is the tokenOut + struct FraxswapRoute { + address tokenOut; + uint256 amountOut; + uint256 percentOfHop; + bytes[] steps; + bytes[] nextHops; + } + + /// @notice A swap step in a specific route. routes can have multiple swap steps + /// @dev a single swap to a token from the token of the previous hop in the route + /// @param swapType type of the swap performed (Uniswap V2, Fraxswap,Curve, etc) + /// @param directFundNextPool 0 = funds go via router, 1 = fund are sent directly to pool + /// @param directFundThisPool 0 = funds go via router, 1 = fund are sent directly to pool + /// @param tokenOut the target token of the step. output token address + /// @param pool address of the pool to use in the step + /// @param extraParam1 extra data used in the step + /// @param extraParam2 extra data used in the step + /// @param percentOfHop percentage of all of the steps in this route (hop) + struct FraxswapStepData { + uint8 swapType; + uint8 directFundNextPool; + uint8 directFundThisPool; + address tokenOut; + address pool; + uint256 extraParam1; + uint256 extraParam2; + uint256 percentOfHop; + } + + /// @notice struct for Uniswap V3 callback + /// @param tokenIn address of input token + /// @param directFundThisPool this pool already been funded + struct SwapCallbackData { + address tokenIn; + bool directFundThisPool; + } + + /// @notice Routing event emitted every swap + /// @param tokenIn address of the original input token + /// @param tokenOut address of the final output token + /// @param amountIn input amount for original input token + /// @param amountOut output amount for the final output token + event Routed(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut); + function encodeRoute( address tokenOut, uint256 percentOfHop, @@ -32,9 +95,6 @@ interface IFraxswapRouterMultihop { uint256 extraParam2, uint256 percentOfHop ) external pure returns (bytes memory out); - function owner() external view returns (address); - function renounceOwnership() external; - function swap(FraxswapParams memory params) external returns (uint256 amountOut); - function transferOwnership(address newOwner) external; + function swap(FraxswapParams memory params) external payable returns (uint256 amountOut); function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes memory _data) external; } diff --git a/src/contracts/periphery/interfaces/IPoolInterface.sol b/src/contracts/periphery/interfaces/IPoolInterface.sol new file mode 100644 index 0000000..2cd3bf1 --- /dev/null +++ b/src/contracts/periphery/interfaces/IPoolInterface.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice Interface used to call pool specific functions +interface IPoolInterface { + // Fraxswap + function executeVirtualOrders(uint256 blockNumber) external; + + function getAmountOut(uint256 amountIn, address tokenIn) external view returns (uint256); + + // Fraxlend + function deposit(uint256 userId, address userAddress) external returns (uint256 _sharesReceived); + + // FrxETHMinter + function submitAndGive(address recipient) external payable; + + // Curve + function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external; + + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external payable returns (uint256); + + function exchange( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy, + address receiver + ) external payable returns (uint256); + + function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256); + + function exchange_received( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy, + address receiver + ) external returns (uint256); + + // Saddle + function swap( + uint8 tokenIndexFrom, + uint8 tokenIndexTo, + uint256 dx, + uint256 minDy, + uint256 deadline + ) external returns (uint256); + + //FPI Mint/Redeem + function mintFPI(uint256 frax_in, uint256 min_fpi_out) external returns (uint256 fpi_out); + + function redeemFPI(uint256 fpi_in, uint256 min_frax_out) external returns (uint256 frax_out); + + // ERC4626 redeem + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); + + // generic swap wrapper + function swap( + address tokenIn, + address tokenOut, + uint256 amountIn, + address target + ) external returns (uint256 amountOut); +}