diff --git a/src/FluxRouter.sol b/src/FluxRouter.sol new file mode 100644 index 0000000..e873e40 --- /dev/null +++ b/src/FluxRouter.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +import {IFluxFactory} from "./interfaces/IFluxFactory.sol"; +import {IFluxPair} from "./interfaces/IFluxPair.sol"; +import {IErc20} from "./interfaces/IErc20.sol"; +import {FluxLibrary} from "./libraries/FluxLibrary.sol"; + +/// @title User-facing entrypoint for liquidity and multi-hop swaps. +contract FluxRouter { + address public immutable factory; + + modifier ensure(uint256 deadline) { + require(deadline >= block.timestamp, "FluxRouter: EXPIRED"); + _; + } + + constructor(address factoryAddress) { + factory = factoryAddress; + } + + function _safeTransferFrom(address token, address from, address to, uint256 value) private { + (bool success, bytes memory returndata) = + token.call(abi.encodeWithSelector(IErc20.transferFrom.selector, from, to, value)); + require( + success && (returndata.length == 0 || abi.decode(returndata, (bool))), "FluxRouter: TRANSFER_FROM_FAILED" + ); + } + + function _addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin + ) private returns (uint256 amountA, uint256 amountB) { + if (IFluxFactory(factory).getPair(tokenA, tokenB) == address(0)) { + IFluxFactory(factory).createPair(tokenA, tokenB); + } + (uint256 reserveA, uint256 reserveB) = FluxLibrary.getReserves(factory, tokenA, tokenB); + if (reserveA == 0 && reserveB == 0) { + (amountA, amountB) = (amountADesired, amountBDesired); + } else { + uint256 amountBOptimal = FluxLibrary.quote(amountADesired, reserveA, reserveB); + if (amountBOptimal <= amountBDesired) { + require(amountBOptimal >= amountBMin, "FluxRouter: INSUFFICIENT_B_AMOUNT"); + (amountA, amountB) = (amountADesired, amountBOptimal); + } else { + uint256 amountAOptimal = FluxLibrary.quote(amountBDesired, reserveB, reserveA); + assert(amountAOptimal <= amountADesired); + require(amountAOptimal >= amountAMin, "FluxRouter: INSUFFICIENT_A_AMOUNT"); + (amountA, amountB) = (amountAOptimal, amountBDesired); + } + } + } + + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external ensure(deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity) { + (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); + address pair = FluxLibrary.pairFor(factory, tokenA, tokenB); + _safeTransferFrom(tokenA, msg.sender, pair, amountA); + _safeTransferFrom(tokenB, msg.sender, pair, amountB); + liquidity = IFluxPair(pair).mint(to); + } + + function removeLiquidity( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) public ensure(deadline) returns (uint256 amountA, uint256 amountB) { + address pair = FluxLibrary.pairFor(factory, tokenA, tokenB); + IFluxPair(pair).transferFrom(msg.sender, pair, liquidity); + (uint256 amount0, uint256 amount1) = IFluxPair(pair).burn(to); + (address token0,) = FluxLibrary.sortTokens(tokenA, tokenB); + (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); + require(amountA >= amountAMin, "FluxRouter: INSUFFICIENT_A_AMOUNT"); + require(amountB >= amountBMin, "FluxRouter: INSUFFICIENT_B_AMOUNT"); + } + + function _swap(uint256[] memory amounts, address[] memory path, address to) private { + for (uint256 i; i < path.length - 1; i++) { + (address input, address output) = (path[i], path[i + 1]); + (address token0,) = FluxLibrary.sortTokens(input, output); + uint256 amountOut = amounts[i + 1]; + (uint256 amount0Out, uint256 amount1Out) = + input == token0 ? (uint256(0), amountOut) : (amountOut, uint256(0)); + address recipient = i < path.length - 2 ? FluxLibrary.pairFor(factory, output, path[i + 2]) : to; + IFluxPair(FluxLibrary.pairFor(factory, input, output)).swap(amount0Out, amount1Out, recipient, new bytes(0)); + } + } + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external ensure(deadline) returns (uint256[] memory amounts) { + amounts = FluxLibrary.getAmountsOut(factory, amountIn, path); + require(amounts[amounts.length - 1] >= amountOutMin, "FluxRouter: INSUFFICIENT_OUTPUT_AMOUNT"); + _safeTransferFrom(path[0], msg.sender, FluxLibrary.pairFor(factory, path[0], path[1]), amounts[0]); + _swap(amounts, path, to); + } + + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + address[] calldata path, + address to, + uint256 deadline + ) external ensure(deadline) returns (uint256[] memory amounts) { + amounts = FluxLibrary.getAmountsIn(factory, amountOut, path); + require(amounts[0] <= amountInMax, "FluxRouter: EXCESSIVE_INPUT_AMOUNT"); + _safeTransferFrom(path[0], msg.sender, FluxLibrary.pairFor(factory, path[0], path[1]), amounts[0]); + _swap(amounts, path, to); + } + + function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB) { + return FluxLibrary.quote(amountA, reserveA, reserveB); + } + + function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) + external + pure + returns (uint256 amountOut) + { + return FluxLibrary.getAmountOut(amountIn, reserveIn, reserveOut); + } + + function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) + external + pure + returns (uint256 amountIn) + { + return FluxLibrary.getAmountIn(amountOut, reserveIn, reserveOut); + } + + function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts) { + return FluxLibrary.getAmountsOut(factory, amountIn, path); + } + + function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts) { + return FluxLibrary.getAmountsIn(factory, amountOut, path); + } +}