From 7c51597282d7ff6f5a4522701f76e12f7e52210f Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 27 Oct 2020 09:17:33 -0300 Subject: [PATCH] Add uniswap adapters for liquidity swap and repay with collateral --- contracts/adapters/BaseUniswapAdapter.sol | 215 ++++++++++++++++++ .../adapters/UniswapLiquiditySwapAdapter.sol | 63 +++++ contracts/adapters/UniswapRepayAdapter.sol | 71 ++++++ 3 files changed, 349 insertions(+) create mode 100644 contracts/adapters/BaseUniswapAdapter.sol create mode 100644 contracts/adapters/UniswapLiquiditySwapAdapter.sol create mode 100644 contracts/adapters/UniswapRepayAdapter.sol diff --git a/contracts/adapters/BaseUniswapAdapter.sol b/contracts/adapters/BaseUniswapAdapter.sol new file mode 100644 index 00000000..79b55df2 --- /dev/null +++ b/contracts/adapters/BaseUniswapAdapter.sol @@ -0,0 +1,215 @@ +// 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 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 Take action with the swap left overs as configured in the parameters + * @param reserve address of the asset + * @param user address + * @param flashLoanDebt need to be repaid + */ + function pullATokenAndRepayFlashLoan( + address reserve, + address user, + uint256 flashLoanDebt + ) internal { + address reserveAToken = getAToken(reserve); + + // transfer from user to adapter + IERC20(reserveAToken).safeTransferFrom(user, address(this), flashLoanDebt); + + // withdraw reserve + pool.withdraw(reserve, flashLoanDebt); + + // Repay flashloan + IERC20(reserve).approve(address(pool), flashLoanDebt); + } +} diff --git a/contracts/adapters/UniswapLiquiditySwapAdapter.sol b/contracts/adapters/UniswapLiquiditySwapAdapter.sol new file mode 100644 index 00000000..62125264 --- /dev/null +++ b/contracts/adapters/UniswapLiquiditySwapAdapter.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; +pragma experimental ABIEncoderV2; + +import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol'; +import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; +import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; +import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; +import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; + +/** + * @title UniswapLiquiditySwapAdapter + * @notice Uniswap V2 Adapter to swap liquidity using a flash loan. + * @author Aave + **/ +contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver { + + constructor( + ILendingPoolAddressesProvider _addressesProvider, + IUniswapV2Router02 _uniswapRouter + ) + public + BaseUniswapAdapter(_addressesProvider, _uniswapRouter) + {} + + /** + * @dev Swaps the received reserve amount from the flashloan into the asset specified in the params. + * The received funds from the swap are then deposited into the protocol on behalf of the user. + * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and + * repay the flash loan. + * @param reserve Address to be swapped + * @param amount Amount of the reserve to be swapped + * @param fee Fee of the flash loan + * @param params Additional variadic field to include extra params. Expected parameters: + * address assetToSwapTo Address of the reserve to be swapped to and deposited + * address user The address of the user + * uint256 slippage The max slippage percentage allowed for the swap + */ + function executeOperation( + address reserve, + uint256 amount, + uint256 fee, + bytes calldata params + ) external override returns (bool) { + ( + address assetToSwapTo, + address user, + uint256 slippage + ) = abi.decode(params, (address, address, uint256)); + require(slippage < MAX_SLIPPAGE_PERCENT && slippage >= MIN_SLIPPAGE_PERCENT, 'SLIPPAGE_OUT_OF_RANGE'); + + uint256 receivedAmount = swapExactTokensForTokens(reserve, assetToSwapTo, amount, slippage); + + // Deposit new reserve + IERC20(assetToSwapTo).approve(address(pool), receivedAmount); + pool.deposit(assetToSwapTo, receivedAmount, user, 0); + + uint256 flashLoanDebt = amount.add(fee); + pullATokenAndRepayFlashLoan(reserve, user, flashLoanDebt); + + return true; + } +} diff --git a/contracts/adapters/UniswapRepayAdapter.sol b/contracts/adapters/UniswapRepayAdapter.sol new file mode 100644 index 00000000..2bb8a8a8 --- /dev/null +++ b/contracts/adapters/UniswapRepayAdapter.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; +pragma experimental ABIEncoderV2; + +import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol'; +import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; +import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; +import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; +import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; + +/** + * @title UniswapRepayAdapter + * @notice Uniswap V2 Adapter to perform a repay of a debt using a flash loan. + * @author Aave + **/ +contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { + + constructor( + ILendingPoolAddressesProvider _addressesProvider, + IUniswapV2Router02 _uniswapRouter + ) + public + BaseUniswapAdapter(_addressesProvider, _uniswapRouter) + {} + + /** + * @dev Swaps the received reserve amount into the asset specified in the params. The received funds from the swap are + * then used to repay a debt on the protocol on behalf of the user. + * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and + * repay the flash loan. + * @param reserve Address to be swapped + * @param amount Amount of the reserve to be swapped + * @param fee Fee of the flash loan + * @param params Additional variadic field to include extra params. Expected parameters: + * address assetToSwapTo Address of the reserve to be swapped to and deposited + * address user The address of the user + * uint256 leftOverAction Flag indicating what to do with the left over balance from the swap: + * (0) Deposit back + * (1) Direct transfer to user + * uint256 repayAmount Amount of debt to be repaid + * uint256 rateMode The rate modes of the debt to be repaid + */ + function executeOperation( + address reserve, + uint256 amount, + uint256 fee, + bytes calldata params + ) external override returns (bool) { + ( + address assetToSwapTo, + address user, + uint256 leftOverAction, + uint256 repayAmount, + uint256 rateMode + ) = abi.decode(params, (address, address, uint256, uint256, uint256)); + + swapTokensForExactTokens(reserve, assetToSwapTo, amount, repayAmount); + + // Repay debt + IERC20(assetToSwapTo).approve(address(pool), repayAmount); + pool.repay(assetToSwapTo, repayAmount, rateMode, user); + + uint256 flashLoanDebt = amount.add(fee); + pullATokenAndRepayFlashLoan(reserve, user, flashLoanDebt); + + // Take care of reserve leftover from the swap + sendLeftOver(reserve, flashLoanDebt, leftOverAction, user); + + return true; + } +}