The protocol deducts maintenance margin value of the account from the collateral upon liquidation. The loss can be greater than maintenance margin. As a result, the protocol incures debt even when the debt can be covered with trader's collateral.
The account is considered liquidatable if the margin balance is less than the required maintenance margin. The protocol deducts the requiredMaintenanceMargin
to cover the debt. However, it is possible that debt is more than the requiredMaintenanceMargin
resulting in bad debt for the protocol.
pragma solidity 0.8.25;
import { UD60x18, ud60x18, convert as ud60x18Convert } from "@prb-math/UD60x18.sol";
import { SD59x18, sd59x18 } from "@prb-math/SD59x18.sol";
import {Test} from "forge-std/Test.sol";
import "forge-std/console.sol";
contract LiquidationIncursProtocolDebt is Test {
function test_liquidation() external {
UD60x18 btcFillPrice = ud60x18Convert(68818);
UD60x18 ethPrice = ud60x18Convert(3312);
uint initialMarginRate = 0.01e18;
uint maintenanceMarginRate = 0.005e18;
int256 positionSize = 50e18;
uint256 weethLTV = 0.7e18;
uint256 userEthDeposit = 20e18;
uint256 userEthCollateral = ud60x18(userEthDeposit).mul(ud60x18(weethLTV)).intoUint256();
UD60x18 notionalValue = sd59x18(positionSize).abs().intoUD60x18().mul(btcFillPrice);
UD60x18 initialMargin = ud60x18(initialMarginRate).mul(notionalValue);
assert(ud60x18(userEthCollateral).mul(ethPrice).gt(initialMargin));
UD60x18 ethNewPrice = ethPrice;
UD60x18 btcNewFillPrice = ud60x18Convert(68231);
SD59x18 priceShift = btcNewFillPrice.intoSD59x18().sub(btcFillPrice.intoSD59x18());
SD59x18 unrealizedPnlUsdX18 = sd59x18(positionSize).mul(priceShift);
notionalValue = sd59x18(positionSize).abs().intoUD60x18().mul(btcNewFillPrice);
UD60x18 maintenanceMargin = ud60x18(maintenanceMarginRate).mul(notionalValue);
UD60x18 marginBalance = ud60x18(userEthCollateral).mul(ethNewPrice);
assert(maintenanceMargin.gt(marginBalance.intoSD59x18().add(unrealizedPnlUsdX18).intoUD60x18()));
uint decimals = 18;
emit log_named_decimal_int("Loss (USD)", unrealizedPnlUsdX18.intoInt256(), 18);
emit log_named_decimal_uint("Deducted Amount (maintenance margin) (USD)", maintenanceMargin.intoUint256(), 18);
emit log_named_decimal_uint("Trader collateral value (USD)", ud60x18(userEthDeposit).mul(ethNewPrice).intoUint256(), decimals);
emit log_named_decimal_int("Protocol debt (USD)", maintenanceMargin.intoSD59x18().sub(unrealizedPnlUsdX18.abs()).intoInt256(), decimals);
}
}
Protocol incurs unnecessary bad debt upon liquidation even when it can be covered using trader's collateral.