DiffusalLiquidationEngine
Handles liquidation of undercollateralized users
The DiffusalLiquidationEngine contract liquidates users whose equity falls below the maintenance margin threshold. It closes positions at penalized prices, pays a bounty to liquidators, and draws from the insurance fund to cover shortfalls. This is a high-risk contract with significant impact on user funds.
Overview
Liquidation is triggered when a user's equity falls below their maintenance margin:
Liquidation Process
1. Verify user is liquidatable (equity < MM, not MMM)
2. Calculate debt (IM - equity)
3. Close positions at penalized mark prices
4. Calculate liquidator bounty (5% of debt, capped at proceeds)
5. Pay bounty to liquidator first (always)
6. Apply remaining proceeds to debt
7. If proceeds after bounty >= debt:
- Credit surplus to user
8. If proceeds after bounty < debt (shortfall):
- Draw from insurance fundPartial vs Full Liquidation
| Type | Positions Closed | Use Case |
|---|---|---|
| Full | All positions | User is deeply underwater |
| Partial | Proportional (debt/IM fraction) | Often restores health without full wipeout |
Proportional Liquidation: Partial liquidation closes a fraction of positions equal to debt / initialMargin, ensuring just enough positions are liquidated to cover the debt. If partial liquidation doesn't restore health, the engine automatically escalates to full liquidation.
Key Concepts
Debt Calculation
Debt represents how much the user is underwater:
Penalty Rate
Positions are closed at penalized prices to discourage risky behavior and compensate the system. The penalty scales with implied volatility. See Liquidation: Volatility-Adjusted Penalty for the complete formula and rate table.
Liquidation Prices
| Position Type | Liquidation Price |
|---|---|
| Long | Mark × (1 - penalty) — sold at discount |
| Short | Mark × (1 + penalty) — bought back at premium |
Liquidator Bounty
Note: Bounty is always paid first to incentivize liquidators, even in shortfall cases. The bounty is capped at total proceeds to ensure we don't pay more than available. Remaining proceeds are applied to debt, and insurance covers any shortfall.
Storage & State
The contract uses ERC-7201 namespaced storage for upgradeability:
/// @custom:storage-location erc7201:diffusal.storage.LiquidationEngine
struct LiquidationEngineStorage {
address owner;
mapping(address => bool) operators;
address collateralVault;
address positionManager;
address seriesRegistry;
address quoter;
address oracle;
address insuranceFund;
uint256 partialLiquidationRatio; // Default 50%
}LiquidationInfo Struct
struct LiquidationInfo {
bool isLiquidatable; // Can be liquidated
uint256 debt; // IM - equity (USDC)
uint256 estimatedProceeds; // Expected from closing positions
uint256 estimatedBounty; // Expected liquidator reward
uint256 positionCount; // Number of positions
}LiquidationResult Struct
struct LiquidationResult {
address user; // User liquidated
address liquidator; // Who triggered liquidation
uint256 debt; // Amount underwater
uint256 totalProceeds; // Actual proceeds from closing
uint256 bounty; // Bounty paid to liquidator
uint256 insuranceUsed; // Amount drawn from insurance
uint256 positionsLiquidated; // Positions closed
int256 newEquity; // User's equity after liquidation
bool isPartial; // Partial or full liquidation
}View Functions
calculateDebt
Returns the user's debt (how much underwater).
function calculateDebt(address user) public view returns (uint256 debt)calculatePenaltyRate
Returns the penalty rate for a trading pair.
function calculatePenaltyRate(bytes32 pairId) public view returns (uint256 penaltyRate)Returns: Penalty rate in WAD (e.g., 0.01e18 = 1%).
getLiquidationInfo
Returns comprehensive liquidation information.
function getLiquidationInfo(address user) external view returns (LiquidationInfo memory info)Useful for:
- Checking if a user is liquidatable
- Estimating proceeds and bounty before execution
- Monitoring user health
getInsuranceFundBalance
Returns the current insurance fund balance.
function getInsuranceFundBalance() external view returns (uint256)Liquidation Functions
These functions are permissionless—anyone can liquidate an undercollateralized user.
liquidate
Performs full liquidation (closes all positions).
function liquidate(address user) external nonReentrant returns (LiquidationResult memory result)Requirements:
- User must be liquidatable
- User must not be an MMM
Emits: UserLiquidated, PositionLiquidated (per position)
liquidatePartial
Performs partial liquidation using proportional position closure.
function liquidatePartial(address user) external nonReentrant returns (LiquidationResult memory result)Behavior:
- Calculates the proportion of positions to close:
debt / initialMargin - Closes that fraction of positions (minimum 1 position)
- Re-checks if user is healthy after partial liquidation
- If still unhealthy, automatically liquidates remaining positions
Use case: Often sufficient to restore user to healthy state without full wipeout.
Emits: Same as full liquidation.
Owner Functions
setOperator
Authorizes or deauthorizes an operator.
function setOperator(address operator, bool authorized) external onlyOwnersetPartialLiquidationRatio
Sets the ratio for partial liquidations.
function setPartialLiquidationRatio(uint256 ratio) external onlyOwnerConstraints: 0 < ratio <= WAD (0-100%)
Emits: PartialLiquidationRatioUpdated
setInsuranceFund
Sets the insurance fund address.
function setInsuranceFund(address insuranceFund_) external onlyOwnerContract Reference Setters
function setCollateralVault(address vault_) external onlyOwner
function setPositionManager(address manager_) external onlyOwner
function setSeriesRegistry(address registry_) external onlyOwner
function setQuoter(address quoter_) external onlyOwner
function setOracle(address oracle_) external onlyOwnertransferOwnership
Transfers contract ownership.
function transferOwnership(address newOwner) external onlyOwnerEvents
| Event | Parameters | Description |
|---|---|---|
UserLiquidated | user, liquidator, debt, proceeds, bounty, isPartial | Liquidation completed |
PositionLiquidated | user, seriesId, positionSize, markPrice, liquidationPrice, proceeds | Individual position closed |
InsuranceFundUpdated | oldBalance, newBalance | Insurance fund balance changed |
PartialLiquidationRatioUpdated | oldRatio, newRatio | Partial ratio changed |
OperatorUpdated | operator, authorized | Operator status changed |
OwnershipTransferred | previousOwner, newOwner | Ownership changed |
CollateralVaultUpdated | oldVault, newVault | Collateral vault address changed |
PositionManagerUpdated | oldManager, newManager | Position manager address changed |
SeriesRegistryUpdated | oldRegistry, newRegistry | Series registry address changed |
QuoterUpdated | oldQuoter, newQuoter | Quoter address changed |
OracleUpdated | oldOracle, newOracle | Oracle address changed |
InsuranceFundAddressUpdated | oldFund, newFund | Insurance fund address changed |
Liquidation Example
Setup
- User has 10 long ETH calls
- Mark price: $100 per contract
- Penalty rate: 1.5%
- Debt: $500
- Deposit: $800
Calculation
Step 1: Calculate liquidation price
Long position → sell at discount
Liquidation price = $100 × (1 - 1.5%) = $98.50Step 2: Calculate proceeds
Proceeds = 10 contracts × $98.50 = $985Step 3: Calculate bounty
Bounty = min($500 × 5%, $985) = min($25, $985) = $25Step 4: Settlement
1. Pay bounty first: $25 to liquidator
2. Remaining proceeds: $985 - $25 = $960
3. Apply to debt: $960 >= $500 ✓
4. Credit to user: $960 - $500 = $460Result:
- Liquidator earns $25 bounty (paid first)
- User loses positions, retains 800 = $1260
Integration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalCollateralVault | Equity/margin queries, collateral transfers |
| DiffusalOptionsPositionManager | Position queries, zeroing |
| DiffusalOptionsSeriesRegistry | Series info for pricing |
| DiffusalOptionsQuoter | Mark prices |
| DiffusalOracle | Volatility for penalty calculation |
| DiffusalInsuranceFund | Shortfall coverage |
Used By
| Contract | Purpose |
|---|---|
| Liquidation bots | Permissionless liquidation triggering |
| Keepers | Automated liquidation monitoring |
Security Considerations
Permissionless Liquidation
Anyone can liquidate an undercollateralized user:
function liquidate(address user) external nonReentrant returns (...)This ensures the protocol remains solvent without relying on trusted actors.
MMM Protection
Main Market Makers cannot be liquidated:
if (pm.isMmm(user)) revert Errors.CannotLiquidateMmm();Reentrancy Protection
All liquidation functions use nonReentrant modifier.
Shortfall Handling
When proceeds don't cover debt, the settlement order is:
- Bounty paid first (capped at total proceeds) to incentivize liquidators
- Remaining proceeds applied to debt
- Insurance fund covers shortfall (debt - proceedsAfterBounty)
- If insurance insufficient, bad debt remains
// Always pay bounty to incentivize liquidators, even in shortfall cases
actualBountyPaid = bounty > totalProceeds ? totalProceeds : bounty;
if (actualBountyPaid > 0) {
vault.creditCollateral(msg.sender, actualBountyPaid);
}
uint256 proceedsAfterBounty = totalProceeds - actualBountyPaid;
if (proceedsAfterBounty >= debt) {
// Sufficient proceeds - credit remaining to user
uint256 remaining = proceedsAfterBounty - debt;
if (remaining > 0) vault.creditCollateral(user, remaining);
} else {
// Shortfall - use insurance fund
insuranceUsed = _coverShortfall(debt - proceedsAfterBounty);
}Oracle Dependency
Liquidations rely on accurate oracle prices. If oracle is stale or manipulated:
- Penalty calculation may be incorrect
- Mark prices may be wrong
- System could liquidate healthy users or miss unhealthy ones
Code Reference
Source: packages/contracts/src/DiffusalLiquidationEngine.sol
Interface: packages/contracts/src/interfaces/IDiffusalLiquidationEngine.sol
Testnet: View on MonadVision
Key Constants
// From Constants.sol
uint256 constant LIQUIDATION_PENALTY_BASE = 0.01e18; // 1%
uint256 constant LIQUIDATION_PENALTY_IV_BASELINE = 0.5e18; // 50%
uint256 constant LIQUIDATOR_BOUNTY_RATE = 0.05e18; // 5%
uint256 constant WAD = 1e18;Related
- Liquidation (Protocol) — High-level liquidation mechanics
- Margin System (Protocol) — IM/MM calculation
- DiffusalInsuranceFund — Shortfall coverage
- DiffusalCollateralVault — Equity calculation