Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix liquidation rounding #638

Merged
merged 11 commits into from
Dec 12, 2023
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;
}
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

/// @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");
}
}
Loading