diff --git a/src/contracts/periphery/FraxswapRouter.sol b/src/contracts/periphery/FraxswapRouter.sol index efb6871..a77d6e3 100644 --- a/src/contracts/periphery/FraxswapRouter.sol +++ b/src/contracts/periphery/FraxswapRouter.sol @@ -373,12 +373,11 @@ contract FraxswapRouter { uint256 amountOutput; { // scope to avoid stack too deep errors - ( - uint256 reserveInput, - uint256 reserveOutput, - uint256 twammReserveInput, - uint256 twammReserveOutput - ) = FraxswapRouterLibrary.getReservesWithTwamm(factory, input, output); + (uint256 reserveInput, , uint256 twammReserveInput, ) = FraxswapRouterLibrary.getReservesWithTwamm( + factory, + input, + output + ); amountInput = IERC20(input).balanceOf(address(pair)) - reserveInput - twammReserveInput; amountOutput = pair.getAmountOut(amountInput, input); } @@ -455,19 +454,11 @@ contract FraxswapRouter { return FraxswapRouterLibrary.quote(amountA, reserveA, reserveB); } - function getAmountOut( - uint256 amountIn, - uint256 reserveIn, - uint256 reserveOut - ) public pure virtual returns (uint256 amountOut) { + function getAmountOut(uint256, uint256, uint256) public pure virtual returns (uint256) { revert("Deprecated: Use getAmountsOut"); // depends on the fee of the pool } - function getAmountIn( - uint256 amountOut, - uint256 reserveIn, - uint256 reserveOut - ) public pure virtual returns (uint256 amountIn) { + function getAmountIn(uint256, uint256, uint256) public pure virtual returns (uint256) { revert("Deprecated: Use getAmountsIn"); // depends on the fee of the pool } diff --git a/src/contracts/periphery/FraxswapRouterMultihop.sol b/src/contracts/periphery/FraxswapRouterMultihop.sol index 42b51f6..01111ee 100644 --- a/src/contracts/periphery/FraxswapRouterMultihop.sol +++ b/src/contracts/periphery/FraxswapRouterMultihop.sol @@ -12,8 +12,8 @@ pragma abicoder v2; // ==================================================================== // ===================== Fraxswap Router Multihop ===================== // ==================================================================== -// 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"; @@ -23,19 +23,24 @@ 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; IWETH public immutable WETH9; address public immutable FRAX; + bool public immutable CHECK_AMOUNTOUT_IN_ROUTER; - constructor(IWETH _WETH9, address _FRAX) { + constructor(IWETH _WETH9, address _FRAX, bool _CHECK_AMOUNTOUT_IN_ROUTER) { WETH9 = _WETH9; FRAX = _FRAX; + CHECK_AMOUNTOUT_IN_ROUTER = _CHECK_AMOUNTOUT_IN_ROUTER; } /// @notice modifier for checking deadline @@ -44,10 +49,8 @@ contract FraxswapRouterMultihop is ReentrancyGuard { _; } - /// @notice only accept ETH via fallback from the WETH contract - receive() external payable { - assert(msg.sender == address(WETH9)); - } + /// @notice accept ETH + receive() external payable {} /// --------------------------- /// --------- Public ---------- @@ -59,10 +62,21 @@ contract FraxswapRouterMultihop is ReentrancyGuard { function swap( FraxswapParams memory params ) external payable nonReentrant checkDeadline(params.deadline) returns (uint256 amountOut) { + if (params.recipient == address(0)) params.recipient = msg.sender; + FraxswapRoute memory route = abi.decode(params.route, (FraxswapRoute)); + route.tokenOut = params.tokenIn; + route.amountOut = params.amountIn; + FraxswapRoute memory firstRoute = abi.decode(route.nextHops[0], (FraxswapRoute)); + if (params.tokenIn == address(0)) { // ETH sent in via msg.value require(msg.value == params.amountIn, "FSR:II"); // Insufficient input ETH + } else if (params.tokenIn == address(WETH9) && msg.value > 0) { + // ETH sent in via msg.value + require(msg.value == params.amountIn, "FSR:II"); // Insufficient input ETH + WETH9.deposit{ value: msg.value }(); } else { + require(msg.value == 0, "FSR:IE"); // ETH transfer that is not used, prevent mistakes if (params.v != 0) { // use permit instead of approval uint256 amount = params.approveMax ? type(uint256).max : params.amountIn; @@ -76,33 +90,48 @@ contract FraxswapRouterMultihop is ReentrancyGuard { params.s ); } + address targetPool = address(this); + if (firstRoute.steps.length > 0) { + FraxswapStepData memory step = abi.decode(firstRoute.steps[0], (FraxswapStepData)); + if (step.directFundThisPool == 1) targetPool = step.pool; + } // Pull tokens into the Router Contract - TransferHelper.safeTransferFrom(params.tokenIn, msg.sender, address(this), params.amountIn); + TransferHelper.safeTransferFrom(params.tokenIn, msg.sender, targetPool, params.amountIn); + } + bool outputETH = params.tokenOut == address(0); // save gas + uint256 initialBalance; + if (!CHECK_AMOUNTOUT_IN_ROUTER) { + initialBalance = outputETH ? params.recipient.balance : IERC20(params.tokenOut).balanceOf(params.recipient); } - - FraxswapRoute memory route = abi.decode(params.route, (FraxswapRoute)); - route.tokenOut = params.tokenIn; - route.amountOut = params.amountIn; - for (uint256 i; i < route.nextHops.length; ++i) { - FraxswapRoute memory nextRoute = abi.decode(route.nextHops[i], (FraxswapRoute)); - executeAllHops(route, nextRoute); + FraxswapRoute memory nextRoute = i == 0 ? firstRoute : abi.decode(route.nextHops[i], (FraxswapRoute)); + executeAllHops(route, nextRoute, params.recipient); } - bool outputETH = params.tokenOut == address(0); // save gas - amountOut = outputETH ? address(this).balance : IERC20(params.tokenOut).balanceOf(address(this)); - // Check output amounts and send to recipient (IMPORTANT CHECK) - require(amountOut >= params.amountOutMinimum, "FSR:IO"); // Insufficient output + if (outputETH && amountOut == 0) { + amountOut = IERC20(address(WETH9)).balanceOf(address(this)); + if (amountOut > 0) WETH9.withdraw(amountOut); + } - if (outputETH) { - // sending ETH - (bool success, ) = payable(params.recipient).call{ value: amountOut }(""); - require(success, "FSR:Invalid transfer"); - } else { - TransferHelper.safeTransfer(params.tokenOut, params.recipient, amountOut); + if (amountOut > 0) { + if (outputETH) { + // sending ETH + (bool success, ) = payable(params.recipient).call{ value: amountOut }(""); + require(success, "FSR:Invalid transfer"); + } else { + TransferHelper.safeTransfer(params.tokenOut, params.recipient, amountOut); + } } + 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 emit Routed(params.tokenIn, params.tokenOut, params.amountIn, amountOut); } @@ -133,19 +162,20 @@ contract FraxswapRouterMultihop is ReentrancyGuard { function executeSwap( FraxswapRoute memory prevRoute, FraxswapRoute memory route, - FraxswapStepData memory step + FraxswapStepData memory step, + address recipient ) internal returns (uint256 amountOut) { uint256 amountIn = getAmountForPct(step.percentOfHop, route.percentOfHop, prevRoute.amountOut); if (step.swapType < 2) { // Fraxswap/Uni v2 - bool zeroForOne = prevRoute.tokenOut < step.tokenOut; + 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(); @@ -158,51 +188,41 @@ contract FraxswapRouterMultihop is ReentrancyGuard { IUniswapV2Pair(step.pool).swap( zeroForOne ? 0 : amountOut, zeroForOne ? amountOut : 0, - step.directFundNextPool == 1 ? getNextDirectFundingPool(route) : address(this), + step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this), new bytes(0) ); } else if (step.swapType == 2) { // Uni v3 - bool zeroForOne = prevRoute.tokenOut < step.tokenOut; + bool zeroForOne = (step.extraParam2 == 0 && prevRoute.tokenOut < step.tokenOut) || step.extraParam2 == 1; + bytes memory callBackData = abi.encode( + SwapCallbackData({ tokenIn: prevRoute.tokenOut, directFundThisPool: step.directFundThisPool == 1 }) + ); (int256 amount0, int256 amount1) = IUniswapV3Pool(step.pool).swap( - step.directFundNextPool == 1 ? getNextDirectFundingPool(route) : address(this), + step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this), zeroForOne, amountIn.toInt256(), zeroForOne ? 4_295_128_740 : 1_461_446_703_485_210_103_287_273_052_203_988_822_378_723_970_341, // Do not fail because of price - abi.encode( - SwapCallbackData({ tokenIn: prevRoute.tokenOut, directFundThisPool: step.directFundThisPool == 1 }) - ) + callBackData ); amountOut = uint256(zeroForOne ? -amount1 : -amount0); } 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 - uint256 value = 0; - if (prevRoute.tokenOut == address(WETH9)) { - // WETH send as ETH - WETH9.withdraw(amountIn); - value = amountIn; - } else { - TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); - } - amountOut = PoolInterface(step.pool).exchange{ value: value }( + // Curve exchange, with returns (no ETH) + TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); + amountOut = IPoolInterface(step.pool).exchange( int128(int256(step.extraParam1)), int128(int256(step.extraParam2)), amountIn, 0 ); - if (route.tokenOut == address(WETH9)) { - // Wrap the received ETH as WETH - WETH9.deposit{ value: amountOut }(); - } } 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, @@ -211,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, @@ -222,22 +242,25 @@ 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) { - // Fraxlend + // ERC4626/Fraxlend deposit TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); - amountOut = PoolInterface(step.pool).deposit(amountIn, address(this)); + amountOut = IPoolInterface(step.pool).deposit( + amountIn, + step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this) + ); } else if (step.swapType == 9) { // FrxETHMinter if (step.extraParam1 == 0 && prevRoute.tokenOut == address(WETH9)) { // Unwrap WETH WETH9.withdraw(amountIn); } - PoolInterface(step.pool).submitAndGive{ value: amountIn }( - step.directFundNextPool == 1 ? getNextDirectFundingPool(route) : address(this) + IPoolInterface(step.pool).submitAndGive{ value: amountIn }( + step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this) ); amountOut = amountIn; // exchange 1 for 1 } else if (step.swapType == 10) { @@ -250,41 +273,105 @@ contract FraxswapRouterMultihop is ReentrancyGuard { WETH9.deposit{ value: amountIn }(); } amountOut = amountIn; // exchange 1 for 1 - } else if (step.swapType == 999) { - // Generic Pool + } else if (step.swapType == 11) { + // Curve exchange, with returns and ETH + uint256 value = 0; + if (prevRoute.tokenOut == address(WETH9)) { + // WETH send as ETH + WETH9.withdraw(amountIn); + value = amountIn; + } else if (prevRoute.tokenOut == address(0)) { + value = amountIn; + } else { + TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); + } + amountOut = IPoolInterface(step.pool).exchange{ value: value }( + int128(int256(step.extraParam1)), + int128(int256(step.extraParam2)), + amountIn, + 0 + ); + if (route.tokenOut == address(WETH9)) { + // Wrap the received ETH as WETH + WETH9.deposit{ value: amountOut }(); + } + } else if (step.swapType == 12) { + // Curve exchange_received (NG) if (step.directFundThisPool == 0) { // this pool is funded by router TransferHelper.safeTransfer(prevRoute.tokenOut, step.pool, amountIn); } - amountOut = PoolInterface(step.pool).swap( - prevRoute.tokenOut, - route.tokenOut, + amountOut = IPoolInterface(step.pool).exchange_received( + int128(int256(step.extraParam1)), + int128(int256(step.extraParam2)), amountIn, - step.directFundNextPool == 1 ? getNextDirectFundingPool(route) : address(this) + 0, + step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this) + ); + } else if (step.swapType == 13) { + // Redeem ERC4626 + amountOut = IPoolInterface(step.pool).redeem( + amountIn, + step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this), + address(this) + ); + } else if (step.swapType == 14) { + // Curve exchange, with returns + receiver (no ETH) + TransferHelper.safeApprove(prevRoute.tokenOut, step.pool, amountIn); + amountOut = IPoolInterface(step.pool).exchange( + int128(int256(step.extraParam1)), + int128(int256(step.extraParam2)), + amountIn, + 0, + step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this) + ); + } else if (step.swapType == 255) { + // Delegatecall + bytes4 signature = bytes4(uint32(step.extraParam1)); + (bool success, bytes memory data) = address(address(uint160(step.extraParam1 >> 32))).delegatecall( + abi.encodeWithSelector( + signature, + prevRoute.tokenOut, + step.tokenOut, + amountIn, + step.pool, + step.directFundThisPool, + step.directFundNextPool == 1 ? getNextDirectFundingPool(route, recipient) : address(this), + step.extraParam2 + ) ); + require(success, "FSR:Delecatecall failed"); + amountOut = abi.decode(data, (uint256)); } - - emit Swapped(step.pool, prevRoute.tokenOut, step.tokenOut, amountIn, amountOut); } /// @notice Function that will loop through and execute swap steps /// @param prevRoute previous hop of the route /// @param route current hop of the route - function executeAllStepsForRoute(FraxswapRoute memory prevRoute, FraxswapRoute memory route) internal { + function executeAllStepsForRoute( + FraxswapRoute memory prevRoute, + FraxswapRoute memory route, + address recipient + ) internal { for (uint256 j; j < route.steps.length; ++j) { FraxswapStepData memory step = abi.decode(route.steps[j], (FraxswapStepData)); - route.amountOut += executeSwap(prevRoute, route, step); + route.amountOut += executeSwap( + prevRoute, + route, + step, + j == route.steps.length - 1 ? recipient : address(0) + ); } } /// @notice Function that will loop through and execute route hops and execute all steps for each hop /// @param prevRoute previous hop of the route /// @param route current hop of the route - function executeAllHops(FraxswapRoute memory prevRoute, FraxswapRoute memory route) internal { - executeAllStepsForRoute(prevRoute, route); + function executeAllHops(FraxswapRoute memory prevRoute, FraxswapRoute memory route, address recipient) internal { + executeAllStepsForRoute(prevRoute, route, route.nextHops.length == 0 ? recipient : address(0)); for (uint256 i; i < route.nextHops.length; ++i) { FraxswapRoute memory nextRoute = abi.decode(route.nextHops[i], (FraxswapRoute)); - executeAllHops(route, nextRoute); + executeAllHops(route, nextRoute, recipient); } } @@ -357,7 +444,11 @@ contract FraxswapRouterMultihop is ReentrancyGuard { /// @notice Utility function to get the next pool to directly fund /// @return nextPoolAddress address of the next pool - function getNextDirectFundingPool(FraxswapRoute memory route) internal pure returns (address nextPoolAddress) { + function getNextDirectFundingPool( + FraxswapRoute memory route, + address recipient + ) internal pure returns (address nextPoolAddress) { + if (recipient != address(0)) return recipient; require(route.steps.length == 1 && route.nextHops.length == 1, "FSR: DFRoutes"); // directFunding FraxswapRoute memory nextRoute = abi.decode(route.nextHops[0], (FraxswapRoute)); @@ -381,153 +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 Swap event emitted every swap - /// @param pool address of the pool to used in the step - /// @param tokenIn token address of the input token - /// @param tokenOut token address of the output token - /// @param amountIn amount of input token - /// @param amountOut amount of output token - event Swapped(address pool, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut); - - /// @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_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) 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); - - // 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); +}