Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor liquidation on deprecated market #1305

Merged
merged 8 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions contracts/aave-v2/ExitPositionsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils {
uint256 collateralTokenUnit; // The collateral token unit considering its decimals.
uint256 borrowedReserveDecimals; // The number of decimals of the borrowed asset in the reserve.
uint256 borrowedTokenUnit; // The unit of borrowed token considering its decimals.
uint256 closeFactor; // The close factor used during the liquidation.
bool liquidationAllowed; // Whether the liquidation is allowed or not.
}

// Struct to avoid stack too deep.
Expand Down Expand Up @@ -221,23 +223,22 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils {
_updateIndexes(_poolTokenBorrowed);
_updateIndexes(_poolTokenCollateral);

if (!borrowedMarket.isDeprecated && !_liquidationAllowed(_borrower))
revert UnauthorisedLiquidate();

address tokenBorrowedAddress = market[_poolTokenBorrowed].underlyingToken;
LiquidateVars memory vars;
(vars.closeFactor, vars.liquidationAllowed) = _liquidationAllowed(
_borrower,
borrowedMarket.isDeprecated
);
if (!vars.liquidationAllowed) revert UnauthorisedLiquidate();
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved

uint256 amountToLiquidate = Math.min(
_amount,
_getUserBorrowBalanceInOf(_poolTokenBorrowed, _borrower).percentMul(
DEFAULT_LIQUIDATION_CLOSE_FACTOR
) // Max liquidatable debt.
_getUserBorrowBalanceInOf(_poolTokenBorrowed, _borrower).percentMul(vars.closeFactor) // Max liquidatable debt.
);

MathisGD marked this conversation as resolved.
Show resolved Hide resolved
address tokenCollateralAddress = market[_poolTokenCollateral].underlyingToken;

IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());

