From e257eed05d00311b2403eb35f5a214c29ec642bd Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Fri, 4 Oct 2024 19:51:13 +0200 Subject: [PATCH] feat: restrict ltv < LLTV --- src/PreLiquidation.sol | 11 ++++---- src/libraries/ErrorsLib.sol | 2 ++ test/BaseTest.sol | 18 +++++-------- test/PreLiquidationErrorTest.sol | 12 ++++----- test/PreLiquidationTest.sol | 46 ++++++++++++++++++++++++++++++-- 5 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index da2dc3f..a50c318 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -146,9 +146,9 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { // The following require is equivalent to checking that borrowed > collateralQuoted.wMulDown(PRE_LLTV). require(ltv > PRE_LLTV, ErrorsLib.NotPreLiquidatablePosition()); - uint256 preLIF = UtilsLib.min( - (ltv - PRE_LLTV).wDivDown(LLTV - PRE_LLTV).wMulDown(PRE_LIF_2 - PRE_LIF_1) + PRE_LIF_1, PRE_LIF_2 - ); + require(ltv <= LLTV, ErrorsLib.LiquidatablePosition()); + + uint256 preLIF = (ltv - PRE_LLTV).wDivDown(LLTV - PRE_LLTV).wMulDown(PRE_LIF_2 - PRE_LIF_1) + PRE_LIF_1; if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); @@ -163,9 +163,8 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { // Note that the pre-liquidation close factor can be greater than WAD (100%). // In this case the position can be fully pre-liquidated. - uint256 preLCF = UtilsLib.min( - (ltv - PRE_LLTV).wDivDown(LLTV - PRE_LLTV).wMulDown(PRE_LCF_2 - PRE_LCF_1) + PRE_LCF_1, PRE_LCF_2 - ); + uint256 preLCF = (ltv - PRE_LLTV).wDivDown(LLTV - PRE_LLTV).wMulDown(PRE_LCF_2 - PRE_LCF_1) + PRE_LCF_1; + uint256 repayableShares = uint256(position.borrowShares).wMulDown(preLCF); require(repaidShares <= repayableShares, ErrorsLib.PreLiquidationTooLarge(repaidShares, repayableShares)); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 5c46196..38439b3 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -22,6 +22,8 @@ library ErrorsLib { error NotPreLiquidatablePosition(); + error LiquidatablePosition(); + error PreLiquidationTooLarge(uint256 repaidShares, uint256 repayableShares); error NotMorpho(); diff --git a/test/BaseTest.sol b/test/BaseTest.sol index 3fa9adb..953df08 100644 --- a/test/BaseTest.sol +++ b/test/BaseTest.sol @@ -136,21 +136,15 @@ contract BaseTest is Test { view returns (uint256) { - return UtilsLib.min( - (ltv - preLiquidationParams.preLltv).wDivDown(marketParams.lltv - preLiquidationParams.preLltv).wMulDown( - preLiquidationParams.preLCF2 - preLiquidationParams.preLCF1 - ) + preLiquidationParams.preLCF1, - preLiquidationParams.preLCF2 - ); + return (ltv - preLiquidationParams.preLltv).wDivDown(marketParams.lltv - preLiquidationParams.preLltv).wMulDown( + preLiquidationParams.preLCF2 - preLiquidationParams.preLCF1 + ) + preLiquidationParams.preLCF1; } function _preLIF(PreLiquidationParams memory preLiquidationParams, uint256 ltv) internal view returns (uint256) { - return UtilsLib.min( - (ltv - preLiquidationParams.preLltv).wDivDown(marketParams.lltv - preLiquidationParams.preLltv).wMulDown( - preLiquidationParams.preLIF2 - preLiquidationParams.preLIF1 - ) + preLiquidationParams.preLIF1, - preLiquidationParams.preLIF2 - ); + return (ltv - preLiquidationParams.preLltv).wDivDown(marketParams.lltv - preLiquidationParams.preLltv).wMulDown( + preLiquidationParams.preLIF2 - preLiquidationParams.preLIF1 + ) + preLiquidationParams.preLIF1; } function _getBorrowBounds( diff --git a/test/PreLiquidationErrorTest.sol b/test/PreLiquidationErrorTest.sol index ce4b1ef..35928fe 100644 --- a/test/PreLiquidationErrorTest.sol +++ b/test/PreLiquidationErrorTest.sol @@ -36,7 +36,7 @@ contract PreLiquidationErrorTest is BaseTest { preLiqOracle: marketParams.oracle }); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLltvTooHigh.selector)); + vm.expectRevert(ErrorsLib.PreLltvTooHigh.selector); factory.createPreLiquidation(id, preLiquidationParams); } @@ -53,7 +53,7 @@ contract PreLiquidationErrorTest is BaseTest { }); preLiquidationParams.preLCF2 = bound(preLiquidationParams.preLCF2, 0, preLiquidationParams.preLCF1 - 1); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLCFDecreasing.selector)); + vm.expectRevert(ErrorsLib.PreLCFDecreasing.selector); factory.createPreLiquidation(id, preLiquidationParams); } @@ -69,7 +69,7 @@ contract PreLiquidationErrorTest is BaseTest { preLiqOracle: marketParams.oracle }); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLIFTooLow.selector)); + vm.expectRevert(ErrorsLib.PreLIFTooLow.selector); factory.createPreLiquidation(id, preLiquidationParams); } @@ -87,7 +87,7 @@ contract PreLiquidationErrorTest is BaseTest { preLiquidationParams.preLIF2 = bound(preLiquidationParams.preLIF2, WAD.wDivDown(marketParams.lltv) + 1, type(uint256).max); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLIFTooHigh.selector)); + vm.expectRevert(ErrorsLib.PreLIFTooHigh.selector); factory.createPreLiquidation(id, preLiquidationParams); } @@ -105,12 +105,12 @@ contract PreLiquidationErrorTest is BaseTest { preLiquidationParams.preLIF2 = bound(preLiquidationParams.preLIF2, WAD, preLiquidationParams.preLIF1 - 1); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLIFDecreasing.selector)); + vm.expectRevert(ErrorsLib.PreLIFDecreasing.selector); factory.createPreLiquidation(id, preLiquidationParams); } function testNonexistentMarket(PreLiquidationParams memory preLiquidationParams) public virtual { - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonexistentMarket.selector)); + vm.expectRevert(ErrorsLib.NonexistentMarket.selector); factory.createPreLiquidation(Id.wrap(bytes32(0)), preLiquidationParams); } diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 70daf88..3545df6 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -32,6 +32,48 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { factory = new PreLiquidationFactory(address(MORPHO)); } + function testPreLiquidationLiquidatable( + PreLiquidationParams memory preLiquidationParams, + uint256 collateralAmount, + uint256 borrowAmount, + uint256 newPrice + ) public virtual { + preLiquidationParams = boundPreLiquidationParameters({ + preLiquidationParams: preLiquidationParams, + minPreLltv: WAD / 2, + maxPreLltv: marketParams.lltv - 1, + minPreLCF: WAD / 100, + maxPreLCF: WAD, + minPreLIF: WAD, + maxPreLIF: WAD.wDivDown(lltv), + preLiqOracle: marketParams.oracle + }); + + collateralAmount = bound(collateralAmount, minCollateral, maxCollateral); + (uint256 collateralQuoted, uint256 borrowPreLiquidationThreshold, uint256 borrowLiquidationThreshold) = + _getBorrowBounds(preLiquidationParams, marketParams, collateralAmount); + borrowAmount = bound(borrowAmount, borrowPreLiquidationThreshold + 1, borrowLiquidationThreshold); + + _preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, LIQUIDATOR); + + uint256 ltv = borrowAmount.wDivUp(collateralQuoted); + uint256 prevPrice = oracle.price(); + newPrice = bound(newPrice, prevPrice / 10, prevPrice.wDivDown(marketParams.lltv).wMulDown(ltv)); + oracle.setPrice(newPrice); + + uint256 newLtv = borrowAmount.wDivUp(collateralAmount.mulDivDown(newPrice, ORACLE_PRICE_SCALE)); + vm.assume(newLtv > marketParams.lltv); + + vm.startPrank(LIQUIDATOR); + Position memory position = MORPHO.position(id, BORROWER); + + uint256 closeFactor = _closeFactor(preLiquidationParams, newLtv); + uint256 repayableShares = uint256(position.borrowShares).wMulDown(closeFactor); + + vm.expectRevert(ErrorsLib.LiquidatablePosition.selector); + preLiquidation.preLiquidate(BORROWER, 0, repayableShares, hex""); + } + function testPreLiquidationShares( PreLiquidationParams memory preLiquidationParams, uint256 collateralAmount, @@ -191,7 +233,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { vm.startPrank(LIQUIDATOR); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NotPreLiquidatablePosition.selector)); + vm.expectRevert(ErrorsLib.NotPreLiquidatablePosition.selector); preLiquidation.preLiquidate(BORROWER, 0, 1, hex""); vm.warp(block.timestamp + 12); @@ -249,7 +291,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { vm.startPrank(LIQUIDATOR); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NotPreLiquidatablePosition.selector)); + vm.expectRevert(ErrorsLib.NotPreLiquidatablePosition.selector); preLiquidation.preLiquidate(BORROWER, 0, 1, hex""); } }