pragma solidity >=0.4.23 <0.6.0; import '@sonicxchain/soxswap-core/contracts/interfaces/ISoxswapFactory.sol'; import '@sonicxchain/soxswap-core/contracts/interfaces/ISoxswapPair.sol'; import '@sonicxchain/soxswap-lib/contracts/libraries/TransferHelper.sol'; import '@sonicxchain/soxswap-lib/contracts/libraries/AddressStringUtil.sol'; import './interfaces/ISoxswapRouter.sol'; import './libraries/SoxswapLibrary.sol'; import './libraries/SafeMath2.sol'; import './interfaces/ISRC20.sol'; import '@sonicxchain/src20/contracts/SRC20/IMint.sol'; contract SoxswapRouter is ISoxswapRouter { using SafeMath2 for uint; address public factory; address public WSOX; modifier ensure(uint deadline) { require(deadline >= block.timestamp, 'SoxswapRouter: EXPIRED'); _; } constructor(address _factory, address _wsox) public { factory = _factory; WSOX = _wsox; } // receive() external payable { function() external payable { assert(msg.sender == WSOX); // only accept SOX via fallback from the WSOX contract } // **** ADD LIQUIDITY **** function _addLiquidity( address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin ) internal returns (uint amountA, uint amountB) { // create the pair if it doesn't exist yet if (ISoxswapFactory(factory).getPair(tokenA, tokenB) == address(0)) { ISoxswapFactory(factory).createPair(tokenA, tokenB); } (uint reserveA, uint reserveB) = SoxswapLibrary.getReserves(factory, tokenA, tokenB); if (reserveA == 0 && reserveB == 0) { (amountA, amountB) = (amountADesired, amountBDesired); } else { uint amountBOptimal = SoxswapLibrary.quote(amountADesired, reserveA, reserveB); if (amountBOptimal <= amountBDesired) { require(amountBOptimal >= amountBMin, 'SoxswapRouter: INSUFFICIENT_B_AMOUNT'); (amountA, amountB) = (amountADesired, amountBOptimal); } else { uint amountAOptimal = SoxswapLibrary.quote(amountBDesired, reserveB, reserveA); assert(amountAOptimal <= amountADesired); require(amountAOptimal >= amountAMin, 'SoxswapRouter: INSUFFICIENT_A_AMOUNT'); (amountA, amountB) = (amountAOptimal, amountBDesired); } } } function addLiquidity( address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline ) external ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) { (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); address pair = SoxswapLibrary.pairFor(factory, tokenA, tokenB); TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA); TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB); liquidity = ISoxswapPair(pair).mint(to); } function addLiquiditySOX( address token, uint amountTokenDesired, uint amountTokenMin, uint amountSOXMin, address to, uint deadline ) external payable ensure(deadline) returns (uint amountToken, uint amountSOX, uint liquidity) { (amountToken, amountSOX) = _addLiquidity( token, WSOX, amountTokenDesired, msg.value, amountTokenMin, amountSOXMin ); address payable pair = SoxswapLibrary.pairFor(factory, token, WSOX); TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken); assert(IMint(WSOX).mint(pair, amountSOX)); liquidity = ISoxswapPair(pair).mint(to); // refund dust sox, if any if (msg.value > amountSOX) TransferHelper.safeTransferSOX(msg.sender, msg.value - amountSOX); } // **** REMOVE LIQUIDITY **** function removeLiquidity( address tokenA, address tokenB, uint liquidity, uint amountAMin, uint amountBMin, address payable to, uint deadline ) public ensure(deadline) returns (uint amountA, uint amountB) { address pair = SoxswapLibrary.pairFor(factory, tokenA, tokenB); ISoxswapPair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair (uint amount0, uint amount1) = ISoxswapPair(pair).burn(to); (address token0,) = SoxswapLibrary.sortTokens(tokenA, tokenB); (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); require(amountA >= amountAMin, 'SoxswapRouter: INSUFFICIENT_A_AMOUNT'); require(amountB >= amountBMin, 'SoxswapRouter: INSUFFICIENT_B_AMOUNT'); } function removeLiquiditySOX( address token, uint liquidity, uint amountTokenMin, uint amountSOXMin, address payable to, uint deadline ) public ensure(deadline) returns (uint amountToken, uint amountSOX) { (amountToken, amountSOX) = removeLiquidity( token, WSOX, liquidity, amountTokenMin, amountSOXMin, address (this), deadline ); TransferHelper.safeTransfer(token, to, amountToken); IMint(WSOX).burn(amountSOX); TransferHelper.safeTransferSOX(to, amountSOX); } function removeLiquidityWithPermit( address tokenA, address tokenB, uint liquidity, uint amountAMin, uint amountBMin, address payable to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external returns (uint amountA, uint amountB) { address pair = SoxswapLibrary.pairFor(factory, tokenA, tokenB); uint value = approveMax ? uint(-1) : liquidity; ISoxswapPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s); (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline); } function removeLiquiditySOXWithPermit( address token, uint liquidity, uint amountTokenMin, uint amountSOXMin, address payable to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external returns (uint amountToken, uint amountSOX) { address pair = SoxswapLibrary.pairFor(factory, token, WSOX); uint value = approveMax ? uint(-1) : liquidity; ISoxswapPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s); (amountToken, amountSOX) = removeLiquiditySOX(token, liquidity, amountTokenMin, amountSOXMin, to, deadline); } // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) **** function removeLiquiditySOXSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountSOXMin, address payable to, uint deadline ) public ensure(deadline) returns (uint amountSOX) { (, amountSOX) = removeLiquidity( token, WSOX, liquidity, amountTokenMin, amountSOXMin, address(this), deadline ); TransferHelper.safeTransfer(token, to, ISRC20(token).balanceOf(address(this))); IMint(WSOX).burn(amountSOX); TransferHelper.safeTransferSOX(to, amountSOX); } function removeLiquiditySOXWithPermitSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountSOXMin, address payable to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external returns (uint amountSOX) { address pair = SoxswapLibrary.pairFor(factory, token, WSOX); uint value = approveMax ? uint(-1) : liquidity; ISoxswapPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s); amountSOX = removeLiquiditySOXSupportingFeeOnTransferTokens( token, liquidity, amountTokenMin, amountSOXMin, to, deadline ); } // **** SWAP **** // requires the initial amount to have already been sent to the first pair function _swap(uint[] memory amounts, address[] memory path, address payable _to) internal { for (uint i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); (address token0,) = SoxswapLibrary.sortTokens(input, output); uint amountOut = amounts[i + 1]; (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0)); address payable to = i < path.length - 2 ? SoxswapLibrary.pairFor(factory, output, path[i + 2]) : _to; ISoxswapPair(SoxswapLibrary.pairFor(factory, input, output)).swap( amount0Out, amount1Out, to, new bytes(0) ); } } function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address payable to, uint deadline ) external ensure(deadline) returns (uint[] memory amounts) { amounts = SoxswapLibrary.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'SoxswapRouter: INSUFFICIENT_OUTPUT_AMOUNT'); TransferHelper.safeTransferFrom( path[0], msg.sender, SoxswapLibrary.pairFor(factory, path[0], path[1]), amounts[0] ); _swap(amounts, path, to); } function swapTokensForExactTokens( uint amountOut, uint amountInMax, address[] calldata path, address payable to, uint deadline ) external ensure(deadline) returns (uint[] memory amounts) { amounts = SoxswapLibrary.getAmountsIn(factory, amountOut, path); require(amounts[0] <= amountInMax, 'SoxswapRouter: EXCESSIVE_INPUT_AMOUNT'); TransferHelper.safeTransferFrom( path[0], msg.sender, SoxswapLibrary.pairFor(factory, path[0], path[1]), amounts[0] ); _swap(amounts, path, to); } function swapExactSOXForTokens(uint amountOutMin, address[] calldata path, address payable to, uint deadline) external payable ensure(deadline) returns (uint[] memory amounts) { require(path[0] == WSOX, 'SoxswapRouter: INVALID_PATH'); amounts = SoxswapLibrary.getAmountsOut(factory, msg.value, path); require(amounts[amounts.length - 1] >= amountOutMin, 'SoxswapRouter: INSUFFICIENT_OUTPUT_AMOUNT'); IMint(WSOX).mint(SoxswapLibrary.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); } function swapTokensForExactSOX(uint amountOut, uint amountInMax, address[] calldata path, address payable to, uint deadline) external ensure(deadline) returns (uint[] memory amounts) { require(path[path.length - 1] == WSOX, 'SoxswapRouter: INVALID_PATH'); amounts = SoxswapLibrary.getAmountsIn(factory, amountOut, path); require(amounts[0] <= amountInMax, 'SoxswapRouter: EXCESSIVE_INPUT_AMOUNT'); TransferHelper.safeTransferFrom( path[0], msg.sender, SoxswapLibrary.pairFor(factory, path[0], path[1]), amounts[0] ); _swap(amounts, path, address(this)); IMint(WSOX).burn(amounts[amounts.length - 1]); TransferHelper.safeTransferSOX(to, amounts[amounts.length - 1]); } function swapExactTokensForSOX(uint amountIn, uint amountOutMin, address[] calldata path, address payable to, uint deadline) external ensure(deadline) returns (uint[] memory amounts) { require(path[path.length - 1] == WSOX, 'SoxswapRouter: INVALID_PATH'); amounts = SoxswapLibrary.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'SoxswapRouter: INSUFFICIENT_OUTPUT_AMOUNT'); TransferHelper.safeTransferFrom( path[0], msg.sender, SoxswapLibrary.pairFor(factory, path[0], path[1]), amounts[0] ); _swap(amounts, path, address(this)); IMint(WSOX).burn(amounts[amounts.length - 1]); TransferHelper.safeTransferSOX(to, amounts[amounts.length - 1]); } function swapSOXForExactTokens(uint amountOut, address[] calldata path, address payable to, uint deadline) external payable ensure(deadline) returns (uint[] memory amounts) { require(path[0] == WSOX, 'SoxswapRouter: INVALID_PATH'); amounts = SoxswapLibrary.getAmountsIn(factory, amountOut, path); require(amounts[0] <= msg.value, 'SoxswapRouter: EXCESSIVE_INPUT_AMOUNT'); IMint(WSOX).mint(SoxswapLibrary.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); // refund dust sox, if any if (msg.value > amounts[0]) TransferHelper.safeTransferSOX(msg.sender, msg.value - amounts[0]); } // **** SWAP (supporting fee-on-transfer tokens) **** // requires the initial amount to have already been sent to the first pair function _swapSupportingFeeOnTransferTokens(address[] memory path, address payable _to) internal { for (uint i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); (address token0,) = SoxswapLibrary.sortTokens(input, output); ISoxswapPair pair = ISoxswapPair(SoxswapLibrary.pairFor(factory, input, output)); uint amountInput; uint amountOutput; { // scope to avoid stack too deep errors (uint reserve0, uint reserve1,) = pair.getReserves(); (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0); amountInput = ISRC20(input).balanceOf(address(pair)).sub(reserveInput); amountOutput = SoxswapLibrary.getAmountOut(amountInput, reserveInput, reserveOutput); } (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0)); address payable to = i < path.length - 2 ? SoxswapLibrary.pairFor(factory, output, path[i + 2]) : _to; pair.swap(amount0Out, amount1Out, to, new bytes(0)); } } function swapExactTokensForTokensSupportingFeeOnTransferTokens( uint amountIn, uint amountOutMin, address[] calldata path, address payable to, uint deadline ) external ensure(deadline) { TransferHelper.safeTransferFrom( path[0], msg.sender, SoxswapLibrary.pairFor(factory, path[0], path[1]), amountIn ); uint balanceBefore = ISRC20(path[path.length - 1]).balanceOf(to); _swapSupportingFeeOnTransferTokens(path, to); require( ISRC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin, 'SoxswapRouter: INSUFFICIENT_OUTPUT_AMOUNT' ); } function swapExactSOXForTokensSupportingFeeOnTransferTokens( uint amountOutMin, address[] calldata path, address payable to, uint deadline ) external payable ensure(deadline) { require(path[0] == WSOX, 'SoxswapRouter: INVALID_PATH'); uint amountIn = msg.value; IMint(WSOX).mint(SoxswapLibrary.pairFor(factory, path[0], path[1]), amountIn); uint balanceBefore = ISRC20(path[path.length - 1]).balanceOf(to); _swapSupportingFeeOnTransferTokens(path, to); require( ISRC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin, 'SoxswapRouter: INSUFFICIENT_OUTPUT_AMOUNT' ); } function swapExactTokensForSOXSupportingFeeOnTransferTokens( uint amountIn, uint amountOutMin, address[] calldata path, address payable to, uint deadline ) external ensure(deadline) { require(path[path.length - 1] == WSOX, 'SoxswapRouter: INVALID_PATH'); TransferHelper.safeTransferFrom( path[0], msg.sender, SoxswapLibrary.pairFor(factory, path[0], path[1]), amountIn ); _swapSupportingFeeOnTransferTokens(path, address(this)); uint amountOut = ISRC20(WSOX).balanceOf(address(this)); require(amountOut >= amountOutMin, 'SoxswapRouter: INSUFFICIENT_OUTPUT_AMOUNT'); IMint(WSOX).burn(amountOut); TransferHelper.safeTransferSOX(to, amountOut); } // **** LIBRARY FUNCTIONS **** function quote(uint amountA, uint reserveA, uint reserveB) public pure returns (uint amountB) { return SoxswapLibrary.quote(amountA, reserveA, reserveB); } function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) { return SoxswapLibrary.getAmountOut(amountIn, reserveIn, reserveOut); } function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) public pure returns (uint amountIn) { return SoxswapLibrary.getAmountIn(amountOut, reserveIn, reserveOut); } function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts) { return SoxswapLibrary.getAmountsOut(factory, amountIn, path); } function getAmountsIn(uint amountOut, address[] memory path) public view returns (uint[] memory amounts) { return SoxswapLibrary.getAmountsIn(factory, amountOut, path); } function _safeBalance(address token, address account) private view returns(uint256 _balance) { _balance = ISRC20(token).balanceOf(account); } }