// SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.6.8; pragma experimental ABIEncoderV2; import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol'; import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol'; import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; import {ILendingPool} from '../interfaces/ILendingPool.sol'; import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol'; import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; import {IERC20WithPermit} from '../interfaces/IERC20WithPermit.sol'; /** * @title BaseUniswapAdapter * @notice Implements the logic for performing assets swaps in Uniswap V2 * @author Aave **/ contract BaseUniswapAdapter { using SafeMath for uint256; using PercentageMath for uint256; using SafeERC20 for IERC20; struct PermitSignature { uint256 amount; uint256 deadline; uint8 v; bytes32 r; 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 uint256 public constant FLASHLOAN_PREMIUM_TOTAL = 9; // USD oracle asset address address public constant USD_ADDRESS = 0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96; ILendingPool public immutable POOL; IPriceOracleGetter public immutable ORACLE; IUniswapV2Router02 public immutable UNISWAP_ROUTER; event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); constructor(ILendingPoolAddressesProvider addressesProvider, IUniswapV2Router02 uniswapRouter) public { POOL = ILendingPool(addressesProvider.getLendingPool()); ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle()); UNISWAP_ROUTER = uniswapRouter; } /** * @dev Given an input asset amount, returns the maximum output amount of the other asset and the prices * @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 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) */ function getAmountsOut(uint256 amountIn, address reserveIn, address reserveOut) external view returns (uint256, uint256, uint256, uint256) { AmountCalc memory results = _getAmountsOut(reserveIn, reserveOut, amountIn); return ( results.calculatedAmount, results.relativePrice, results.amountInUsd, results.amountOutUsd ); } /** * @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 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 getAmountsIn(uint256 amountOut, address reserveIn, address reserveOut) external view returns (uint256, uint256, uint256, uint256) { AmountCalc memory results = _getAmountsIn(reserveIn, reserveOut, amountOut); return ( results.calculatedAmount, results.relativePrice, results.amountInUsd, results.amountOutUsd ); } /** * @dev Swaps an exact `amountToSwap` of an asset to another * @param assetToSwapFrom Origin asset * @param assetToSwapTo Destination asset * @param amountToSwap Exact amount of `assetToSwapFrom` to be swapped * @param minAmountOut the min amount of `assetToSwapTo` to be received from the swap * @return the amount received from the swap */ function _swapExactTokensForTokens( address assetToSwapFrom, address assetToSwapTo, uint256 amountToSwap, uint256 minAmountOut ) internal returns (uint256) { uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom); uint256 toAssetDecimals = _getDecimals(assetToSwapTo); uint256 fromAssetPrice = _getPrice(assetToSwapFrom); uint256 toAssetPrice = _getPrice(assetToSwapTo); uint256 expectedMinAmountOut = amountToSwap .mul(fromAssetPrice.mul(10**toAssetDecimals)) .div(toAssetPrice.mul(10**fromAssetDecimals)) .percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(MAX_SLIPPAGE_PERCENT)); require(expectedMinAmountOut < minAmountOut, 'minAmountOut exceed max slippage'); IERC20(assetToSwapFrom).approve(address(UNISWAP_ROUTER), amountToSwap); address[] memory path = new address[](2); path[0] = assetToSwapFrom; path[1] = assetToSwapTo; uint256[] memory amounts = UNISWAP_ROUTER.swapExactTokensForTokens(amountToSwap, minAmountOut, path, address(this), block.timestamp); emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[1]); return amounts[1]; } /** * @dev Receive an exact amount `amountToReceive` of `assetToSwapTo` tokens for as few `assetToSwapFrom` tokens as * possible. * @param assetToSwapFrom Origin asset * @param assetToSwapTo Destination asset * @param maxAmountToSwap Max amount of `assetToSwapFrom` allowed to be swapped * @param amountToReceive Exact amount of `assetToSwapTo` to receive * @return the amount swapped */ function _swapTokensForExactTokens( address assetToSwapFrom, address assetToSwapTo, uint256 maxAmountToSwap, uint256 amountToReceive ) internal returns (uint256) { uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom); uint256 toAssetDecimals = _getDecimals(assetToSwapTo); uint256 fromAssetPrice = _getPrice(assetToSwapFrom); uint256 toAssetPrice = _getPrice(assetToSwapTo); uint256 expectedMaxAmountToSwap = amountToReceive .mul(toAssetPrice.mul(10**fromAssetDecimals)) .div(fromAssetPrice.mul(10**toAssetDecimals)) .percentMul(PercentageMath.PERCENTAGE_FACTOR.add(MAX_SLIPPAGE_PERCENT)); require(maxAmountToSwap < expectedMaxAmountToSwap, 'maxAmountToSwap exceed max slippage'); IERC20(assetToSwapFrom).approve(address(UNISWAP_ROUTER), maxAmountToSwap); address[] memory path = new address[](2); path[0] = assetToSwapFrom; path[1] = assetToSwapTo; uint256[] memory amounts = UNISWAP_ROUTER.swapTokensForExactTokens(amountToReceive, maxAmountToSwap, path, address(this), block.timestamp); emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[1]); return amounts[0]; } /** * @dev Get the price of the asset from the oracle denominated in eth * @param asset address * @return eth price for the asset */ function _getPrice(address asset) internal view returns (uint256) { return ORACLE.getAssetPrice(asset); } /** * @dev Get the decimals of an asset * @return number of decimals of the asset */ function _getDecimals(address asset) internal view returns (uint256) { return IERC20Detailed(asset).decimals(); } /** * @dev Get the aToken associated to the asset * @return address of the aToken */ function _getReserveData(address asset) internal view returns (ReserveLogic.ReserveData memory) { return POOL.getReserveData(asset); } /** * @dev Pull the ATokens from the user * @param reserve address of the asset * @param reserveAToken address of the aToken of the reserve * @param user address * @param amount of tokens to be transferred to the contract * @param permitSignature struct containing the permit signature */ function _pullAToken( address reserve, address reserveAToken, address user, uint256 amount, PermitSignature memory permitSignature ) internal { if (_usePermit(permitSignature)) { IERC20WithPermit(reserveAToken).permit( user, address(this), permitSignature.amount, permitSignature.deadline, permitSignature.v, permitSignature.r, permitSignature.s ); } // transfer from user to adapter IERC20(reserveAToken).safeTransferFrom(user, address(this), amount); // withdraw reserve POOL.withdraw(reserve, amount, address(this)); } /** * @dev Tells if the permit method should be called by inspecting if there is a valid signature. * If signature params are set to 0, then permit won't be called. * @param signature struct containing the permit signature * @return whether or not permit should be called */ function _usePermit(PermitSignature memory signature) internal pure returns (bool) { return !(uint256(signature.deadline) == uint256(signature.v) && uint256(signature.deadline) == 0); } /** * @dev Calculates the value denominated in USD * @param reserve Address of the reserve * @param amount Amount of the reserve * @param decimals Decimals of the reserve * @return whether or not permit should be called */ function _calcUsdValue(address reserve, uint256 amount, uint256 decimals) internal view returns (uint256) { uint256 ethUsdPrice = _getPrice(USD_ADDRESS); uint256 reservePrice = _getPrice(reserve); return amount .mul(reservePrice) .div(10**decimals) .mul(ethUsdPrice) .div(10**18); } /** * @dev Given an input asset amount, returns the maximum output amount of the other asset * @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 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 (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(finalAmountIn, path); 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); // Add flash loan fee uint256 finalAmountIn = amounts[0].add(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) ); } }