Diffusal

DiffusalOptionsPositionManager

Single source of truth for all option positions in the protocol

The DiffusalOptionsPositionManager contract is the central registry for all option positions across the Diffusal protocol. It implements the Four-Instrument Model which tracks both option balances (long/short) and premium balances (receivable/payable) separately.


Overview

The position manager serves as the single source of truth for position state:

FunctionDescription
Option BalanceNet option position per user per series (signed int256)
Premium BalanceNet premium balance per user per series (signed int256)
MMM StatusMain Market Maker designation (non-liquidatable)

Four-Instrument Model

The protocol uses a four-instrument accounting model:

┌─────────────────────────────────────────────────────────────────────────────┐
│                          FOUR-INSTRUMENT MODEL                               │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                    Option Balance (optionBalance)                       │ │
│  │                                                                         │ │
│  │  ┌────────────────────────────────┐  ┌────────────────────────────────┐  │ │
│  │  │      +optionBalance            │  │       -optionBalance           │  │ │
│  │  │       OPTION LONG              │  │        OPTION SHORT            │  │ │
│  │  │    Owns option rights          │  │    Owes option obligations     │  │ │
│  │  │   Profit if ITM at expiry      │  │    Loss if ITM at expiry       │  │ │
│  │  └────────────────────────────────┘  └────────────────────────────────┘  │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                   Premium Balance (premiumBalance)                      │ │
│  │                                                                         │ │
│  │  ┌────────────────────────────────┐  ┌────────────────────────────────┐  │ │
│  │  │     +premiumBalance            │  │      -premiumBalance           │  │ │
│  │  │     PREMIUM RECEIVER           │  │       PREMIUM PAYER            │  │ │
│  │  │  Owed premium at settlement    │  │   Owes premium at settlement   │  │ │
│  │  │     (typically shorts)         │  │      (typically longs)         │  │ │
│  │  └────────────────────────────────┘  └────────────────────────────────┘  │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                              │                      │                        │
│                              └──────────┬───────────┘                        │
│                                         ▼                                    │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                     Net Settlement Calculation                          │ │
│  │   netSettlement = (intrinsicValue x optionBalance) + premiumBalance     │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│                      Example: Long 10 ITM Calls                              │
│                                                                              │
│   optionBalance = +10                                                        │
│   premiumBalance = -500 USDC (paid)                                          │
│   intrinsicValue = $200 per contract                                         │
│   netSettlement = (200 x 10) + (-500) = $1,500 credit                        │
└─────────────────────────────────────────────────────────────────────────────┘
InstrumentBalanceMeaning
Option LongoptionBalance > 0Owns option rights
Option ShortoptionBalance < 0Owes option obligations
Premium ReceivablepremiumBalance > 0Owed premium at settlement
Premium PayablepremiumBalance < 0Owes premium at settlement

Option Balance Sign Convention

SignPosition TypeMeaning
> 0LongOwns option rights
< 0ShortOwes option obligations
= 0FlatNo position

Key Concepts

Option Balance vs Premium Balance

The contract tracks two separate balance types:

Option Balance (PositionInfo.optionBalance)

  • Signed value representing net option exposure
  • Positive = long options (owns rights)
  • Negative = short options (owes obligations)
  • Used for margin and settlement calculations

Premium Balance (PositionInfo.premiumBalance)

  • Signed value representing premium obligations
  • Positive = receivable (owed to user at settlement)
  • Negative = payable (user owes at settlement)
  • Accumulated from trades over time

Settlement Calculation

At settlement, the net settlement amount combines both balances:

netSettlement = (intrinsicValue × optionBalance) + premiumBalance

This model enables accurate tracking of both option payoffs and premium flows.

Main Market Makers (MMMs)

Addresses designated as MMMs have special treatment:

FeatureRegular UserMMM
Can be liquidatedYesNo
Can sign RFQ quotesNoYes
Position limitsNormal marginTrusted

Storage & State

/// @custom:storage-location erc7201:diffusal.storage.PositionManager
struct PositionManagerStorage {
    address owner;
    address pendingOwner;                                                         // Two-step ownership
    address portfolioManager;                                                     // Portfolio management
    mapping(address => bool) operators;                                           // Authorized operators
    mapping(address => bool) mmms;                                                // Main Market Makers
    // Portfolio-aware mappings (user → portfolioId → seriesId → data)
    mapping(address => mapping(uint256 => mapping(bytes32 => PositionInfo))) portfolioPositions;
    address seriesRegistry;  // For OI tracking
    mapping(address => mapping(uint256 => bytes32[])) portfolioSeriesIds;
    mapping(address => mapping(uint256 => mapping(bytes32 => uint256))) portfolioSeriesIdIndex;
}

