From 289ad5e766c904049bfc3f85716f5bedc6441e50 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Thu, 7 Dec 2023 12:26:10 +0100 Subject: [PATCH 1/8] fix: liquidation rounding --- src/Morpho.sol | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index f755904b6..9a562577c 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -358,24 +358,19 @@ contract Morpho is IMorphoStaticTyping { require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION); - uint256 repaidAssets; - { - // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). - uint256 liquidationIncentiveFactor = UtilsLib.min( - MAX_LIQUIDATION_INCENTIVE_FACTOR, - WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) - ); + // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). + uint256 liquidationIncentiveFactor = UtilsLib.min( + MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) + ); - if (seizedAssets > 0) { - repaidAssets = - seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor); - repaidShares = repaidAssets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); - } else { - repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); - seizedAssets = - repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - } + if (seizedAssets > 0) { + repaidShares = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor) + .toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + } else { + seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) + .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } + uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); position[id][borrower].borrowShares -= repaidShares.toUint128(); market[id].totalBorrowShares -= repaidShares.toUint128(); From f4272f935fc185d4bdcbb41a537f72589ded2225 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Thu, 7 Dec 2023 15:03:16 +0100 Subject: [PATCH 2/8] fix: compilation without via-ir --- src/Morpho.sol | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index 9a562577c..b4d93c089 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -358,17 +358,21 @@ contract Morpho is IMorphoStaticTyping { require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION); - // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). - uint256 liquidationIncentiveFactor = UtilsLib.min( - MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) - ); + { + // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). + uint256 liquidationIncentiveFactor = UtilsLib.min( + MAX_LIQUIDATION_INCENTIVE_FACTOR, + WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) + ); - if (seizedAssets > 0) { - repaidShares = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor) - .toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); - } else { - seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) - .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + if (seizedAssets > 0) { + repaidShares = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp( + liquidationIncentiveFactor + ).toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + } else { + seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) + .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + } } uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); From e14da6e1dc7a9df24ae2d52a555dc69fc93a0d99 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Thu, 7 Dec 2023 15:03:57 +0100 Subject: [PATCH 3/8] test: test liquidation fix --- .../integration/LiquidateIntegrationTest.sol | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/forge/integration/LiquidateIntegrationTest.sol b/test/forge/integration/LiquidateIntegrationTest.sol index 0fb9325c9..4b05a589f 100644 --- a/test/forge/integration/LiquidateIntegrationTest.sol +++ b/test/forge/integration/LiquidateIntegrationTest.sol @@ -206,8 +206,8 @@ contract LiquidateIntegrationTest is BaseTest { sharesRepaid = bound(sharesRepaid, 1, Math.min(borrowShares, maxSharesRepaid)); uint256 expectedRepaid = sharesRepaid.toAssetsUp(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id)); - uint256 expectedSeized = - expectedRepaid.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, params.priceCollateral); + uint256 expectedSeized = sharesRepaid.toAssetsDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id)) + .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, params.priceCollateral); loanToken.setBalance(LIQUIDATOR, params.amountBorrowed); @@ -353,4 +353,28 @@ contract LiquidateIntegrationTest is BaseTest { vm.prank(LIQUIDATOR); morpho.liquidate(marketParams, BORROWER, collateralAmount, 0, hex""); } + + function testSeizedAssetsRoundUp() public { + _setLltv(0.75e18); + _supply(100e18); + + uint256 amountCollateral = 400; + uint256 amountBorrowed = 300; + collateralToken.setBalance(BORROWER, amountCollateral); + + vm.startPrank(BORROWER); + morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex""); + morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER); + vm.stopPrank(); + + oracle.setPrice(ORACLE_PRICE_SCALE - 0.01e18); + + loanToken.setBalance(LIQUIDATOR, amountBorrowed); + + vm.prank(LIQUIDATOR); + (uint256 seizedAssets, uint256 repaidAssets) = morpho.liquidate(marketParams, BORROWER, 0, 1, hex""); + + assertEq(seizedAssets, 0, "seizedAssets"); + assertEq(repaidAssets, 1, "repaidAssets"); + } } From 53938ce89f37488886666f2f9f24ebb8e027813b Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Thu, 7 Dec 2023 17:34:36 +0100 Subject: [PATCH 4/8] ci(foundry): use test profile --- .github/workflows/foundry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 4334bdbf6..99b493082 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -39,7 +39,7 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 - name: Run Forge tests in ${{ matrix.type }} mode - run: forge test -vvv + run: yarn test:forge -vvv env: FOUNDRY_FUZZ_RUNS: ${{ matrix.fuzz-runs }} FOUNDRY_FUZZ_MAX_TEST_REJECTS: ${{ matrix.max-test-rejects }} From f5ecb61430c7a30258b343091c64e5c13d6ea02b Mon Sep 17 00:00:00 2001 From: MathisGD Date: Fri, 8 Dec 2023 13:14:31 +0100 Subject: [PATCH 5/8] refactor: fix stack too deep --- src/Morpho.sol | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index b4d93c089..707674cf0 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -340,6 +340,11 @@ contract Morpho is IMorphoStaticTyping { /* LIQUIDATION */ + struct Vars { + uint256 collateralPrice; + uint256 liquidationIncentiveFactor; + } + /// @inheritdoc IMorphoBase function liquidate( MarketParams memory marketParams, @@ -354,25 +359,23 @@ contract Morpho is IMorphoStaticTyping { _accrueInterest(marketParams, id); - uint256 collateralPrice = IOracle(marketParams.oracle).price(); + Vars memory vars; + vars.collateralPrice = IOracle(marketParams.oracle).price(); - require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION); + require(!_isHealthy(marketParams, id, borrower, vars.collateralPrice), ErrorsLib.HEALTHY_POSITION); - { - // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). - uint256 liquidationIncentiveFactor = UtilsLib.min( - MAX_LIQUIDATION_INCENTIVE_FACTOR, - WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) - ); + // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). + vars.liquidationIncentiveFactor = UtilsLib.min( + MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) + ); - if (seizedAssets > 0) { - repaidShares = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp( - liquidationIncentiveFactor - ).toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); - } else { - seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) - .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - } + if (seizedAssets > 0) { + repaidShares = seizedAssets.mulDivUp(vars.collateralPrice, ORACLE_PRICE_SCALE).wDivUp( + vars.liquidationIncentiveFactor + ).toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + } else { + seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) + .wMulDown(vars.liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, vars.collateralPrice); } uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); From bab5dea3b3d9f7517de0497b256615c35fe355a2 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Fri, 8 Dec 2023 13:38:14 +0100 Subject: [PATCH 6/8] no memory vars --- src/Morpho.sol | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index 707674cf0..1ed573294 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -340,11 +340,6 @@ contract Morpho is IMorphoStaticTyping { /* LIQUIDATION */ - struct Vars { - uint256 collateralPrice; - uint256 liquidationIncentiveFactor; - } - /// @inheritdoc IMorphoBase function liquidate( MarketParams memory marketParams, @@ -359,23 +354,25 @@ contract Morpho is IMorphoStaticTyping { _accrueInterest(marketParams, id); - Vars memory vars; - vars.collateralPrice = IOracle(marketParams.oracle).price(); + uint256 collateralPrice = IOracle(marketParams.oracle).price(); - require(!_isHealthy(marketParams, id, borrower, vars.collateralPrice), ErrorsLib.HEALTHY_POSITION); + require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION); - // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). - vars.liquidationIncentiveFactor = UtilsLib.min( - MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) - ); + { + // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). + uint liquidationIncentiveFactor = UtilsLib.min( + MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) + ); - if (seizedAssets > 0) { - repaidShares = seizedAssets.mulDivUp(vars.collateralPrice, ORACLE_PRICE_SCALE).wDivUp( - vars.liquidationIncentiveFactor - ).toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); - } else { - seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) - .wMulDown(vars.liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, vars.collateralPrice); + if (seizedAssets > 0) { + uint repaidIntermediate = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp( + liquidationIncentiveFactor + ); + repaidShares = repaidIntermediate.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + } else { + seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) + .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + } } uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); From d7bc725520ad05102723849e8ce5cf225915677c Mon Sep 17 00:00:00 2001 From: MathisGD Date: Fri, 8 Dec 2023 15:22:54 +0100 Subject: [PATCH 7/8] chore: fmt --- src/Morpho.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index 1ed573294..1587280bc 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -360,15 +360,15 @@ contract Morpho is IMorphoStaticTyping { { // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))). - uint liquidationIncentiveFactor = UtilsLib.min( - MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) + uint256 liquidationIncentiveFactor = UtilsLib.min( + MAX_LIQUIDATION_INCENTIVE_FACTOR, + WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv)) ); if (seizedAssets > 0) { - uint repaidIntermediate = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp( - liquidationIncentiveFactor - ); - repaidShares = repaidIntermediate.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + uint256 intermediateVar = + seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor); + repaidShares = intermediateVar.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); } else { seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); From cc70d7793a1cdcdaa43ba2ef91153ec38cab9a21 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Tue, 12 Dec 2023 09:42:53 +0100 Subject: [PATCH 8/8] style: naming temp --- src/Morpho.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index 606552c19..89d6db7d4 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -369,9 +369,9 @@ contract Morpho is IMorphoStaticTyping { ); if (seizedAssets > 0) { - uint256 intermediateVar = + uint256 temp = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor); - repaidShares = intermediateVar.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); + repaidShares = temp.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); } else { seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares) .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);