// SPDX-License-Identifier: agpl-3.0 pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol'; import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; import {DataTypes} from '../protocol/libraries/types/DataTypes.sol'; import {Helpers} from '../protocol/libraries/helpers/Helpers.sol'; import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; import {IAToken} from '../interfaces/IAToken.sol'; import {ReserveConfiguration} from '../protocol/libraries/configuration/ReserveConfiguration.sol'; /** * @title UniswapLiquiditySwapAdapter * @notice Uniswap V2 Adapter to swap liquidity. * @author Aave **/ contract FlashLiquidationAdapter is BaseUniswapAdapter { using ReserveConfiguration for DataTypes.ReserveConfigurationMap; uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000; struct LiquidationParams { address collateralAsset; address debtAsset; address user; uint256 debtToCover; bool useEthPath; } struct LiquidationCallLocalVars { uint256 userCollateralBalance; uint256 userStableDebt; uint256 userVariableDebt; uint256 maxLiquidatableDebt; uint256 actualDebtToLiquidate; uint256 maxAmountCollateralToLiquidate; uint256 maxCollateralToLiquidate; uint256 debtAmountNeeded; uint256 collateralPrice; uint256 debtAssetPrice; uint256 liquidationBonus; uint256 collateralDecimals; uint256 debtAssetDecimals; IAToken collateralAtoken; } constructor( ILendingPoolAddressesProvider addressesProvider, IUniswapV2Router02 uniswapRouter, address wethAddress ) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {} /** * @dev Liquidate a non-healthy position collateral-wise, with a Health Factor below 1, using Flash Loan and Uniswap to repay flash loan premium. * - The caller (liquidator) with a flash loan covers `debtToCover` amount of debt of the user getting liquidated, and receives * a proportionally amount of the `collateralAsset` plus a bonus to cover market risk minus the flash loan premium. * @param assets Address of asset to be swapped * @param amounts Amount of the asset to be swapped * @param premiums Fee of the flash loan * @param initiator Address of the caller * @param params Additional variadic field to include extra params. Expected parameters: * address collateralAsset The collateral asset to release and will be exchanged to pay the flash loan premium * address debtAsset The asset that must be covered * address user The user address with a Health Factor below 1 * uint256 debtToCover The amount of debt to cover * bool useEthPath Use WETH as connector path between the collateralAsset and debtAsset at Uniswap */ function executeOperation( address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, address initiator, bytes calldata params ) external override returns (bool) { require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL'); LiquidationParams memory decodedParams = _decodeParams(params); require(assets.length == 1 && assets[0] == decodedParams.debtAsset, 'INCONSISTENT_PARAMS'); _liquidateAndSwap( decodedParams.collateralAsset, decodedParams.debtAsset, decodedParams.user, decodedParams.debtToCover, decodedParams.useEthPath, amounts[0], premiums[0], initiator ); return true; } /** * @dev * @param collateralAsset The collateral asset to release and will be exchanged to pay the flash loan premium * @param debtAsset The asset that must be covered * @param user The user address with a Health Factor below 1 * @param debtToCover The amount of debt to coverage, can be max(-1) to liquidate all possible debt * @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise * @param coverAmount Amount of asset requested at the flash loan to liquidate the user position * @param premium Fee of the requested flash loan * @param initiator Address of the caller */ function _liquidateAndSwap( address collateralAsset, address debtAsset, address user, uint256 debtToCover, bool useEthPath, uint256 coverAmount, uint256 premium, address initiator ) internal { DataTypes.ReserveData memory collateralReserve = LENDING_POOL.getReserveData(collateralAsset); DataTypes.ReserveData memory debtReserve = LENDING_POOL.getReserveData(debtAsset); LiquidationCallLocalVars memory vars; (vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebtMemory( user, debtReserve ); vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress); vars.maxLiquidatableDebt = vars.userStableDebt.add(vars.userVariableDebt).percentMul( LIQUIDATION_CLOSE_FACTOR_PERCENT ); vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user); vars.actualDebtToLiquidate = debtToCover > vars.maxLiquidatableDebt ? vars.maxLiquidatableDebt : debtToCover; ( vars.maxCollateralToLiquidate, vars.debtAmountNeeded ) = _calculateAvailableCollateralToLiquidate( collateralReserve, debtReserve, collateralAsset, debtAsset, vars.actualDebtToLiquidate, vars.userCollateralBalance ); require(coverAmount >= vars.debtAmountNeeded, 'Not enought cover amount requested'); uint256 flashLoanDebt = coverAmount.add(premium); require(IERC20(debtAsset).approve(address(LENDING_POOL), debtToCover), 'Approval error'); // Liquidate the user position and release the underlying collateral LENDING_POOL.liquidationCall(collateralAsset, debtAsset, user, debtToCover, false); // Swap released collateral into the debt asset, to repay the flash loan uint256 soldAmount = _swapTokensForExactTokens( collateralAsset, debtAsset, vars.maxCollateralToLiquidate, flashLoanDebt, useEthPath ); // Repay flash loan IERC20(debtAsset).approve(address(LENDING_POOL), flashLoanDebt); uint256 remainingTokens = vars.maxCollateralToLiquidate.sub(soldAmount); // Transfer remaining tokens to initiator if (remainingTokens > 0) { IERC20(collateralAsset).transfer(initiator, remainingTokens); } } /** * @dev Decodes the information encoded in the flash loan params * @param params Additional variadic field to include extra params. Expected parameters: * address collateralAsset The collateral asset to claim * address debtAsset The asset that must be covered and will be exchanged to pay the flash loan premium * address user The user address with a Health Factor below 1 * uint256 debtToCover The amount of debt to cover * bool useEthPath Use WETH as connector path between the collateralAsset and debtAsset at Uniswap * @return LiquidationParams struct containing decoded params */ function _decodeParams(bytes memory params) internal pure returns (LiquidationParams memory) { ( address collateralAsset, address debtAsset, address user, uint256 debtToCover, bool useEthPath ) = abi.decode(params, (address, address, address, uint256, bool)); return LiquidationParams(collateralAsset, debtAsset, user, debtToCover, useEthPath); } /** * @dev Calculates how much of a specific collateral can be liquidated, given * a certain amount of debt asset. * - This function needs to be called after all the checks to validate the liquidation have been performed, * otherwise it might fail. * @param collateralReserve The data of the collateral reserve * @param debtReserve The data of the debt reserve * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover * @param userCollateralBalance The collateral balance for the specific `collateralAsset` of the user being liquidated * @return collateralAmount: The maximum amount that is possible to liquidate given all the liquidation constraints * (user balance, close factor) * debtAmountNeeded: The amount to repay with the liquidation **/ function _calculateAvailableCollateralToLiquidate( DataTypes.ReserveData memory collateralReserve, DataTypes.ReserveData memory debtReserve, address collateralAsset, address debtAsset, uint256 debtToCover, uint256 userCollateralBalance ) internal view returns (uint256, uint256) { uint256 collateralAmount = 0; uint256 debtAmountNeeded = 0; LiquidationCallLocalVars memory vars; vars.collateralPrice = ORACLE.getAssetPrice(collateralAsset); vars.debtAssetPrice = ORACLE.getAssetPrice(debtAsset); (, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve .configuration .getParamsMemory(); (, , , vars.debtAssetDecimals, ) = debtReserve.configuration.getParamsMemory(); // This is the maximum possible amount of the selected collateral that can be liquidated, given the // max amount of liquidatable debt vars.maxAmountCollateralToLiquidate = vars .debtAssetPrice .mul(debtToCover) .mul(10**vars.collateralDecimals) .percentMul(vars.liquidationBonus) .div(vars.collateralPrice.mul(10**vars.debtAssetDecimals)); if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) { collateralAmount = userCollateralBalance; debtAmountNeeded = vars .collateralPrice .mul(collateralAmount) .mul(10**vars.debtAssetDecimals) .div(vars.debtAssetPrice.mul(10**vars.collateralDecimals)) .percentDiv(vars.liquidationBonus); } else { collateralAmount = vars.maxAmountCollateralToLiquidate; debtAmountNeeded = debtToCover; } return (collateralAmount, debtAmountNeeded); } }