@@ -1340,6 +1340,212 @@ contract MorphoBorrowPositionTest is Test {
13401340 assertTrue (borrowPosition.isHealthy (), "Position should be healthy after rapid ops " );
13411341 }
13421342
1343+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
1344+ /* LIQUIDATION TESTS */
1345+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
1346+
1347+ function test_liquidation_PositionBecomesUnhealthyAfterPriceDropAndGetsLiquidated () public {
1348+ uint256 collateralAmount = COLLATERAL_AMOUNT;
1349+
1350+ // Supply liquidity for borrowing and liquidation
1351+ _supplyLiquidity (LOAN_AMOUNT * 10 );
1352+
1353+ // Supply collateral
1354+ collateralToken.setBalance (positionManager, collateralAmount);
1355+ vm.prank (positionManager);
1356+ borrowPosition.supplyCollateral (collateralAmount);
1357+
1358+ // Borrow near max (80% of max)
1359+ uint256 maxBorrow = borrowPosition.maxBorrow ();
1360+ uint256 borrowAmountActual = (maxBorrow * 80 ) / 100 ;
1361+
1362+ vm.prank (positionManager);
1363+ borrowPosition.borrow (borrowAmountActual);
1364+
1365+ // Verify position is healthy initially
1366+ assertTrue (borrowPosition.isHealthy (), "Position should be healthy initially " );
1367+ uint256 collateralBefore = borrowPosition.totalCollateral ();
1368+ uint256 borrowedBefore = borrowPosition.totalBorrowed ();
1369+
1370+ // Crash the collateral price to make position unhealthy
1371+ // Drop price by 50% to trigger liquidation
1372+ oracle.setPrice (DEFAULT_ORACLE_PRICE / 2 );
1373+
1374+ // Position should now be unhealthy
1375+ assertFalse (borrowPosition.isHealthy (), "Position should be unhealthy after price drop " );
1376+
1377+ // Setup liquidator
1378+ address liquidator = makeAddr ("liquidator " );
1379+
1380+ // Give liquidator loan tokens to repay debt
1381+ loanToken.setBalance (liquidator, borrowedBefore);
1382+ vm.startPrank (liquidator);
1383+ loanToken.approve (address (morpho), type (uint256 ).max);
1384+
1385+ // Liquidate the position - Morpho allows liquidating unhealthy positions
1386+ // Liquidator seizes collateral by repaying debt
1387+ (uint256 seizedAssets , uint256 repaidShares ) = morpho.liquidate (
1388+ marketParams,
1389+ address (borrowPosition), // borrower address
1390+ borrowedBefore, // assets to seize (up to full debt)
1391+ 0 , // min shares received
1392+ "" // data
1393+ );
1394+
1395+ vm.stopPrank ();
1396+
1397+ // Verify liquidation occurred
1398+ assertGt (seizedAssets, 0 , "Should have seized collateral " );
1399+ assertGt (repaidShares, 0 , "Should have repaid debt " );
1400+
1401+ // Verify position state after liquidation
1402+ uint256 collateralAfter = borrowPosition.totalCollateral ();
1403+ uint256 borrowedAfter = borrowPosition.totalBorrowed ();
1404+
1405+ assertLt (collateralAfter, collateralBefore, "Collateral should decrease after liquidation " );
1406+ assertLt (borrowedAfter, borrowedBefore, "Borrowed amount should decrease after liquidation " );
1407+
1408+ // Note: Due to liquidation incentive, the position might still be unhealthy
1409+ // or even have zero collateral left after full liquidation.
1410+ // The key is that debt and collateral both decreased.
1411+ }
1412+
1413+ function test_liquidation_PartialLiquidationReducesDebt () public {
1414+ uint256 collateralAmount = COLLATERAL_AMOUNT;
1415+
1416+ // Supply liquidity
1417+ _supplyLiquidity (LOAN_AMOUNT * 10 );
1418+
1419+ // Supply collateral
1420+ collateralToken.setBalance (positionManager, collateralAmount);
1421+ vm.prank (positionManager);
1422+ borrowPosition.supplyCollateral (collateralAmount);
1423+
1424+ // Borrow near max (90% of max)
1425+ uint256 maxBorrow = borrowPosition.maxBorrow ();
1426+ uint256 borrowAmountActual = (maxBorrow * 90 ) / 100 ;
1427+
1428+ vm.prank (positionManager);
1429+ borrowPosition.borrow (borrowAmountActual);
1430+
1431+ assertTrue (borrowPosition.isHealthy (), "Position should be healthy initially " );
1432+
1433+ // Price drops 30% - makes position unhealthy
1434+ oracle.setPrice ((DEFAULT_ORACLE_PRICE * 70 ) / 100 );
1435+
1436+ assertFalse (borrowPosition.isHealthy (), "Position should be unhealthy after price drop " );
1437+
1438+ // Liquidator performs partial liquidation
1439+ address liquidator = makeAddr ("liquidator " );
1440+ uint256 borrowedBefore = borrowPosition.totalBorrowed ();
1441+ uint256 partialRepayAmount = borrowedBefore / 2 ; // Repay 50% of debt
1442+
1443+ loanToken.setBalance (liquidator, partialRepayAmount);
1444+ vm.startPrank (liquidator);
1445+ loanToken.approve (address (morpho), type (uint256 ).max);
1446+
1447+ morpho.liquidate (marketParams, address (borrowPosition), partialRepayAmount, 0 , "" );
1448+
1449+ vm.stopPrank ();
1450+
1451+ // Debt should be reduced
1452+ uint256 borrowedAfter = borrowPosition.totalBorrowed ();
1453+ assertLt (borrowedAfter, borrowedBefore, "Debt should be reduced after liquidation " );
1454+
1455+ // Note: Due to liquidation bonus, position might still be unhealthy after partial liquidation
1456+ // The test verifies that liquidation reduces debt, not necessarily restores full health
1457+ }
1458+
1459+ function test_liquidation_OwnerCanRepayInsteadOfBeingLiquidated () public {
1460+ uint256 collateralAmount = COLLATERAL_AMOUNT;
1461+
1462+ // Supply liquidity
1463+ _supplyLiquidity (LOAN_AMOUNT * 10 );
1464+
1465+ // Supply collateral and borrow
1466+ collateralToken.setBalance (positionManager, collateralAmount);
1467+ vm.prank (positionManager);
1468+ borrowPosition.supplyCollateral (collateralAmount);
1469+
1470+ uint256 maxBorrow = borrowPosition.maxBorrow ();
1471+ uint256 borrowAmountActual = (maxBorrow * 85 ) / 100 ;
1472+
1473+ vm.prank (positionManager);
1474+ borrowPosition.borrow (borrowAmountActual);
1475+
1476+ assertTrue (borrowPosition.isHealthy (), "Position should be healthy initially " );
1477+
1478+ // Price drops 40%
1479+ oracle.setPrice ((DEFAULT_ORACLE_PRICE * 60 ) / 100 );
1480+
1481+ assertFalse (borrowPosition.isHealthy (), "Position should be unhealthy after price drop " );
1482+
1483+ // Owner repays enough to restore health
1484+ uint256 borrowedBefore = borrowPosition.totalBorrowed ();
1485+ uint256 repayAmount = borrowedBefore / 2 ;
1486+
1487+ loanToken.setBalance (positionManager, repayAmount);
1488+ vm.prank (positionManager);
1489+ borrowPosition.repay (repayAmount);
1490+
1491+ // Position should be healthy again
1492+ assertTrue (borrowPosition.isHealthy (), "Position should be healthy after repayment " );
1493+
1494+ // Verify debt decreased
1495+ assertLt (borrowPosition.totalBorrowed (), borrowedBefore, "Debt should be reduced " );
1496+ }
1497+
1498+ function test_liquidation_VerifyCollateralAndDebtStateAfterFullLiquidation () public {
1499+ uint256 collateralAmount = COLLATERAL_AMOUNT;
1500+
1501+ // Supply liquidity
1502+ _supplyLiquidity (LOAN_AMOUNT * 10 );
1503+
1504+ // Supply collateral and borrow
1505+ collateralToken.setBalance (positionManager, collateralAmount);
1506+ vm.prank (positionManager);
1507+ borrowPosition.supplyCollateral (collateralAmount);
1508+
1509+ uint256 maxBorrow = borrowPosition.maxBorrow ();
1510+ uint256 borrowAmountActual = (maxBorrow * 95 ) / 100 ;
1511+
1512+ vm.prank (positionManager);
1513+ borrowPosition.borrow (borrowAmountActual);
1514+
1515+ // Severe price drop (60% crash)
1516+ oracle.setPrice ((DEFAULT_ORACLE_PRICE * 40 ) / 100 );
1517+
1518+ assertFalse (borrowPosition.isHealthy (), "Position should be unhealthy " );
1519+
1520+ // Liquidator liquidates entire position
1521+ address liquidator = makeAddr ("liquidator " );
1522+ uint256 borrowedAmount = borrowPosition.totalBorrowed ();
1523+ uint256 collateralBefore = borrowPosition.totalCollateral ();
1524+
1525+ loanToken.setBalance (liquidator, borrowedAmount * 2 ); // Excess to ensure full liquidation
1526+ vm.startPrank (liquidator);
1527+ loanToken.approve (address (morpho), type (uint256 ).max);
1528+
1529+ // Try to seize all collateral to fully liquidate
1530+ (uint256 seizedAssets , uint256 repaidShares ) =
1531+ morpho.liquidate (marketParams, address (borrowPosition), collateralBefore, 0 , "" );
1532+
1533+ vm.stopPrank ();
1534+
1535+ // Verify liquidation results
1536+ assertGt (seizedAssets, 0 , "Should have seized collateral " );
1537+
1538+ // After liquidation, collateral and debt should be reduced significantly
1539+ uint256 collateralAfter = borrowPosition.totalCollateral ();
1540+ uint256 borrowedAfter = borrowPosition.totalBorrowed ();
1541+
1542+ assertLt (collateralAfter, collateralBefore, "Collateral should be seized " );
1543+
1544+ // Verify that liquidation occurred and debt was reduced
1545+ // Note: In Morpho, liquidation includes a bonus, so the position may be fully liquidated
1546+ // with both collateral and debt approaching zero
1547+ }
1548+
13431549 /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
13441550 /* AUTHORIZATION TESTS */
13451551 /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
0 commit comments