feat(router): add constant-product swap router
This commit is contained in:
1 parent
3c6d80aabb
commit
e04164cd24
1 file changed
+158
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user