LiquidateVars memory vars;
address tokenCollateralAddress = market[_poolTokenCollateral].underlyingToken;
address tokenBorrowedAddress = market[_poolTokenBorrowed].underlyingToken;
{
ILendingPool poolMem = pool;
(, , vars.liquidationBonus, vars.collateralReserveDecimals, ) = poolMem
Expand Down Expand Up @@ -316,7 +317,6 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils {
emit P2PBorrowDeltaUpdated(_poolToken, deltasMem.p2pBorrowDelta);

ERC20 underlyingToken = ERC20(market[_poolToken].underlyingToken);

_borrowFromPool(underlyingToken, _amount);
_supplyToPool(underlyingToken, _amount);

Expand Down Expand Up @@ -691,8 +691,21 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils {

/// @dev Checks if the user is liquidatable.
/// @param _user The user to check.
/// @return Whether the user is liquidatable or not.
function _liquidationAllowed(address _user) internal returns (bool) {
return _getUserHealthFactor(_user, address(0), 0) < HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
/// @param _isDeprecated Whether the market is deprecated or not.
/// @return closeFactor The close factor to apply.
/// @return liquidationAllowed Whether the liquidation is allowed or not.
function _liquidationAllowed(address _user, bool _isDeprecated)
internal
returns (uint256 closeFactor, bool liquidationAllowed)
{
if (_isDeprecated) {
// Allow liquidation of the whole debt.
closeFactor = MAX_BASIS_POINTS;
liquidationAllowed = true;
} else {
closeFactor = DEFAULT_LIQUIDATION_CLOSE_FACTOR;
liquidationAllowed = (_getUserHealthFactor(_user, address(0), 0) <
HEALTH_FACTOR_LIQUIDATION_THRESHOLD);
}
}
}
46 changes: 27 additions & 19 deletions contracts/aave-v3/ExitPositionsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils {
uint256 borrowedTokenUnit; // The unit of borrowed token considering its decimals.
uint256 borrowedTokenPrice; // The price of the borrowed token.
uint256 amountToLiquidate; // The amount of debt token to repay.
uint256 closeFactor;
bool liquidationAllowed;
uint256 closeFactor; // The close factor used during the liquidation.
bool liquidationAllowed; // Whether the liquidation is allowed or not.
}

// Struct to avoid stack too deep.
Expand Down Expand Up @@ -229,10 +229,11 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils {
_updateIndexes(_poolTokenCollateral);

LiquidateVars memory vars;
if (!borrowedMarket.isDeprecated) {
(vars.closeFactor, vars.liquidationAllowed) = _liquidationAllowed(_borrower);
if (!vars.liquidationAllowed) revert UnauthorisedLiquidate();
} else vars.closeFactor = DEFAULT_LIQUIDATION_CLOSE_FACTOR;
(vars.closeFactor, vars.liquidationAllowed) = _liquidationAllowed(
_borrower,
borrowedMarket.isDeprecated
);
if (!vars.liquidationAllowed) revert UnauthorisedLiquidate();

vars.amountToLiquidate = Math.min(
_amount,
Expand Down Expand Up @@ -700,23 +701,30 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils {

/// @dev Checks if the user is liquidatable.
/// @param _user The user to check.
/// @param _isDeprecated Whether the market is deprecated or not.
/// @return closeFactor The close factor to apply.
/// @return liquidationAllowed Whether the liquidation is allowed or not.
function _liquidationAllowed(address _user)
function _liquidationAllowed(address _user, bool _isDeprecated)
internal
returns (uint256 closeFactor, bool liquidationAllowed)
{
uint256 healthFactor = _getUserHealthFactor(_user, address(0), 0);
address priceOracleSentinel = addressesProvider.getPriceOracleSentinel();

closeFactor = healthFactor > MINIMUM_HEALTH_FACTOR_LIQUIDATION_THRESHOLD
? DEFAULT_LIQUIDATION_CLOSE_FACTOR
: MAX_LIQUIDATION_CLOSE_FACTOR;

if (priceOracleSentinel != address(0))
liquidationAllowed = (healthFactor < MINIMUM_HEALTH_FACTOR_LIQUIDATION_THRESHOLD ||
(IPriceOracleSentinel(priceOracleSentinel).isLiquidationAllowed() &&
healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD));
else liquidationAllowed = healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
if (_isDeprecated) {
// Allow liquidation of the whole debt.
closeFactor = MAX_BASIS_POINTS;
liquidationAllowed = true;
} else {
uint256 healthFactor = _getUserHealthFactor(_user, address(0), 0);
address priceOracleSentinel = addressesProvider.getPriceOracleSentinel();

closeFactor = healthFactor > MINIMUM_HEALTH_FACTOR_LIQUIDATION_THRESHOLD
? DEFAULT_LIQUIDATION_CLOSE_FACTOR
: MAX_LIQUIDATION_CLOSE_FACTOR;

if (priceOracleSentinel != address(0))
liquidationAllowed = (healthFactor < MINIMUM_HEALTH_FACTOR_LIQUIDATION_THRESHOLD ||
(IPriceOracleSentinel(priceOracleSentinel).isLiquidationAllowed() &&
healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD));
else liquidationAllowed = healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
}
}
}
13 changes: 9 additions & 4 deletions contracts/compound/PositionsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ contract PositionsManager is IPositionsManager, MatchingEngine {
uint256 supplyBalance;
uint256 borrowedPrice;
uint256 amountToSeize;
uint256 closeFactor;
}

/// LOGIC ///
Expand Down Expand Up @@ -500,13 +501,17 @@ contract PositionsManager is IPositionsManager, MatchingEngine {
_updateP2PIndexes(_poolTokenBorrowed);
_updateP2PIndexes(_poolTokenCollateral);

if (!borrowedMarket.isDeprecated && !_isLiquidatable(_borrower, address(0), 0, 0))
revert UnauthorisedLiquidate();

LiquidateVars memory vars;
if (borrowedMarket.isDeprecated)
vars.closeFactor = WAD; // Allow liquidation of the whole debt.
else {
if (!_isLiquidatable(_borrower, address(0), 0, 0)) revert UnauthorisedLiquidate();
vars.closeFactor = comptroller.closeFactorMantissa();
}

vars.borrowBalance = _getUserBorrowBalanceInOf(_poolTokenBorrowed, _borrower);

if (_amount > vars.borrowBalance.mul(comptroller.closeFactorMantissa()))
if (_amount > vars.borrowBalance.mul(vars.closeFactor))
revert AmountAboveWhatAllowedToRepay(); // Same mechanism as Compound. Liquidator cannot repay more than part of the debt (cf close factor on Compound).

_safeRepayLogic(_poolTokenBorrowed, msg.sender, _borrower, _amount, 0);
Expand Down
28 changes: 24 additions & 4 deletions test-foundry/aave-v2/TestLiquidate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,39 @@ contract TestLiquidate is TestSetup {
borrower1.borrow(aDai, amount);

(, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(aUsdc, address(borrower1));
(, uint256 borrowOnPoolbefore) = morpho.borrowBalanceInOf(aDai, address(borrower1));
(, uint256 borrowOnPoolBefore) = morpho.borrowBalanceInOf(aDai, address(borrower1));

// Liquidate
uint256 toRepay = amount / 2;
uint256 toRepay = borrowOnPoolBefore.rayMul(pool.getReserveNormalizedVariableDebt(dai)); // Full liquidation.
User liquidator = borrower3;
liquidator.approve(dai, address(morpho), toRepay);
liquidator.liquidate(aDai, aUsdc, address(borrower1), toRepay);

(, uint256 supplyOnPoolAfter) = morpho.supplyBalanceInOf(aUsdc, address(borrower1));
(, uint256 borrowOnPoolAfter) = morpho.borrowBalanceInOf(aDai, address(borrower1));

assertLt(borrowOnPoolAfter, borrowOnPoolbefore);
assertLt(supplyOnPoolAfter, supplyOnPoolBefore);
ExitPositionsManager.LiquidateVars memory vars;
(, , vars.liquidationBonus, vars.collateralReserveDecimals, ) = pool
.getConfiguration(usdc)
.getParamsMemory();
uint256 collateralPrice = oracle.getAssetPrice(usdc);
vars.collateralTokenUnit = 10**vars.collateralReserveDecimals;

{
(, , , vars.borrowedReserveDecimals, ) = pool.getConfiguration(dai).getParamsMemory();
uint256 borrowedPrice = oracle.getAssetPrice(dai);
vars.borrowedTokenUnit = 10**vars.borrowedReserveDecimals;

uint256 amountToSeize = (toRepay * borrowedPrice * vars.collateralTokenUnit) /
(vars.borrowedTokenUnit * collateralPrice).percentMul(vars.liquidationBonus);

uint256 expectedSupplyOnPoolAfter = supplyOnPoolBefore -
amountToSeize.rayDiv(pool.getReserveNormalizedIncome(usdc));

assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 1e10);
}

assertEq(borrowOnPoolAfter, 0);
}

// A user liquidates a borrower that has not enough collateral to cover for his debt.
Expand Down
28 changes: 24 additions & 4 deletions test-foundry/aave-v3/TestLiquidate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,39 @@ contract TestLiquidate is TestSetup {
borrower1.borrow(aDai, amount);

(, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(aUsdc, address(borrower1));
(, uint256 borrowOnPoolbefore) = morpho.borrowBalanceInOf(aDai, address(borrower1));
(, uint256 borrowOnPoolBefore) = morpho.borrowBalanceInOf(aDai, address(borrower1));

// Liquidate
uint256 toRepay = amount / 2;
uint256 toRepay = borrowOnPoolBefore.rayMul(pool.getReserveNormalizedVariableDebt(dai)); // Full liquidation.
User liquidator = borrower3;
liquidator.approve(dai, address(morpho), toRepay);
liquidator.liquidate(aDai, aUsdc, address(borrower1), toRepay);

(, uint256 supplyOnPoolAfter) = morpho.supplyBalanceInOf(aUsdc, address(borrower1));
(, uint256 borrowOnPoolAfter) = morpho.borrowBalanceInOf(aDai, address(borrower1));

assertLt(borrowOnPoolAfter, borrowOnPoolbefore);
assertLt(supplyOnPoolAfter, supplyOnPoolBefore);
ExitPositionsManager.LiquidateVars memory vars;
(, , vars.liquidationBonus, vars.collateralReserveDecimals, , ) = pool
.getConfiguration(usdc)
.getParams();
uint256 collateralPrice = oracle.getAssetPrice(usdc);
vars.collateralTokenUnit = 10**vars.collateralReserveDecimals;

{
(, , , vars.borrowedReserveDecimals, , ) = pool.getConfiguration(dai).getParams();
uint256 borrowedPrice = oracle.getAssetPrice(dai);
vars.borrowedTokenUnit = 10**vars.borrowedReserveDecimals;

uint256 amountToSeize = (toRepay * borrowedPrice * vars.collateralTokenUnit) /
(vars.borrowedTokenUnit * collateralPrice).percentMul(vars.liquidationBonus);

uint256 expectedSupplyOnPoolAfter = supplyOnPoolBefore -
amountToSeize.rayDiv(pool.getReserveNormalizedIncome(usdc));

assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 1e15);
}

assertApproxEqAbs(borrowOnPoolAfter, 0, 1e15);
}

// A user liquidates a borrower that has not enough collateral to cover for his debt.
Expand Down
19 changes: 15 additions & 4 deletions test-foundry/compound/TestLiquidate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,30 @@ contract TestLiquidate is TestSetup {
moveOneBlockForwardBorrowRepay();

(, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(cUsdc, address(borrower1));
(, uint256 borrowOnPoolbefore) = morpho.borrowBalanceInOf(cDai, address(borrower1));
(, uint256 borrowOnPoolBefore) = morpho.borrowBalanceInOf(cDai, address(borrower1));

// Liquidate
uint256 toRepay = amount / 3;
uint256 toRepay = amount; // Full liquidation.
User liquidator = borrower3;
liquidator.approve(dai, address(morpho), toRepay);
liquidator.liquidate(cDai, cUsdc, address(borrower1), toRepay);

(, uint256 supplyOnPoolAfter) = morpho.supplyBalanceInOf(cUsdc, address(borrower1));
(, uint256 borrowOnPoolAfter) = morpho.borrowBalanceInOf(cDai, address(borrower1));

assertLt(borrowOnPoolAfter, borrowOnPoolbefore);
assertLt(supplyOnPoolAfter, supplyOnPoolBefore);
uint256 collateralPrice = oracle.getUnderlyingPrice(cUsdc);
uint256 borrowedPrice = oracle.getUnderlyingPrice(cDai);

uint256 amountToSeize = toRepay
.mul(comptroller.liquidationIncentiveMantissa())
.mul(borrowedPrice)
.div(collateralPrice);

uint256 expectedSupplyOnPoolAfter = supplyOnPoolBefore -
amountToSeize.div(ICToken(cUsdc).exchangeRateCurrent());

assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 2);
assertApproxEqAbs(borrowOnPoolAfter, 0, 1e15);
}

// A user liquidates a borrower that has not enough collateral to cover for his debt.
Expand Down