PositionInfo Struct

struct PositionInfo {
    int256 optionBalance;   // Net option balance (positive = long, negative = short)
    int256 premiumBalance;  // Net premium balance (positive = receivable, negative = payable)
    uint256 lastUpdated;    // Last update timestamp
}

External Functions

MMM Functions

isMmm

Checks if an address is a Main Market Maker.

function isMmm(address account) external view returns (bool)

isLiquidatable

Checks if an address can be liquidated.

function isLiquidatable(address account) external view returns (bool)

Returns false for MMMs, true for everyone else.


owner

Returns the contract owner address.

function owner() external view returns (address)

isOperator

Checks if an address is an authorized operator.

function isOperator(address account) external view returns (bool)

Returns: true if the address is authorized to call operator functions.


Operator Functions

These functions can only be called by authorized operators. All position operations are portfolio-aware.

updatePositionInPortfolio

Updates a user's position in a specific portfolio.

function updatePositionInPortfolio(
    address user,
    uint256 portfolioId,
    bytes32 seriesId,
    int256 optionDelta,
    int256 premiumDelta
) external returns (int256 newOptionBalance)
ParameterTypeDescription
useraddressUser whose position to update
portfolioIduint256Portfolio ID to update
seriesIdbytes32Option series identifier
optionDeltaint256Change in option balance (+ = buy, - = sell)
premiumDeltaint256Change in premium balance (+ = receivable, - = payable)

Behavior:

  • Updates option balance: newOptionBalance = oldOptionBalance + optionDelta
  • Updates premium balance: newPremiumBalance = oldPremiumBalance + premiumDelta
  • Enforces MAX_POSITIONS_PER_PORTFOLIO (16) limit
  • Validates portfolio exists via PortfolioManager
  • Emits PositionUpdated

Owner Functions

setOperator

Authorizes or deauthorizes an operator.

function setOperator(address operator, bool authorized) external onlyOwner

Authorized operators:

  • DiffusalOptionsOrderBook
  • DiffusalOptionsRFQ
  • DiffusalSettlementEngine
  • DiffusalLiquidationEngine

Emits: OperatorUpdated


setMmm

Designates or removes MMM status.

function setMmm(address mmm, bool authorized) external onlyOwner

Emits: MMMUpdated


Two-Step Ownership Transfer

The contract uses a two-step ownership transfer pattern for safety.

transferOwnership

Initiates ownership transfer. The new owner must call acceptOwnership() to complete.

function transferOwnership(address newOwner) external onlyOwner

Emits: OwnershipTransferStarted


pendingOwner

Returns the pending owner address during a transfer.

function pendingOwner() external view returns (address)

acceptOwnership

Completes ownership transfer. Must be called by pending owner.

function acceptOwnership() external

Emits: OwnershipTransferred


Events

EventParametersDescription
OptionsBoughtuser, seriesId, optionDelta, premiumDeltaUser bought options
OptionsSolduser, seriesId, optionDelta, premiumDeltaUser sold options
PositionUpdateduser, seriesId, portfolioId, optionDelta, premiumDelta, newOptionBalance, newPremiumBalancePosition updated in portfolio
OperatorUpdatedoperator, authorizedOperator status changed
MMMUpdatedmmm, authorizedMMM status changed
OwnershipTransferStartedpreviousOwner, newOwnerTwo-step transfer initiated
OwnershipTransferredpreviousOwner, newOwnerOwnership transfer completed
PortfolioManagerUpdatedoldManager, newManagerPortfolio manager address changed

Four-Instrument Model Logic

Trade Flow Example

When a buyer purchases options from a seller:

Buyer (long position):

  • optionDelta = +size (receives option rights)
  • premiumDelta = -premium (owes premium to seller)

Seller (short position):

  • optionDelta = -size (takes on obligations)
  • premiumDelta = +premium (receives premium from buyer)

Settlement Calculation

At option expiry, each user's net settlement amount is:

netSettlement = (intrinsicValue × optionBalance) + premiumBalance

Example:

  • User has long 10 calls with optionBalance = +10
  • Premium balance = -5 USDC (they paid premium)
  • Call expires ITM with intrinsic = $2
  • Net settlement = (2 × 10) + (-5) = $15 credit

