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:
| Function | Description |
|---|---|
| Position Tracking | Net position per user per series (signed int256) |
| Entry Premium | Volume-weighted average entry for PnL calculation |
| MMM Status | Main Market Maker designation (non-liquidatable) |
| Balance Tracking | Separate long/short balances for pair minting |
Position Sign Convention
| Sign | Position Type | Meaning |
|---|---|---|
> 0 | Long | Owns option rights (pays premium) |
< 0 | Short | Owes option obligations (receives premium) |
= 0 | Flat | No 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
longBalanceandshortBalance - 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:
| 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;
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)| Parameter | Type | Description |
|---|---|---|
user | address | User whose position to update |
seriesId | bytes32 | Option series identifier |
sizeDelta | int256 | Change in position (+ = buy, - = sell) |
premium | uint256 | Premium 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
OptionsBoughtorOptionsSold
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 onlyOperatorEffect: 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 onlyOperatorRequirements: 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 onlyOwnerAuthorized operators:
- DiffusalOptionsOrderBook
- DiffusalOptionsRFQ
- DiffusalSettlementEngine
- DiffusalLiquidationEngine
Emits: OperatorUpdated
setMmm
Designates or removes MMM status.
function setMmm(address mmm, bool authorized) external onlyOwnerEmits: MMMUpdated
transferOwnership
Transfers contract ownership.
function transferOwnership(address newOwner) external onlyOwnerEmits: OwnershipTransferred
Events
| Event | Parameters | Description |
|---|---|---|
OptionsBought | user, seriesId, amount, premium | User bought options (size increased) |
OptionsSold | user, seriesId, amount, premium | User sold options (size decreased) |
PairsMinted | user, seriesId, amount | Long+short pair created |
PairsBurned | user, seriesId, amount | Long+short pair destroyed |
OperatorUpdated | operator, authorized | Operator status changed |
MMMUpdated | mmm, authorized | MMM status changed |
OwnershipTransferred | previousOwner, newOwner | Ownership 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
| 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
Testnet: View on MonadVision
Related
- DiffusalCollateralVault — Margin calculations using positions
- Margin System (Protocol) — How positions affect margin
- Protocol Design — Position tracking overview