diff --git a/src/CellarRouter.sol b/src/CellarRouter.sol index 296e86ca..cb34452f 100644 --- a/src/CellarRouter.sol +++ b/src/CellarRouter.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.13; import { ERC20 } from "@solmate/tokens/ERC20.sol"; import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol"; import { ERC4626 } from "./base/ERC4626.sol"; -import { ISwapRouter as UniswapV3Router } from "./interfaces/ISwapRouter.sol"; +import { IUniswapV3Router as UniswapV3Router } from "./interfaces/IUniswapV3Router.sol"; import { IUniswapV2Router02 as UniswapV2Router } from "./interfaces/IUniswapV2Router02.sol"; import { ICellarRouter } from "./interfaces/ICellarRouter.sol"; diff --git a/src/SwapRouter.sol b/src/SwapRouter.sol new file mode 100644 index 00000000..89171748 --- /dev/null +++ b/src/SwapRouter.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.13; + +import { ERC20 } from "@solmate/tokens/ERC20.sol"; +import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol"; +import { IUniswapV2Router02 as UniswapV2Router } from "./interfaces/IUniswapV2Router02.sol"; +import { IUniswapV3Router as UniswapV3Router } from "./interfaces/IUniswapV3Router.sol"; + +contract SwapRouter { + using SafeTransferLib for ERC20; + + enum Exchanges { + UNIV2, + UNIV3 + } + /** @notice Planned additions + BALANCERV2, + CURVE, + ONEINCH + */ + mapping(Exchanges => bytes4) public idToSelector; + + // ========================================== CONSTRUCTOR ========================================== + + /** + * @notice Uniswap V2 swap router contract. Used for swapping if pool fees are not specified. + */ + UniswapV2Router public immutable uniswapV2Router; // 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D + + /** + * @notice Uniswap V3 swap router contract. Used for swapping if pool fees are specified. + */ + UniswapV3Router public immutable uniswapV3Router; // 0xE592427A0AEce92De3Edee1F18E0157C05861564 + + /** + * + */ + constructor(UniswapV2Router _uniswapV2Router, UniswapV3Router _uniswapV3Router) { + //set up all exchanges + uniswapV2Router = _uniswapV2Router; + uniswapV3Router = _uniswapV3Router; + + //set up mapping between ids and selectors + idToSelector[Exchanges.UNIV2] = SwapRouter(this).swapWithUniV2.selector; + idToSelector[Exchanges.UNIV3] = SwapRouter(this).swapWithUniV3.selector; + } + + // ======================================= SWAP OPERATIONS ======================================= + + function swap(Exchanges id, bytes memory swapData) external returns (uint256 swapOutAmount) { + (bool success, bytes memory result) = address(this).call(abi.encodeWithSelector(idToSelector[id], swapData)); + require(success, "Failed to perform swap"); + swapOutAmount = abi.decode(result, (uint256)); + } + + function swapWithUniV2(bytes memory swapData) public returns (uint256 swapOutAmount) { + (address[] memory path, uint256 assets, uint256 assetsOutMin, address recipient) = abi.decode( + swapData, + (address[], uint256, uint256, address) + ); + ERC20 assetIn = ERC20(path[0]); + // Approve assets to be swapped through the router. + assetIn.safeApprove(address(uniswapV2Router), assets); + + // Execute the swap. + uint256[] memory amountsOut = uniswapV2Router.swapExactTokensForTokens( + assets, + assetsOutMin, + path, + recipient, + block.timestamp + 60 + ); + swapOutAmount = amountsOut[1]; + } + + function swapWithUniV3(bytes memory swapData) public returns (uint256 swapOutAmount) { + (address[] memory path, uint24[] memory poolFees, uint256 assets, uint256 assetsOutMin, address recipient) = abi + .decode(swapData, (address[], uint24[], uint256, uint256, address)); + ERC20 assetIn = ERC20(path[0]); + + // Approve assets to be swapped through the router. + assetIn.safeApprove(address(uniswapV3Router), assets); + + // Encode swap parameters. + bytes memory encodePackedPath = abi.encodePacked(address(assetIn)); + for (uint256 i = 1; i < path.length; i++) + encodePackedPath = abi.encodePacked(encodePackedPath, poolFees[i - 1], path[i]); + + // Execute the swap. + swapOutAmount = uniswapV3Router.exactInput( + UniswapV3Router.ExactInputParams({ + path: encodePackedPath, + recipient: recipient, + deadline: block.timestamp + 60, + amountIn: assets, + amountOutMinimum: assetsOutMin + }) + ); + } +} diff --git a/src/interfaces/ISwapRouter.sol b/src/interfaces/IUniswapV3Router.sol similarity index 98% rename from src/interfaces/ISwapRouter.sol rename to src/interfaces/IUniswapV3Router.sol index d1170f69..cb7d2e91 100644 --- a/src/interfaces/ISwapRouter.sol +++ b/src/interfaces/IUniswapV3Router.sol @@ -22,7 +22,7 @@ interface IUniswapV3SwapCallback { /// @title Router token swapping functionality /// @notice Functions for swapping tokens via Uniswap V3 -interface ISwapRouter is IUniswapV3SwapCallback { +interface IUniswapV3Router is IUniswapV3SwapCallback { struct ExactInputSingleParams { address tokenIn; address tokenOut; diff --git a/src/mocks/MockSwapRouter.sol b/src/mocks/MockSwapRouter.sol index 22a04220..870a608e 100644 --- a/src/mocks/MockSwapRouter.sol +++ b/src/mocks/MockSwapRouter.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.13; import { ERC20 } from "@solmate/tokens/ERC20.sol"; import { Math } from "src/utils/Math.sol"; -import { ISwapRouter } from "../interfaces/ISwapRouter.sol"; +import { IUniswapV3Router } from "../interfaces/IUniswapV3Router.sol"; library BytesLib { function slice( @@ -202,7 +202,7 @@ contract MockSwapRouter { return amountOut; } - function exactInput(ISwapRouter.ExactInputParams memory params) external returns (uint256) { + function exactInput(IUniswapV3Router.ExactInputParams memory params) external returns (uint256) { (address tokenIn, address tokenOut, ) = params.path.decodeFirstPool(); while (params.path.hasMultiplePools()) { diff --git a/test/CellarRouter.t.sol b/test/CellarRouter.t.sol index 868169ac..9dc52282 100644 --- a/test/CellarRouter.t.sol +++ b/test/CellarRouter.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.13; import { ERC20 } from "@solmate/tokens/ERC20.sol"; import { ERC4626 } from "src/base/ERC4626.sol"; import { CellarRouter } from "src/CellarRouter.sol"; -import { ISwapRouter as UniswapV3Router } from "src/interfaces/ISwapRouter.sol"; +import { IUniswapV3Router as UniswapV3Router } from "src/interfaces/IUniswapV3Router.sol"; import { IUniswapV2Router02 as UniswapV2Router } from "src/interfaces/IUniswapV2Router02.sol"; import { MockERC20 } from "src/mocks/MockERC20.sol"; import { MockERC4626 } from "src/mocks/MockERC4626.sol";