Diffusal
Contracts

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):

StepDescription
1Series expires (block.timestamp ≥ expiry)
2Series is settled with TWAP price
3Settlement engine processes individual positions
4Longs 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 TypeITM When
CallSpot > Strike
PutStrike > 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

Settlement Amount=Intrinsic Value×Position Size\text{Settlement Amount} = \text{Intrinsic Value} \times |\text{Position Size}|
  • 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)
ParameterTypeDescription
seriesIdbytes32Option series identifier
positionSizeint256Position 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:

  1. Check if already settled → return early if yes
  2. Get position from PositionManager
  3. If position = 0 → mark settled, return
  4. Verify series is settled
  5. Calculate intrinsic value and settlement amount
  6. Credit longs / debit shorts via Vault
  7. Zero out position
  8. 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:

  1. Calculate all settlement amounts and aggregate totals
  2. Collect from shorts first (debit via Vault, caps at available deposit)
  3. If short collection < long payout, cover shortfall from InsuranceFund
  4. Pay longs (prorate if shortfall not fully covered by insurance)
  5. Zero out all positions

Emits: SeriesSettlementCompleted with aggregate stats.


Owner Functions

setOperator

Authorizes or deauthorizes an operator.

function setOperator(address operator, bool authorized) external onlyOwner

Emits: 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 onlyOwner

transferOwnership

Transfers contract ownership.

function transferOwnership(address newOwner) external onlyOwner

Emits: OwnershipTransferred


Events

EventParametersDescription
PositionSettleduser, seriesId, positionSize, settlementAmountIndividual position settled
SeriesSettlementCompletedseriesId, usersSettled, totalLongPayout, totalShortCollectionBatch settlement summary
OperatorUpdatedoperator, authorizedOperator status changed
OwnershipTransferredpreviousOwner, newOwnerOwnership changed
PositionManagerUpdatedoldManager, newManagerPosition manager address changed
SeriesRegistryUpdatedoldRegistry, newRegistrySeries registry address changed
CollateralVaultUpdatedoldVault, newVaultCollateral vault address changed
InsuranceFundUpdatedoldFund, newFundInsurance 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 = $5000

Result: 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 = $0

Result: 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) = -$2000

Result: User's deposit debited by $2000.


Integration Points

Depends On

ContractPurpose
DiffusalOptionsPositionManagerPosition queries and zeroing
DiffusalOptionsSeriesRegistrySettlement price lookup
DiffusalCollateralVaultCredit/debit operations
DiffusalInsuranceFundShortfall coverage for batch settlement

Used By

ContractPurpose
Keepers/AdminTrigger 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 conversion

On this page