// 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 {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 {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol'; import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.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; using ReserveConfiguration for ReserveConfiguration.Map; // Max slippage percent allow by param uint256 public constant MAX_SLIPPAGE_PERCENT = 3000; // 30% // Min slippage percent allow by param uint256 public constant MIN_SLIPPAGE_PERCENT = 10; // 0,1% ILendingPoolAddressesProvider public immutable addressesProvider; IUniswapV2Router02 public immutable uniswapRouter; ILendingPool public immutable pool; event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); constructor(ILendingPoolAddressesProvider _addressesProvider, IUniswapV2Router02 _uniswapRouter) public { addressesProvider = _addressesProvider; pool = ILendingPool(_addressesProvider.getLendingPool()); uniswapRouter = _uniswapRouter; } /** * @dev Given an input asset amount, returns the maximum output amount of the other asset * @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 amountOut */ function getAmountOut(uint256 amountIn, address reserveIn, address reserveOut) public view returns (uint256) { address[] memory path = new address[](2); path[0] = reserveIn; path[1] = reserveOut; uint256[] memory amounts = uniswapRouter.getAmountsOut(amountIn, path); return amounts[1]; } /** * @dev Returns the minimum input asset amount required to buy the given output asset amount * @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 */ function getAmountIn(uint256 amountOut, address reserveIn, address reserveOut) public view returns (uint256) { address[] memory path = new address[](2); path[0] = reserveIn; path[1] = reserveOut; uint256[] memory amounts = uniswapRouter.getAmountsIn(amountOut, path); return amounts[0]; } /** * @dev Swaps an `amountToSwap` of an asset to another * @param assetToSwapFrom Origin asset * @param assetToSwapTo Destination asset * @param amountToSwap Exact amount of `assetToSwapFrom` to be swapped * @param slippage the max slippage percentage allowed for the swap * @return the amount received from the swap */ function swapExactTokensForTokens( address assetToSwapFrom, address assetToSwapTo, uint256 amountToSwap, uint256 slippage ) internal returns (uint256) { uint256 fromAssetDecimals = getDecimals(assetToSwapFrom); uint256 toAssetDecimals = getDecimals(assetToSwapTo); (uint256 fromAssetPrice, uint256 toAssetPrice) = getPrices(assetToSwapFrom, assetToSwapTo); uint256 amountOutMin = amountToSwap .mul(fromAssetPrice.mul(10**toAssetDecimals)) .div(toAssetPrice.mul(10**fromAssetDecimals)) .percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(slippage)); IERC20(assetToSwapFrom).approve(address(uniswapRouter), amountToSwap); address[] memory path = new address[](2); path[0] = assetToSwapFrom; path[1] = assetToSwapTo; uint256[] memory amounts = uniswapRouter.swapExactTokensForTokens(amountToSwap, amountOutMin, path, address(this), block.timestamp); require(amounts[1] >= amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); 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 received from the swap */ function swapTokensForExactTokens( address assetToSwapFrom, address assetToSwapTo, uint256 maxAmountToSwap, uint256 amountToReceive ) internal returns (uint256) { uint256 fromAssetDecimals = getDecimals(assetToSwapFrom); uint256 toAssetDecimals = getDecimals(assetToSwapTo); (uint256 fromAssetPrice, uint256 toAssetPrice) = getPrices(assetToSwapFrom, 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(uniswapRouter), maxAmountToSwap); address[] memory path = new address[](2); path[0] = assetToSwapFrom; path[1] = assetToSwapTo; uint256[] memory amounts = uniswapRouter.swapTokensForExactTokens(amountToReceive, maxAmountToSwap, path, address(this), block.timestamp); require(amounts[1] >= amountToReceive, 'INSUFFICIENT_OUTPUT_AMOUNT'); emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[1]); return amounts[1]; } /** * @dev Get assets prices from the oracle denominated in eth * @param assetToSwapFrom first asset * @param assetToSwapTo second asset * @return fromAssetPrice eth price for the first asset * @return toAssetPrice eth price for the second asset */ function getPrices( address assetToSwapFrom, address assetToSwapTo ) internal view returns (uint256 fromAssetPrice, uint256 toAssetPrice) { IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); fromAssetPrice = oracle.getAssetPrice(assetToSwapFrom); toAssetPrice = oracle.getAssetPrice(assetToSwapTo); } /** * @dev Get the decimals of an asset * @return number of decimals of the asset */ function getDecimals(address asset) internal view returns (uint256) { ReserveConfiguration.Map memory configuration = pool.getConfiguration(asset); (, , , uint256 decimals, ) = configuration.getParamsMemory(); return decimals; } /** * @dev Get the aToken associated to the asset * @return address of the aToken */ function getAToken(address asset) internal view returns (address) { ReserveLogic.ReserveData memory reserve = pool.getReserveData(asset); return reserve.aTokenAddress; } /** * @dev Take action with the swap left overs as configured in the parameters * @param asset address of the asset * @param reservedAmount Amount reserved to be used by the contract to repay the flash loan * @param leftOverAction Flag indicating what to do with the left over balance from the swap: * (0) Deposit back * (1) Direct transfer to user * @param user address */ function sendLeftOver(address asset, uint256 reservedAmount, uint256 leftOverAction, address user) internal { uint256 balance = IERC20(asset).balanceOf(address(this)); uint256 assetLeftOver = balance.sub(reservedAmount); if (assetLeftOver > 0) { if (leftOverAction == 0) { IERC20(asset).approve(address(pool), balance); pool.deposit(asset, assetLeftOver, user, 0); } else { IERC20(asset).transfer(user, assetLeftOver); } } } /** * @dev Pull the ATokens from the user * @param reserve address of the asset * @param user address * @param amount of tokens to be transferred to the contract */ function pullAToken( address reserve, address user, uint256 amount ) internal { address reserveAToken = getAToken(reserve); // transfer from user to adapter IERC20(reserveAToken).safeTransferFrom(user, address(this), amount); // withdraw reserve pool.withdraw(reserve, amount); } /** * @dev Pull the ATokens from the user and use them to repay the flashloan * @param reserve address of the asset * @param user address * @param flashLoanDebt need to be repaid */ function pullATokenAndRepayFlashLoan( address reserve, address user, uint256 flashLoanDebt ) internal { pullAToken(reserve, user, flashLoanDebt); // Repay flashloan IERC20(reserve).approve(address(pool), flashLoanDebt); } }