Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
38 changes: 20 additions & 18 deletions src/Morpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ contract Morpho is IMorphoStaticTyping {

/* LIQUIDATION */

struct Vars {
uint256 collateralPrice;
uint256 liquidationIncentiveFactor;
}

/// @inheritdoc IMorphoBase
function liquidate(
MarketParams memory marketParams,
Expand All @@ -354,28 +359,25 @@ 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);

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))).
vars.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(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);

position[id][borrower].borrowShares -= repaidShares.toUint128();
market[id].totalBorrowShares -= repaidShares.toUint128();
Expand Down
28 changes: 26 additions & 2 deletions test/forge/integration/LiquidateIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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");
}
}