From 74fbc843ff7ac75e4132c0b560befda5a84e1ecc Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 21 Sep 2022 16:53:54 +0200 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20(#1303)=20Liquidate=20full=20am?= =?UTF-8?q?ount=20when=20market=20is=20deprecated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/aave-v2/ExitPositionsManager.sol | 27 +++++++++++----------- contracts/aave-v3/ExitPositionsManager.sol | 6 ++--- contracts/compound/PositionsManager.sol | 10 ++++---- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/contracts/aave-v2/ExitPositionsManager.sol b/contracts/aave-v2/ExitPositionsManager.sol index 7520c0b3e..1989ed998 100644 --- a/contracts/aave-v2/ExitPositionsManager.sol +++ b/contracts/aave-v2/ExitPositionsManager.sol @@ -128,6 +128,7 @@ 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. } // Struct to avoid stack too deep. @@ -221,11 +222,13 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { _updateIndexes(_poolTokenBorrowed); _updateIndexes(_poolTokenCollateral); - if (!borrowedMarket.isDeprecated && !_liquidationAllowed(_borrower)) - revert UnauthorisedLiquidate(); + LiquidateVars memory vars; + if (!borrowedMarket.isDeprecated) { + vars.closeFactor = DEFAULT_LIQUIDATION_CLOSE_FACTOR; + if (!_liquidationAllowed(_borrower)) revert UnauthorisedLiquidate(); + } else vars.closeFactor = MAX_BASIS_POINTS; // Allow liquidation of the whole debt. address tokenBorrowedAddress = market[_poolTokenBorrowed].underlyingToken; - uint256 amountToLiquidate = Math.min( _amount, _getUserBorrowBalanceInOf(_poolTokenBorrowed, _borrower).percentMul( @@ -237,16 +240,13 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); - LiquidateVars memory vars; - { - ILendingPool poolMem = pool; - (, , vars.liquidationBonus, vars.collateralReserveDecimals, ) = poolMem - .getConfiguration(tokenCollateralAddress) - .getParamsMemory(); - (, , , vars.borrowedReserveDecimals, ) = poolMem - .getConfiguration(tokenBorrowedAddress) - .getParamsMemory(); - } + ILendingPool poolMem = pool; + (, , vars.liquidationBonus, vars.collateralReserveDecimals, ) = poolMem + .getConfiguration(tokenCollateralAddress) + .getParamsMemory(); + (, , , vars.borrowedReserveDecimals, ) = poolMem + .getConfiguration(tokenBorrowedAddress) + .getParamsMemory(); unchecked { vars.collateralTokenUnit = 10**vars.collateralReserveDecimals; @@ -316,7 +316,6 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { emit P2PBorrowDeltaUpdated(_poolToken, deltasMem.p2pBorrowDelta); ERC20 underlyingToken = ERC20(market[_poolToken].underlyingToken); - _borrowFromPool(underlyingToken, _amount); _supplyToPool(underlyingToken, _amount); diff --git a/contracts/aave-v3/ExitPositionsManager.sol b/contracts/aave-v3/ExitPositionsManager.sol index 0b78bd29c..0c7dadf85 100644 --- a/contracts/aave-v3/ExitPositionsManager.sol +++ b/contracts/aave-v3/ExitPositionsManager.sol @@ -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. @@ -232,7 +232,7 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { if (!borrowedMarket.isDeprecated) { (vars.closeFactor, vars.liquidationAllowed) = _liquidationAllowed(_borrower); if (!vars.liquidationAllowed) revert UnauthorisedLiquidate(); - } else vars.closeFactor = DEFAULT_LIQUIDATION_CLOSE_FACTOR; + } else vars.closeFactor = MAX_BASIS_POINTS; // Allow liquidation of the whole debt. vars.amountToLiquidate = Math.min( _amount, diff --git a/contracts/compound/PositionsManager.sol b/contracts/compound/PositionsManager.sol index a442e2c82..8dbd24694 100644 --- a/contracts/compound/PositionsManager.sol +++ b/contracts/compound/PositionsManager.sol @@ -500,14 +500,16 @@ contract PositionsManager is IPositionsManager, MatchingEngine { _updateP2PIndexes(_poolTokenBorrowed); _updateP2PIndexes(_poolTokenCollateral); - if (!borrowedMarket.isDeprecated && !_isLiquidatable(_borrower, address(0), 0, 0)) - revert UnauthorisedLiquidate(); + uint256 closeFactor; + if (!borrowedMarket.isDeprecated) { + closeFactor = comptroller.closeFactorMantissa(); + if (!_isLiquidatable(_borrower, address(0), 0, 0)) revert UnauthorisedLiquidate(); + } else closeFactor = WAD; // Allow liquidation of the whole debt. LiquidateVars memory vars; vars.borrowBalance = _getUserBorrowBalanceInOf(_poolTokenBorrowed, _borrower); - if (_amount > vars.borrowBalance.mul(comptroller.closeFactorMantissa())) - revert AmountAboveWhatAllowedToRepay(); // Same mechanism as Compound. Liquidator cannot repay more than part of the debt (cf close factor on Compound). + if (_amount > vars.borrowBalance.mul(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); From cca205cfe47aa33ffeeb7ad900f71da5ad85c8c5 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 21 Sep 2022 16:59:03 +0200 Subject: [PATCH 2/8] =?UTF-8?q?=E2=9C=85=20(#1303)=20Update=20test=20to=20?= =?UTF-8?q?liquidate=20full=20amount?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-foundry/aave-v2/TestLiquidate.t.sol | 2 +- test-foundry/aave-v3/TestLiquidate.t.sol | 2 +- test-foundry/compound/TestLiquidate.t.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-foundry/aave-v2/TestLiquidate.t.sol b/test-foundry/aave-v2/TestLiquidate.t.sol index 11e80115d..c52389cd0 100644 --- a/test-foundry/aave-v2/TestLiquidate.t.sol +++ b/test-foundry/aave-v2/TestLiquidate.t.sol @@ -40,7 +40,7 @@ contract TestLiquidate is TestSetup { (, uint256 borrowOnPoolbefore) = morpho.borrowBalanceInOf(aDai, address(borrower1)); // Liquidate - uint256 toRepay = amount / 2; + uint256 toRepay = amount; // Full liquidation. User liquidator = borrower3; liquidator.approve(dai, address(morpho), toRepay); liquidator.liquidate(aDai, aUsdc, address(borrower1), toRepay); diff --git a/test-foundry/aave-v3/TestLiquidate.t.sol b/test-foundry/aave-v3/TestLiquidate.t.sol index 2bc4ab420..8d68bbad0 100644 --- a/test-foundry/aave-v3/TestLiquidate.t.sol +++ b/test-foundry/aave-v3/TestLiquidate.t.sol @@ -40,7 +40,7 @@ contract TestLiquidate is TestSetup { (, uint256 borrowOnPoolbefore) = morpho.borrowBalanceInOf(aDai, address(borrower1)); // Liquidate - uint256 toRepay = amount / 2; + uint256 toRepay = amount; // Full liquidation. User liquidator = borrower3; liquidator.approve(dai, address(morpho), toRepay); liquidator.liquidate(aDai, aUsdc, address(borrower1), toRepay); diff --git a/test-foundry/compound/TestLiquidate.t.sol b/test-foundry/compound/TestLiquidate.t.sol index 358ef639a..af1c37f0d 100644 --- a/test-foundry/compound/TestLiquidate.t.sol +++ b/test-foundry/compound/TestLiquidate.t.sol @@ -41,7 +41,7 @@ contract TestLiquidate is TestSetup { (, 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); From f238267ee9949fb7b34b99e9a031ff4faf751170 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 21 Sep 2022 17:04:52 +0200 Subject: [PATCH 3/8] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20(#1303)=20Small=20opti?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/aave-v2/ExitPositionsManager.sol | 2 +- contracts/compound/PositionsManager.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/aave-v2/ExitPositionsManager.sol b/contracts/aave-v2/ExitPositionsManager.sol index 1989ed998..c3ff7e535 100644 --- a/contracts/aave-v2/ExitPositionsManager.sol +++ b/contracts/aave-v2/ExitPositionsManager.sol @@ -224,8 +224,8 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { LiquidateVars memory vars; if (!borrowedMarket.isDeprecated) { - vars.closeFactor = DEFAULT_LIQUIDATION_CLOSE_FACTOR; if (!_liquidationAllowed(_borrower)) revert UnauthorisedLiquidate(); + vars.closeFactor = DEFAULT_LIQUIDATION_CLOSE_FACTOR; } else vars.closeFactor = MAX_BASIS_POINTS; // Allow liquidation of the whole debt. address tokenBorrowedAddress = market[_poolTokenBorrowed].underlyingToken; diff --git a/contracts/compound/PositionsManager.sol b/contracts/compound/PositionsManager.sol index 8dbd24694..09d6b9fc0 100644 --- a/contracts/compound/PositionsManager.sol +++ b/contracts/compound/PositionsManager.sol @@ -502,8 +502,8 @@ contract PositionsManager is IPositionsManager, MatchingEngine { uint256 closeFactor; if (!borrowedMarket.isDeprecated) { - closeFactor = comptroller.closeFactorMantissa(); if (!_isLiquidatable(_borrower, address(0), 0, 0)) revert UnauthorisedLiquidate(); + closeFactor = comptroller.closeFactorMantissa(); } else closeFactor = WAD; // Allow liquidation of the whole debt. LiquidateVars memory vars; From b0c51a29a69953bafd8f11a1fd44a1e48635d5d0 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 21 Sep 2022 17:12:08 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=A9=B9=20(#1303)=20Fix=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/aave-v2/ExitPositionsManager.sol | 4 +--- contracts/compound/PositionsManager.sol | 11 ++++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/contracts/aave-v2/ExitPositionsManager.sol b/contracts/aave-v2/ExitPositionsManager.sol index c3ff7e535..29f972a63 100644 --- a/contracts/aave-v2/ExitPositionsManager.sol +++ b/contracts/aave-v2/ExitPositionsManager.sol @@ -231,9 +231,7 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { address tokenBorrowedAddress = market[_poolTokenBorrowed].underlyingToken; 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. ); address tokenCollateralAddress = market[_poolTokenCollateral].underlyingToken; diff --git a/contracts/compound/PositionsManager.sol b/contracts/compound/PositionsManager.sol index 09d6b9fc0..fc9b2ada1 100644 --- a/contracts/compound/PositionsManager.sol +++ b/contracts/compound/PositionsManager.sol @@ -218,6 +218,7 @@ contract PositionsManager is IPositionsManager, MatchingEngine { uint256 supplyBalance; uint256 borrowedPrice; uint256 amountToSeize; + uint256 closeFactor; } /// LOGIC /// @@ -500,16 +501,16 @@ contract PositionsManager is IPositionsManager, MatchingEngine { _updateP2PIndexes(_poolTokenBorrowed); _updateP2PIndexes(_poolTokenCollateral); - uint256 closeFactor; + LiquidateVars memory vars; if (!borrowedMarket.isDeprecated) { if (!_isLiquidatable(_borrower, address(0), 0, 0)) revert UnauthorisedLiquidate(); - closeFactor = comptroller.closeFactorMantissa(); - } else closeFactor = WAD; // Allow liquidation of the whole debt. + vars.closeFactor = comptroller.closeFactorMantissa(); + } else vars.closeFactor = WAD; // Allow liquidation of the whole debt. - LiquidateVars memory vars; vars.borrowBalance = _getUserBorrowBalanceInOf(_poolTokenBorrowed, _borrower); - if (_amount > vars.borrowBalance.mul(closeFactor)) revert AmountAboveWhatAllowedToRepay(); // Same mechanism as Compound. Liquidator cannot repay more than part of the debt (cf close factor on Compound). + 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); From 409816726a84f2eb7c5b7bff668e5b2bbb99ee8d Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 21 Sep 2022 17:41:40 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(#1303)=20Avoid=20stac?= =?UTF-8?q?k=20too=20deep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/aave-v2/ExitPositionsManager.sol | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/contracts/aave-v2/ExitPositionsManager.sol b/contracts/aave-v2/ExitPositionsManager.sol index 29f972a63..76709eeda 100644 --- a/contracts/aave-v2/ExitPositionsManager.sol +++ b/contracts/aave-v2/ExitPositionsManager.sol @@ -238,13 +238,15 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); - ILendingPool poolMem = pool; - (, , vars.liquidationBonus, vars.collateralReserveDecimals, ) = poolMem - .getConfiguration(tokenCollateralAddress) - .getParamsMemory(); - (, , , vars.borrowedReserveDecimals, ) = poolMem - .getConfiguration(tokenBorrowedAddress) - .getParamsMemory(); + { + ILendingPool poolMem = pool; + (, , vars.liquidationBonus, vars.collateralReserveDecimals, ) = poolMem + .getConfiguration(tokenCollateralAddress) + .getParamsMemory(); + (, , , vars.borrowedReserveDecimals, ) = poolMem + .getConfiguration(tokenBorrowedAddress) + .getParamsMemory(); + } unchecked { vars.collateralTokenUnit = 10**vars.collateralReserveDecimals; From aad9e51ed76a30f6c1ac77b038c6c0fffa759b1c Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 22 Sep 2022 10:27:09 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20(#1303)=20Improve=20pe?= =?UTF-8?q?rformance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/aave-v2/ExitPositionsManager.sol | 34 ++++++++++++------ contracts/aave-v3/ExitPositionsManager.sol | 42 +++++++++++++--------- contracts/compound/PositionsManager.sol | 6 ++-- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/contracts/aave-v2/ExitPositionsManager.sol b/contracts/aave-v2/ExitPositionsManager.sol index 76709eeda..e889a124d 100644 --- a/contracts/aave-v2/ExitPositionsManager.sol +++ b/contracts/aave-v2/ExitPositionsManager.sol @@ -129,6 +129,7 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { 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. @@ -223,21 +224,21 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { _updateIndexes(_poolTokenCollateral); LiquidateVars memory vars; - if (!borrowedMarket.isDeprecated) { - if (!_liquidationAllowed(_borrower)) revert UnauthorisedLiquidate(); - vars.closeFactor = DEFAULT_LIQUIDATION_CLOSE_FACTOR; - } else vars.closeFactor = MAX_BASIS_POINTS; // Allow liquidation of the whole debt. + (vars.closeFactor, vars.liquidationAllowed) = _liquidationAllowed( + _borrower, + borrowedMarket.isDeprecated + ); + if (!vars.liquidationAllowed) revert UnauthorisedLiquidate(); - address tokenBorrowedAddress = market[_poolTokenBorrowed].underlyingToken; uint256 amountToLiquidate = Math.min( _amount, _getUserBorrowBalanceInOf(_poolTokenBorrowed, _borrower).percentMul(vars.closeFactor) // Max liquidatable debt. ); - address tokenCollateralAddress = market[_poolTokenCollateral].underlyingToken; - IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); + address tokenCollateralAddress = market[_poolTokenCollateral].underlyingToken; + address tokenBorrowedAddress = market[_poolTokenBorrowed].underlyingToken; { ILendingPool poolMem = pool; (, , vars.liquidationBonus, vars.collateralReserveDecimals, ) = poolMem @@ -690,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); + } } } diff --git a/contracts/aave-v3/ExitPositionsManager.sol b/contracts/aave-v3/ExitPositionsManager.sol index 0c7dadf85..34f444333 100644 --- a/contracts/aave-v3/ExitPositionsManager.sol +++ b/contracts/aave-v3/ExitPositionsManager.sol @@ -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 = MAX_BASIS_POINTS; // Allow liquidation of the whole debt. + (vars.closeFactor, vars.liquidationAllowed) = _liquidationAllowed( + _borrower, + borrowedMarket.isDeprecated + ); + if (!vars.liquidationAllowed) revert UnauthorisedLiquidate(); vars.amountToLiquidate = Math.min( _amount, @@ -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; + } } } diff --git a/contracts/compound/PositionsManager.sol b/contracts/compound/PositionsManager.sol index fc9b2ada1..d62ff2954 100644 --- a/contracts/compound/PositionsManager.sol +++ b/contracts/compound/PositionsManager.sol @@ -502,10 +502,12 @@ contract PositionsManager is IPositionsManager, MatchingEngine { _updateP2PIndexes(_poolTokenCollateral); LiquidateVars memory vars; - if (!borrowedMarket.isDeprecated) { + 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(); - } else vars.closeFactor = WAD; // Allow liquidation of the whole debt. + } vars.borrowBalance = _getUserBorrowBalanceInOf(_poolTokenBorrowed, _borrower); From 45a9764e10bad3bc8fc7401252a65351355decf4 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 22 Sep 2022 14:25:34 +0200 Subject: [PATCH 7/8] =?UTF-8?q?=E2=9C=85=20(#1303)=20Improve=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-foundry/aave-v2/TestLiquidate.t.sol | 27 ++++++++++++++++++++--- test-foundry/aave-v3/TestLiquidate.t.sol | 27 ++++++++++++++++++++--- test-foundry/compound/TestLiquidate.t.sol | 17 +++++++++++--- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/test-foundry/aave-v2/TestLiquidate.t.sol b/test-foundry/aave-v2/TestLiquidate.t.sol index c52389cd0..2bba7e059 100644 --- a/test-foundry/aave-v2/TestLiquidate.t.sol +++ b/test-foundry/aave-v2/TestLiquidate.t.sol @@ -37,7 +37,6 @@ contract TestLiquidate is TestSetup { borrower1.borrow(aDai, amount); (, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(aUsdc, address(borrower1)); - (, uint256 borrowOnPoolbefore) = morpho.borrowBalanceInOf(aDai, address(borrower1)); // Liquidate uint256 toRepay = amount; // Full liquidation. @@ -48,8 +47,30 @@ contract TestLiquidate is TestSetup { (, 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.liquidationBonus) / (vars.borrowedTokenUnit * collateralPrice * 10_000); + + uint256 expectedSupplyOnPoolAfter = supplyOnPoolBefore - + underlyingToScaledBalance(amountToSeize, pool.getReserveNormalizedIncome(usdc)); + + assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 2); + } + + assertEq(borrowOnPoolAfter, 0); } // A user liquidates a borrower that has not enough collateral to cover for his debt. diff --git a/test-foundry/aave-v3/TestLiquidate.t.sol b/test-foundry/aave-v3/TestLiquidate.t.sol index 8d68bbad0..0c0bbd1a2 100644 --- a/test-foundry/aave-v3/TestLiquidate.t.sol +++ b/test-foundry/aave-v3/TestLiquidate.t.sol @@ -37,7 +37,6 @@ contract TestLiquidate is TestSetup { borrower1.borrow(aDai, amount); (, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(aUsdc, address(borrower1)); - (, uint256 borrowOnPoolbefore) = morpho.borrowBalanceInOf(aDai, address(borrower1)); // Liquidate uint256 toRepay = amount; // Full liquidation. @@ -48,8 +47,30 @@ contract TestLiquidate is TestSetup { (, 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.collateralReserveDecimals, , ) = pool.getConfiguration(dai).getParams(); + uint256 borrowedPrice = oracle.getAssetPrice(dai); + vars.borrowedTokenUnit = 10**vars.borrowedReserveDecimals; + + uint256 amountToSeize = (toRepay * + borrowedPrice * + vars.collateralTokenUnit * + vars.liquidationBonus) / (vars.borrowedTokenUnit * collateralPrice * 10_000); + + uint256 expectedSupplyOnPoolAfter = supplyOnPoolBefore - + underlyingToScaledBalance(amountToSeize, pool.getReserveNormalizedIncome(usdc)); + + assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 2); + } + + assertEq(borrowOnPoolAfter, 0); } // A user liquidates a borrower that has not enough collateral to cover for his debt. diff --git a/test-foundry/compound/TestLiquidate.t.sol b/test-foundry/compound/TestLiquidate.t.sol index af1c37f0d..f95dbd01e 100644 --- a/test-foundry/compound/TestLiquidate.t.sol +++ b/test-foundry/compound/TestLiquidate.t.sol @@ -38,7 +38,7 @@ 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; // Full liquidation. @@ -49,8 +49,19 @@ contract TestLiquidate is TestSetup { (, 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. From 8d19785d2df7aec1b18ba6417f15e8c54e2d53b8 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 22 Sep 2022 16:25:31 +0200 Subject: [PATCH 8/8] =?UTF-8?q?=E2=9C=85=20(#1303)=20Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-foundry/aave-v2/TestLiquidate.t.sol | 13 ++++++------- test-foundry/aave-v3/TestLiquidate.t.sol | 17 ++++++++--------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/test-foundry/aave-v2/TestLiquidate.t.sol b/test-foundry/aave-v2/TestLiquidate.t.sol index 2bba7e059..c92564e02 100644 --- a/test-foundry/aave-v2/TestLiquidate.t.sol +++ b/test-foundry/aave-v2/TestLiquidate.t.sol @@ -37,9 +37,10 @@ contract TestLiquidate is TestSetup { borrower1.borrow(aDai, amount); (, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(aUsdc, address(borrower1)); + (, uint256 borrowOnPoolBefore) = morpho.borrowBalanceInOf(aDai, address(borrower1)); // Liquidate - uint256 toRepay = amount; // Full liquidation. + 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); @@ -59,15 +60,13 @@ contract TestLiquidate is TestSetup { uint256 borrowedPrice = oracle.getAssetPrice(dai); vars.borrowedTokenUnit = 10**vars.borrowedReserveDecimals; - uint256 amountToSeize = (toRepay * - borrowedPrice * - vars.collateralTokenUnit * - vars.liquidationBonus) / (vars.borrowedTokenUnit * collateralPrice * 10_000); + uint256 amountToSeize = (toRepay * borrowedPrice * vars.collateralTokenUnit) / + (vars.borrowedTokenUnit * collateralPrice).percentMul(vars.liquidationBonus); uint256 expectedSupplyOnPoolAfter = supplyOnPoolBefore - - underlyingToScaledBalance(amountToSeize, pool.getReserveNormalizedIncome(usdc)); + amountToSeize.rayDiv(pool.getReserveNormalizedIncome(usdc)); - assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 2); + assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 1e10); } assertEq(borrowOnPoolAfter, 0); diff --git a/test-foundry/aave-v3/TestLiquidate.t.sol b/test-foundry/aave-v3/TestLiquidate.t.sol index 0c0bbd1a2..99e64a518 100644 --- a/test-foundry/aave-v3/TestLiquidate.t.sol +++ b/test-foundry/aave-v3/TestLiquidate.t.sol @@ -37,9 +37,10 @@ contract TestLiquidate is TestSetup { borrower1.borrow(aDai, amount); (, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(aUsdc, address(borrower1)); + (, uint256 borrowOnPoolBefore) = morpho.borrowBalanceInOf(aDai, address(borrower1)); // Liquidate - uint256 toRepay = amount; // Full liquidation. + 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); @@ -55,22 +56,20 @@ contract TestLiquidate is TestSetup { vars.collateralTokenUnit = 10**vars.collateralReserveDecimals; { - (, , , vars.collateralReserveDecimals, , ) = pool.getConfiguration(dai).getParams(); + (, , , vars.borrowedReserveDecimals, , ) = pool.getConfiguration(dai).getParams(); uint256 borrowedPrice = oracle.getAssetPrice(dai); vars.borrowedTokenUnit = 10**vars.borrowedReserveDecimals; - uint256 amountToSeize = (toRepay * - borrowedPrice * - vars.collateralTokenUnit * - vars.liquidationBonus) / (vars.borrowedTokenUnit * collateralPrice * 10_000); + uint256 amountToSeize = (toRepay * borrowedPrice * vars.collateralTokenUnit) / + (vars.borrowedTokenUnit * collateralPrice).percentMul(vars.liquidationBonus); uint256 expectedSupplyOnPoolAfter = supplyOnPoolBefore - - underlyingToScaledBalance(amountToSeize, pool.getReserveNormalizedIncome(usdc)); + amountToSeize.rayDiv(pool.getReserveNormalizedIncome(usdc)); - assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 2); + assertApproxEqAbs(supplyOnPoolAfter, expectedSupplyOnPoolAfter, 1e15); } - assertEq(borrowOnPoolAfter, 0); + assertApproxEqAbs(borrowOnPoolAfter, 0, 1e15); } // A user liquidates a borrower that has not enough collateral to cover for his debt.