From c7bc4710db36095e92d6f66c8d1d6974adcfba8c Mon Sep 17 00:00:00 2001 From: defifofum Date: Thu, 9 Mar 2023 12:00:48 -0600 Subject: [PATCH 1/3] feat: Dynamically pull in INIT_CODE_HASH on deployment --- contracts/ApeRouter.sol | 121 ++++++++++++++++----------- contracts/interfaces/IApeFactory.sol | 2 + contracts/libraries/ApeLibrary.sol | 19 ++++- contracts/libraries/ApePairMath.sol | 107 +++++++++++++++++++++++ 4 files changed, 195 insertions(+), 54 deletions(-) create mode 100644 contracts/libraries/ApePairMath.sol diff --git a/contracts/ApeRouter.sol b/contracts/ApeRouter.sol index ee5a32f..bf4073e 100644 --- a/contracts/ApeRouter.sol +++ b/contracts/ApeRouter.sol @@ -1,12 +1,25 @@ pragma solidity =0.6.6; /* - * ApeSwapFinance - * App: https://apeswap.finance - * Medium: https://ape-swap.medium.com - * Twitter: https://twitter.com/ape_swap + ______ ______ + / \ / \ +| ▓▓▓▓▓▓\ ______ ______ | ▓▓▓▓▓▓\__ __ __ ______ ______ +| ▓▓__| ▓▓/ \ / \| ▓▓___\▓▓ \ | \ | \| \ / \ +| ▓▓ ▓▓ ▓▓▓▓▓▓\ ▓▓▓▓▓▓\\▓▓ \| ▓▓ | ▓▓ | ▓▓ \▓▓▓▓▓▓\ ▓▓▓▓▓▓\ +| ▓▓▓▓▓▓▓▓ ▓▓ | ▓▓ ▓▓ ▓▓_\▓▓▓▓▓▓\ ▓▓ | ▓▓ | ▓▓/ ▓▓ ▓▓ | ▓▓ +| ▓▓ | ▓▓ ▓▓__/ ▓▓ ▓▓▓▓▓▓▓▓ \__| ▓▓ ▓▓_/ ▓▓_/ ▓▓ ▓▓▓▓▓▓▓ ▓▓__/ ▓▓ +| ▓▓ | ▓▓ ▓▓ ▓▓\▓▓ \\▓▓ ▓▓\▓▓ ▓▓ ▓▓\▓▓ ▓▓ ▓▓ ▓▓ + \▓▓ \▓▓ ▓▓▓▓▓▓▓ \▓▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓▓▓▓\▓▓▓▓ \▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ + | ▓▓ | ▓▓ + | ▓▓ | ▓▓ + \▓▓ \▓▓ + * App: https://ApeSwap.finance + * Medium: https://ape-swap.medium.com + * Twitter: https://twitter.com/ape_swap * Telegram: https://t.me/ape_swap * Announcements: https://t.me/ape_swap_news + * Reddit: https://reddit.com/r/ApeSwap + * Instagram: https://instagram.com/ApeSwap.finance * GitHub: https://github.com/ApeSwapFinance */ @@ -14,17 +27,18 @@ import './interfaces/IApeFactory.sol'; import '@uniswap/lib/contracts/libraries/TransferHelper.sol'; import './interfaces/IApeRouter02.sol'; -import './libraries/ApeLibrary.sol'; +import './libraries/ApePairMath.sol'; import './libraries/SafeMath.sol'; import './interfaces/IERC20.sol'; import './interfaces/IWETH.sol'; -contract ApeRouter is IApeRouter02 { +contract ApeRouter is ApePairMath, IApeRouter02 { using SafeMath for uint; address public immutable override factory; address public immutable override WETH; - + bytes32 private _INIT_CODE_PAIR_HASH; + modifier ensure(uint deadline) { require(deadline >= block.timestamp, 'ApeRouter: EXPIRED'); _; @@ -33,12 +47,17 @@ contract ApeRouter is IApeRouter02 { constructor(address _factory, address _WETH) public { factory = _factory; WETH = _WETH; + _INIT_CODE_PAIR_HASH = IApeFactory(_factory).INIT_CODE_PAIR_HASH(); } receive() external payable { assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract } + function INIT_CODE_PAIR_HASH() internal view override returns (bytes32) { + return _INIT_CODE_PAIR_HASH; + } + // **** ADD LIQUIDITY **** function _addLiquidity( address tokenA, @@ -52,16 +71,16 @@ contract ApeRouter is IApeRouter02 { if (IApeFactory(factory).getPair(tokenA, tokenB) == address(0)) { IApeFactory(factory).createPair(tokenA, tokenB); } - (uint reserveA, uint reserveB) = ApeLibrary.getReserves(factory, tokenA, tokenB); + (uint reserveA, uint reserveB) = _getReserves(factory, tokenA, tokenB); if (reserveA == 0 && reserveB == 0) { (amountA, amountB) = (amountADesired, amountBDesired); } else { - uint amountBOptimal = ApeLibrary.quote(amountADesired, reserveA, reserveB); + uint amountBOptimal = _quote(amountADesired, reserveA, reserveB); if (amountBOptimal <= amountBDesired) { require(amountBOptimal >= amountBMin, 'ApeRouter: INSUFFICIENT_B_AMOUNT'); (amountA, amountB) = (amountADesired, amountBOptimal); } else { - uint amountAOptimal = ApeLibrary.quote(amountBDesired, reserveB, reserveA); + uint amountAOptimal = _quote(amountBDesired, reserveB, reserveA); assert(amountAOptimal <= amountADesired); require(amountAOptimal >= amountAMin, 'ApeRouter: INSUFFICIENT_A_AMOUNT'); (amountA, amountB) = (amountAOptimal, amountBDesired); @@ -79,7 +98,7 @@ contract ApeRouter is IApeRouter02 { uint deadline ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) { (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); - address pair = ApeLibrary.pairFor(factory, tokenA, tokenB); + address pair = _pairFor(factory, tokenA, tokenB); TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA); TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB); liquidity = IApePair(pair).mint(to); @@ -100,7 +119,7 @@ contract ApeRouter is IApeRouter02 { amountTokenMin, amountETHMin ); - address pair = ApeLibrary.pairFor(factory, token, WETH); + address pair = _pairFor(factory, token, WETH); TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken); IWETH(WETH).deposit{value: amountETH}(); assert(IWETH(WETH).transfer(pair, amountETH)); @@ -119,10 +138,10 @@ contract ApeRouter is IApeRouter02 { address to, uint deadline ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) { - address pair = ApeLibrary.pairFor(factory, tokenA, tokenB); + address pair = _pairFor(factory, tokenA, tokenB); TransferHelper.safeTransferFrom(pair, msg.sender, pair, liquidity); // send liquidity to pair (uint amount0, uint amount1) = IApePair(pair).burn(to); - (address token0,) = ApeLibrary.sortTokens(tokenA, tokenB); + (address token0,) = _sortTokens(tokenA, tokenB); (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); require(amountA >= amountAMin, 'ApeRouter: INSUFFICIENT_A_AMOUNT'); require(amountB >= amountBMin, 'ApeRouter: INSUFFICIENT_B_AMOUNT'); @@ -158,7 +177,7 @@ contract ApeRouter is IApeRouter02 { uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external virtual override returns (uint amountA, uint amountB) { - address pair = ApeLibrary.pairFor(factory, tokenA, tokenB); + address pair = _pairFor(factory, tokenA, tokenB); uint value = approveMax ? uint(-1) : liquidity; IApePair(pair).permit(msg.sender, address(this), value, deadline, v, r, s); (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline); @@ -172,7 +191,7 @@ contract ApeRouter is IApeRouter02 { uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external virtual override returns (uint amountToken, uint amountETH) { - address pair = ApeLibrary.pairFor(factory, token, WETH); + address pair = _pairFor(factory, token, WETH); uint value = approveMax ? uint(-1) : liquidity; IApePair(pair).permit(msg.sender, address(this), value, deadline, v, r, s); (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline); @@ -209,7 +228,7 @@ contract ApeRouter is IApeRouter02 { uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external virtual override returns (uint amountETH) { - address pair = ApeLibrary.pairFor(factory, token, WETH); + address pair = _pairFor(factory, token, WETH); uint value = approveMax ? uint(-1) : liquidity; IApePair(pair).permit(msg.sender, address(this), value, deadline, v, r, s); amountETH = removeLiquidityETHSupportingFeeOnTransferTokens( @@ -222,11 +241,11 @@ contract ApeRouter is IApeRouter02 { function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual { for (uint i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); - (address token0,) = ApeLibrary.sortTokens(input, output); + (address token0,) = _sortTokens(input, output); uint amountOut = amounts[i + 1]; (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0)); - address to = i < path.length - 2 ? ApeLibrary.pairFor(factory, output, path[i + 2]) : _to; - IApePair(ApeLibrary.pairFor(factory, input, output)).swap( + address to = i < path.length - 2 ? _pairFor(factory, output, path[i + 2]) : _to; + IApePair(_pairFor(factory, input, output)).swap( amount0Out, amount1Out, to, new bytes(0) ); } @@ -238,10 +257,10 @@ contract ApeRouter is IApeRouter02 { address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts) { - amounts = ApeLibrary.getAmountsOut(factory, amountIn, path); + amounts = _getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'ApeRouter: INSUFFICIENT_OUTPUT_AMOUNT'); TransferHelper.safeTransferFrom( - path[0], msg.sender, ApeLibrary.pairFor(factory, path[0], path[1]), amounts[0] + path[0], msg.sender, _pairFor(factory, path[0], path[1]), amounts[0] ); _swap(amounts, path, to); } @@ -252,10 +271,10 @@ contract ApeRouter is IApeRouter02 { address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts) { - amounts = ApeLibrary.getAmountsIn(factory, amountOut, path); + amounts = _getAmountsIn(factory, amountOut, path); require(amounts[0] <= amountInMax, 'ApeRouter: EXCESSIVE_INPUT_AMOUNT'); TransferHelper.safeTransferFrom( - path[0], msg.sender, ApeLibrary.pairFor(factory, path[0], path[1]), amounts[0] + path[0], msg.sender, _pairFor(factory, path[0], path[1]), amounts[0] ); _swap(amounts, path, to); } @@ -268,10 +287,10 @@ contract ApeRouter is IApeRouter02 { returns (uint[] memory amounts) { require(path[0] == WETH, 'ApeRouter: INVALID_PATH'); - amounts = ApeLibrary.getAmountsOut(factory, msg.value, path); + amounts = _getAmountsOut(factory, msg.value, path); require(amounts[amounts.length - 1] >= amountOutMin, 'ApeRouter: INSUFFICIENT_OUTPUT_AMOUNT'); IWETH(WETH).deposit{value: amounts[0]}(); - assert(IWETH(WETH).transfer(ApeLibrary.pairFor(factory, path[0], path[1]), amounts[0])); + assert(IWETH(WETH).transfer(_pairFor(factory, path[0], path[1]), amounts[0])); _swap(amounts, path, to); } function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) @@ -282,10 +301,10 @@ contract ApeRouter is IApeRouter02 { returns (uint[] memory amounts) { require(path[path.length - 1] == WETH, 'ApeRouter: INVALID_PATH'); - amounts = ApeLibrary.getAmountsIn(factory, amountOut, path); + amounts = _getAmountsIn(factory, amountOut, path); require(amounts[0] <= amountInMax, 'ApeRouter: EXCESSIVE_INPUT_AMOUNT'); TransferHelper.safeTransferFrom( - path[0], msg.sender, ApeLibrary.pairFor(factory, path[0], path[1]), amounts[0] + path[0], msg.sender, _pairFor(factory, path[0], path[1]), amounts[0] ); _swap(amounts, path, address(this)); IWETH(WETH).withdraw(amounts[amounts.length - 1]); @@ -299,10 +318,10 @@ contract ApeRouter is IApeRouter02 { returns (uint[] memory amounts) { require(path[path.length - 1] == WETH, 'ApeRouter: INVALID_PATH'); - amounts = ApeLibrary.getAmountsOut(factory, amountIn, path); + amounts = _getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'ApeRouter: INSUFFICIENT_OUTPUT_AMOUNT'); TransferHelper.safeTransferFrom( - path[0], msg.sender, ApeLibrary.pairFor(factory, path[0], path[1]), amounts[0] + path[0], msg.sender, _pairFor(factory, path[0], path[1]), amounts[0] ); _swap(amounts, path, address(this)); IWETH(WETH).withdraw(amounts[amounts.length - 1]); @@ -317,10 +336,10 @@ contract ApeRouter is IApeRouter02 { returns (uint[] memory amounts) { require(path[0] == WETH, 'ApeRouter: INVALID_PATH'); - amounts = ApeLibrary.getAmountsIn(factory, amountOut, path); + amounts = _getAmountsIn(factory, amountOut, path); require(amounts[0] <= msg.value, 'ApeRouter: EXCESSIVE_INPUT_AMOUNT'); IWETH(WETH).deposit{value: amounts[0]}(); - assert(IWETH(WETH).transfer(ApeLibrary.pairFor(factory, path[0], path[1]), amounts[0])); + assert(IWETH(WETH).transfer(_pairFor(factory, path[0], path[1]), amounts[0])); _swap(amounts, path, to); // refund dust eth, if any if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]); @@ -331,18 +350,18 @@ contract ApeRouter is IApeRouter02 { function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual { for (uint i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); - (address token0,) = ApeLibrary.sortTokens(input, output); - IApePair pair = IApePair(ApeLibrary.pairFor(factory, input, output)); + (address token0,) = _sortTokens(input, output); + IApePair pair = IApePair(_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 = IERC20(input).balanceOf(address(pair)).sub(reserveInput); - amountOutput = ApeLibrary.getAmountOut(amountInput, reserveInput, reserveOutput); + amountOutput = _getAmountOut(amountInput, reserveInput, reserveOutput); } (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0)); - address to = i < path.length - 2 ? ApeLibrary.pairFor(factory, output, path[i + 2]) : _to; + address to = i < path.length - 2 ? _pairFor(factory, output, path[i + 2]) : _to; pair.swap(amount0Out, amount1Out, to, new bytes(0)); } } @@ -354,7 +373,7 @@ contract ApeRouter is IApeRouter02 { uint deadline ) external virtual override ensure(deadline) { TransferHelper.safeTransferFrom( - path[0], msg.sender, ApeLibrary.pairFor(factory, path[0], path[1]), amountIn + path[0], msg.sender, _pairFor(factory, path[0], path[1]), amountIn ); uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to); _swapSupportingFeeOnTransferTokens(path, to); @@ -378,7 +397,7 @@ contract ApeRouter is IApeRouter02 { require(path[0] == WETH, 'ApeRouter: INVALID_PATH'); uint amountIn = msg.value; IWETH(WETH).deposit{value: amountIn}(); - assert(IWETH(WETH).transfer(ApeLibrary.pairFor(factory, path[0], path[1]), amountIn)); + assert(IWETH(WETH).transfer(_pairFor(factory, path[0], path[1]), amountIn)); uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to); _swapSupportingFeeOnTransferTokens(path, to); require( @@ -400,7 +419,7 @@ contract ApeRouter is IApeRouter02 { { require(path[path.length - 1] == WETH, 'ApeRouter: INVALID_PATH'); TransferHelper.safeTransferFrom( - path[0], msg.sender, ApeLibrary.pairFor(factory, path[0], path[1]), amountIn + path[0], msg.sender, _pairFor(factory, path[0], path[1]), amountIn ); _swapSupportingFeeOnTransferTokens(path, address(this)); uint amountOut = IERC20(WETH).balanceOf(address(this)); @@ -410,47 +429,47 @@ contract ApeRouter is IApeRouter02 { } // **** LIBRARY FUNCTIONS **** - function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) { - return ApeLibrary.quote(amountA, reserveA, reserveB); + function quote(uint amountA, uint reserveA, uint reserveB) external pure virtual override returns (uint amountB) { + return _quote(amountA, reserveA, reserveB); } function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) - public + external pure virtual override returns (uint amountOut) { - return ApeLibrary.getAmountOut(amountIn, reserveIn, reserveOut); + return _getAmountOut(amountIn, reserveIn, reserveOut); } function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) - public + external pure virtual override returns (uint amountIn) { - return ApeLibrary.getAmountIn(amountOut, reserveIn, reserveOut); + return _getAmountIn(amountOut, reserveIn, reserveOut); } - function getAmountsOut(uint amountIn, address[] memory path) - public + function getAmountsOut(uint amountIn, address[] calldata path) + external view virtual override returns (uint[] memory amounts) { - return ApeLibrary.getAmountsOut(factory, amountIn, path); + return _getAmountsOut(factory, amountIn, path); } - function getAmountsIn(uint amountOut, address[] memory path) - public + function getAmountsIn(uint amountOut, address[] calldata path) + external view virtual override returns (uint[] memory amounts) { - return ApeLibrary.getAmountsIn(factory, amountOut, path); + return _getAmountsIn(factory, amountOut, path); } } diff --git a/contracts/interfaces/IApeFactory.sol b/contracts/interfaces/IApeFactory.sol index 3d79091..332b69b 100644 --- a/contracts/interfaces/IApeFactory.sol +++ b/contracts/interfaces/IApeFactory.sol @@ -3,6 +3,8 @@ pragma solidity >=0.6.6; interface IApeFactory { event PairCreated(address indexed token0, address indexed token1, address pair, uint); + function INIT_CODE_PAIR_HASH() external view returns (bytes32); + function feeTo() external view returns (address); function feeToSetter() external view returns (address); diff --git a/contracts/libraries/ApeLibrary.sol b/contracts/libraries/ApeLibrary.sol index 1a09659..e6fe318 100644 --- a/contracts/libraries/ApeLibrary.sol +++ b/contracts/libraries/ApeLibrary.sol @@ -1,12 +1,25 @@ pragma solidity >=0.5.0; /* - * ApeSwapFinance - * App: https://apeswap.finance + ______ ______ + / \ / \ +| ▓▓▓▓▓▓\ ______ ______ | ▓▓▓▓▓▓\__ __ __ ______ ______ +| ▓▓__| ▓▓/ \ / \| ▓▓___\▓▓ \ | \ | \| \ / \ +| ▓▓ ▓▓ ▓▓▓▓▓▓\ ▓▓▓▓▓▓\\▓▓ \| ▓▓ | ▓▓ | ▓▓ \▓▓▓▓▓▓\ ▓▓▓▓▓▓\ +| ▓▓▓▓▓▓▓▓ ▓▓ | ▓▓ ▓▓ ▓▓_\▓▓▓▓▓▓\ ▓▓ | ▓▓ | ▓▓/ ▓▓ ▓▓ | ▓▓ +| ▓▓ | ▓▓ ▓▓__/ ▓▓ ▓▓▓▓▓▓▓▓ \__| ▓▓ ▓▓_/ ▓▓_/ ▓▓ ▓▓▓▓▓▓▓ ▓▓__/ ▓▓ +| ▓▓ | ▓▓ ▓▓ ▓▓\▓▓ \\▓▓ ▓▓\▓▓ ▓▓ ▓▓\▓▓ ▓▓ ▓▓ ▓▓ + \▓▓ \▓▓ ▓▓▓▓▓▓▓ \▓▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓▓▓▓\▓▓▓▓ \▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ + | ▓▓ | ▓▓ + | ▓▓ | ▓▓ + \▓▓ \▓▓ + * App: https://ApeSwap.finance * Medium: https://ape-swap.medium.com - * Twitter: https://twitter.com/ape_swap + * Twitter: https://twitter.com/ape_swap * Telegram: https://t.me/ape_swap * Announcements: https://t.me/ape_swap_news + * Reddit: https://reddit.com/r/ApeSwap + * Instagram: https://instagram.com/ApeSwap.finance * GitHub: https://github.com/ApeSwapFinance */ diff --git a/contracts/libraries/ApePairMath.sol b/contracts/libraries/ApePairMath.sol new file mode 100644 index 0000000..b0827ce --- /dev/null +++ b/contracts/libraries/ApePairMath.sol @@ -0,0 +1,107 @@ +pragma solidity >=0.5.0; + +/* + ______ ______ + / \ / \ +| ▓▓▓▓▓▓\ ______ ______ | ▓▓▓▓▓▓\__ __ __ ______ ______ +| ▓▓__| ▓▓/ \ / \| ▓▓___\▓▓ \ | \ | \| \ / \ +| ▓▓ ▓▓ ▓▓▓▓▓▓\ ▓▓▓▓▓▓\\▓▓ \| ▓▓ | ▓▓ | ▓▓ \▓▓▓▓▓▓\ ▓▓▓▓▓▓\ +| ▓▓▓▓▓▓▓▓ ▓▓ | ▓▓ ▓▓ ▓▓_\▓▓▓▓▓▓\ ▓▓ | ▓▓ | ▓▓/ ▓▓ ▓▓ | ▓▓ +| ▓▓ | ▓▓ ▓▓__/ ▓▓ ▓▓▓▓▓▓▓▓ \__| ▓▓ ▓▓_/ ▓▓_/ ▓▓ ▓▓▓▓▓▓▓ ▓▓__/ ▓▓ +| ▓▓ | ▓▓ ▓▓ ▓▓\▓▓ \\▓▓ ▓▓\▓▓ ▓▓ ▓▓\▓▓ ▓▓ ▓▓ ▓▓ + \▓▓ \▓▓ ▓▓▓▓▓▓▓ \▓▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓▓▓▓\▓▓▓▓ \▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ + | ▓▓ | ▓▓ + | ▓▓ | ▓▓ + \▓▓ \▓▓ + * App: https://ApeSwap.finance + * Medium: https://ape-swap.medium.com + * Twitter: https://twitter.com/ape_swap + * Telegram: https://t.me/ape_swap + * Announcements: https://t.me/ape_swap_news + * Reddit: https://reddit.com/r/ApeSwap + * Instagram: https://instagram.com/ApeSwap.finance + * GitHub: https://github.com/ApeSwapFinance + */ + +import '../interfaces/IApePair.sol'; +import "./SafeMath.sol"; + +abstract contract ApePairMath { + using SafeMath for uint; + + function INIT_CODE_PAIR_HASH() internal view virtual returns (bytes32); + + // returns sorted token addresses, used to handle return values from pairs sorted in this order + function _sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { + require(tokenA != tokenB, 'ApePairMath: IDENTICAL_ADDRESSES'); + (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + require(token0 != address(0), 'ApePairMath: ZERO_ADDRESS'); + } + + // calculates the CREATE2 address for a pair without making any external calls + function _pairFor(address factory, address tokenA, address tokenB) internal view returns (address pair) { + (address token0, address token1) = _sortTokens(tokenA, tokenB); + pair = address(uint(keccak256(abi.encodePacked( + hex'ff', + factory, + keccak256(abi.encodePacked(token0, token1)), + INIT_CODE_PAIR_HASH() + )))); + } + + // fetches and sorts the reserves for a pair + function _getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) { + (address token0,) = _sortTokens(tokenA, tokenB); + _pairFor(factory, tokenA, tokenB); + (uint reserve0, uint reserve1,) = IApePair(_pairFor(factory, tokenA, tokenB)).getReserves(); + (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + } + + // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset + function _quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) { + require(amountA > 0, 'ApePairMath: INSUFFICIENT_AMOUNT'); + require(reserveA > 0 && reserveB > 0, 'ApePairMath: INSUFFICIENT_LIQUIDITY'); + amountB = amountA.mul(reserveB) / reserveA; + } + + // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset + function _getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) { + require(amountIn > 0, 'ApePairMath: INSUFFICIENT_INPUT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'ApePairMath: INSUFFICIENT_LIQUIDITY'); + uint amountInWithFee = amountIn.mul(998); + uint numerator = amountInWithFee.mul(reserveOut); + uint denominator = reserveIn.mul(1000).add(amountInWithFee); + amountOut = numerator / denominator; + } + + // given an output amount of an asset and pair reserves, returns a required input amount of the other asset + function _getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) { + require(amountOut > 0, 'ApePairMath: INSUFFICIENT_OUTPUT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'ApePairMath: INSUFFICIENT_LIQUIDITY'); + uint numerator = reserveIn.mul(amountOut).mul(1000); + uint denominator = reserveOut.sub(amountOut).mul(998); + amountIn = (numerator / denominator).add(1); + } + + // performs chained getAmountOut calculations on any number of pairs + function _getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) { + require(path.length >= 2, 'ApePairMath: INVALID_PATH'); + amounts = new uint[](path.length); + amounts[0] = amountIn; + for (uint i; i < path.length - 1; i++) { + (uint reserveIn, uint reserveOut) = _getReserves(factory, path[i], path[i + 1]); + amounts[i + 1] = _getAmountOut(amounts[i], reserveIn, reserveOut); + } + } + + // performs chained getAmountIn calculations on any number of pairs + function _getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) { + require(path.length >= 2, 'ApePairMath: INVALID_PATH'); + amounts = new uint[](path.length); + amounts[amounts.length - 1] = amountOut; + for (uint i = path.length - 1; i > 0; i--) { + (uint reserveIn, uint reserveOut) = _getReserves(factory, path[i - 1], path[i]); + amounts[i - 1] = _getAmountIn(amounts[i], reserveIn, reserveOut); + } + } +} From 811bace30fd29281b80ba4da9cc4942ff7f33225 Mon Sep 17 00:00:00 2001 From: defifofum Date: Thu, 9 Mar 2023 12:07:29 -0600 Subject: [PATCH 2/3] refactor: Make INIT_CODE_HASH public --- contracts/ApeRouter.sol | 2 +- contracts/libraries/ApePairMath.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/ApeRouter.sol b/contracts/ApeRouter.sol index bf4073e..7dca881 100644 --- a/contracts/ApeRouter.sol +++ b/contracts/ApeRouter.sol @@ -54,7 +54,7 @@ contract ApeRouter is ApePairMath, IApeRouter02 { assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract } - function INIT_CODE_PAIR_HASH() internal view override returns (bytes32) { + function INIT_CODE_PAIR_HASH() public view override returns (bytes32) { return _INIT_CODE_PAIR_HASH; } diff --git a/contracts/libraries/ApePairMath.sol b/contracts/libraries/ApePairMath.sol index b0827ce..c9094bc 100644 --- a/contracts/libraries/ApePairMath.sol +++ b/contracts/libraries/ApePairMath.sol @@ -29,7 +29,7 @@ import "./SafeMath.sol"; abstract contract ApePairMath { using SafeMath for uint; - function INIT_CODE_PAIR_HASH() internal view virtual returns (bytes32); + function INIT_CODE_PAIR_HASH() public view virtual returns (bytes32); // returns sorted token addresses, used to handle return values from pairs sorted in this order function _sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { From 473beb9d692c14fe27229d5c1e5aec6758549cf9 Mon Sep 17 00:00:00 2001 From: defifofum Date: Fri, 10 Mar 2023 10:01:40 -0600 Subject: [PATCH 3/3] fix: Arbitrum price getter --- .../utils/ApeOnlyPriceGetterArbitrum.sol | 292 +++++++++++------- 1 file changed, 172 insertions(+), 120 deletions(-) diff --git a/contracts/utils/ApeOnlyPriceGetterArbitrum.sol b/contracts/utils/ApeOnlyPriceGetterArbitrum.sol index 3d4d060..4ebb0f0 100644 --- a/contracts/utils/ApeOnlyPriceGetterArbitrum.sol +++ b/contracts/utils/ApeOnlyPriceGetterArbitrum.sol @@ -1,47 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.6.6; - -/** - * @notice DISCLAIMER: BETA SOFTWARE, PROVIDED AS IS WITH NO WARRANTIES WHATSOEVER. - * This library provides simple price calculations for ApeSwap tokens, accounting - * for commonly used pairings. Will break if USDT, USDC, or DAI goes far off peg. - * - * MUST NOT be used as the sole oracle for sensitive calculations such as - * liquidation, as it is vulnerable to manipulation by flash loans, etc. - */ - -import '../interfaces/IApePair.sol'; -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; - -/** - * @dev Interface for the optional metadata functions from the ERC20 standard. - * - * _Available since v4.1._ - */ -interface IERC20Metadata is IERC20 { - /** - * @dev Returns the name of the token. - */ - function name() external view returns (string memory); - - /** - * @dev Returns the symbol of the token. - */ - function symbol() external view returns (string memory); - - /** - * @dev Returns the decimals places of the token. - */ - function decimals() external view returns (uint8); -} +pragma solidity =0.6.6; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../interfaces/IApePair.sol"; + +// This library provides simple price calculations for ApeSwap tokens, accounting +// for commonly used pairings. Will break if USDT, USDC, or DAI goes far off peg. +// Should NOT be used as the sole oracle for sensitive calculations such as +// liquidation, as it is vulnerable to manipulation by flash loans, etc. BETA +// SOFTWARE, PROVIDED AS IS WITH NO WARRANTIES WHATSOEVER. + + library ApeOnlyPriceGetterArbitrum { - address public constant FACTORY = 0xCf083Be4164828f00cAE704EC15a36D711491284; //ApeFactory - bytes32 public constant INITCODEHASH = hex'ae7373e804a043c4c08107a81def627eeb3792e211fb4711fcfe32f0e4c45fd5'; // for pairs created by ApeFactory + address public constant FACTORY = 0xCf083Be4164828f00cAE704EC15a36D711491284; //ApeSwap Factory + + bytes32 public constant INITCODEHASH = + hex"ae7373e804a043c4c08107a81def627eeb3792e211fb4711fcfe32f0e4c45fd5"; // for pairs created by ApeFactory //All returned prices calculated with this precision (18 decimals) - uint public constant DECIMALS = 18; - uint private constant PRECISION = 10 ** DECIMALS; //1e18 == $1 + uint256 private constant PRECISION = 10**DECIMALS; //1e18 == $1 + uint256 public constant DECIMALS = 18; //Token addresses address constant WNATIVE = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; @@ -50,113 +29,155 @@ library ApeOnlyPriceGetterArbitrum { address constant USDT = 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; //Token value constants - uint private constant USC_RAW_PRICE = 1e18; + uint256 private constant USDC_USDT_RAW_PRICE = 1e6; - //Normalized to specified number of decimals based on token's decimals and - //specified number of decimals - function getPrice(address token, uint _decimals) external view returns (uint) { + //Ape LP addresses + address constant USDC_WNATIVE_PAIR = 0xC53e453E4A6953887bf447162D1dC9E1e7f16f60; // usdc is token1 + address constant DAI_WNATIVE_PAIR = 0xeBca41a83b658519F9Cf9fEB63CAe9f2A5112023; // dai is token1 + address constant USDT_WNATIVE_PAIR = 0xBEb125e43B46F757ece0428cdE20cce336aF962E; // usdt is token1 + + //Normalized to specified number of decimals based on token's decimals and specified number of decimals + + function getPrice(address token, uint256 _decimals) + external + view + returns (uint256) + { return normalize(getRawPrice(token), token, _decimals); } - function getLPPrice(address token, uint _decimals) external view returns (uint) { + function getLPPrice(address token, uint256 _decimals) + external + view + returns (uint256) + { return normalize(getRawLPPrice(token), token, _decimals); } - function getPrices(address[] calldata tokens, uint _decimals) external view returns (uint[] memory prices) { + function getPrices(address[] calldata tokens, uint256 _decimals) + external + view + returns (uint256[] memory prices) + { prices = getRawPrices(tokens); - for (uint i; i < prices.length; i++) { + for (uint256 i; i < prices.length; i++) { prices[i] = normalize(prices[i], tokens[i], _decimals); } } - function getLPPrices(address[] calldata tokens, uint _decimals) external view returns (uint[] memory prices) { + function getLPPrices(address[] calldata tokens, uint256 _decimals) + external + view + returns (uint256[] memory prices) + { prices = getRawLPPrices(tokens); - for (uint i; i < prices.length; i++) { + for (uint256 i; i < prices.length; i++) { prices[i] = normalize(prices[i], tokens[i], _decimals); } } //returns the price of any token in USD based on common pairings; zero on failure - function getRawPrice(address token) public view returns (uint) { - uint pegPrice = pegTokenPrice(token); + function getRawPrice(address token) public view returns (uint256) { + uint256 pegPrice = pegTokenPrice(token); if (pegPrice != 0) return pegPrice; return getRawPrice(token, getNativePrice()); } //returns the prices of multiple tokens, zero on failure - function getRawPrices(address[] memory tokens) public view returns (uint[] memory prices) { - prices = new uint[](tokens.length); - uint nativePrice = getNativePrice(); + function getRawPrices(address[] memory tokens) + public + view + returns (uint256[] memory prices) + { + prices = new uint256[](tokens.length); + uint256 nativePrice = getNativePrice(); - for (uint i; i < prices.length; i++) { + for (uint256 i; i < prices.length; i++) { address token = tokens[i]; - - uint pegPrice = pegTokenPrice(token, nativePrice); + uint256 pegPrice = pegTokenPrice(token, nativePrice); if (pegPrice != 0) prices[i] = pegPrice; else prices[i] = getRawPrice(token, nativePrice); } } - // returns the current USD price of the Native currency based on primary stablecoin pairs - function getNativePrice() public view returns (uint) { - (uint daiReserve0, uint daiReserve1, ) = IApePair(pairFor(WNATIVE, DAI)).getReserves(); - uint nativeTotal = tokensAreSorted(WNATIVE, DAI) ? daiReserve0 : daiReserve1; - uint usdTotal = tokensAreSorted(WNATIVE, DAI) ? daiReserve1 : daiReserve0; - - (uint usdcReserve0, uint usdcReserve1, ) = IApePair(pairFor(WNATIVE, USDC)).getReserves(); - nativeTotal += tokensAreSorted(WNATIVE, USDC) ? usdcReserve0 : usdcReserve1; - usdTotal += tokensAreSorted(WNATIVE, USDC) ? usdcReserve1 : usdcReserve0; - - (uint usdtReserve0, uint usdtReserve1, ) = IApePair(pairFor(WNATIVE, USDT)).getReserves(); - nativeTotal += tokensAreSorted(WNATIVE, USDT) ? usdtReserve0 : usdtReserve1; - usdTotal += tokensAreSorted(WNATIVE, USDT) ? usdtReserve1 : usdtReserve0; - - return (usdTotal * PRECISION) / nativeTotal; - } - //returns the value of a LP token if it is one, or the regular price if it isn't LP - function getRawLPPrice(address token) internal view returns (uint) { - uint pegPrice = pegTokenPrice(token); + function getRawLPPrice(address token) internal view returns (uint256) { + uint256 pegPrice = pegTokenPrice(token); if (pegPrice != 0) return pegPrice; + return getRawLPPrice(token, getNativePrice()); } //returns the prices of multiple tokens which may or may not be LPs - function getRawLPPrices(address[] memory tokens) internal view returns (uint[] memory prices) { - prices = new uint[](tokens.length); - uint nativePrice = getNativePrice(); + function getRawLPPrices(address[] memory tokens) + internal + view + returns (uint256[] memory prices) + { + prices = new uint256[](tokens.length); + uint256 nativePrice = getNativePrice(); - for (uint i; i < prices.length; i++) { + for (uint256 i; i < prices.length; i++) { address token = tokens[i]; - - uint pegPrice = pegTokenPrice(token, nativePrice); + uint256 pegPrice = pegTokenPrice(token, nativePrice); if (pegPrice != 0) prices[i] = pegPrice; else prices[i] = getRawLPPrice(token, nativePrice); } } + //returns the current USD price of BNB based on primary stablecoin pairs + function getNativePrice() public view returns (uint256) { + /// @dev WBNB happens to be token0 for each pair + (uint256 wNativeReserve0, uint256 daiReserve, ) = IApePair(DAI_WNATIVE_PAIR) + .getReserves(); + + (uint256 wNativeReserve1, uint256 usdcReserve, ) = IApePair(USDC_WNATIVE_PAIR) + .getReserves(); + + (uint256 wNativeReserve2, uint256 usdtReserve, ) = IApePair(USDT_WNATIVE_PAIR) + .getReserves(); + + uint256 wNativeTotal = wNativeReserve0 + wNativeReserve1 + wNativeReserve2; + uint256 usdTotal = daiReserve * PRECISION + (usdcReserve + usdtReserve) * PRECISION / USDC_USDT_RAW_PRICE * PRECISION; + + return usdTotal / wNativeTotal; + } + //Calculate LP token value in USD. Generally compatible with any UniswapV2 pair but will always price underlying //tokens using ape prices. If the provided token is not a LP, it will attempt to price the token as a //standard token. This is useful for MasterChef farms which stake both single tokens and pairs - function getRawLPPrice(address lp, uint nativePrice) internal view returns (uint) { - //if not a LP, handle as a standard token - try IApePair(lp).getReserves() returns (uint112 reserve0, uint112 reserve1, uint32) { + function getRawLPPrice(address lp, uint256 nativePrice) + internal + view + returns (uint256) + { + //if not a LP, handle as a standard token + try IApePair(lp).getReserves() returns ( + uint112 reserve0, + uint112 reserve1, + uint32 + ) { address token0 = IApePair(lp).token0(); address token1 = IApePair(lp).token1(); - uint totalSupply = IApePair(lp).totalSupply(); + uint256 totalSupply = IApePair(lp).totalSupply(); //price0*reserve0+price1*reserve1 - uint totalValue = getRawPrice(token0, nativePrice) * reserve0 + getRawPrice(token1, nativePrice) * reserve1; + + uint256 totalValue = getRawPrice(token0, nativePrice) * + reserve0 + + getRawPrice(token1, nativePrice) * + reserve1; + return totalValue / totalSupply; } catch { return getRawPrice(lp, nativePrice); @@ -164,15 +185,19 @@ library ApeOnlyPriceGetterArbitrum { } // checks for primary tokens and returns the correct predetermined price if possible, otherwise calculates price - function getRawPrice(address token, uint nativePrice) internal view returns (uint rawPrice) { - uint pegPrice = pegTokenPrice(token, nativePrice); + function getRawPrice(address token, uint256 nativePrice) + internal + view + returns (uint256 rawPrice) + { + uint256 pegPrice = pegTokenPrice(token, nativePrice); if (pegPrice != 0) return pegPrice; - uint numTokens; - uint pairedValue; - uint lpTokens; - uint lpValue; + uint256 numTokens; + uint256 pairedValue; + uint256 lpTokens; + uint256 lpValue; (lpTokens, lpValue) = pairTokensAndValue(token, WNATIVE); numTokens += lpTokens; @@ -194,33 +219,52 @@ library ApeOnlyPriceGetterArbitrum { } //if one of the peg tokens, returns that price, otherwise zero - - function pegTokenPrice(address token, uint nativePrice) private pure returns (uint) { - if (token == USDT || token == USDC || token == DAI) return PRECISION; + function pegTokenPrice(address token, uint256 nativePrice) + private + pure + returns (uint256) + { + if (token == USDC || token == USDT) return PRECISION * 1e12; + if (token == DAI) return PRECISION; if (token == WNATIVE) return nativePrice; return 0; } - function pegTokenPrice(address token) private view returns (uint) { - if (token == USDT || token == USDC || token == DAI) return PRECISION; + function pegTokenPrice(address token) private view returns (uint256) { + if (token == USDC || token == USDT) return PRECISION * 1e12; + if (token == DAI) return PRECISION; if (token == WNATIVE) return getNativePrice(); return 0; } //returns the number of tokens and the USD value within a single LP. peg is one of the listed primary, pegPrice is the predetermined USD value of this token - function pairTokensAndValue(address token, address peg) private view returns (uint tokenNum, uint pegValue) { + function pairTokensAndValue(address token, address peg) + private + view + returns (uint256 tokenNum, uint256 pegValue) + { address tokenPegPair = pairFor(token, peg); + // if the address has no contract deployed, the pair doesn't exist uint256 size; + assembly { size := extcodesize(tokenPegPair) } if (size == 0) return (0, 0); - try IApePair(tokenPegPair).getReserves() returns (uint112 reserve0, uint112 reserve1, uint32) { - uint reservePeg; - (tokenNum, reservePeg) = token < peg ? (reserve0, reserve1) : (reserve1, reserve0); + try IApePair(tokenPegPair).getReserves() returns ( + uint112 reserve0, + uint112 reserve1, + uint32 + ) { + uint256 reservePeg; + + (tokenNum, reservePeg) = token < peg + ? (reserve0, reserve1) + : (reserve1, reserve0); + pegValue = reservePeg * pegTokenPrice(peg); } catch { return (0, 0); @@ -228,39 +272,47 @@ library ApeOnlyPriceGetterArbitrum { } //normalize a token price to a specified number of decimals - function normalize(uint price, address token, uint _decimals) private view returns (uint) { - uint tokenDecimals; - - try IERC20Metadata(token).decimals() returns (uint8 dec) { + function normalize( + uint256 price, + address token, + uint256 _decimals + ) private view returns (uint256) { + uint256 tokenDecimals; + + try ERC20(token).decimals() returns (uint8 dec) { tokenDecimals = dec; } catch { tokenDecimals = 18; } - if (tokenDecimals + _decimals <= 2 * DECIMALS) return price / 10 ** (2 * DECIMALS - tokenDecimals - _decimals); - else return price * 10 ** (_decimals + tokenDecimals - 2 * DECIMALS); - } - - function tokensAreSorted(address tokenA, address tokenB) private pure returns (bool) { - return tokenA < tokenB; - } - - function sortTokens(address tokenA, address tokenB) private pure returns (address token0, address token1) { - (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + if (tokenDecimals + _decimals <= 2 * DECIMALS) + return price / 10**(2 * DECIMALS - tokenDecimals - _decimals); + else return price * 10**(_decimals + tokenDecimals - 2 * DECIMALS); } // calculates the CREATE2 address for a pair without making any external calls - function pairFor(address tokenA, address tokenB) private pure returns (address pair) { - (address token0, address token1) = sortTokens(tokenA, tokenB); + function pairFor(address tokenA, address tokenB) + private + pure + returns (address pair) + { + (address token0, address token1) = tokenA < tokenB + ? (tokenA, tokenB) + : (tokenB, tokenA); pair = address( uint160( - uint( + uint256( keccak256( - abi.encodePacked(hex'ff', FACTORY, keccak256(abi.encodePacked(token0, token1)), INITCODEHASH) + abi.encodePacked( + hex"ff", + FACTORY, + keccak256(abi.encodePacked(token0, token1)), + INITCODEHASH + ) ) ) ) ); } -} +} \ No newline at end of file