mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Added flash liquidation adapter first iteration
This commit is contained in:
parent
eea6d38f24
commit
b432008d06
545
contracts/adapters/BaseUniswapAdapter.sol
Normal file
545
contracts/adapters/BaseUniswapAdapter.sol
Normal file
|
@ -0,0 +1,545 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity 0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {PercentageMath} from '../protocol/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 {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol';
|
||||
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
|
||||
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
|
||||
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
|
||||
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
||||
import {IERC20WithPermit} from '../interfaces/IERC20WithPermit.sol';
|
||||
import {FlashLoanReceiverBase} from '../flashloan/base/FlashLoanReceiverBase.sol';
|
||||
import {IBaseUniswapAdapter} from './interfaces/IBaseUniswapAdapter.sol';
|
||||
|
||||
/**
|
||||
* @title BaseUniswapAdapter
|
||||
* @notice Implements the logic for performing assets swaps in Uniswap V2
|
||||
* @author Aave
|
||||
**/
|
||||
abstract contract BaseUniswapAdapter is FlashLoanReceiverBase, IBaseUniswapAdapter, Ownable {
|
||||
using SafeMath for uint256;
|
||||
using PercentageMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
// Max slippage percent allowed
|
||||
uint256 public constant override MAX_SLIPPAGE_PERCENT = 3000; // 30%
|
||||
// FLash Loan fee set in lending pool
|
||||
uint256 public constant override FLASHLOAN_PREMIUM_TOTAL = 9;
|
||||
// USD oracle asset address
|
||||
address public constant override USD_ADDRESS = 0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96;
|
||||
|
||||
// address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; mainnet
|
||||
// address public constant WETH_ADDRESS = 0xd0a1e359811322d97991e03f863a0c30c2cf029c; kovan
|
||||
|
||||
address public immutable override WETH_ADDRESS;
|
||||
IPriceOracleGetter public immutable override ORACLE;
|
||||
IUniswapV2Router02 public immutable override UNISWAP_ROUTER;
|
||||
|
||||
constructor(
|
||||
ILendingPoolAddressesProvider addressesProvider,
|
||||
IUniswapV2Router02 uniswapRouter,
|
||||
address wethAddress
|
||||
) public FlashLoanReceiverBase(addressesProvider) {
|
||||
ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle());
|
||||
UNISWAP_ROUTER = uniswapRouter;
|
||||
WETH_ADDRESS = wethAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
bool withFlash
|
||||
)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
address[] memory
|
||||
)
|
||||
{
|
||||
AmountCalc memory results = _getAmountsOutData(reserveIn, reserveOut, amountIn, withFlash);
|
||||
|
||||
return (
|
||||
results.calculatedAmount,
|
||||
results.relativePrice,
|
||||
results.amountInUsd,
|
||||
results.amountOutUsd,
|
||||
results.path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
bool withFlash
|
||||
)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
address[] memory
|
||||
)
|
||||
{
|
||||
AmountCalc memory results = _getAmountsInData(reserveIn, reserveOut, amountOut, withFlash);
|
||||
|
||||
return (
|
||||
results.calculatedAmount,
|
||||
results.relativePrice,
|
||||
results.amountInUsd,
|
||||
results.amountOutUsd,
|
||||
results.path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
bool useEthPath
|
||||
) 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;
|
||||
if (useEthPath) {
|
||||
path = new address[](3);
|
||||
path[0] = assetToSwapFrom;
|
||||
path[1] = WETH_ADDRESS;
|
||||
path[2] = assetToSwapTo;
|
||||
} else {
|
||||
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[amounts.length - 1]);
|
||||
|
||||
return amounts[amounts.length - 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,
|
||||
bool useEthPath
|
||||
) internal returns (uint256) {
|
||||
address[] memory path;
|
||||
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);
|
||||
|
||||
if (useEthPath) {
|
||||
path = new address[](3);
|
||||
path[0] = assetToSwapFrom;
|
||||
path[1] = WETH_ADDRESS;
|
||||
path[2] = assetToSwapTo;
|
||||
} else {
|
||||
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[amounts.length - 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 (DataTypes.ReserveData memory) {
|
||||
return LENDING_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
|
||||
LENDING_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);
|
||||
}
|
||||
|
||||
struct AmountOutVars {
|
||||
uint256 finalAmountIn;
|
||||
address[] simplePath;
|
||||
uint256[] amountsWithoutWeth;
|
||||
uint256[] amountsWithWeth;
|
||||
address[] pathWithWeth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 _getAmountsOutData(
|
||||
address reserveIn,
|
||||
address reserveOut,
|
||||
uint256 amountIn,
|
||||
bool withFlash
|
||||
) internal view returns (AmountCalc memory) {
|
||||
AmountOutVars memory vars;
|
||||
// Subtract flash loan fee
|
||||
vars.finalAmountIn = amountIn.sub(
|
||||
withFlash ? amountIn.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000) : 0
|
||||
);
|
||||
|
||||
vars.simplePath = new address[](2);
|
||||
vars.simplePath[0] = reserveIn;
|
||||
vars.simplePath[1] = reserveOut;
|
||||
|
||||
vars.pathWithWeth = new address[](3);
|
||||
if (reserveIn != WETH_ADDRESS && reserveOut != WETH_ADDRESS) {
|
||||
vars.pathWithWeth[0] = reserveIn;
|
||||
vars.pathWithWeth[1] = WETH_ADDRESS;
|
||||
vars.pathWithWeth[2] = reserveOut;
|
||||
|
||||
try UNISWAP_ROUTER.getAmountsOut(vars.finalAmountIn, vars.pathWithWeth) returns (
|
||||
uint256[] memory resultsWithWeth
|
||||
) {
|
||||
vars.amountsWithWeth = resultsWithWeth;
|
||||
} catch {
|
||||
vars.amountsWithWeth = new uint256[](3);
|
||||
}
|
||||
} else {
|
||||
vars.amountsWithWeth = new uint256[](3);
|
||||
}
|
||||
|
||||
uint256 bestAmountOut;
|
||||
try UNISWAP_ROUTER.getAmountsOut(vars.finalAmountIn, vars.simplePath) returns (
|
||||
uint256[] memory resultAmounts
|
||||
) {
|
||||
vars.amountsWithoutWeth = resultAmounts;
|
||||
|
||||
bestAmountOut = (vars.amountsWithWeth[2] > vars.amountsWithoutWeth[1])
|
||||
? vars.amountsWithWeth[2]
|
||||
: vars.amountsWithoutWeth[1];
|
||||
} catch {
|
||||
vars.amountsWithoutWeth = new uint256[](2);
|
||||
bestAmountOut = vars.amountsWithWeth[2];
|
||||
}
|
||||
|
||||
uint256 reserveInDecimals = _getDecimals(reserveIn);
|
||||
uint256 reserveOutDecimals = _getDecimals(reserveOut);
|
||||
|
||||
uint256 outPerInPrice =
|
||||
vars.finalAmountIn.mul(10**18).mul(10**reserveOutDecimals).div(
|
||||
bestAmountOut.mul(10**reserveInDecimals)
|
||||
);
|
||||
|
||||
return
|
||||
AmountCalc(
|
||||
bestAmountOut,
|
||||
outPerInPrice,
|
||||
_calcUsdValue(reserveIn, amountIn, reserveInDecimals),
|
||||
_calcUsdValue(reserveOut, bestAmountOut, reserveOutDecimals),
|
||||
(bestAmountOut == 0) ? new address[](2) : (bestAmountOut == vars.amountsWithoutWeth[1])
|
||||
? vars.simplePath
|
||||
: vars.pathWithWeth
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 _getAmountsInData(
|
||||
address reserveIn,
|
||||
address reserveOut,
|
||||
uint256 amountOut,
|
||||
bool withFlash
|
||||
) internal view returns (AmountCalc memory) {
|
||||
(uint256[] memory amounts, address[] memory path) =
|
||||
_getAmountsInAndPath(reserveIn, reserveOut, amountOut);
|
||||
|
||||
// Add flash loan fee
|
||||
uint256 finalAmountIn =
|
||||
amounts[0].add(withFlash ? amounts[0].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000) : 0);
|
||||
|
||||
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),
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates the 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 uint256[] amounts Array containing the amountIn and amountOut for a swap
|
||||
*/
|
||||
function _getAmountsInAndPath(
|
||||
address reserveIn,
|
||||
address reserveOut,
|
||||
uint256 amountOut
|
||||
) internal view returns (uint256[] memory, address[] memory) {
|
||||
address[] memory simplePath = new address[](2);
|
||||
simplePath[0] = reserveIn;
|
||||
simplePath[1] = reserveOut;
|
||||
|
||||
uint256[] memory amountsWithoutWeth;
|
||||
uint256[] memory amountsWithWeth;
|
||||
address[] memory pathWithWeth = new address[](3);
|
||||
|
||||
if (reserveIn != WETH_ADDRESS && reserveOut != WETH_ADDRESS) {
|
||||
pathWithWeth[0] = reserveIn;
|
||||
pathWithWeth[1] = WETH_ADDRESS;
|
||||
pathWithWeth[2] = reserveOut;
|
||||
|
||||
try UNISWAP_ROUTER.getAmountsIn(amountOut, pathWithWeth) returns (
|
||||
uint256[] memory resultsWithWeth
|
||||
) {
|
||||
amountsWithWeth = resultsWithWeth;
|
||||
} catch {
|
||||
amountsWithWeth = new uint256[](3);
|
||||
}
|
||||
} else {
|
||||
amountsWithWeth = new uint256[](3);
|
||||
}
|
||||
|
||||
try UNISWAP_ROUTER.getAmountsIn(amountOut, simplePath) returns (
|
||||
uint256[] memory resultAmounts
|
||||
) {
|
||||
amountsWithoutWeth = resultAmounts;
|
||||
|
||||
return
|
||||
(amountsWithWeth[2] > amountsWithoutWeth[1])
|
||||
? (amountsWithWeth, pathWithWeth)
|
||||
: (amountsWithoutWeth, simplePath);
|
||||
} catch {
|
||||
return (amountsWithWeth, pathWithWeth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates the 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 uint256[] amounts Array containing the amountIn and amountOut for a swap
|
||||
*/
|
||||
function _getAmountsIn(
|
||||
address reserveIn,
|
||||
address reserveOut,
|
||||
uint256 amountOut,
|
||||
bool useEthPath
|
||||
) internal view returns (uint256[] memory) {
|
||||
address[] memory path;
|
||||
|
||||
if (useEthPath) {
|
||||
path = new address[](3);
|
||||
path[0] = reserveIn;
|
||||
path[1] = WETH_ADDRESS;
|
||||
path[2] = reserveOut;
|
||||
} else {
|
||||
path = new address[](2);
|
||||
path[0] = reserveIn;
|
||||
path[1] = reserveOut;
|
||||
}
|
||||
|
||||
return UNISWAP_ROUTER.getAmountsIn(amountOut, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency rescue for token stucked on this contract, as failsafe mechanism
|
||||
* - Funds should never remain in this contract more time than during transactions
|
||||
* - Only callable by the owner
|
||||
**/
|
||||
function rescueTokens(IERC20 token) external onlyOwner {
|
||||
token.transfer(owner(), token.balanceOf(address(this)));
|
||||
}
|
||||
}
|
254
contracts/adapters/FlashLiquidationAdapter.sol
Normal file
254
contracts/adapters/FlashLiquidationAdapter.sol
Normal file
|
@ -0,0 +1,254 @@
|
|||
// 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.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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Transfer remaining profit to initiator
|
||||
if (vars.maxCollateralToLiquidate.sub(soldAmount) > 0) {
|
||||
IERC20(collateralAsset).transfer(initiator, vars.maxCollateralToLiquidate.sub(soldAmount));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
92
contracts/adapters/interfaces/IBaseUniswapAdapter.sol
Normal file
92
contracts/adapters/interfaces/IBaseUniswapAdapter.sol
Normal file
|
@ -0,0 +1,92 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity 0.6.12;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
||||
import {IUniswapV2Router02} from '../../interfaces/IUniswapV2Router02.sol';
|
||||
|
||||
interface IBaseUniswapAdapter {
|
||||
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
|
||||
|
||||
struct PermitSignature {
|
||||
uint256 amount;
|
||||
uint256 deadline;
|
||||
uint8 v;
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
}
|
||||
|
||||
struct AmountCalc {
|
||||
uint256 calculatedAmount;
|
||||
uint256 relativePrice;
|
||||
uint256 amountInUsd;
|
||||
uint256 amountOutUsd;
|
||||
address[] path;
|
||||
}
|
||||
|
||||
function WETH_ADDRESS() external returns (address);
|
||||
|
||||
function MAX_SLIPPAGE_PERCENT() external returns (uint256);
|
||||
|
||||
function FLASHLOAN_PREMIUM_TOTAL() external returns (uint256);
|
||||
|
||||
function USD_ADDRESS() external returns (address);
|
||||
|
||||
function ORACLE() external returns (IPriceOracleGetter);
|
||||
|
||||
function UNISWAP_ROUTER() external returns (IUniswapV2Router02);
|
||||
|
||||
/**
|
||||
* @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)
|
||||
* @return address[] The exchange path
|
||||
*/
|
||||
function getAmountsOut(
|
||||
uint256 amountIn,
|
||||
address reserveIn,
|
||||
address reserveOut,
|
||||
bool withFlash
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
address[] memory
|
||||
);
|
||||
|
||||
/**
|
||||
* @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)
|
||||
* @return address[] The exchange path
|
||||
*/
|
||||
function getAmountsIn(
|
||||
uint256 amountOut,
|
||||
address reserveIn,
|
||||
address reserveOut,
|
||||
bool withFlash
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
address[] memory
|
||||
);
|
||||
}
|
16
contracts/interfaces/IERC20WithPermit.sol
Normal file
16
contracts/interfaces/IERC20WithPermit.sol
Normal file
|
@ -0,0 +1,16 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity 0.6.12;
|
||||
|
||||
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||
|
||||
interface IERC20WithPermit is IERC20 {
|
||||
function permit(
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 value,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external;
|
||||
}
|
30
contracts/interfaces/IUniswapV2Router02.sol
Normal file
30
contracts/interfaces/IUniswapV2Router02.sol
Normal file
|
@ -0,0 +1,30 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity 0.6.12;
|
||||
|
||||
interface IUniswapV2Router02 {
|
||||
function swapExactTokensForTokens(
|
||||
uint256 amountIn,
|
||||
uint256 amountOutMin,
|
||||
address[] calldata path,
|
||||
address to,
|
||||
uint256 deadline
|
||||
) external returns (uint256[] memory amounts);
|
||||
|
||||
function swapTokensForExactTokens(
|
||||
uint256 amountOut,
|
||||
uint256 amountInMax,
|
||||
address[] calldata path,
|
||||
address to,
|
||||
uint256 deadline
|
||||
) external returns (uint256[] memory amounts);
|
||||
|
||||
function getAmountsOut(uint256 amountIn, address[] calldata path)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
|
||||
function getAmountsIn(uint256 amountOut, address[] calldata path)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory amounts);
|
||||
}
|
Loading…
Reference in New Issue
Block a user