DiffusalSettlementEngine
Handles settlement of expired options positions
The DiffusalSettlementEngine contract settles expired option positions by calculating intrinsic value and processing collateral transfers. Long positions receive payouts while short positions have their obligations debited. This is a high-risk contract as it handles the final distribution of user funds at option expiry.
Overview
Settlement occurs after an option series expires and has been settled (via SeriesRegistry.settle):
| Step | Description |
|---|---|
| 1 | Series expires (block.timestamp ≥ expiry) |
| 2 | Series is settled with TWAP price |
| 3 | Settlement engine processes individual positions |
| 4 | Longs receive payout, shorts pay obligation |
Intrinsic Value
At expiry, options have only intrinsic value (no time value). See Options Settlement: Intrinsic Value for the complete formulas.
| Option Type | ITM When |
|---|---|
| Call | Spot > Strike |
| Put | Strike > Spot |
Key Concepts
Settlement Flow
1. Verify series is settled (isSettled = true)
2. Get user's position size from PositionManager
3. Calculate intrinsic value
4. Calculate settlement amount (signed)
- Long (+size): receives payout (positive)
- Short (-size): pays obligation (negative)
5. Process collateral via Vault
- Longs: vault.creditCollateral(user, payout)
- Shorts: vault.debitCollateral(user, obligation)
6. Zero out position via PositionManager
7. Mark position as settled (prevents double-settlement)Settlement Amount Calculation
- Long positions: Receive this amount (positive)
- Short positions: Pay this amount (negative)
Double-Settlement Prevention
Each user-series combination can only be settled once:
mapping(address => mapping(bytes32 => bool)) private _settledPositions;Storage & State
The contract uses ERC-7201 namespaced storage for upgradeability:
/// @custom:storage-location erc7201:diffusal.storage.SettlementEngine
struct SettlementEngineStorage {
address owner;
mapping(address => bool) operators;
address positionManager;
address seriesRegistry;
address collateralVault;
address insuranceFund;
mapping(address => mapping(bytes32 => bool)) settledPositions;
}SettlementResult Struct
struct SettlementResult {
address user; // User being settled
bytes32 seriesId; // Option series
int256 positionSize; // Position before settlement
uint256 intrinsicValue; // Per-contract intrinsic (WAD)
int256 settlementAmount; // Total payout/obligation (USDC, signed)
bool settled; // Whether settlement succeeded
}View Functions
calculateIntrinsicValue
Calculates the intrinsic value per contract.
function calculateIntrinsicValue(bytes32 seriesId) public view returns (uint256 intrinsicValue)Returns: Intrinsic value in WAD (1e18).
Reverts: OptionNotSettled if series hasn't been settled.
Logic:
if (isCall) {
// Call: max(0, settlementPrice - strike)
if (settlementPrice > strike) {
intrinsicValue = settlementPrice - strike;
}
} else {
// Put: max(0, strike - settlementPrice)
if (strike > settlementPrice) {
intrinsicValue = strike - settlementPrice;
}
}insuranceFund
Returns the insurance fund address.
function insuranceFund() external view returns (address)calculateSettlementAmount
Calculates the total settlement amount for a position.
function calculateSettlementAmount(bytes32 seriesId, int256 positionSize)
public view returns (int256 settlementAmount)| Parameter | Type | Description |
|---|---|---|
seriesId | bytes32 | Option series identifier |
positionSize | int256 | Position size (signed) |
Returns: Settlement amount in USDC (6 decimals), signed:
- Positive = user receives payout (long ITM)
- Negative = user pays obligation (short ITM)
isPositionSettled
Checks if a position has been settled.
function isPositionSettled(address user, bytes32 seriesId) external view returns (bool)Settlement Functions
These functions require operator authorization.
settlePosition
Settles a single user's position.
function settlePosition(address user, bytes32 seriesId)
external onlyOperator returns (SettlementResult memory result)Requirements:
- Series must be settled (via SeriesRegistry)
- Position not already settled
Process:
- Check if already settled → return early if yes
- Get position from PositionManager
- If position = 0 → mark settled, return
- Verify series is settled
- Calculate intrinsic value and settlement amount
- Credit longs / debit shorts via Vault
- Zero out position
- Mark as settled
Emits: PositionSettled
settlePositionBatch
Settles multiple positions for a single series.
function settlePositionBatch(address[] calldata users, bytes32 seriesId)
external onlyOperator returns (SettlementResult[] memory results)Use case: Efficient batch settlement of all positions in a series.
Batch Settlement Flow:
- Calculate all settlement amounts and aggregate totals
- Collect from shorts first (debit via Vault, caps at available deposit)
- If short collection < long payout, cover shortfall from InsuranceFund
- Pay longs (prorate if shortfall not fully covered by insurance)
- Zero out all positions
Emits: SeriesSettlementCompleted with aggregate stats.
Owner Functions
setOperator
Authorizes or deauthorizes an operator.
function setOperator(address operator, bool authorized) external onlyOwnerEmits: OperatorUpdated
setPositionManager / setSeriesRegistry / setCollateralVault / setInsuranceFund
Updates contract references.
function setPositionManager(address positionManager_) external onlyOwner
function setSeriesRegistry(address seriesRegistry_) external onlyOwner
function setCollateralVault(address collateralVault_) external onlyOwner
function setInsuranceFund(address insuranceFund_) external onlyOwnertransferOwnership
Transfers contract ownership.
function transferOwnership(address newOwner) external onlyOwnerEmits: OwnershipTransferred
Events
| Event | Parameters | Description |
|---|---|---|
PositionSettled | user, seriesId, positionSize, settlementAmount | Individual position settled |
SeriesSettlementCompleted | seriesId, usersSettled, totalLongPayout, totalShortCollection | Batch settlement summary |
OperatorUpdated | operator, authorized | Operator status changed |
OwnershipTransferred | previousOwner, newOwner | Ownership changed |
PositionManagerUpdated | oldManager, newManager | Position manager address changed |
SeriesRegistryUpdated | oldRegistry, newRegistry | Series registry address changed |
CollateralVaultUpdated | oldVault, newVault | Collateral vault address changed |
InsuranceFundUpdated | oldFund, newFund | Insurance fund address changed |
Settlement Examples
Example 1: ITM Call
Setup:
- ETH-USD Call, Strike = $3000
- Settlement price = $3500
- User has +10 contracts (long)
Calculation:
Intrinsic value = max(0, 3500 - 3000) = $500 per contract
Settlement amount = $500 × 10 = $5000Result: User receives $5000 credit.
Example 2: OTM Put
Setup:
- ETH-USD Put, Strike = $2800
- Settlement price = $3000
- User has -5 contracts (short)
Calculation:
Intrinsic value = max(0, 2800 - 3000) = $0 per contract
Settlement amount = $0Result: No transfer needed—option expires worthless.
Example 3: ITM Put (Short)
Setup:
- ETH-USD Put, Strike = $3200
- Settlement price = $3000
- User has -10 contracts (short)
Calculation:
Intrinsic value = max(0, 3200 - 3000) = $200 per contract
Settlement amount = -($200 × 10) = -$2000Result: User's deposit debited by $2000.
Integration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalOptionsPositionManager | Position queries and zeroing |
| DiffusalOptionsSeriesRegistry | Settlement price lookup |
| DiffusalCollateralVault | Credit/debit operations |
| DiffusalInsuranceFund | Shortfall coverage for batch settlement |
Used By
| Contract | Purpose |
|---|---|
| Keepers/Admin | Trigger settlement after expiry |
Security Considerations
Operator-Only Access
Settlement can only be triggered by authorized operators:
modifier onlyOperator() {
if (!_operators[msg.sender]) revert Errors.NotOperator();
_;
}Double-Settlement Prevention
Each position can only be settled once:
if (_settledPositions[user][seriesId]) {
return result; // Already settled
}
// ... process settlement ...
_settledPositions[user][seriesId] = true;Series Settlement Verification
Positions can only be settled after the series itself is settled:
if (!sr.isSettled(seriesId)) revert Errors.OptionNotSettled();Debit Capping
When debiting shorts, the vault caps at available deposit:
uint256 actualDebit = amount > deposits[user] ? deposits[user] : amount;This prevents underflow but may leave protocol with bad debt (covered by insurance fund).
Code Reference
Source: packages/contracts/src/DiffusalSettlementEngine.sol
Interface: packages/contracts/src/interfaces/IDiffusalSettlementEngine.sol
Testnet: View on MonadVision
Key Constants
// From Constants.sol
uint256 constant WAD = 1e18;
uint256 constant WAD_TO_USDC = 1e12; // WAD to USDC conversionRelated
- Options Settlement (Protocol) — High-level settlement mechanics
- DiffusalOptionsSeriesRegistry — Series settlement with TWAP
- DiffusalCollateralVault — Collateral transfers