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:
| Function | Description |
|---|---|
| Option Balance | Net option position per user per series (signed int256) |
| Premium Balance | Net premium balance per user per series (signed int256) |
| MMM Status | Main 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 │
└─────────────────────────────────────────────────────────────────────────────┘| Instrument | Balance | Meaning |
|---|---|---|
| Option Long | optionBalance > 0 | Owns option rights |
| Option Short | optionBalance < 0 | Owes option obligations |
| Premium Receivable | premiumBalance > 0 | Owed premium at settlement |
| Premium Payable | premiumBalance < 0 | Owes premium at settlement |
Option Balance Sign Convention
| Sign | Position Type | Meaning |
|---|---|---|
> 0 | Long | Owns option rights |
< 0 | Short | Owes option obligations |
= 0 | Flat | No 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) + premiumBalanceThis model enables accurate tracking of both option payoffs and premium flows.
Main Market Makers (MMMs)
Addresses designated as MMMs have special treatment:
| Feature | Regular User | MMM |
|---|---|---|
| Can be liquidated | Yes | No |
| Can sign RFQ quotes | No | Yes |
| Position limits | Normal margin | Trusted |
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)| Parameter | Type | Description |
|---|---|---|
user | address | User whose position to update |
portfolioId | uint256 | Portfolio ID to update |
seriesId | bytes32 | Option series identifier |
optionDelta | int256 | Change in option balance (+ = buy, - = sell) |
premiumDelta | int256 | Change 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 onlyOwnerAuthorized operators:
- DiffusalOptionsOrderBook
- DiffusalOptionsRFQ
- DiffusalSettlementEngine
- DiffusalLiquidationEngine
Emits: OperatorUpdated
setMmm
Designates or removes MMM status.
function setMmm(address mmm, bool authorized) external onlyOwnerEmits: 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 onlyOwnerEmits: 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() externalEmits: OwnershipTransferred
Events
| Event | Parameters | Description |
|---|---|---|
OptionsBought | user, seriesId, optionDelta, premiumDelta | User bought options |
OptionsSold | user, seriesId, optionDelta, premiumDelta | User sold options |
PositionUpdated | user, seriesId, portfolioId, optionDelta, premiumDelta, newOptionBalance, newPremiumBalance | Position updated in portfolio |
OperatorUpdated | operator, authorized | Operator status changed |
MMMUpdated | mmm, authorized | MMM status changed |
OwnershipTransferStarted | previousOwner, newOwner | Two-step transfer initiated |
OwnershipTransferred | previousOwner, newOwner | Ownership transfer completed |
PortfolioManagerUpdated | oldManager, newManager | Portfolio 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) + premiumBalanceExample:
- 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 += 100Key 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
| Contract | Purpose |
|---|---|
| DiffusalPortfolioManager | Portfolio existence checks |
Used By
| Contract | Purpose |
|---|---|
| DiffusalOptionsOrderBook | Position updates on fills |
| DiffusalOptionsRFQ | Position updates, MMM verification |
| DiffusalCollateralVault | Position queries for margin |
| DiffusalSettlementEngine | Position zeroing on settlement |
| DiffusalLiquidationEngine | Position 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) removalThis 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
Related
- DiffusalPortfolioManager — Portfolio management for positions
- DiffusalCollateralVault — Margin calculations using positions
- Margin System (Protocol) — How positions affect margin
- Protocol Design — Position tracking overview