Premium Balance Aggregation

The getTotalPremiumBalance function sums premium balances across all series:

int256 total = 0;
for (uint256 i = 0; i < userSeriesIds.length; i++) {
    total += positions[user][seriesIds[i]].premiumBalance;
}
return total;

This is used in margin calculations to account for premium obligations.

Open Interest Tracking

When positions are updated, the Position Manager automatically reports changes to the Series Registry for Open Interest (OI) tracking:

Trade: User buys 100 BTC calls
├── PositionManager.updatePositionInPortfolio(user, ..., +100, ...)
├── User's optionBalance: 0 → +100
└── SeriesRegistry.updateOpenInterest(BTC_USDC, isCall=true, +100)

Result: Pair's callOI += 100

Key Points:

  • OI is tracked at the pair level with two buckets: Calls and Puts
  • Only positive balances (longs) are tracked directly; shorts are implicit (Long OI = Short OI always)
  • OI caps can be set per pair to limit total exposure
  • This prevents P2P trading from creating unbounded OI that the MMM would be forced to absorb on liquidation

See Series Registry for OI caps and management.


View Functions

All view functions are portfolio-aware. The position manager supports portfolio-aware position tracking, enabling gas-bounded liquidations with a maximum of 16 positions per portfolio.

getPortfolioOptionBalance

Returns the option balance for a user in a specific portfolio and series.

function getPortfolioOptionBalance(address user, uint256 portfolioId, bytes32 seriesId)
    external view returns (int256 optionBalance)

getPortfolioPremiumBalance

Returns the premium balance for a user in a specific portfolio and series.

function getPortfolioPremiumBalance(address user, uint256 portfolioId, bytes32 seriesId)
    external view returns (int256 premiumBalance)

getPortfolioTotalPremiumBalance

Returns the total premium balance across all series in a portfolio.

function getPortfolioTotalPremiumBalance(address user, uint256 portfolioId)
    external view returns (int256 totalPremiumBalance)

getPortfolioPositionInfo

Returns full position info for a user in a specific portfolio and series.

function getPortfolioPositionInfo(address user, uint256 portfolioId, bytes32 seriesId)
    external view returns (PositionInfo memory info)

getPortfolioSeriesIds

Returns all active series IDs for a user in a specific portfolio.

function getPortfolioSeriesIds(address user, uint256 portfolioId)
    external view returns (bytes32[] memory ids)

getPortfolioPositionCount

Returns the number of active positions for a user in a specific portfolio.

function getPortfolioPositionCount(address user, uint256 portfolioId)
    external view returns (uint256 count)

getMaxPositionsPerPortfolio

Returns the maximum number of positions allowed per portfolio (gas exhaustion protection).

function getMaxPositionsPerPortfolio() external view returns (uint256)

portfolioManager

Returns the portfolio manager address.

function portfolioManager() external view returns (address)

Integration Points

Depends On

ContractPurpose
DiffusalPortfolioManagerPortfolio existence checks

Used By

ContractPurpose
DiffusalOptionsOrderBookPosition updates on fills
DiffusalOptionsRFQPosition updates, MMM verification
DiffusalCollateralVaultPosition queries for margin
DiffusalSettlementEnginePosition zeroing on settlement
DiffusalLiquidationEnginePosition closing on liquidation

Security Considerations

Operator-Only Updates

Position state can only be modified by authorized operators:

modifier onlyOperator() {
    if (!$.operators[msg.sender]) revert Errors.NotOperator();
    _;
}

This ensures positions can only change through valid trading, settlement, or liquidation.

Series Tracking Efficiency

The contract maintains an efficient series tracking system:

mapping(address => bytes32[]) userSeriesIds;      // Array for iteration
mapping(address => mapping(bytes32 => uint256)) seriesIdIndex; // Index for O(1) removal

This enables:

  • O(1) series lookup
  • O(1) series removal (swap and pop)
  • Efficient iteration over user positions

MMM Non-Liquidatable Status

MMMs cannot be liquidated:

function isLiquidatable(address account) external view returns (bool) {
    return !$.mmms[account];
}

This requires high trust in MMM operators to maintain adequate collateral.


Code Reference

Source: packages/contracts/src/DiffusalOptionsPositionManager.sol

Interface: packages/contracts/src/interfaces/IDiffusalOptionsPositionManager.sol


On this page