mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Merge and fix conflicts
This commit is contained in:
commit
6842978706
|
@ -299,6 +299,22 @@ interface ILendingPool {
|
||||||
uint16 referralCode
|
uint16 referralCode
|
||||||
) external;
|
) external;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||||
|
* - It's not possible to release one asset to swap for the same
|
||||||
|
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||||
|
* @param fromAsset Asset to swap from
|
||||||
|
* @param toAsset Asset to swap to
|
||||||
|
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||||
|
**/
|
||||||
|
function swapLiquidity(
|
||||||
|
address receiverAddress,
|
||||||
|
address fromAsset,
|
||||||
|
address toAsset,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
bytes calldata params
|
||||||
|
) external;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev accessory functions to fetch data from the core contract
|
* @dev accessory functions to fetch data from the core contract
|
||||||
**/
|
**/
|
||||||
|
|
|
@ -2,20 +2,19 @@
|
||||||
pragma solidity ^0.6.8;
|
pragma solidity ^0.6.8;
|
||||||
|
|
||||||
interface ISwapAdapter {
|
interface ISwapAdapter {
|
||||||
|
/**
|
||||||
/**
|
* @dev Swaps an `amountToSwap` of an asset to another, approving a `fundsDestination` to pull the funds
|
||||||
* @dev Swaps an `amountToSwap` of an asset to another, approving a `fundsDestination` to pull the funds
|
* @param assetToSwapFrom Origin asset
|
||||||
* @param assetToSwapFrom Origin asset
|
* @param assetToSwapTo Destination asset
|
||||||
* @param assetToSwapTo Destination asset
|
* @param amountToSwap How much `assetToSwapFrom` needs to be swapped
|
||||||
* @param amountToSwap How much `assetToSwapFrom` needs to be swapped
|
* @param fundsDestination Address that will be pulling the swapped funds
|
||||||
* @param fundsDestination Address that will be pulling the swapped funds
|
* @param params Additional variadic field to include extra params
|
||||||
* @param params Additional variadic field to include extra params
|
*/
|
||||||
*/
|
function executeOperation(
|
||||||
function executeOperation(
|
address assetToSwapFrom,
|
||||||
address assetToSwapFrom,
|
address assetToSwapTo,
|
||||||
address assetToSwapTo,
|
uint256 amountToSwap,
|
||||||
uint256 amountToSwap,
|
address fundsDestination,
|
||||||
address fundsDestination,
|
bytes calldata params
|
||||||
bytes calldata params
|
) external;
|
||||||
) external;
|
|
||||||
}
|
}
|
|
@ -20,6 +20,7 @@ import {UserConfiguration} from '../libraries/configuration/UserConfiguration.so
|
||||||
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
|
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
|
||||||
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
|
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
|
||||||
import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol';
|
import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol';
|
||||||
|
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
||||||
import {LendingPoolLiquidationManager} from './LendingPoolLiquidationManager.sol';
|
import {LendingPoolLiquidationManager} from './LendingPoolLiquidationManager.sol';
|
||||||
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
||||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||||
|
@ -584,6 +585,43 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||||
|
* - It's not possible to release one asset to swap for the same
|
||||||
|
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||||
|
* @param fromAsset Asset to swap from
|
||||||
|
* @param toAsset Asset to swap to
|
||||||
|
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||||
|
**/
|
||||||
|
function swapLiquidity(
|
||||||
|
address receiverAddress,
|
||||||
|
address fromAsset,
|
||||||
|
address toAsset,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
bytes calldata params
|
||||||
|
) external override {
|
||||||
|
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
||||||
|
|
||||||
|
//solium-disable-next-line
|
||||||
|
(bool success, bytes memory result) = liquidationManager.delegatecall(
|
||||||
|
abi.encodeWithSignature(
|
||||||
|
'swapLiquidity(address,address,address,uint256,bytes)',
|
||||||
|
receiverAddress,
|
||||||
|
fromAsset,
|
||||||
|
toAsset,
|
||||||
|
amountToSwap,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
);
|
||||||
|
require(success, Errors.FAILED_COLLATERAL_SWAP);
|
||||||
|
|
||||||
|
(uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
|
||||||
|
|
||||||
|
if (returnCode != 0) {
|
||||||
|
revert(string(abi.encodePacked(returnMessage)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev accessory functions to fetch data from the core contract
|
* @dev accessory functions to fetch data from the core contract
|
||||||
**/
|
**/
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {PercentageMath} from '../libraries/math/PercentageMath.sol';
|
||||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||||
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
||||||
import {Errors} from '../libraries/helpers/Errors.sol';
|
import {Errors} from '../libraries/helpers/Errors.sol';
|
||||||
|
import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title LendingPoolLiquidationManager contract
|
* @title LendingPoolLiquidationManager contract
|
||||||
|
@ -91,15 +92,6 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
uint256 swappedCollateralAmount
|
uint256 swappedCollateralAmount
|
||||||
);
|
);
|
||||||
|
|
||||||
enum LiquidationErrors {
|
|
||||||
NO_ERROR,
|
|
||||||
NO_COLLATERAL_AVAILABLE,
|
|
||||||
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
|
||||||
CURRRENCY_NOT_BORROWED,
|
|
||||||
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
|
||||||
NOT_ENOUGH_LIQUIDITY
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LiquidationCallLocalVars {
|
struct LiquidationCallLocalVars {
|
||||||
uint256 userCollateralBalance;
|
uint256 userCollateralBalance;
|
||||||
uint256 userStableDebt;
|
uint256 userStableDebt;
|
||||||
|
@ -115,6 +107,29 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
uint256 healthFactor;
|
uint256 healthFactor;
|
||||||
IAToken collateralAtoken;
|
IAToken collateralAtoken;
|
||||||
bool isCollateralEnabled;
|
bool isCollateralEnabled;
|
||||||
|
address principalAToken;
|
||||||
|
uint256 errorCode;
|
||||||
|
string errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwapLiquidityLocalVars {
|
||||||
|
uint256 healthFactor;
|
||||||
|
uint256 amountToReceive;
|
||||||
|
uint256 userBalanceBefore;
|
||||||
|
IAToken fromReserveAToken;
|
||||||
|
IAToken toReserveAToken;
|
||||||
|
uint256 errorCode;
|
||||||
|
string errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AvailableCollateralToLiquidateLocalVars {
|
||||||
|
uint256 userCompoundedBorrowBalance;
|
||||||
|
uint256 liquidationBonus;
|
||||||
|
uint256 collateralPrice;
|
||||||
|
uint256 principalCurrencyPrice;
|
||||||
|
uint256 maxAmountCollateralToLiquidate;
|
||||||
|
uint256 principalDecimals;
|
||||||
|
uint256 collateralDecimals;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,8 +156,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
uint256 purchaseAmount,
|
uint256 purchaseAmount,
|
||||||
bool receiveAToken
|
bool receiveAToken
|
||||||
) external returns (uint256, string memory) {
|
) external returns (uint256, string memory) {
|
||||||
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
|
||||||
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||||
|
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
||||||
UserConfiguration.Map storage userConfig = usersConfig[user];
|
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||||
|
|
||||||
LiquidationCallLocalVars memory vars;
|
LiquidationCallLocalVars memory vars;
|
||||||
|
@ -155,43 +170,29 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
addressesProvider.getPriceOracle()
|
addressesProvider.getPriceOracle()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
|
||||||
return (
|
|
||||||
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
|
||||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
|
||||||
|
|
||||||
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
|
||||||
|
|
||||||
vars.isCollateralEnabled =
|
|
||||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
|
||||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
|
||||||
|
|
||||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
|
||||||
if (!vars.isCollateralEnabled) {
|
|
||||||
return (
|
|
||||||
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
|
||||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated
|
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated
|
||||||
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
|
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
|
||||||
user,
|
user,
|
||||||
principalReserve
|
principalReserve
|
||||||
);
|
);
|
||||||
|
|
||||||
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
|
||||||
return (
|
collateralReserve,
|
||||||
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
principalReserve,
|
||||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
userConfig,
|
||||||
);
|
vars.healthFactor,
|
||||||
|
vars.userStableDebt,
|
||||||
|
vars.userVariableDebt
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||||
|
return (vars.errorCode, vars.errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
//all clear - calculate the max principal amount that can be liquidated
|
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
||||||
|
|
||||||
|
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
||||||
|
|
||||||
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
|
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
|
||||||
LIQUIDATION_CLOSE_FACTOR_PERCENT
|
LIQUIDATION_CLOSE_FACTOR_PERCENT
|
||||||
);
|
);
|
||||||
|
@ -227,7 +228,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
);
|
);
|
||||||
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
|
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
|
||||||
return (
|
return (
|
||||||
uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
uint256(Errors.LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
||||||
Errors.NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
|
Errors.NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -299,7 +300,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
receiveAToken
|
receiveAToken
|
||||||
);
|
);
|
||||||
|
|
||||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -322,9 +323,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
address receiver,
|
address receiver,
|
||||||
bytes calldata params
|
bytes calldata params
|
||||||
) external returns (uint256, string memory) {
|
) external returns (uint256, string memory) {
|
||||||
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
|
||||||
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||||
|
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
||||||
UserConfiguration.Map storage userConfig = usersConfig[user];
|
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||||
|
|
||||||
LiquidationCallLocalVars memory vars;
|
LiquidationCallLocalVars memory vars;
|
||||||
|
@ -337,36 +337,20 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
addressesProvider.getPriceOracle()
|
addressesProvider.getPriceOracle()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
|
||||||
msg.sender != user && vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
|
||||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.sender != user) {
|
|
||||||
vars.isCollateralEnabled =
|
|
||||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
|
||||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
|
||||||
|
|
||||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
|
||||||
if (!vars.isCollateralEnabled) {
|
|
||||||
return (
|
|
||||||
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
|
||||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
|
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
|
||||||
|
|
||||||
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateRepayWithCollateral(
|
||||||
return (
|
collateralReserve,
|
||||||
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
debtReserve,
|
||||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
userConfig,
|
||||||
);
|
user,
|
||||||
|
vars.healthFactor,
|
||||||
|
vars.userStableDebt,
|
||||||
|
vars.userVariableDebt
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||||
|
return (vars.errorCode, vars.errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt);
|
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt);
|
||||||
|
@ -410,7 +394,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
|
usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
address principalAToken = debtReserve.aTokenAddress;
|
vars.principalAToken = debtReserve.aTokenAddress;
|
||||||
|
|
||||||
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
||||||
ISwapAdapter(receiver).executeOperation(
|
ISwapAdapter(receiver).executeOperation(
|
||||||
|
@ -423,8 +407,13 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
|
|
||||||
//updating debt reserve
|
//updating debt reserve
|
||||||
debtReserve.updateCumulativeIndexesAndTimestamp();
|
debtReserve.updateCumulativeIndexesAndTimestamp();
|
||||||
debtReserve.updateInterestRates(principal, principalAToken, vars.actualAmountToLiquidate, 0);
|
debtReserve.updateInterestRates(
|
||||||
IERC20(principal).transferFrom(receiver, principalAToken, vars.actualAmountToLiquidate);
|
principal,
|
||||||
|
vars.principalAToken,
|
||||||
|
vars.actualAmountToLiquidate,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
IERC20(principal).transferFrom(receiver, vars.principalAToken, vars.actualAmountToLiquidate);
|
||||||
|
|
||||||
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
||||||
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
|
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
|
||||||
|
@ -456,17 +445,100 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
||||||
vars.maxCollateralToLiquidate
|
vars.maxCollateralToLiquidate
|
||||||
);
|
);
|
||||||
|
|
||||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AvailableCollateralToLiquidateLocalVars {
|
/**
|
||||||
uint256 userCompoundedBorrowBalance;
|
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||||
uint256 liquidationBonus;
|
* - It's not possible to release one asset to swap for the same
|
||||||
uint256 collateralPrice;
|
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||||
uint256 principalCurrencyPrice;
|
* @param fromAsset Asset to swap from
|
||||||
uint256 maxAmountCollateralToLiquidate;
|
* @param toAsset Asset to swap to
|
||||||
uint256 principalDecimals;
|
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||||
uint256 collateralDecimals;
|
**/
|
||||||
|
function swapLiquidity(
|
||||||
|
address receiverAddress,
|
||||||
|
address fromAsset,
|
||||||
|
address toAsset,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
bytes calldata params
|
||||||
|
) external returns (uint256, string memory) {
|
||||||
|
ReserveLogic.ReserveData storage fromReserve = reserves[fromAsset];
|
||||||
|
ReserveLogic.ReserveData storage toReserve = reserves[toAsset];
|
||||||
|
|
||||||
|
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
|
||||||
|
SwapLiquidityLocalVars memory vars;
|
||||||
|
|
||||||
|
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateSwapLiquidity(
|
||||||
|
fromReserve,
|
||||||
|
toReserve,
|
||||||
|
fromAsset,
|
||||||
|
toAsset
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||||
|
return (vars.errorCode, vars.errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
vars.fromReserveAToken = IAToken(fromReserve.aTokenAddress);
|
||||||
|
vars.toReserveAToken = IAToken(toReserve.aTokenAddress);
|
||||||
|
|
||||||
|
fromReserve.updateCumulativeIndexesAndTimestamp();
|
||||||
|
toReserve.updateCumulativeIndexesAndTimestamp();
|
||||||
|
|
||||||
|
if (vars.fromReserveAToken.balanceOf(msg.sender) == amountToSwap) {
|
||||||
|
usersConfig[msg.sender].setUsingAsCollateral(fromReserve.id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fromReserve.updateInterestRates(fromAsset, address(vars.fromReserveAToken), 0, amountToSwap);
|
||||||
|
|
||||||
|
vars.fromReserveAToken.burn(
|
||||||
|
msg.sender,
|
||||||
|
receiverAddress,
|
||||||
|
amountToSwap,
|
||||||
|
fromReserve.liquidityIndex
|
||||||
|
);
|
||||||
|
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
||||||
|
ISwapAdapter(receiverAddress).executeOperation(
|
||||||
|
fromAsset,
|
||||||
|
toAsset,
|
||||||
|
amountToSwap,
|
||||||
|
address(this),
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
vars.amountToReceive = IERC20(toAsset).balanceOf(receiverAddress);
|
||||||
|
if (vars.amountToReceive != 0) {
|
||||||
|
IERC20(toAsset).transferFrom(
|
||||||
|
receiverAddress,
|
||||||
|
address(vars.toReserveAToken),
|
||||||
|
vars.amountToReceive
|
||||||
|
);
|
||||||
|
vars.toReserveAToken.mint(msg.sender, vars.amountToReceive, toReserve.liquidityIndex);
|
||||||
|
toReserve.updateInterestRates(
|
||||||
|
toAsset,
|
||||||
|
address(vars.toReserveAToken),
|
||||||
|
vars.amountToReceive,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
|
||||||
|
msg.sender,
|
||||||
|
reserves,
|
||||||
|
usersConfig[msg.sender],
|
||||||
|
reservesList,
|
||||||
|
addressesProvider.getPriceOracle()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (vars.healthFactor < GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD),
|
||||||
|
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -39,8 +39,10 @@ library Errors {
|
||||||
string public constant CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27'; // 'The actual balance of the protocol is inconsistent'
|
string public constant CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27'; // 'The actual balance of the protocol is inconsistent'
|
||||||
string public constant INVALID_FLASHLOAN_MODE = '43'; //Invalid flashloan mode selected
|
string public constant INVALID_FLASHLOAN_MODE = '43'; //Invalid flashloan mode selected
|
||||||
string public constant BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54'; // User borrows on behalf, but allowance are too small
|
string public constant BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54'; // User borrows on behalf, but allowance are too small
|
||||||
string public constant REENTRANCY_NOT_ALLOWED = '52';
|
string public constant REENTRANCY_NOT_ALLOWED = '57';
|
||||||
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';
|
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';
|
||||||
|
string public constant FAILED_COLLATERAL_SWAP = '55';
|
||||||
|
string public constant INVALID_EQUAL_ASSETS_TO_SWAP = '56';
|
||||||
|
|
||||||
// require error messages - aToken
|
// require error messages - aToken
|
||||||
string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool'
|
string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool'
|
||||||
|
@ -78,4 +80,15 @@ library Errors {
|
||||||
// pausable error message
|
// pausable error message
|
||||||
string public constant IS_PAUSED = '54'; // 'Pool is paused'
|
string public constant IS_PAUSED = '54'; // 'Pool is paused'
|
||||||
string public constant NOT_PAUSED = '55'; // 'Pool is not paused'
|
string public constant NOT_PAUSED = '55'; // 'Pool is not paused'
|
||||||
|
enum LiquidationErrors {
|
||||||
|
NO_ERROR,
|
||||||
|
NO_COLLATERAL_AVAILABLE,
|
||||||
|
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
||||||
|
CURRRENCY_NOT_BORROWED,
|
||||||
|
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
||||||
|
NOT_ENOUGH_LIQUIDITY,
|
||||||
|
NO_ACTIVE_RESERVE,
|
||||||
|
HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD,
|
||||||
|
INVALID_EQUAL_ASSETS_TO_SWAP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ library ReserveLogic {
|
||||||
ReserveData storage reserve,
|
ReserveData storage reserve,
|
||||||
uint256 totalLiquidity,
|
uint256 totalLiquidity,
|
||||||
uint256 amount
|
uint256 amount
|
||||||
) internal {
|
) external {
|
||||||
uint256 amountToLiquidityRatio = amount.wadToRay().rayDiv(totalLiquidity.wadToRay());
|
uint256 amountToLiquidityRatio = amount.wadToRay().rayDiv(totalLiquidity.wadToRay());
|
||||||
|
|
||||||
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
|
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
|
||||||
|
@ -249,7 +249,7 @@ library ReserveLogic {
|
||||||
address aTokenAddress,
|
address aTokenAddress,
|
||||||
uint256 liquidityAdded,
|
uint256 liquidityAdded,
|
||||||
uint256 liquidityTaken
|
uint256 liquidityTaken
|
||||||
) internal {
|
) external {
|
||||||
UpdateInterestRatesLocalVars memory vars;
|
UpdateInterestRatesLocalVars memory vars;
|
||||||
|
|
||||||
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
|
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
|
||||||
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
|
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
|
||||||
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
||||||
import {Errors} from '../helpers/Errors.sol';
|
import {Errors} from '../helpers/Errors.sol';
|
||||||
|
import {Helpers} from '../helpers/Helpers.sol';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title ReserveLogic library
|
* @title ReserveLogic library
|
||||||
|
@ -329,4 +330,139 @@ library ValidationLogic {
|
||||||
require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL);
|
require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL);
|
||||||
require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE);
|
require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Validates the liquidationCall() action
|
||||||
|
* @param collateralReserve The reserve data of the collateral
|
||||||
|
* @param principalReserve The reserve data of the principal
|
||||||
|
* @param userConfig The user configuration
|
||||||
|
* @param userHealthFactor The user's health factor
|
||||||
|
* @param userStableDebt Total stable debt balance of the user
|
||||||
|
* @param userVariableDebt Total variable debt balance of the user
|
||||||
|
**/
|
||||||
|
function validateLiquidationCall(
|
||||||
|
ReserveLogic.ReserveData storage collateralReserve,
|
||||||
|
ReserveLogic.ReserveData storage principalReserve,
|
||||||
|
UserConfiguration.Map storage userConfig,
|
||||||
|
uint256 userHealthFactor,
|
||||||
|
uint256 userStableDebt,
|
||||||
|
uint256 userVariableDebt
|
||||||
|
) internal view returns (uint256, string memory) {
|
||||||
|
if (
|
||||||
|
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
|
||||||
|
) {
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||||
|
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||||
|
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||||
|
|
||||||
|
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||||
|
if (!isCollateralEnabled) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||||
|
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userStableDebt == 0 && userVariableDebt == 0) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||||
|
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Validates the repayWithCollateral() action
|
||||||
|
* @param collateralReserve The reserve data of the collateral
|
||||||
|
* @param principalReserve The reserve data of the principal
|
||||||
|
* @param userConfig The user configuration
|
||||||
|
* @param user The address of the user
|
||||||
|
* @param userHealthFactor The user's health factor
|
||||||
|
* @param userStableDebt Total stable debt balance of the user
|
||||||
|
* @param userVariableDebt Total variable debt balance of the user
|
||||||
|
**/
|
||||||
|
function validateRepayWithCollateral(
|
||||||
|
ReserveLogic.ReserveData storage collateralReserve,
|
||||||
|
ReserveLogic.ReserveData storage principalReserve,
|
||||||
|
UserConfiguration.Map storage userConfig,
|
||||||
|
address user,
|
||||||
|
uint256 userHealthFactor,
|
||||||
|
uint256 userStableDebt,
|
||||||
|
uint256 userVariableDebt
|
||||||
|
) internal view returns (uint256, string memory) {
|
||||||
|
if (
|
||||||
|
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
|
||||||
|
) {
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
msg.sender != user && userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||||
|
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.sender != user) {
|
||||||
|
bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||||
|
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||||
|
|
||||||
|
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||||
|
if (!isCollateralEnabled) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||||
|
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userStableDebt == 0 && userVariableDebt == 0) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||||
|
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Validates the swapLiquidity() action
|
||||||
|
* @param fromReserve The reserve data of the asset to swap from
|
||||||
|
* @param toReserve The reserve data of the asset to swap to
|
||||||
|
* @param fromAsset Address of the asset to swap from
|
||||||
|
* @param toAsset Address of the asset to swap to
|
||||||
|
**/
|
||||||
|
function validateSwapLiquidity(
|
||||||
|
ReserveLogic.ReserveData storage fromReserve,
|
||||||
|
ReserveLogic.ReserveData storage toReserve,
|
||||||
|
address fromAsset,
|
||||||
|
address toAsset
|
||||||
|
) internal view returns (uint256, string memory) {
|
||||||
|
if (!fromReserve.configuration.getActive() || !toReserve.configuration.getActive()) {
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromAsset == toAsset) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.INVALID_EQUAL_ASSETS_TO_SWAP),
|
||||||
|
Errors.INVALID_EQUAL_ASSETS_TO_SWAP
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,57 +4,56 @@ pragma solidity ^0.6.8;
|
||||||
import {MintableERC20} from '../tokens/MintableERC20.sol';
|
import {MintableERC20} from '../tokens/MintableERC20.sol';
|
||||||
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
|
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
|
||||||
import {ISwapAdapter} from '../../interfaces/ISwapAdapter.sol';
|
import {ISwapAdapter} from '../../interfaces/ISwapAdapter.sol';
|
||||||
import {ILendingPool} from "../../interfaces/ILendingPool.sol";
|
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
|
||||||
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||||
|
|
||||||
contract MockSwapAdapter is ISwapAdapter {
|
contract MockSwapAdapter is ISwapAdapter {
|
||||||
|
uint256 internal _amountToReturn;
|
||||||
|
bool internal _tryReentrancy;
|
||||||
|
ILendingPoolAddressesProvider public addressesProvider;
|
||||||
|
|
||||||
uint256 internal _amountToReturn;
|
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
|
||||||
bool internal _tryReentrancy;
|
|
||||||
ILendingPoolAddressesProvider public addressesProvider;
|
|
||||||
|
|
||||||
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
|
constructor(ILendingPoolAddressesProvider provider) public {
|
||||||
|
addressesProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(ILendingPoolAddressesProvider provider) public {
|
function setAmountToReturn(uint256 amount) public {
|
||||||
addressesProvider = provider;
|
_amountToReturn = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTryReentrancy(bool tryReentrancy) public {
|
||||||
|
_tryReentrancy = tryReentrancy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeOperation(
|
||||||
|
address assetToSwapFrom,
|
||||||
|
address assetToSwapTo,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
address fundsDestination,
|
||||||
|
bytes calldata params
|
||||||
|
) external override {
|
||||||
|
params;
|
||||||
|
IERC20(assetToSwapFrom).transfer(address(1), amountToSwap); // We don't want to keep funds here
|
||||||
|
MintableERC20(assetToSwapTo).mint(_amountToReturn);
|
||||||
|
IERC20(assetToSwapTo).approve(fundsDestination, _amountToReturn);
|
||||||
|
|
||||||
|
if (_tryReentrancy) {
|
||||||
|
ILendingPool(fundsDestination).repayWithCollateral(
|
||||||
|
assetToSwapFrom,
|
||||||
|
assetToSwapTo,
|
||||||
|
address(1), // Doesn't matter, we just want to test the reentrancy
|
||||||
|
1 ether, // Same
|
||||||
|
address(1), // Same
|
||||||
|
'0x'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAmountToReturn(uint256 amount) public {
|
emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, _amountToReturn);
|
||||||
_amountToReturn = amount;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function setTryReentrancy(bool tryReentrancy) public {
|
function burnAsset(IERC20 asset, uint256 amount) public {
|
||||||
_tryReentrancy = tryReentrancy;
|
uint256 amountToBurn = (amount == type(uint256).max) ? asset.balanceOf(address(this)) : amount;
|
||||||
}
|
asset.transfer(address(0), amountToBurn);
|
||||||
|
}
|
||||||
function executeOperation(
|
|
||||||
address assetToSwapFrom,
|
|
||||||
address assetToSwapTo,
|
|
||||||
uint256 amountToSwap,
|
|
||||||
address fundsDestination,
|
|
||||||
bytes calldata params
|
|
||||||
) external override {
|
|
||||||
params;
|
|
||||||
IERC20(assetToSwapFrom).transfer(address(1), amountToSwap); // We don't want to keep funds here
|
|
||||||
MintableERC20(assetToSwapTo).mint(_amountToReturn);
|
|
||||||
IERC20(assetToSwapTo).approve(fundsDestination, _amountToReturn);
|
|
||||||
|
|
||||||
if (_tryReentrancy) {
|
|
||||||
ILendingPool(fundsDestination).repayWithCollateral(
|
|
||||||
assetToSwapFrom,
|
|
||||||
assetToSwapTo,
|
|
||||||
address(1), // Doesn't matter, we just want to test the reentrancy
|
|
||||||
1 ether, // Same
|
|
||||||
address(1), // Same
|
|
||||||
"0x"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, _amountToReturn);
|
|
||||||
}
|
|
||||||
|
|
||||||
function burnAsset(IERC20 asset, uint256 amount) public {
|
|
||||||
uint256 amountToBurn = (amount == type(uint256).max) ? asset.balanceOf(address(this)) : amount;
|
|
||||||
asset.transfer(address(0), amountToBurn);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -493,9 +493,6 @@
|
||||||
"MockSwapAdapter": {
|
"MockSwapAdapter": {
|
||||||
"buidlerevm": {
|
"buidlerevm": {
|
||||||
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
|
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
|
||||||
},
|
|
||||||
"localhost": {
|
|
||||||
"address": "0x749258D38b0473d96FEcc14cC5e7DCE12d7Bd6f6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
198
test/collateral-swap.spec.ts
Normal file
198
test/collateral-swap.spec.ts
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
import {makeSuite, TestEnv} from './helpers/make-suite';
|
||||||
|
import {MockSwapAdapter} from '../types/MockSwapAdapter';
|
||||||
|
import {getMockSwapAdapter} from '../helpers/contracts-helpers';
|
||||||
|
import {ProtocolErrors} from '../helpers/types';
|
||||||
|
import {ethers} from 'ethers';
|
||||||
|
import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants';
|
||||||
|
import {getContractsData, getTxCostAndTimestamp} from './helpers/actions';
|
||||||
|
import {calcExpectedATokenBalance} from './helpers/utils/calculations';
|
||||||
|
import {waitForTx} from './__setup.spec';
|
||||||
|
import {advanceBlock, timeLatest} from '../helpers/misc-utils';
|
||||||
|
|
||||||
|
const {expect} = require('chai');
|
||||||
|
|
||||||
|
makeSuite('LendingPool SwapDeposit function', (testEnv: TestEnv) => {
|
||||||
|
let _mockSwapAdapter = {} as MockSwapAdapter;
|
||||||
|
const {HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD} = ProtocolErrors;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
_mockSwapAdapter = await getMockSwapAdapter();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposits WETH into the reserve', async () => {
|
||||||
|
const {pool, weth, users} = testEnv;
|
||||||
|
const amountToDeposit = ethers.utils.parseEther('1');
|
||||||
|
|
||||||
|
for (const signer of [weth.signer, users[2].signer]) {
|
||||||
|
const connectedWETH = weth.connect(signer);
|
||||||
|
await connectedWETH.mint(amountToDeposit);
|
||||||
|
await connectedWETH.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
await pool
|
||||||
|
.connect(signer)
|
||||||
|
.deposit(weth.address, amountToDeposit, await signer.getAddress(), '0');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('User tries to swap more then he can, revert expected', async () => {
|
||||||
|
const {pool, weth, dai} = testEnv;
|
||||||
|
await expect(
|
||||||
|
pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
ethers.utils.parseEther('1.1'),
|
||||||
|
'0x10'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('55');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User tries to swap asset on equal asset, revert expected', async () => {
|
||||||
|
const {pool, weth} = testEnv;
|
||||||
|
await expect(
|
||||||
|
pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
weth.address,
|
||||||
|
ethers.utils.parseEther('0.1'),
|
||||||
|
'0x10'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('56');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User tries to swap more then available on the reserve', async () => {
|
||||||
|
const {pool, weth, dai, users, aEth, deployer} = testEnv;
|
||||||
|
|
||||||
|
await pool.borrow(weth.address, ethers.utils.parseEther('0.1'), 1, 0, deployer.address);
|
||||||
|
await pool.connect(users[2].signer).withdraw(weth.address, ethers.utils.parseEther('1'));
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
ethers.utils.parseEther('1'),
|
||||||
|
'0x10'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('55');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User tries to swap correct amount', async () => {
|
||||||
|
const {pool, weth, dai, aEth, aDai} = testEnv;
|
||||||
|
const userAddress = await pool.signer.getAddress();
|
||||||
|
const amountToSwap = ethers.utils.parseEther('0.25');
|
||||||
|
|
||||||
|
const amountToReturn = ethers.utils.parseEther('0.5');
|
||||||
|
await _mockSwapAdapter.setAmountToReturn(amountToReturn);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: wethReserveDataBefore,
|
||||||
|
userData: wethUserDataBefore,
|
||||||
|
} = await getContractsData(weth.address, userAddress, testEnv);
|
||||||
|
|
||||||
|
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
|
||||||
|
dai.address,
|
||||||
|
userAddress,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const reserveBalanceWETHBefore = await weth.balanceOf(aEth.address);
|
||||||
|
const reserveBalanceDAIBefore = await dai.balanceOf(aDai.address);
|
||||||
|
|
||||||
|
const txReceipt = await waitForTx(
|
||||||
|
await pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
amountToSwap,
|
||||||
|
'0x10'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const {txTimestamp} = await getTxCostAndTimestamp(txReceipt);
|
||||||
|
const userATokenBalanceWETHAfter = await aEth.balanceOf(userAddress);
|
||||||
|
const userATokenBalanceDAIAfter = await aDai.balanceOf(userAddress);
|
||||||
|
|
||||||
|
const reserveBalanceWETHAfter = await weth.balanceOf(aEth.address);
|
||||||
|
const reserveBalanceDAIAfter = await dai.balanceOf(aDai.address);
|
||||||
|
|
||||||
|
expect(userATokenBalanceWETHAfter.toString()).to.be.equal(
|
||||||
|
calcExpectedATokenBalance(wethReserveDataBefore, wethUserDataBefore, txTimestamp)
|
||||||
|
.minus(amountToSwap.toString())
|
||||||
|
.toString(),
|
||||||
|
'was burned incorrect amount of user funds'
|
||||||
|
);
|
||||||
|
expect(userATokenBalanceDAIAfter.toString()).to.be.equal(
|
||||||
|
calcExpectedATokenBalance(daiReserveDataBefore, daiUserDataBefore, txTimestamp)
|
||||||
|
.plus(amountToReturn.toString())
|
||||||
|
.toString(),
|
||||||
|
'was minted incorrect amount of user funds'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(reserveBalanceWETHAfter.toString()).to.be.equal(
|
||||||
|
reserveBalanceWETHBefore.sub(amountToSwap).toString(),
|
||||||
|
'was sent incorrect amount if reserve funds'
|
||||||
|
);
|
||||||
|
expect(reserveBalanceDAIAfter.toString()).to.be.equal(
|
||||||
|
reserveBalanceDAIBefore.add(amountToReturn).toString(),
|
||||||
|
'was received incorrect amount if reserve funds'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User tries to drop HF below one', async () => {
|
||||||
|
const {pool, weth, dai, deployer} = testEnv;
|
||||||
|
const amountToSwap = ethers.utils.parseEther('0.3');
|
||||||
|
|
||||||
|
const amountToReturn = ethers.utils.parseEther('0.5');
|
||||||
|
await _mockSwapAdapter.setAmountToReturn(amountToReturn);
|
||||||
|
|
||||||
|
await pool.borrow(weth.address, ethers.utils.parseEther('0.3'), 1, 0, deployer.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.swapLiquidity(_mockSwapAdapter.address, weth.address, dai.address, amountToSwap, '0x10')
|
||||||
|
).to.be.revertedWith(HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set usage as collateral to false if no leftovers after swap', async () => {
|
||||||
|
const {pool, weth, dai, aEth, users} = testEnv;
|
||||||
|
const userAddress = await pool.signer.getAddress();
|
||||||
|
|
||||||
|
// add more liquidity to allow user 0 to swap everything he has
|
||||||
|
await weth.connect(users[2].signer).mint(ethers.utils.parseEther('1'));
|
||||||
|
await pool
|
||||||
|
.connect(users[2].signer)
|
||||||
|
.deposit(weth.address, ethers.utils.parseEther('1'), users[2].address, '0');
|
||||||
|
|
||||||
|
// cleanup borrowings, to be abe to swap whole weth
|
||||||
|
const amountToRepay = ethers.utils.parseEther('0.5');
|
||||||
|
await weth.mint(amountToRepay);
|
||||||
|
await pool.repay(weth.address, amountToRepay, '1', userAddress);
|
||||||
|
const txTimestamp = (await timeLatest()).plus(100);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: wethReserveDataBefore,
|
||||||
|
userData: wethUserDataBefore,
|
||||||
|
} = await getContractsData(weth.address, userAddress, testEnv);
|
||||||
|
const amountToSwap = calcExpectedATokenBalance(
|
||||||
|
wethReserveDataBefore,
|
||||||
|
wethUserDataBefore,
|
||||||
|
txTimestamp.plus('1')
|
||||||
|
);
|
||||||
|
|
||||||
|
await advanceBlock(txTimestamp.toNumber());
|
||||||
|
|
||||||
|
await pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
amountToSwap.toString(),
|
||||||
|
'0x10'
|
||||||
|
);
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
userAddress,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.equal(
|
||||||
|
false,
|
||||||
|
'usageAsCollateralEnabled are not set to false'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,3 +1,5 @@
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import {TestEnv, makeSuite} from './helpers/make-suite';
|
import {TestEnv, makeSuite} from './helpers/make-suite';
|
||||||
import {APPROVAL_AMOUNT_LENDING_POOL, oneRay} from '../helpers/constants';
|
import {APPROVAL_AMOUNT_LENDING_POOL, oneRay} from '../helpers/constants';
|
||||||
import {
|
import {
|
||||||
|
@ -8,7 +10,6 @@ import {
|
||||||
import {ethers} from 'ethers';
|
import {ethers} from 'ethers';
|
||||||
import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver';
|
import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver';
|
||||||
import {ProtocolErrors, eContractid} from '../helpers/types';
|
import {ProtocolErrors, eContractid} from '../helpers/types';
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import {VariableDebtToken} from '../types/VariableDebtToken';
|
import {VariableDebtToken} from '../types/VariableDebtToken';
|
||||||
import {StableDebtToken} from '../types/StableDebtToken';
|
import {StableDebtToken} from '../types/StableDebtToken';
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
_mockFlashLoanReceiver = await getMockFlashLoanReceiver();
|
_mockFlashLoanReceiver = await getMockFlashLoanReceiver();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Deposits ETH into the reserve', async () => {
|
it('Deposits WETH into the reserve', async () => {
|
||||||
const {pool, weth} = testEnv;
|
const {pool, weth} = testEnv;
|
||||||
const userAddress = await pool.signer.getAddress();
|
const userAddress = await pool.signer.getAddress();
|
||||||
const amountToDeposit = ethers.utils.parseEther('1');
|
const amountToDeposit = ethers.utils.parseEther('1');
|
||||||
|
|
|
@ -720,7 +720,7 @@ const getDataBeforeAction = async (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTxCostAndTimestamp = async (tx: ContractReceipt) => {
|
export const getTxCostAndTimestamp = async (tx: ContractReceipt) => {
|
||||||
if (!tx.blockNumber || !tx.transactionHash || !tx.cumulativeGasUsed) {
|
if (!tx.blockNumber || !tx.transactionHash || !tx.cumulativeGasUsed) {
|
||||||
throw new Error('No tx blocknumber');
|
throw new Error('No tx blocknumber');
|
||||||
}
|
}
|
||||||
|
|
|
@ -919,16 +919,14 @@ const calcExpectedScaledATokenBalance = (
|
||||||
.minus(amountTaken.rayDiv(index));
|
.minus(amountTaken.rayDiv(index));
|
||||||
};
|
};
|
||||||
|
|
||||||
const calcExpectedATokenBalance = (
|
export const calcExpectedATokenBalance = (
|
||||||
reserveDataBeforeAction: ReserveData,
|
reserveDataBeforeAction: ReserveData,
|
||||||
userDataBeforeAction: UserReserveData,
|
userDataBeforeAction: UserReserveData,
|
||||||
currentTimestamp: BigNumber
|
currentTimestamp: BigNumber
|
||||||
) => {
|
) => {
|
||||||
const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp);
|
const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp);
|
||||||
|
|
||||||
const {
|
const {scaledATokenBalance: scaledBalanceBeforeAction} = userDataBeforeAction;
|
||||||
scaledATokenBalance: scaledBalanceBeforeAction,
|
|
||||||
} = userDataBeforeAction;
|
|
||||||
|
|
||||||
return scaledBalanceBeforeAction.rayMul(index);
|
return scaledBalanceBeforeAction.rayMul(index);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {makeSuite} from './helpers/make-suite';
|
||||||
import {ProtocolErrors, RateMode} from '../helpers/types';
|
import {ProtocolErrors, RateMode} from '../helpers/types';
|
||||||
import {calcExpectedStableDebtTokenBalance} from './helpers/utils/calculations';
|
import {calcExpectedStableDebtTokenBalance} from './helpers/utils/calculations';
|
||||||
import {getUserData} from './helpers/utils/helpers';
|
import {getUserData} from './helpers/utils/helpers';
|
||||||
|
import {parseEther} from 'ethers/lib/utils';
|
||||||
|
|
||||||
const chai = require('chai');
|
const chai = require('chai');
|
||||||
|
|
||||||
|
@ -23,6 +24,26 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
|
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("It's not possible to liquidate on a non-active collateral or a non active principal", async () => {
|
||||||
|
const {configurator, weth, pool, users, dai} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
await configurator.deactivateReserve(weth.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.liquidationCall(weth.address, dai.address, user.address, parseEther('1000'), false)
|
||||||
|
).to.be.revertedWith('2');
|
||||||
|
|
||||||
|
await configurator.activateReserve(weth.address);
|
||||||
|
|
||||||
|
await configurator.deactivateReserve(dai.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.liquidationCall(weth.address, dai.address, user.address, parseEther('1000'), false)
|
||||||
|
).to.be.revertedWith('2');
|
||||||
|
|
||||||
|
await configurator.activateReserve(dai.address);
|
||||||
|
});
|
||||||
|
|
||||||
it('LIQUIDATION - Deposits WETH, borrows DAI', async () => {
|
it('LIQUIDATION - Deposits WETH, borrows DAI', async () => {
|
||||||
const {dai, weth, users, pool, oracle} = testEnv;
|
const {dai, weth, users, pool, oracle} = testEnv;
|
||||||
const depositor = users[0];
|
const depositor = users[0];
|
||||||
|
|
|
@ -40,6 +40,43 @@ export const expectRepayWithCollateralEvent = (
|
||||||
|
|
||||||
makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
const {IS_PAUSED} = ProtocolErrors;
|
const {IS_PAUSED} = ProtocolErrors;
|
||||||
|
it("It's not possible to repayWithCollateral() on a non-active collateral or a non active principal", async () => {
|
||||||
|
const {configurator, weth, pool, users, dai, mockSwapAdapter} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
await configurator.deactivateReserve(weth.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
parseEther('100'),
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('2');
|
||||||
|
|
||||||
|
await configurator.activateReserve(weth.address);
|
||||||
|
|
||||||
|
await configurator.deactivateReserve(dai.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
parseEther('100'),
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('2');
|
||||||
|
|
||||||
|
await configurator.activateReserve(dai.address);
|
||||||
|
});
|
||||||
|
|
||||||
it('User 1 provides some liquidity for others to borrow', async () => {
|
it('User 1 provides some liquidity for others to borrow', async () => {
|
||||||
const {pool, weth, dai, usdc, deployer} = testEnv;
|
const {pool, weth, dai, usdc, deployer} = testEnv;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user