Diffusal
Contracts

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 tracks net positions (long vs short), maintains volume-weighted entry premiums for PnL calculation, and manages Main Market Maker (MMM) designations.


Overview

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

FunctionDescription
Position TrackingNet position per user per series (signed int256)
Entry PremiumVolume-weighted average entry for PnL calculation
MMM StatusMain Market Maker designation (non-liquidatable)
Balance TrackingSeparate long/short balances for pair minting

Position Sign Convention

SignPosition TypeMeaning
> 0LongOwns option rights (pays premium)
< 0ShortOwes option obligations (receives premium)
= 0FlatNo position

Key Concepts

Net Position vs Balances

The contract tracks two different views of positions:

Net Position (PositionInfo.size)

  • Signed value representing net exposure
  • Long 10 + Short 3 = Net +7
  • Used for margin and PnL calculations

Gross Balances (BalanceInfo)

  • Separate longBalance and shortBalance
  • Used for pair minting/burning operations
  • Example: long 10, short 3 → {longBalance: 10, shortBalance: 3}

Entry Premium Tracking

The contract maintains a volume-weighted average entry premium:

// When adding to position
uint256 newEntryPremium = (oldNotional + deltaNotional) / newAbsSize;

// When reducing position
// Entry premium stays the same (realized PnL calculated externally)

This enables accurate PnL calculation when closing positions.

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;
    mapping(address => bool) operators;                           // Authorized operators
    mapping(address => bool) mmms;                                // Main Market Makers
    mapping(address => mapping(bytes32 => PositionInfo)) positions; // User → Series → Position
    mapping(address => mapping(bytes32 => BalanceInfo)) balances;   // User → Series → Balances
    mapping(address => bytes32[]) userSeriesIds;                  // User → Active series list
    mapping(address => mapping(bytes32 => uint256)) seriesIdIndex; // User → Series → Index
}

PositionInfo Struct

struct PositionInfo {
    int256 size;           // Net position size (signed)
    uint256 entryPremium;  // Volume-weighted entry premium (WAD)
    uint256 lastUpdated;   // Last update timestamp
}

BalanceInfo Struct

struct BalanceInfo {
    uint256 longBalance;   // Gross long balance
    uint256 shortBalance;  // Gross short balance
}

External Functions

Position Reading

getPosition

Returns the net position size for a user in a series.

function getPosition(address user, bytes32 seriesId) external view returns (int256 size)

getPositionInfo

Returns full position information including entry premium.

function getPositionInfo(address user, bytes32 seriesId) external view returns (PositionInfo memory info)

getUserSeriesIds

Returns all series IDs where a user has active positions.

function getUserSeriesIds(address user) external view returns (bytes32[] memory seriesIds)

Note: Used by margin calculations to iterate over user's portfolio.


getUserPositionCount

Returns the number of active positions for a user.

function getUserPositionCount(address user) external view returns (uint256 count)

getPositionsBatch

Returns positions for multiple series in one call.

function getPositionsBatch(address user, bytes32[] calldata seriesIds)
    external view returns (int256[] memory sizes)

getBalanceInfo

Returns gross long and short balances.

function getBalanceInfo(address user, bytes32 seriesId)
    external view returns (BalanceInfo memory balance)

getLongBalance / getShortBalance

Returns individual balance components.

function getLongBalance(address user, bytes32 seriesId) external view returns (uint256)
function getShortBalance(address user, bytes32 seriesId) external view returns (uint256)

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.


Operator Functions

These functions can only be called by authorized operators.

updatePosition

Updates a user's position and entry premium.

function updatePosition(
    address user,
    bytes32 seriesId,
    int256 sizeDelta,
    uint256 premium
) external onlyOperator returns (int256 newSize)
ParameterTypeDescription
useraddressUser whose position to update
seriesIdbytes32Option series identifier
sizeDeltaint256Change in position (+ = buy, - = sell)
premiumuint256Premium for this trade (WAD)

Behavior:

  • Updates net position: newSize = oldSize + sizeDelta
  • Updates entry premium (volume-weighted when adding)
  • Adds/removes series from user's tracking array
  • Emits OptionsBought or OptionsSold

updatePositionBatch

Updates multiple positions in one call.

function updatePositionBatch(
    address[] calldata users,
    bytes32[] calldata seriesIds,
    int256[] calldata sizeDeltas,
    uint256[] calldata premiums
) external onlyOperator returns (int256[] memory newSizes)

mintPair

Creates equal long and short balances for a user.

function mintPair(address user, bytes32 seriesId, uint256 amount) external onlyOperator

Effect: Adds amount to both longBalance and shortBalance.

Use case: Creating matched positions for market making.

Emits: PairsMinted


burnPair

Removes equal long and short balances.

function burnPair(address user, bytes32 seriesId, uint256 amount) external onlyOperator

Requirements: User must have at least amount in both balances.

Emits: PairsBurned


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


transferOwnership

Transfers contract ownership.

function transferOwnership(address newOwner) external onlyOwner

Emits: OwnershipTransferred


Events

EventParametersDescription
OptionsBoughtuser, seriesId, amount, premiumUser bought options (size increased)
OptionsSolduser, seriesId, amount, premiumUser sold options (size decreased)
PairsMinteduser, seriesId, amountLong+short pair created
PairsBurneduser, seriesId, amountLong+short pair destroyed
OperatorUpdatedoperator, authorizedOperator status changed
MMMUpdatedmmm, authorizedMMM status changed
OwnershipTransferredpreviousOwner, newOwnerOwnership changed

Entry Premium Logic

Adding to Position

When sizeDelta has the same sign as oldSize (or oldSize = 0):

uint256 oldNotional = abs(oldSize) * entryPremium;
uint256 deltaNotional = abs(sizeDelta) * premium;
uint256 newAbsSize = abs(newSize);
entryPremium = (oldNotional + deltaNotional) / newAbsSize;

Example:

  • Existing: 100 contracts @ 0.05 premium
  • New trade: 50 contracts @ 0.08 premium
  • New entry: (100×0.05 + 50×0.08) / 150 = 0.06 premium

Reducing Position

When sizeDelta has opposite sign from oldSize:

  • Entry premium remains unchanged
  • Realized PnL calculated externally: (exitPremium - entryPremium) × closedSize

Integration Points

Depends On

None (base-level contract)

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

Testnet: View on MonadVision


On this page