DiffusalPortfolioManager
Portfolio management for gas-bounded cross-margin liquidations
The DiffusalPortfolioManager contract manages user portfolios for gas-bounded cross-margin liquidations. Each user can have multiple portfolios, with each portfolio having a maximum of 16 positions. Cross-margin applies within each portfolio only.
Overview
The portfolio manager enables users to organize their positions into separate portfolios:
| Function | Description |
|---|---|
| Multiple Portfolios | Each user can create multiple portfolios |
| Position Limits | Max 16 positions per portfolio for gas-bounded liquidation |
| Cross-Margin Scope | Cross-margin applies within each portfolio only |
| Position Transfer | Move positions between portfolios |
| Auto-Creation | Default portfolio (0) auto-created on first deposit |
Why Portfolios?
The primary motivation for portfolios is gas-bounded liquidation:
| Problem | Solution |
|---|---|
| Unbounded position iterations | Max 16 positions per portfolio |
| Gas-expensive liquidation | Each portfolio is independent liquidation unit |
| Single margin pool risk | Isolated risk across portfolios |
UX Flow
Normal Users
- Call
deposit()on CollateralVault - System auto-creates portfolio 0 (default)
- All trades go to portfolio 0
- Cross-margin across up to 16 positions
Power Users
- Use default portfolio 0 until hitting 16-position limit
- Create additional portfolios via
depositToPortfolio() - Organize positions strategically across portfolios
- Transfer positions between portfolios as needed
Storage & State
/// @custom:storage-location erc7201:diffusal.storage.PortfolioManager
struct PortfolioManagerStorage {
address owner; // Contract owner
address pendingOwner; // Two-step ownership transfer
address positionManager; // PositionManager reference
address vault; // CollateralVault reference
address marginCalculator; // MarginCalculator reference (health checks)
mapping(address => bool) operators; // Authorized operators
mapping(address => uint256[]) userPortfolios; // User -> portfolio IDs
mapping(address => mapping(uint256 => bool)) portfolioExists; // User -> portfolioId -> exists
mapping(address => uint256) nextPortfolioId; // User -> next ID to assign
mapping(address => mapping(uint256 => uint256)) portfolioIndex; // User -> portfolioId -> array index
}External Functions
Portfolio Management
createPortfolio
Creates a new portfolio for a user. Only callable by operators.
function createPortfolio(address user) external returns (uint256 portfolioId)| Parameter | Type | Description |
|---|---|---|
user | address | User to create a portfolio for |
| Return | Type | Description |
|---|---|---|
portfolioId | uint256 | ID of the newly created portfolio |
Behavior:
- Assigns the next available portfolio ID for the user
- Marks portfolio as existing
- Adds to user's portfolio list
- Increments counter for next portfolio
Emits: PortfolioCreated(user, portfolioId)
deletePortfolio
Deletes an empty portfolio. Only callable by operators.
function deletePortfolio(address user, uint256 portfolioId) external| Parameter | Type | Description |
|---|---|---|
user | address | User who owns the portfolio |
portfolioId | uint256 | ID of portfolio to delete |
Requirements:
- Portfolio must exist
- Portfolio must be empty (no positions, no collateral)
Note: Any empty portfolio can be deleted, including portfolio 0 (the default portfolio).
Emits: PortfolioDeleted(user, portfolioId)
transferPosition
Transfers a position between portfolios. Only callable by operators.
function transferPosition(
address user,
uint256 fromPortfolioId,
uint256 toPortfolioId,
bytes32 seriesId,
int256 amount
) external| Parameter | Type | Description |
|---|---|---|
user | address | User who owns both portfolios |
fromPortfolioId | uint256 | Source portfolio ID |
toPortfolioId | uint256 | Destination portfolio ID |
seriesId | bytes32 | Series of the position to transfer |
amount | int256 | Amount to transfer (can be partial) |
Behavior:
- Validates both portfolios exist
- Removes position from source portfolio
- Adds position to destination portfolio
- Validates margin health on both portfolios
Emits: PositionTransferred(user, fromPortfolioId, toPortfolioId, seriesId, amount)
View Functions
getUserPortfolios
Returns all portfolio IDs for a user.
function getUserPortfolios(address user) external view returns (uint256[] memory portfolioIds)portfolioExists
Checks if a portfolio exists.
function portfolioExists(address user, uint256 portfolioId) external view returns (bool exists)getPortfolioCount
Returns the number of portfolios a user has.
function getPortfolioCount(address user) external view returns (uint256 count)getNextPortfolioId
Returns the next portfolio ID that will be assigned for a user.
function getNextPortfolioId(address user) external view returns (uint256 nextId)isOperator
Checks if an address is an authorized operator.
function isOperator(address operator) external view returns (bool authorized)marginCalculator
Returns the margin calculator address.
function marginCalculator() external view returns (address)paused
Returns whether the contract is paused.
function paused() external view returns (bool)Owner Functions
setOperator
Authorizes or revokes an operator.
function setOperator(address operator, bool authorized) externalAuthorized operators:
- DiffusalCollateralVault (creates portfolios on first deposit)
- DiffusalOptionsPositionManager (tracks positions per portfolio)
Emits: OperatorUpdated(operator, authorized)
setPositionManager
Sets the position manager address.
function setPositionManager(address _positionManager) externalEmits: PositionManagerUpdated(_positionManager)
setVault
Sets the collateral vault address.
function setVault(address _vault) externalEmits: CollateralVaultUpdated(_vault)
setMarginCalculator
Sets the margin calculator address.
function setMarginCalculator(address _marginCalculator) externalEmits: MarginCalculatorUpdated(oldCalculator, newCalculator)
pause / unpause
Pauses or unpauses the contract.
function pause() external
function unpause() externaltransferOwnership / acceptOwnership
Two-step ownership transfer pattern.
function transferOwnership(address newOwner) external
function acceptOwnership() externalFlow:
- Current owner calls
transferOwnership(newOwner) - New owner calls
acceptOwnership()to complete transfer
Emits: OwnershipTransferStarted, OwnershipTransferred
Events
| Event | Parameters | Description |
|---|---|---|
PortfolioCreated | user, portfolioId | New portfolio created |
PortfolioDeleted | user, portfolioId | Portfolio deleted |
PositionTransferred | user, fromPortfolioId, toPortfolioId, seriesId, amount | Position moved between portfolios |
OperatorUpdated | operator, authorized | Operator status changed |
PositionManagerUpdated | positionManager | Position manager address updated |
CollateralVaultUpdated | vault | Collateral vault address updated |
MarginCalculatorUpdated | oldCalculator, newCalculator | Margin calculator address changed |
OwnershipTransferStarted | previousOwner, newOwner | Ownership transfer initiated |
OwnershipTransferred | previousOwner, newOwner | Ownership transferred |
Portfolio-Aware System Flow
Deposit Flow
┌────────────────────────────────────────────┐
│ User calls deposit() on CollateralVault │
└──────────────────────┬─────────────────────┘
▼
┌─────────────────┐
│ First deposit? │
└────────┬────────┘
Yes ┌───────────┴───────────┐ No
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ CollateralVault │ │ Deposit to existing │
│ calls createPortfolio │ │ portfolio │
│ () - Creates │ └───────────┬───────────┘
│ portfolio 0 │ │
└───────────┬───────────┘ │
└─────────────┬────────────┘
▼
┌────────────────────────────────────────────┐
│ Collateral credited to user's portfolio │
└────────────────────────────────────────────┘Trading Flow
┌─────────────────────┐
│ User submits order │
└──────────┬──────────┘
▼
┌───────────────────────────────┐
│ Trade executed │
│ (OrderBook or RFQ) │
└──────────────┬────────────────┘
▼
┌────────────────────────────────────────────────┐
│ PositionManager.updatePosition() called │
│ Position tracked per user + portfolio + series│
└───────────────────────┬────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ CollateralVault.checkMargin() │
│ Margin checked per portfolio (max 16 positions) │
└──────────────────────────────────────────────────┘Liquidation Flow
┌──────────────────────────────────┐
│ Liquidation triggered │
│ (Equity < MM) │
└───────────────┬──────────────────┘
▼
┌──────────────────────────────────┐
│ LiquidationEngine identifies │
│ underwater portfolio │
└───────────────┬──────────────────┘
▼
┌──────────────────────────────────┐
│ Liquidate positions within │
│ that portfolio only (max 16) │
└───────────────┬──────────────────┘
▼
┌──────────────────────────────────┐
│ Other portfolios unaffected │
└──────────────────────────────────┘Integration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalOptionsPositionManager | Position tracking per portfolio |
| DiffusalCollateralVault | Collateral per portfolio |
Used By
| Contract | Purpose |
|---|---|
| DiffusalCollateralVault | Creates portfolios on deposit |
| DiffusalLiquidationEngine | Portfolio-bounded liquidation |
Security Considerations
Gas-Bounded Operations
The 16-position limit per portfolio ensures:
- Liquidation gas costs are bounded and predictable
- No single liquidation can exceed block gas limit
- Keepers can reliably estimate liquidation costs
Operator-Only Mutations
All portfolio modifications require operator authorization:
modifier onlyOperator() {
if (!_getPortfolioManagerStorage().operators[msg.sender]) revert Errors.NotOperator();
_;
}Empty Portfolio Deletion
Any empty portfolio can be deleted, including portfolio 0. A portfolio is considered empty when it has no positions and no collateral:
if (positionManager.getPositionCount(user, portfolioId) > 0) revert Errors.PortfolioNotEmpty();
if (vault.getDeposit(user, portfolioId) > 0) revert Errors.PortfolioNotEmpty();Two-Step Ownership Transfer
Prevents accidental ownership loss through the accept pattern.
Pausable
Emergency pause capability for all portfolio operations.
Code Reference
Source: packages/contracts/src/DiffusalPortfolioManager.sol
Interface: packages/contracts/src/interfaces/IDiffusalPortfolioManager.sol
Related
- DiffusalCollateralVault — Portfolio margin enforcement
- DiffusalOptionsPositionManager — Position tracking
- DiffusalLiquidationEngine — Portfolio liquidation
- Margin System (Protocol) — Cross-margin mechanics