From 48b9a603a79678f90a30de6bc03ff3370a1dcd42 Mon Sep 17 00:00:00 2001
From: Gerardo Nardelli <gerardo.nardelli@altoros.com>
Date: Thu, 5 Nov 2020 11:33:11 -0300
Subject: [PATCH] Update getAmountsIn to return prices

---
 contracts/adapters/BaseUniswapAdapter.sol | 119 ++++++++++++++++------
 test/uniswapAdapters.spec.ts              |  96 +++++++++++++++--
 2 files changed, 176 insertions(+), 39 deletions(-)

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);
       });
     });
   });