From 131573adaaeaca43c49ebcd5a96bbb05654c5bfc Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Wed, 16 Aug 2023 15:34:09 +0200 Subject: [PATCH 01/14] feat: add argument to liquidate function --- src/Morpho.sol | 29 ++++-- src/interfaces/IMorpho.sol | 6 +- test/forge/BaseTest.sol | 2 +- .../TestIntegrationCallbacks.t.sol | 4 +- .../TestIntegrationLiquidate.t.sol | 89 +++++++++++++++++-- test/morpho_tests.tree | 56 ++++++------ 6 files changed, 141 insertions(+), 45 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index d1118f53d..ef5f7649a 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -330,13 +330,16 @@ contract Morpho is IMorpho { /* LIQUIDATION */ /// @inheritdoc IMorpho - function liquidate(Market memory market, address borrower, uint256 seized, bytes calldata data) - external - returns (uint256 assetsRepaid, uint256 sharesRepaid) - { + function liquidate( + Market memory market, + address borrower, + uint256 seized, + uint256 borrowedShares, + bytes calldata data + ) external returns (uint256 assetsRepaid, uint256 sharesRepaid) { Id id = market.id(); require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED); - require(seized != 0, ErrorsLib.ZERO_ASSETS); + require(UtilsLib.exactlyOneZero(seized, borrowedShares), ErrorsLib.INCONSISTENT_INPUT); _accrueInterests(market, id); @@ -344,9 +347,17 @@ contract Morpho is IMorpho { require(!_isHealthy(market, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION); - assetsRepaid = - seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor(market.lltv)); - sharesRepaid = assetsRepaid.toSharesDown(totalBorrow[id], totalBorrowShares[id]); + if (seized > 0) { + assetsRepaid = + seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor(market.lltv)); + sharesRepaid = assetsRepaid.toSharesDown(totalBorrow[id], totalBorrowShares[id]); + } else { + sharesRepaid = borrowedShares; + assetsRepaid = sharesRepaid.toAssetsDown(totalBorrow[id], totalBorrowShares[id]); + seized = assetsRepaid.wMulDown(liquidationIncentiveFactor(market.lltv)).mulDivDown( + ORACLE_PRICE_SCALE, collateralPrice + ); + } borrowShares[id][borrower] -= sharesRepaid; totalBorrowShares[id] -= sharesRepaid; @@ -372,6 +383,8 @@ contract Morpho is IMorpho { if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(assetsRepaid, data); IERC20(market.borrowableToken).safeTransferFrom(msg.sender, address(this), assetsRepaid); + + return (assetsRepaid, sharesRepaid); } /* FLASH LOANS */ diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 0756db018..b97b13fd4 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -213,15 +213,17 @@ interface IMorpho is IFlashLender { /// @notice Liquidates the given `seized` assets to the given `market` of the given `borrower`'s position, /// optionally calling back the caller's `onMorphoLiquidate` function with the given `data`. - /// @dev Seizing more than the collateral balance will underflow and revert without any error message. + /// @dev Either `seized` or `repaidShares` should be zero. + /// Seizing more than the collateral balance will underflow and revert without any error message. /// @dev Repaying more than the borrow balance will underflow and revert without any error message. /// @param market The market of the position. /// @param borrower The owner of the position. /// @param seized The assets of collateral to seize. + /// @param shares The amount of shares to repay. /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. /// @return assetsRepaid The amount of assets repaid. /// @return sharesRepaid The amount of shares repaid. - function liquidate(Market memory market, address borrower, uint256 seized, bytes memory data) + function liquidate(Market memory market, address borrower, uint256 seized, uint256 shares, bytes memory data) external returns (uint256 assetsRepaid, uint256 sharesRepaid); diff --git a/test/forge/BaseTest.sol b/test/forge/BaseTest.sol index 9c8ba1158..1d3cfd44c 100644 --- a/test/forge/BaseTest.sol +++ b/test/forge/BaseTest.sol @@ -19,7 +19,7 @@ contract BaseTest is Test { uint256 internal constant MAX_TEST_AMOUNT = 1e28; uint256 internal constant MIN_TEST_SHARES = MIN_TEST_AMOUNT * SharesMathLib.VIRTUAL_SHARES; uint256 internal constant MAX_TEST_SHARES = MAX_TEST_AMOUNT * SharesMathLib.VIRTUAL_SHARES; - uint256 internal constant MIN_COLLATERAL_PRICE = 1000; + uint256 internal constant MIN_COLLATERAL_PRICE = 1e10; uint256 internal constant MAX_COLLATERAL_PRICE = 1e40; address internal SUPPLIER = _addrFromHashedString("Morpho Supplier"); diff --git a/test/forge/integration/TestIntegrationCallbacks.t.sol b/test/forge/integration/TestIntegrationCallbacks.t.sol index 88545e6e0..9f3260893 100644 --- a/test/forge/integration/TestIntegrationCallbacks.t.sol +++ b/test/forge/integration/TestIntegrationCallbacks.t.sol @@ -142,8 +142,8 @@ contract IntegrationCallbacksTest is borrowableToken.approve(address(morpho), 0); vm.expectRevert(); - morpho.liquidate(market, address(this), toSeize, hex""); - morpho.liquidate(market, address(this), toSeize, abi.encode(this.testLiquidateCallback.selector, hex"")); + morpho.liquidate(market, address(this), toSeize, 0, hex""); + morpho.liquidate(market, address(this), toSeize, 0, abi.encode(this.testLiquidateCallback.selector, hex"")); } function testFlashActions(uint256 amount) public { diff --git a/test/forge/integration/TestIntegrationLiquidate.t.sol b/test/forge/integration/TestIntegrationLiquidate.t.sol index f8ec5c78a..f33d0c773 100644 --- a/test/forge/integration/TestIntegrationLiquidate.t.sol +++ b/test/forge/integration/TestIntegrationLiquidate.t.sol @@ -11,14 +11,24 @@ contract IntegrationLiquidateTest is BaseTest { vm.assume(neq(marketFuzz, market)); vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); - morpho.liquidate(marketFuzz, address(this), 1, hex""); + morpho.liquidate(marketFuzz, address(this), 1, 0, hex""); } function testLiquidateZeroAmount() public { vm.prank(BORROWER); - vm.expectRevert(bytes(ErrorsLib.ZERO_ASSETS)); - morpho.liquidate(market, address(this), 0, hex""); + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); + morpho.liquidate(market, address(this), 0, 0, hex""); + } + + function testLiquidateZeroAmount(uint256 seized, uint256 sharesRepaid) public { + seized = bound(seized, 1, MAX_TEST_AMOUNT); + sharesRepaid = bound(sharesRepaid, 1, MAX_TEST_SHARES); + + vm.prank(BORROWER); + + vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT)); + morpho.liquidate(market, address(this), seized, sharesRepaid, hex""); } function testLiquidateHealthyPosition( @@ -48,10 +58,10 @@ contract IntegrationLiquidateTest is BaseTest { vm.prank(LIQUIDATOR); vm.expectRevert(bytes(ErrorsLib.HEALTHY_POSITION)); - morpho.liquidate(market, BORROWER, amountSeized, hex""); + morpho.liquidate(market, BORROWER, amountSeized, 0, hex""); } - function testLiquidateNoBadDebt( + function testLiquidateSeizedInputNoBadDebt( uint256 amountCollateral, uint256 amountSupplied, uint256 amountBorrowed, @@ -89,7 +99,7 @@ contract IntegrationLiquidateTest is BaseTest { vm.expectEmit(true, true, true, true, address(morpho)); emit EventsLib.Liquidate(id, LIQUIDATOR, BORROWER, expectedRepaid, expectedRepaidShares, amountSeized, 0); - (uint256 returnRepaid, uint256 returnRepaidShares) = morpho.liquidate(market, BORROWER, amountSeized, hex""); + (uint256 returnRepaid, uint256 returnRepaidShares) = morpho.liquidate(market, BORROWER, amountSeized, 0, hex""); uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0) - expectedRepaidShares; @@ -112,6 +122,70 @@ contract IntegrationLiquidateTest is BaseTest { assertEq(collateralToken.balanceOf(LIQUIDATOR), amountSeized, "liquidator collateral balance"); } + function testLiquidateSharesInputNoBadDebt( + uint256 amountCollateral, + uint256 amountSupplied, + uint256 amountBorrowed, + uint256 sharesRepaid, + uint256 priceCollateral + ) public { + (amountCollateral, amountBorrowed, priceCollateral) = + _boundUnhealthyPosition(amountCollateral, amountBorrowed, priceCollateral); + + vm.assume(amountCollateral > 1); + + amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); + _supply(amountSupplied); + + uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0); + uint256 maxRepaidShaires = amountCollateral.mulDivDown(priceCollateral, ORACLE_PRICE_SCALE).wDivDown( + _liquidationIncentive(market.lltv) + ); + vm.assume(maxRepaidShaires != 0); + sharesRepaid = bound(sharesRepaid, 1, min(maxRepaidShaires, expectedBorrowShares)); + uint256 expectedRepaid = sharesRepaid.toAssetsDown(amountBorrowed, expectedBorrowShares); + uint256 expectedSeized = + expectedRepaid.wMulDown(_liquidationIncentive(market.lltv)).mulDivDown(ORACLE_PRICE_SCALE, priceCollateral); + + borrowableToken.setBalance(LIQUIDATOR, amountBorrowed); + collateralToken.setBalance(BORROWER, amountCollateral); + + oracle.setPrice(type(uint256).max / amountCollateral); + + vm.startPrank(BORROWER); + morpho.supplyCollateral(market, amountCollateral, BORROWER, hex""); + morpho.borrow(market, amountBorrowed, 0, BORROWER, BORROWER); + vm.stopPrank(); + + oracle.setPrice(priceCollateral); + + vm.prank(LIQUIDATOR); + + vm.expectEmit(true, true, true, true, address(morpho)); + emit EventsLib.Liquidate(id, LIQUIDATOR, BORROWER, expectedRepaid, sharesRepaid, expectedSeized, 0); + (uint256 returnRepaid, uint256 returnRepaidShares) = morpho.liquidate(market, BORROWER, 0, sharesRepaid, hex""); + + expectedBorrowShares = amountBorrowed.toSharesUp(0, 0) - sharesRepaid; + + assertEq(returnRepaid, expectedRepaid, "returned asset amount"); + assertEq(returnRepaidShares, sharesRepaid, "returned shares amount"); + assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "borrow shares"); + assertEq(morpho.totalBorrow(id), amountBorrowed - expectedRepaid, "total borrow"); + assertEq(morpho.totalBorrowShares(id), expectedBorrowShares, "total borrow shares"); + assertEq(morpho.collateral(id, BORROWER), amountCollateral - expectedSeized, "collateral"); + assertEq(borrowableToken.balanceOf(BORROWER), amountBorrowed, "borrower balance"); + assertEq(borrowableToken.balanceOf(LIQUIDATOR), amountBorrowed - expectedRepaid, "liquidator balance"); + assertEq( + borrowableToken.balanceOf(address(morpho)), + amountSupplied - amountBorrowed + expectedRepaid, + "morpho balance" + ); + assertEq( + collateralToken.balanceOf(address(morpho)), amountCollateral - expectedSeized, "morpho collateral balance" + ); + assertEq(collateralToken.balanceOf(LIQUIDATOR), expectedSeized, "liquidator collateral balance"); + } + struct LiquidateBadDebtTestParams { uint256 incentive; uint256 expectedRepaid; @@ -180,7 +254,8 @@ contract IntegrationLiquidateTest is BaseTest { amountCollateral, params.expectedBadDebt * SharesMathLib.VIRTUAL_SHARES ); - (uint256 returnRepaid, uint256 returnRepaidShares) = morpho.liquidate(market, BORROWER, amountCollateral, hex""); + (uint256 returnRepaid, uint256 returnRepaidShares) = + morpho.liquidate(market, BORROWER, amountCollateral, 0, hex""); assertEq(returnRepaid, params.expectedRepaid, "returned asset amount"); assertEq(returnRepaidShares, params.expectedRepaidShares, "returned shares amount"); diff --git a/test/morpho_tests.tree b/test/morpho_tests.tree index da5dc2720..02bf264d1 100644 --- a/test/morpho_tests.tree +++ b/test/morpho_tests.tree @@ -206,31 +206,37 @@ ├── when market is not created │ └── revert with MARKET_NOT_CREATED └── when market is created - ├── when the assets to seized is zero - │ └── revert with ZERO_ASSETS - └── when the assets to seized is not zero - ├── it should accrue the interests - ├── when position is healthy - │ └── revert with HEALTHY_POSITION - └── when the position is not healthy - ├── it should compute repaid = seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(incentive) - ├── it should compute repaidShares = repaid.toSharesDown(totalBorrow[market.id], totalBorrowShares[market.id]); - ├── it should remove repaidShares from borrowShares[market.id][borrower] - ├── it should remove repaidShares from totalBorrowShares[market.id] - ├── it should remove repaid from totalBorrow[market.id] - ├── it should remove seized from collateral[market.id][borrower] - ├── if after the liquidation the borrower's collateral is 0 - │ └── it should realize bad debt - │ ├── it should compute badDebt = borrowShares[market.id][borrower].toAssetsUp(totalBorrow[market.id], totalBorrowShares[market.id]) - │ ├── it should remove badDebt from totalSupply[market.id] - │ ├── it should remove badDebt from totalBorrow[market.id] - │ ├── it should remove borrowShares[market.id][borrower] from totalBorrowShares[market.id] - │ └── it should set borrowShares[market.id][borrower] to 0 - ├── it should transfer seized of collateral asset to the sender - ├── it should emit Liquidate(market.id, msg.sender, borrower, repaid, repaidShares, seized, badDebtShares) - ├── if data.length > 0 - │ └── it should call sender's onMorphoLiquidate callback - └── it should transfer repaid of borrowable asset from the sender the Morpho + ├── when both assets and shares are null or both assets and shares are not null + │ └─ revert with INCONSISTENT_INPUT + └── when one of assets or shares is null and one of assets or shares is not null + ├── when the assets to seized is zero + │ └── revert with ZERO_ASSETS + └── when the assets to seized is not zero + ├── it should accrue the interests + ├── when position is healthy + │ └── revert with HEALTHY_POSITION + └── when the position is not healthy + ├── when assets is not zero + │ ├── it should compute repaid = seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(incentive) + │ └── it should compute repaidShares = repaid.toSharesDown(totalBorrow[market.id], totalBorrowShares[market.id]); + ├── when repaidShares is not zero + │ ├── it should compute repaid = sharesRepaid.toAssetsDown(totalBorrow[id], totalBorrowShares[id]); + │ └── it should compute seized = assetsRepaid.wMulDown(liquidationIncentiveFactor(market.lltv)).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + ├── it should remove repaidShares from totalBorrowShares[market.id] + ├── it should remove repaid from totalBorrow[market.id] + ├── it should remove seized from collateral[market.id][borrower] + ├── if after the liquidation the borrower's collateral is 0 + │ └── it should realize bad debt + │ ├── it should compute badDebt = borrowShares[market.id][borrower].toAssetsUp(totalBorrow[market.id], totalBorrowShares[market.id]) + │ ├── it should remove badDebt from totalSupply[market.id] + │ ├── it should remove badDebt from totalBorrow[market.id] + │ ├── it should remove borrowShares[market.id][borrower] from totalBorrowShares[market.id] + │ └── it should set borrowShares[market.id][borrower] to 0 + ├── it should transfer seized of collateral asset to the sender + ├── it should emit Liquidate(market.id, msg.sender, borrower, repaid, repaidShares, seized, badDebtShares) + ├── if data.length > 0 + │ └── it should call sender's onMorphoLiquidate callback + └── it should transfer repaid of borrowable asset from the sender the Morpho . └── flashLoan(address token, uint256 assets, bytes calldata data) external ├── it should transfer assets of token from Morpho to the sender From 779521331e7039c6db42cb05f34ef1967def170b Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Wed, 16 Aug 2023 16:38:33 +0200 Subject: [PATCH 02/14] fix: fail on testWithdrawCollateralUnauthorized --- test/forge/integration/TestIntegrationWithdrawCollateral.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/forge/integration/TestIntegrationWithdrawCollateral.t.sol b/test/forge/integration/TestIntegrationWithdrawCollateral.t.sol index 3cf476e52..eb4007716 100644 --- a/test/forge/integration/TestIntegrationWithdrawCollateral.t.sol +++ b/test/forge/integration/TestIntegrationWithdrawCollateral.t.sol @@ -42,6 +42,7 @@ contract IntegrationWithdrawCollateralTest is BaseTest { } function testWithdrawCollateralUnauthorized(address attacker, uint256 amount) public { + vm.assume(attacker != SUPPLIER); amount = bound(amount, 1, MAX_TEST_AMOUNT); collateralToken.setBalance(SUPPLIER, amount); From b8542fbdf1a6298688d12ab9f29070bc929661ab Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Wed, 16 Aug 2023 16:39:53 +0200 Subject: [PATCH 03/14] fix: hardhat --- test/hardhat/Morpho.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hardhat/Morpho.spec.ts b/test/hardhat/Morpho.spec.ts index 938bc6b44..0434aec83 100644 --- a/test/hardhat/Morpho.spec.ts +++ b/test/hardhat/Morpho.spec.ts @@ -180,7 +180,7 @@ describe("Morpho", () => { const seized = closePositions ? assets : assets.div(2); - await morpho.connect(liquidator).liquidate(market, borrower.address, seized, "0x"); + await morpho.connect(liquidator).liquidate(market, borrower.address, seized, 0, "0x"); const remainingCollateral = await morpho.collateral(id, borrower.address); From f935923812a4fd9ea5a54ecabf00749b6c0c8733 Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Thu, 17 Aug 2023 09:46:56 +0200 Subject: [PATCH 04/14] fix: conflicts --- src/interfaces/IMorpho.sol | 2 +- test/forge/integration/TestIntegrationCallbacks.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index cbd24c0b7..9b14271a2 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -223,7 +223,7 @@ interface IMorpho is IFlashLender { /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. /// @return assetsRepaid The amount of assets repaid. /// @return sharesRepaid The amount of shares burned. - function liquidate(Market memory market, address borrower, uint256 seized, bytes memory data) + function liquidate(Market memory market, address borrower, uint256 seized, uint256 shares, bytes memory data) external returns (uint256 assetsRepaid, uint256 sharesRepaid); diff --git a/test/forge/integration/TestIntegrationCallbacks.t.sol b/test/forge/integration/TestIntegrationCallbacks.t.sol index 3e4eadbce..b5d646a7c 100644 --- a/test/forge/integration/TestIntegrationCallbacks.t.sol +++ b/test/forge/integration/TestIntegrationCallbacks.t.sol @@ -140,9 +140,9 @@ contract IntegrationCallbacksTest is borrowableToken.approve(address(morpho), 0); vm.expectRevert(); - morpho.liquidate(market, address(this), collateralAmount, hex""); + morpho.liquidate(market, address(this), collateralAmount, 0, hex""); morpho.liquidate( - market, address(this), collateralAmount, abi.encode(this.testLiquidateCallback.selector, hex"") + market, address(this), collateralAmount, 0, abi.encode(this.testLiquidateCallback.selector, hex"") ); } From 04d9aaeb4556d79e7dfbdea97f1c82edb3a3acbd Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Fri, 18 Aug 2023 23:24:53 +0200 Subject: [PATCH 05/14] fix: lint --- src/Morpho.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index 9f9e06618..59ba9fa32 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -348,15 +348,12 @@ contract Morpho is IMorpho { MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - market.lltv)) ); if (seized > 0) { - assetsRepaid = - seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(incentive); + assetsRepaid = seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(incentive); sharesRepaid = assetsRepaid.toSharesDown(totalBorrow[id], totalBorrowShares[id]); } else { sharesRepaid = borrowedShares; assetsRepaid = sharesRepaid.toAssetsDown(totalBorrow[id], totalBorrowShares[id]); - seized = assetsRepaid.wMulDown(incentive).mulDivDown( - ORACLE_PRICE_SCALE, collateralPrice - ); + seized = assetsRepaid.wMulDown(incentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } borrowShares[id][borrower] -= sharesRepaid; From d5aa5da29922f649369b948e77127a27e5367b44 Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Sat, 19 Aug 2023 11:01:23 +0200 Subject: [PATCH 06/14] fix: lint --- src/Morpho.sol | 4 ++-- src/interfaces/IMorpho.sol | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index ae6b6ffea..6adaf2742 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -356,10 +356,10 @@ contract Morpho is IMorpho { ); if (seized > 0) { assetsRepaid = seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(incentiveFactor); - sharesRepaid = assetsRepaid.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + sharesRepaid = assetsRepaid.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); } else { sharesRepaid = borrowedShares; - assetsRepaid = sharesRepaid.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + assetsRepaid = sharesRepaid.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); seized = assetsRepaid.wMulDown(incentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 13d7ce61d..8c24455c6 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -227,9 +227,13 @@ interface IMorpho { /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. /// @return assetsRepaid The amount of assets repaid. /// @return sharesRepaid The amount of shares burned. - function liquidate(MarketParams memory marketParams, address borrower, uint256 seized, uint256 shares, bytes memory data) - external - returns (uint256 assetsRepaid, uint256 sharesRepaid); + function liquidate( + MarketParams memory marketParams, + address borrower, + uint256 seized, + uint256 shares, + bytes memory data + ) external returns (uint256 assetsRepaid, uint256 sharesRepaid); /// @notice Executes a flash loan. /// @param token The token to flash loan. From 06ba4a5e8cd06ef3b74f37e1cf8074304916f014 Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Sun, 20 Aug 2023 11:45:06 +0200 Subject: [PATCH 07/14] refactor naming --- src/Morpho.sol | 39 ++++++++++--------- src/interfaces/IMorpho.sol | 10 ++--- .../TestIntegrationLiquidate.t.sol | 14 +++---- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index 6adaf2742..8b7f135fe 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -336,13 +336,13 @@ contract Morpho is IMorpho { function liquidate( MarketParams memory marketParams, address borrower, - uint256 seized, - uint256 borrowedShares, + uint256 seizedAssets, + uint256 repaidShares, bytes calldata data - ) external returns (uint256 assetsRepaid, uint256 sharesRepaid) { + ) external returns (uint256, uint256) { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); - require(UtilsLib.exactlyOneZero(seized, borrowedShares), ErrorsLib.INCONSISTENT_INPUT); + require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT); _accrueInterest(marketParams, id); @@ -354,20 +354,21 @@ contract Morpho is IMorpho { uint256 incentiveFactor = UtilsLib.min( MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) ); - if (seized > 0) { - assetsRepaid = seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(incentiveFactor); - sharesRepaid = assetsRepaid.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + + uint256 repaidAssets; + if (seizedAssets > 0) { + repaidAssets = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(incentiveFactor); + repaidShares = repaidAssets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); } else { - sharesRepaid = borrowedShares; - assetsRepaid = sharesRepaid.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); - seized = assetsRepaid.wMulDown(incentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); + seizedAssets = repaidAssets.wMulDown(incentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } - user[id][borrower].borrowShares -= sharesRepaid.toUint128(); - market[id].totalBorrowShares -= sharesRepaid.toUint128(); - market[id].totalBorrowAssets -= assetsRepaid.toUint128(); + user[id][borrower].borrowShares -= repaidShares.toUint128(); + market[id].totalBorrowShares -= repaidShares.toUint128(); + market[id].totalBorrowAssets -= repaidAssets.toUint128(); - user[id][borrower].collateral -= seized.toUint128(); + user[id][borrower].collateral -= seizedAssets.toUint128(); // Realize the bad debt if needed. uint256 badDebtShares; @@ -380,13 +381,15 @@ contract Morpho is IMorpho { user[id][borrower].borrowShares = 0; } - IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seized); + IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets); + + emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares); - emit EventsLib.Liquidate(id, msg.sender, borrower, assetsRepaid, sharesRepaid, seized, badDebtShares); + if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data); - if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(assetsRepaid, data); + IERC20(marketParams.borrowableToken).safeTransferFrom(msg.sender, address(this), repaidAssets); - IERC20(marketParams.borrowableToken).safeTransferFrom(msg.sender, address(this), assetsRepaid); + return (seizedAssets, repaidAssets); } /* FLASH LOANS */ diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 8c24455c6..4af4e29b2 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -222,18 +222,18 @@ interface IMorpho { /// @dev Repaying more than the borrow balance will underflow and revert without any error message. /// @param marketParams The market of the position. /// @param borrower The owner of the position. - /// @param seized The amount of collateral to seize. + /// @param seizedAssets The amount of collateral to seize. /// @param shares The amount of shares to repay. /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. - /// @return assetsRepaid The amount of assets repaid. - /// @return sharesRepaid The amount of shares burned. + /// @return seizedCollateral The amount of assets repaid. + /// @return repaidAssets The amount of repaid asset. function liquidate( MarketParams memory marketParams, address borrower, - uint256 seized, + uint256 seizedAssets, uint256 shares, bytes memory data - ) external returns (uint256 assetsRepaid, uint256 sharesRepaid); + ) external returns (uint256 seizedCollateral, uint256 repaidAssets); /// @notice Executes a flash loan. /// @param token The token to flash loan. diff --git a/test/forge/integration/TestIntegrationLiquidate.t.sol b/test/forge/integration/TestIntegrationLiquidate.t.sol index e4079dbe5..5d498d842 100644 --- a/test/forge/integration/TestIntegrationLiquidate.t.sol +++ b/test/forge/integration/TestIntegrationLiquidate.t.sol @@ -101,12 +101,12 @@ contract IntegrationLiquidateTest is BaseTest { vm.expectEmit(true, true, true, true, address(morpho)); emit EventsLib.Liquidate(id, LIQUIDATOR, BORROWER, expectedRepaid, expectedRepaidShares, amountSeized, 0); - (uint256 returnRepaid, uint256 returnRepaidShares) = morpho.liquidate(market, BORROWER, amountSeized, 0, hex""); + (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(market, BORROWER, amountSeized, 0, hex""); uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0) - expectedRepaidShares; + assertEq(returnSeized, amountSeized, "returned seized amount"); assertEq(returnRepaid, expectedRepaid, "returned asset amount"); - assertEq(returnRepaidShares, expectedRepaidShares, "returned shares amount"); assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "borrow shares"); assertEq(morpho.totalBorrowAssets(id), amountBorrowed - expectedRepaid, "total borrow"); assertEq(morpho.totalBorrowShares(id), expectedBorrowShares, "total borrow shares"); @@ -145,7 +145,7 @@ contract IntegrationLiquidateTest is BaseTest { ); vm.assume(maxRepaidShaires != 0); sharesRepaid = bound(sharesRepaid, 1, min(maxRepaidShaires, expectedBorrowShares)); - uint256 expectedRepaid = sharesRepaid.toAssetsDown(amountBorrowed, expectedBorrowShares); + uint256 expectedRepaid = sharesRepaid.toAssetsUp(amountBorrowed, expectedBorrowShares); uint256 expectedSeized = expectedRepaid.wMulDown(_liquidationIncentive(market.lltv)).mulDivDown(ORACLE_PRICE_SCALE, priceCollateral); @@ -165,12 +165,12 @@ contract IntegrationLiquidateTest is BaseTest { vm.expectEmit(true, true, true, true, address(morpho)); emit EventsLib.Liquidate(id, LIQUIDATOR, BORROWER, expectedRepaid, sharesRepaid, expectedSeized, 0); - (uint256 returnRepaid, uint256 returnRepaidShares) = morpho.liquidate(market, BORROWER, 0, sharesRepaid, hex""); + (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(market, BORROWER, 0, sharesRepaid, hex""); expectedBorrowShares = amountBorrowed.toSharesUp(0, 0) - sharesRepaid; + assertEq(returnSeized, expectedSeized, "returned seized amount"); assertEq(returnRepaid, expectedRepaid, "returned asset amount"); - assertEq(returnRepaidShares, sharesRepaid, "returned shares amount"); assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "borrow shares"); assertEq(morpho.totalBorrowAssets(id), amountBorrowed - expectedRepaid, "total borrow"); assertEq(morpho.totalBorrowShares(id), expectedBorrowShares, "total borrow shares"); @@ -256,11 +256,11 @@ contract IntegrationLiquidateTest is BaseTest { amountCollateral, params.expectedBadDebt * SharesMathLib.VIRTUAL_SHARES ); - (uint256 returnRepaid, uint256 returnRepaidShares) = + (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(market, BORROWER, amountCollateral, 0, hex""); + assertEq(returnSeized, amountCollateral, "returned seized amount"); assertEq(returnRepaid, params.expectedRepaid, "returned asset amount"); - assertEq(returnRepaidShares, params.expectedRepaidShares, "returned shares amount"); assertEq(morpho.collateral(id, BORROWER), 0, "collateral"); assertEq(borrowableToken.balanceOf(BORROWER), amountBorrowed, "borrower balance"); assertEq(borrowableToken.balanceOf(LIQUIDATOR), amountBorrowed - params.expectedRepaid, "liquidator balance"); From 042d3ff34406f70b44dfec0ac139159703aa0d57 Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Sun, 20 Aug 2023 11:55:54 +0200 Subject: [PATCH 08/14] fix: lint --- test/forge/integration/TestIntegrationLiquidate.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/forge/integration/TestIntegrationLiquidate.t.sol b/test/forge/integration/TestIntegrationLiquidate.t.sol index 5d498d842..9ac2b0106 100644 --- a/test/forge/integration/TestIntegrationLiquidate.t.sol +++ b/test/forge/integration/TestIntegrationLiquidate.t.sol @@ -256,8 +256,7 @@ contract IntegrationLiquidateTest is BaseTest { amountCollateral, params.expectedBadDebt * SharesMathLib.VIRTUAL_SHARES ); - (uint256 returnSeized, uint256 returnRepaid) = - morpho.liquidate(market, BORROWER, amountCollateral, 0, hex""); + (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(market, BORROWER, amountCollateral, 0, hex""); assertEq(returnSeized, amountCollateral, "returned seized amount"); assertEq(returnRepaid, params.expectedRepaid, "returned asset amount"); From 8958982fecfd090f5cd156f036bf197493a036b1 Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Sun, 20 Aug 2023 16:50:39 +0200 Subject: [PATCH 09/14] fix: naming --- src/interfaces/IMorpho.sol | 10 ++-- .../TestIntegrationLiquidate.t.sol | 2 +- test/morpho_tests.tree | 59 +++++++++---------- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 54bff98b2..7072b19fa 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -226,17 +226,17 @@ interface IMorpho { /// @param marketParams The market of the position. /// @param borrower The owner of the position. /// @param seizedAssets The amount of collateral to seize. - /// @param shares The amount of shares to repay. + /// @param repaidShares The amount of shares to repay. /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. - /// @return seizedCollateral The amount of assets repaid. - /// @return repaidAssets The amount of repaid asset. + /// @return seizedAssets_ The amount of assets repaid. + /// @return repaidAssets_ The amount of repaid asset. function liquidate( MarketParams memory marketParams, address borrower, uint256 seizedAssets, - uint256 shares, + uint256 repaidShares, bytes memory data - ) external returns (uint256 seizedCollateral, uint256 repaidAssets); + ) external returns (uint256 seizedAssets_, uint256 repaidAssets_); /// @notice Executes a flash loan. /// @param token The token to flash loan. diff --git a/test/forge/integration/TestIntegrationLiquidate.t.sol b/test/forge/integration/TestIntegrationLiquidate.t.sol index 9ac2b0106..a95472664 100644 --- a/test/forge/integration/TestIntegrationLiquidate.t.sol +++ b/test/forge/integration/TestIntegrationLiquidate.t.sol @@ -22,7 +22,7 @@ contract IntegrationLiquidateTest is BaseTest { morpho.liquidate(market, address(this), 0, 0, hex""); } - function testLiquidateZeroAmount(uint256 seized, uint256 sharesRepaid) public { + function testLiquidateInconsistentInput(uint256 seized, uint256 sharesRepaid) public { seized = bound(seized, 1, MAX_TEST_AMOUNT); sharesRepaid = bound(sharesRepaid, 1, MAX_TEST_SHARES); diff --git a/test/morpho_tests.tree b/test/morpho_tests.tree index f40632ef9..1e5e035e7 100644 --- a/test/morpho_tests.tree +++ b/test/morpho_tests.tree @@ -202,41 +202,38 @@ └── when position is not healthy └── revert with INSUFFICIENT_COLLATERAL . -└── liquidate(Market memory market, address borrower, uint256 seized, bytes calldata data) external +└── liquidate(Market memory market, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external ├── when market is not created │ └── revert with MARKET_NOT_CREATED └── when market is created - ├── when both assets and shares are null or both assets and shares are not null + ├── when both seizedAssets and repaidShares are null or both seizedAssets and repaidShares are not null │ └─ revert with INCONSISTENT_INPUT - └── when one of assets or shares is null and one of assets or shares is not null - ├── when the assets to seized is zero - │ └── revert with ZERO_ASSETS - └── when the assets to seized is not zero - ├── it should accrue the interests - ├── when position is healthy - │ └── revert with HEALTHY_POSITION - └── when the position is not healthy - ├── when assets is not zero - │ ├── it should compute repaid = seized.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(incentive) - │ └── it should compute repaidShares = repaid.toSharesDown(totalBorrow[market.id], totalBorrowShares[market.id]); - ├── when repaidShares is not zero - │ ├── it should compute repaid = sharesRepaid.toAssetsDown(totalBorrow[id], totalBorrowShares[id]); - │ └── it should compute seized = assetsRepaid.wMulDown(liquidationIncentiveFactor(market.lltv)).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - ├── it should remove repaidShares from totalBorrowShares[market.id] - ├── it should remove repaid from totalBorrow[market.id] - ├── it should remove seized from collateral[market.id][borrower] - ├── if after the liquidation the borrower's collateral is 0 - │ └── it should realize bad debt - │ ├── it should compute badDebt = borrowShares[market.id][borrower].toAssetsUp(totalBorrow[market.id], totalBorrowShares[market.id]) - │ ├── it should remove badDebt from totalSupply[market.id] - │ ├── it should remove badDebt from totalBorrow[market.id] - │ ├── it should remove borrowShares[market.id][borrower] from totalBorrowShares[market.id] - │ └── it should set borrowShares[market.id][borrower] to 0 - ├── it should transfer seized of collateral asset to the sender - ├── it should emit Liquidate(market.id, msg.sender, borrower, repaid, repaidShares, seized, badDebtShares) - ├── if data.length > 0 - │ └── it should call sender's onMorphoLiquidate callback - └── it should transfer repaid of borrowable asset from the sender the Morpho + └── when one of seizedAssets or repaidShares is null and one of seizedAssets or repaidShares is not null + ├── it should accrue the interests + ├── when position is healthy + │ └── revert with HEALTHY_POSITION + └── when the position is not healthy + ├── when seizedAssets is not zero + │ ├── it should compute assetsRepaid = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor(market.lltv)) + │ └── it should compute repaidShares = assetsRepaid.toSharesDown(totalBorrow[market.id], totalBorrowShares[market.id]) + ├── when repaidShares is not zero + │ ├── it should compute assetsRepaid = repaidShares.toAssetsUp(totalBorrow[market.id], totalBorrowShares[market.id]) + │ └── it should compute seizedAssets = assetsRepaid.wMulDown(incentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice) + ├── it should remove repaidShares from totalBorrowShares[market.id] + ├── it should remove assetsRepaid from totalBorrow[market.id] + ├── it should remove repaidShares from collateral[market.id][borrower] + ├── if after the liquidation the borrower's collateral is 0 + │ └── it should realize bad debt + │ ├── it should compute badDebt = borrowShares[market.id][borrower].toAssetsUp(totalBorrow[market.id], totalBorrowShares[market.id]) + │ ├── it should remove badDebt from totalSupplyAssets[market.id] + │ ├── it should remove badDebt from totalBorrowAssets[market.id] + │ ├── it should remove borrowShares[market.id][borrower] from totalBorrowShares[market.id] + │ └── it should set borrowShares[market.id][borrower] to 0 + ├── it should transfer repaidShares of collateral asset to the sender + ├── it should emit Liquidate(market.id, msg.sender, borrower, assetsRepaid, repaidShares, seizedAssets, badDebtShares) + ├── if data.length > 0 + │ └── it should call sender's onMorphoLiquidate callback + └── it should transfer assetsRepaid of borrowable asset from the sender to Morpho . └── flashLoan(address token, uint256 assets, bytes calldata data) external ├── it should transfer assets of token from Morpho to the sender From 4b347c67598ba418f2364cb600b9b3a512c07d52 Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Mon, 21 Aug 2023 10:20:22 +0200 Subject: [PATCH 10/14] fix: apply suggestions --- src/interfaces/IMorpho.sol | 4 ++-- test/forge/integration/TestIntegrationLiquidate.t.sol | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 7072b19fa..8a6e5c76a 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -221,14 +221,14 @@ interface IMorpho { /// @notice Liquidates the given `seized` assets to the given `market` of the given `borrower`'s position, /// optionally calling back the caller's `onMorphoLiquidate` function with the given `data`. /// @dev Either `seized` or `repaidShares` should be zero. - /// Seizing more than the collateral balance will underflow and revert without any error message. + /// @dev Seizing more than the collateral balance will underflow and revert without any error message. /// @dev Repaying more than the borrow balance will underflow and revert without any error message. /// @param marketParams The market of the position. /// @param borrower The owner of the position. /// @param seizedAssets The amount of collateral to seize. /// @param repaidShares The amount of shares to repay. /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. - /// @return seizedAssets_ The amount of assets repaid. + /// @return seizedAssets_ The amount of assets seized. /// @return repaidAssets_ The amount of repaid asset. function liquidate( MarketParams memory marketParams, diff --git a/test/forge/integration/TestIntegrationLiquidate.t.sol b/test/forge/integration/TestIntegrationLiquidate.t.sol index a95472664..3311b761b 100644 --- a/test/forge/integration/TestIntegrationLiquidate.t.sol +++ b/test/forge/integration/TestIntegrationLiquidate.t.sol @@ -140,11 +140,11 @@ contract IntegrationLiquidateTest is BaseTest { _supply(amountSupplied); uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0); - uint256 maxRepaidShaires = amountCollateral.mulDivDown(priceCollateral, ORACLE_PRICE_SCALE).wDivDown( + uint256 maxRepaidShares = amountCollateral.mulDivDown(priceCollateral, ORACLE_PRICE_SCALE).wDivDown( _liquidationIncentive(market.lltv) ); - vm.assume(maxRepaidShaires != 0); - sharesRepaid = bound(sharesRepaid, 1, min(maxRepaidShaires, expectedBorrowShares)); + vm.assume(maxRepaidShares != 0); + sharesRepaid = bound(sharesRepaid, 1, min(maxRepaidShares, expectedBorrowShares)); uint256 expectedRepaid = sharesRepaid.toAssetsUp(amountBorrowed, expectedBorrowShares); uint256 expectedSeized = expectedRepaid.wMulDown(_liquidationIncentive(market.lltv)).mulDivDown(ORACLE_PRICE_SCALE, priceCollateral); From c757c26dcc8221153bddeeae4274366c0c05259d Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Mon, 21 Aug 2023 10:56:55 +0200 Subject: [PATCH 11/14] fix: adapt liquidate notice --- src/interfaces/IMorpho.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 8a6e5c76a..fa807f082 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -218,8 +218,8 @@ interface IMorpho { function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external; - /// @notice Liquidates the given `seized` assets to the given `market` of the given `borrower`'s position, - /// optionally calling back the caller's `onMorphoLiquidate` function with the given `data`. + /// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seized` collateral assets to the given `market` of the + /// given `borrower`'s position, optionally calling back the caller's `onMorphoLiquidate` function with the given `data`. /// @dev Either `seized` or `repaidShares` should be zero. /// @dev Seizing more than the collateral balance will underflow and revert without any error message. /// @dev Repaying more than the borrow balance will underflow and revert without any error message. From f6439ae0a53f5fc9612df93f3cb316438714813a Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Mon, 21 Aug 2023 11:14:30 +0200 Subject: [PATCH 12/14] fix: apply suggestions --- src/interfaces/IMorpho.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index fa807f082..b756fbbbf 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -229,7 +229,7 @@ interface IMorpho { /// @param repaidShares The amount of shares to repay. /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. /// @return seizedAssets_ The amount of assets seized. - /// @return repaidAssets_ The amount of repaid asset. + /// @return repaidAssets_ The amount of assets repaid. function liquidate( MarketParams memory marketParams, address borrower, From faddcda29f83fc7e64ecb7aa2e1b5924412418ff Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Tue, 22 Aug 2023 12:04:39 +0200 Subject: [PATCH 13/14] fix: lint --- src/interfaces/IMorpho.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 8dcc06250..0a6ba0477 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -214,8 +214,10 @@ interface IMorpho { function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external; - /// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seized` of collateral on the given `market` of the - /// given `borrower`'s position, optionally calling back the caller's `onMorphoLiquidate` function with the given `data`. + /// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seized` of collateral on the given + /// `market` of the + /// given `borrower`'s position, optionally calling back the caller's `onMorphoLiquidate` function with the + /// given `data`. /// @dev Either `seized` or `repaidShares` should be zero. /// @dev Seizing more than the collateral balance will underflow and revert without any error message. /// @dev Repaying more than the borrow balance will underflow and revert without any error message. From f3f421959b7d9026a53df6507252a26d9bb0e7f3 Mon Sep 17 00:00:00 2001 From: Jean-Grimal Date: Tue, 22 Aug 2023 12:07:14 +0200 Subject: [PATCH 14/14] fix: lint natspec --- src/interfaces/IMorpho.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 0a6ba0477..bae6d4b17 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -215,9 +215,8 @@ interface IMorpho { external; /// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seized` of collateral on the given - /// `market` of the - /// given `borrower`'s position, optionally calling back the caller's `onMorphoLiquidate` function with the - /// given `data`. + /// `market` of the given `borrower`'s position, optionally calling back the caller's `onMorphoLiquidate` function + /// with the given `data`. /// @dev Either `seized` or `repaidShares` should be zero. /// @dev Seizing more than the collateral balance will underflow and revert without any error message. /// @dev Repaying more than the borrow balance will underflow and revert without any error message.