|
| 1 | +// SPDX-License-Identifier: agpl-3.0 |
| 2 | +pragma solidity 0.6.12; |
| 3 | +pragma experimental ABIEncoderV2; |
| 4 | + |
| 5 | +import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol'; |
| 6 | +import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; |
| 7 | +import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; |
| 8 | +import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; |
| 9 | +import {DataTypes} from '../protocol/libraries/types/DataTypes.sol'; |
| 10 | +import {Helpers} from '../protocol/libraries/helpers/Helpers.sol'; |
| 11 | +import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; |
| 12 | +import {IAToken} from '../interfaces/IAToken.sol'; |
| 13 | +import {ReserveConfiguration} from '../protocol/libraries/configuration/ReserveConfiguration.sol'; |
| 14 | + |
| 15 | +/** |
| 16 | + * @title UniswapLiquiditySwapAdapter |
| 17 | + * @notice Uniswap V2 Adapter to swap liquidity. |
| 18 | + * @author Aave |
| 19 | + **/ |
| 20 | +contract FlashLiquidationAdapter is BaseUniswapAdapter { |
| 21 | + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; |
| 22 | + uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000; |
| 23 | + |
| 24 | + struct LiquidationParams { |
| 25 | + address collateralAsset; |
| 26 | + address borrowedAsset; |
| 27 | + address user; |
| 28 | + uint256 debtToCover; |
| 29 | + bool useEthPath; |
| 30 | + } |
| 31 | + |
| 32 | + struct LiquidationCallLocalVars { |
| 33 | + uint256 initFlashBorrowedBalance; |
| 34 | + uint256 diffFlashBorrowedBalance; |
| 35 | + uint256 initCollateralBalance; |
| 36 | + uint256 diffCollateralBalance; |
| 37 | + uint256 flashLoanDebt; |
| 38 | + uint256 soldAmount; |
| 39 | + uint256 remainingTokens; |
| 40 | + uint256 borrowedAssetLeftovers; |
| 41 | + } |
| 42 | + |
| 43 | + constructor( |
| 44 | + ILendingPoolAddressesProvider addressesProvider, |
| 45 | + IUniswapV2Router02 uniswapRouter, |
| 46 | + address wethAddress |
| 47 | + ) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {} |
| 48 | + |
| 49 | + /** |
| 50 | + * @dev Liquidate a non-healthy position collateral-wise, with a Health Factor below 1, using Flash Loan and Uniswap to repay flash loan premium. |
| 51 | + * - The caller (liquidator) with a flash loan covers `debtToCover` amount of debt of the user getting liquidated, and receives |
| 52 | + * a proportionally amount of the `collateralAsset` plus a bonus to cover market risk minus the flash loan premium. |
| 53 | + * @param assets Address of asset to be swapped |
| 54 | + * @param amounts Amount of the asset to be swapped |
| 55 | + * @param premiums Fee of the flash loan |
| 56 | + * @param initiator Address of the caller |
| 57 | + * @param params Additional variadic field to include extra params. Expected parameters: |
| 58 | + * address collateralAsset The collateral asset to release and will be exchanged to pay the flash loan premium |
| 59 | + * address borrowedAsset The asset that must be covered |
| 60 | + * address user The user address with a Health Factor below 1 |
| 61 | + * uint256 debtToCover The amount of debt to cover |
| 62 | + * bool useEthPath Use WETH as connector path between the collateralAsset and borrowedAsset at Uniswap |
| 63 | + */ |
| 64 | + function executeOperation( |
| 65 | + address[] calldata assets, |
| 66 | + uint256[] calldata amounts, |
| 67 | + uint256[] calldata premiums, |
| 68 | + address initiator, |
| 69 | + bytes calldata params |
| 70 | + ) external override returns (bool) { |
| 71 | + require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL'); |
| 72 | + |
| 73 | + LiquidationParams memory decodedParams = _decodeParams(params); |
| 74 | + |
| 75 | + require(assets.length == 1 && assets[0] == decodedParams.borrowedAsset, 'INCONSISTENT_PARAMS'); |
| 76 | + |
| 77 | + _liquidateAndSwap( |
| 78 | + decodedParams.collateralAsset, |
| 79 | + decodedParams.borrowedAsset, |
| 80 | + decodedParams.user, |
| 81 | + decodedParams.debtToCover, |
| 82 | + decodedParams.useEthPath, |
| 83 | + amounts[0], |
| 84 | + premiums[0], |
| 85 | + initiator |
| 86 | + ); |
| 87 | + |
| 88 | + return true; |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * @dev |
| 93 | + * @param collateralAsset The collateral asset to release and will be exchanged to pay the flash loan premium |
| 94 | + * @param borrowedAsset The asset that must be covered |
| 95 | + * @param user The user address with a Health Factor below 1 |
| 96 | + * @param debtToCover The amount of debt to coverage, can be max(-1) to liquidate all possible debt |
| 97 | + * @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise |
| 98 | + * @param flashBorrowedAmount Amount of asset requested at the flash loan to liquidate the user position |
| 99 | + * @param premium Fee of the requested flash loan |
| 100 | + * @param initiator Address of the caller |
| 101 | + */ |
| 102 | + function _liquidateAndSwap( |
| 103 | + address collateralAsset, |
| 104 | + address borrowedAsset, |
| 105 | + address user, |
| 106 | + uint256 debtToCover, |
| 107 | + bool useEthPath, |
| 108 | + uint256 flashBorrowedAmount, |
| 109 | + uint256 premium, |
| 110 | + address initiator |
| 111 | + ) internal { |
| 112 | + LiquidationCallLocalVars memory vars; |
| 113 | + vars.initCollateralBalance = IERC20(collateralAsset).balanceOf(address(this)); |
| 114 | + if (collateralAsset != borrowedAsset) { |
| 115 | + vars.initFlashBorrowedBalance = IERC20(borrowedAsset).balanceOf(address(this)); |
| 116 | + |
| 117 | + // Track leftover balance to rescue funds in case of external transfers into this contract |
| 118 | + vars.borrowedAssetLeftovers = vars.initFlashBorrowedBalance.sub(flashBorrowedAmount); |
| 119 | + } |
| 120 | + vars.flashLoanDebt = flashBorrowedAmount.add(premium); |
| 121 | + |
| 122 | + // Approve LendingPool to use debt token for liquidation |
| 123 | + IERC20(borrowedAsset).approve(address(LENDING_POOL), debtToCover); |
| 124 | + |
| 125 | + // Liquidate the user position and release the underlying collateral |
| 126 | + LENDING_POOL.liquidationCall(collateralAsset, borrowedAsset, user, debtToCover, false); |
| 127 | + |
| 128 | + // Discover the liquidated tokens |
| 129 | + uint256 collateralBalanceAfter = IERC20(collateralAsset).balanceOf(address(this)); |
| 130 | + |
| 131 | + // Track only collateral released, not current asset balance of the contract |
| 132 | + vars.diffCollateralBalance = collateralBalanceAfter.sub(vars.initCollateralBalance); |
| 133 | + |
| 134 | + if (collateralAsset != borrowedAsset) { |
| 135 | + // Discover flash loan balance after the liquidation |
| 136 | + uint256 flashBorrowedAssetAfter = IERC20(borrowedAsset).balanceOf(address(this)); |
| 137 | + |
| 138 | + // Use only flash loan borrowed assets, not current asset balance of the contract |
| 139 | + vars.diffFlashBorrowedBalance = flashBorrowedAssetAfter.sub(vars.borrowedAssetLeftovers); |
| 140 | + |
| 141 | + // Swap released collateral into the debt asset, to repay the flash loan |
| 142 | + vars.soldAmount = _swapTokensForExactTokens( |
| 143 | + collateralAsset, |
| 144 | + borrowedAsset, |
| 145 | + vars.diffCollateralBalance, |
| 146 | + vars.flashLoanDebt.sub(vars.diffFlashBorrowedBalance), |
| 147 | + useEthPath |
| 148 | + ); |
| 149 | + vars.remainingTokens = vars.diffCollateralBalance.sub(vars.soldAmount); |
| 150 | + } else { |
| 151 | + vars.remainingTokens = vars.diffCollateralBalance.sub(premium); |
| 152 | + } |
| 153 | + |
| 154 | + // Allow repay of flash loan |
| 155 | + IERC20(borrowedAsset).approve(address(LENDING_POOL), vars.flashLoanDebt); |
| 156 | + |
| 157 | + // Transfer remaining tokens to initiator |
| 158 | + if (vars.remainingTokens > 0) { |
| 159 | + IERC20(collateralAsset).transfer(initiator, vars.remainingTokens); |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + /** |
| 164 | + * @dev Decodes the information encoded in the flash loan params |
| 165 | + * @param params Additional variadic field to include extra params. Expected parameters: |
| 166 | + * address collateralAsset The collateral asset to claim |
| 167 | + * address borrowedAsset The asset that must be covered and will be exchanged to pay the flash loan premium |
| 168 | + * address user The user address with a Health Factor below 1 |
| 169 | + * uint256 debtToCover The amount of debt to cover |
| 170 | + * bool useEthPath Use WETH as connector path between the collateralAsset and borrowedAsset at Uniswap |
| 171 | + * @return LiquidationParams struct containing decoded params |
| 172 | + */ |
| 173 | + function _decodeParams(bytes memory params) internal pure returns (LiquidationParams memory) { |
| 174 | + ( |
| 175 | + address collateralAsset, |
| 176 | + address borrowedAsset, |
| 177 | + address user, |
| 178 | + uint256 debtToCover, |
| 179 | + bool useEthPath |
| 180 | + ) = abi.decode(params, (address, address, address, uint256, bool)); |
| 181 | + |
| 182 | + return LiquidationParams(collateralAsset, borrowedAsset, user, debtToCover, useEthPath); |
| 183 | + } |
| 184 | +} |
0 commit comments