diff --git a/contracts/adapters/BaseUniswapAdapter.sol b/contracts/adapters/BaseUniswapAdapter.sol index 2e52742b..9dbdfbdf 100644 --- a/contracts/adapters/BaseUniswapAdapter.sol +++ b/contracts/adapters/BaseUniswapAdapter.sol @@ -40,6 +40,13 @@ contract BaseUniswapAdapter { bytes32 s; } + struct AmountCalc { + uint256 calculatedAmount; + uint256 relativePrice; + uint256 amountInUsd; + uint256 amountOutUsd; + } + // Max slippage percent allowed uint256 public constant MAX_SLIPPAGE_PERCENT = 3000; // 30% // FLash Loan fee set in lending pool @@ -64,7 +71,7 @@ contract BaseUniswapAdapter { * @param amountIn Amount of reserveIn * @param reserveIn Address of the asset to be swap from * @param reserveOut Address of the asset to be swap to - * @return uint256 Amount out fo the reserveOut + * @return uint256 Amount out of the reserveOut * @return uint256 The price of out amount denominated in the reserveIn currency (18 decimals) * @return uint256 In amount of reserveIn value denominated in USD (8 decimals) * @return uint256 Out amount of reserveOut value denominated in USD (8 decimals) @@ -74,46 +81,39 @@ contract BaseUniswapAdapter { view returns (uint256, uint256, uint256, uint256) { - // Subtract flash loan fee - uint256 finalAmountIn = amountIn.sub(amountIn.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000)); - - uint256 amountOut = _getAmountsOut(reserveIn, reserveOut, finalAmountIn); - - uint256 reserveInDecimals = _getDecimals(reserveIn); - uint256 reserveOutDecimals = _getDecimals(reserveOut); - - uint256 outPerInPrice = finalAmountIn - .mul(10**18) - .mul(10**reserveOutDecimals) - .div(amountOut.mul(10**reserveInDecimals)); + AmountCalc memory results = _getAmountsOut(reserveIn, reserveOut, amountIn); return ( - amountOut, - outPerInPrice, - _calcUsdValue(reserveIn, amountIn, reserveInDecimals), - _calcUsdValue(reserveOut, amountOut, reserveOutDecimals) + results.calculatedAmount, + results.relativePrice, + results.amountInUsd, + results.amountOutUsd ); } /** - * @dev Returns the minimum input asset amount required to buy the given output asset amount + * @dev Returns the minimum input asset amount required to buy the given output asset amount and the prices * @param amountOut Amount of reserveOut * @param reserveIn Address of the asset to be swap from * @param reserveOut Address of the asset to be swap to - * @return uint256 amountIn + * @return uint256 Amount in of the reserveIn + * @return uint256 The price of in amount denominated in the reserveOut currency (18 decimals) + * @return uint256 In amount of reserveIn value denominated in USD (8 decimals) + * @return uint256 Out amount of reserveOut value denominated in USD (8 decimals) */ - function getAmountIn(uint256 amountOut, address reserveIn, address reserveOut) + function getAmountsIn(uint256 amountOut, address reserveIn, address reserveOut) external view - returns (uint256) + returns (uint256, uint256, uint256, uint256) { - address[] memory path = new address[](2); - path[0] = reserveIn; - path[1] = reserveOut; + AmountCalc memory results = _getAmountsIn(reserveIn, reserveOut, amountOut); - uint256[] memory amounts = UNISWAP_ROUTER.getAmountsIn(amountOut, path); - - return amounts[0]; + return ( + results.calculatedAmount, + results.relativePrice, + results.amountInUsd, + results.amountOutUsd + ); } /** @@ -337,15 +337,72 @@ contract BaseUniswapAdapter { * @param reserveIn Address of the asset to be swap from * @param reserveOut Address of the asset to be swap to * @param amountIn Amount of reserveIn - * @return the output amount + * @return Struct containing the following information: + * uint256 Amount out of the reserveOut + * uint256 The price of out amount denominated in the reserveIn currency (18 decimals) + * uint256 In amount of reserveIn value denominated in USD (8 decimals) + * uint256 Out amount of reserveOut value denominated in USD (8 decimals) */ - function _getAmountsOut(address reserveIn, address reserveOut, uint256 amountIn) internal view returns (uint256) { + function _getAmountsOut(address reserveIn, address reserveOut, uint256 amountIn) internal view returns (AmountCalc memory) { + // Subtract flash loan fee + uint256 finalAmountIn = amountIn.sub(amountIn.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000)); + address[] memory path = new address[](2); path[0] = reserveIn; path[1] = reserveOut; - uint256[] memory amounts = UNISWAP_ROUTER.getAmountsOut(amountIn, path); + uint256[] memory amounts = UNISWAP_ROUTER.getAmountsOut(finalAmountIn, path); - return amounts[1]; + uint256 reserveInDecimals = _getDecimals(reserveIn); + uint256 reserveOutDecimals = _getDecimals(reserveOut); + + uint256 outPerInPrice = finalAmountIn + .mul(10**18) + .mul(10**reserveOutDecimals) + .div(amounts[1].mul(10**reserveInDecimals)); + + return AmountCalc( + amounts[1], + outPerInPrice, + _calcUsdValue(reserveIn, amountIn, reserveInDecimals), + _calcUsdValue(reserveOut, amounts[1], reserveOutDecimals) + ); + } + + /** + * @dev Returns the minimum input asset amount required to buy the given output asset amount + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @param amountOut Amount of reserveOut + * @return Struct containing the following information: + * uint256 Amount in of the reserveIn + * uint256 The price of in amount denominated in the reserveOut currency (18 decimals) + * uint256 In amount of reserveIn value denominated in USD (8 decimals) + * uint256 Out amount of reserveOut value denominated in USD (8 decimals) + */ + function _getAmountsIn(address reserveIn, address reserveOut, uint256 amountOut) internal view returns (AmountCalc memory) { + address[] memory path = new address[](2); + path[0] = reserveIn; + path[1] = reserveOut; + + uint256[] memory amounts = UNISWAP_ROUTER.getAmountsIn(amountOut, path); + + // Subtract flash loan fee + uint256 finalAmountIn = amounts[0].sub(amounts[0].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000)); + + uint256 reserveInDecimals = _getDecimals(reserveIn); + uint256 reserveOutDecimals = _getDecimals(reserveOut); + + uint256 inPerOutPrice = amountOut + .mul(10**18) + .mul(10**reserveInDecimals) + .div(finalAmountIn.mul(10**reserveOutDecimals)); + + return AmountCalc( + finalAmountIn, + inPerOutPrice, + _calcUsdValue(reserveIn, finalAmountIn, reserveInDecimals), + _calcUsdValue(reserveOut, amountOut, reserveOutDecimals) + ); } } diff --git a/test/uniswapAdapters.spec.ts b/test/uniswapAdapters.spec.ts index 1ebc21f8..7dc7f03c 100644 --- a/test/uniswapAdapters.spec.ts +++ b/test/uniswapAdapters.spec.ts @@ -109,7 +109,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const outPerInPrice = amountToSwap .mul(parseEther('1')) - .mul('1000000') + .mul('1000000') // usdc 6 decimals .div(expectedUSDCAmount.mul(parseEther('1'))); const lendUsdValue = amountIn @@ -120,7 +120,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const usdcUsdValue = expectedUSDCAmount .mul(usdcPrice) - .div('1000000') + .div('1000000') // usdc 6 decimals .mul(usdPrice) .div(parseEther('1')); @@ -144,17 +144,97 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { }); }); - describe('getAmountIn', () => { + describe('getAmountsIn', () => { it('should return the estimated required amountIn for the asset swap', async () => { - const {weth, dai, uniswapLiquiditySwapAdapter} = testEnv; + const {weth, dai, uniswapLiquiditySwapAdapter, oracle} = testEnv; + const amountIn = parseEther('1'); - const amountOut = parseEther('2'); + const flashloanPremium = amountIn.mul(9).div(10000); + const amountToSwap = amountIn.sub(flashloanPremium); + + const wethPrice = await oracle.getAssetPrice(weth.address); + const daiPrice = await oracle.getAssetPrice(dai.address); + const usdPrice = await oracle.getAssetPrice(USD_ADDRESS); + + const amountOut = await convertToCurrencyDecimals( + dai.address, + new BigNumber(amountIn.toString()).div(daiPrice.toString()).toFixed(0) + ); + + const inPerOutPrice = amountOut + .mul(parseEther('1')) + .mul(parseEther('1')) + .div(amountToSwap.mul(parseEther('1'))); + + const ethUsdValue = amountToSwap + .mul(wethPrice) + .div(parseEther('1')) + .mul(usdPrice) + .div(parseEther('1')); + const daiUsdValue = amountOut + .mul(daiPrice) + .div(parseEther('1')) + .mul(usdPrice) + .div(parseEther('1')); await mockUniswapRouter.setAmountIn(amountOut, weth.address, dai.address, amountIn); - expect( - await uniswapLiquiditySwapAdapter.getAmountIn(amountOut, weth.address, dai.address) - ).to.be.eq(amountIn); + const result = await uniswapLiquiditySwapAdapter.getAmountsIn( + amountOut, + weth.address, + dai.address + ); + + expect(result['0']).to.be.eq(amountToSwap); + expect(result['1']).to.be.eq(inPerOutPrice); + expect(result['2']).to.be.eq(ethUsdValue); + expect(result['3']).to.be.eq(daiUsdValue); + }); + it('should work correctly with different decimals', async () => { + const {lend, usdc, uniswapLiquiditySwapAdapter, oracle} = testEnv; + + const amountIn = parseEther('10'); + const flashloanPremium = amountIn.mul(9).div(10000); + const amountToSwap = amountIn.sub(flashloanPremium); + + const lendPrice = await oracle.getAssetPrice(lend.address); + const usdcPrice = await oracle.getAssetPrice(usdc.address); + const usdPrice = await oracle.getAssetPrice(USD_ADDRESS); + + const amountOut = await convertToCurrencyDecimals( + usdc.address, + new BigNumber(amountToSwap.toString()).div(usdcPrice.toString()).toFixed(0) + ); + + const inPerOutPrice = amountOut + .mul(parseEther('1')) + .mul(parseEther('1')) + .div(amountToSwap.mul('1000000')); // usdc 6 decimals + + const lendUsdValue = amountToSwap + .mul(lendPrice) + .div(parseEther('1')) + .mul(usdPrice) + .div(parseEther('1')); + + const usdcUsdValue = amountOut + .mul(usdcPrice) + .div('1000000') // usdc 6 decimals + .mul(usdPrice) + .div(parseEther('1')); + + await mockUniswapRouter.setAmountIn(amountOut, lend.address, usdc.address, amountIn); + + const result = await uniswapLiquiditySwapAdapter.getAmountsIn( + amountOut, + lend.address, + usdc.address + ); + + expect(result['0']).to.be.eq(amountToSwap); + expect(result['1']).to.be.eq(inPerOutPrice); + expect(result['2']).to.be.eq(lendUsdValue); + expect(result['3']).to.be.eq(usdcUsdValue); }); }); });