Contracts Documentation 0.8.4

Symmio Change-log Version 0.8.4

Settlement of Unrealized PnL (UPNL) in Symmio

In our latest upgrade to Symmio, we've introduced an important feature: the ability to settle unrealized profit and loss (UPNL) through the settleUpnl method. This enhancement addresses the challenges that arise when users leverage their UPNL to open new positions but face difficulties during the closing of positions due to insufficient allocated balances.

Background

In Symmio, users can use their unrealized profits to open new positions, effectively allowing their UPNL to back them up from being liquidated. While this provides greater flexibility and leverage for users, it introduces a potential issue during the closing of positions. When a position is closed, the losing party must pay the profit to the winning party from their allocated balances, i.e., realized PNL, not from UPNL.

Consider a scenario where a user has multiple positions:

  • Position A: Significant unrealized profit.

  • Position B: New position opened using the unrealized profit from Position A.

If Position B incurs a loss, the user may not have sufficient allocated balance to cover the loss when closing Position B, even though their overall UPNL is positive. This situation prevents the hedger (counterparty) from being able to fill the close request, as the required funds are not available in the user's allocated balance.

Previously, we mitigated this by warning users through the frontend that they couldn't close certain positions due to insufficient realized PNL, advising them to close profitable positions first. However, with the introduction of automated features like stop-loss and take-profit bots (e.g., in IntentX), this manual intervention is no longer feasible.

Additionally, users could exploit the force close option by quickly allocating more funds and forcing the close of their positions, potentially leading to losses for hedgers if they cannot react in time.

The settleUpnl Method

To resolve these issues, we've introduced the settleUpnl method, which allows hedgers to settle a portion of the user's unrealized PNL, effectively converting it into realized PNL. This enables the hedger to fill the user's close requests, even when the user lacks sufficient allocated balance.

The settleUpnl method functions similarly to our implementation for funding rates, where we updated the openedPrice of quotes.

Method Interface

struct QuoteSettlementData {
    uint256 quoteId;
    uint256 currentPrice;
    uint8 partyBUpnlIndex;
}

struct SettlementSig {
    bytes reqId;
    uint256 timestamp;
    QuoteSettlementData[] quotesSettlementsData;
    int256[] upnlPartyBs;
    int256 upnlPartyA;
    bytes gatewaySignature;
    SchnorrSign sigs;
}

function settleUpnl(
    SettlementSig memory settlementSig,
    uint256[] memory updatedPrices,
    address partyA
) external whenNotPartyBActionsPaused notLiquidatedPartyA(partyA) {}

Parameters:

  • settlementSig: Contains settlement data, including UPNL of Party A and Party Bs, and signatures.

  • updatedPrices: An array of updated prices for the quotes, reflecting the settlement of PNL.

  • partyA: The address of the user (Party A).

All quotes involved must be associated with the same Party A but can involve multiple Party Bs (hedgers). The method ensures that all parties are solvent before proceeding.

Key Features

  • Nonce Incrementation: The nonce for all parties involved is incremented, ensuring the integrity and synchronization of state changes.

  • Cooldown Mechanism: To prevent hedgers from repeatedly settling UPNL for quotes involving other hedgers, a cooldown period is enforced. A hedger cannot settle UPNL for another hedger's quotes more than once in a period.

    if (!isForceClose && msg.sender != partyB) {
        require(
            block.timestamp >=
                MAStorage.layout().lastUpnlSettlementTimestamp[msg.sender][partyB][partyA] + MAStorage.layout().settlementCooldown,
            "LibSettlement: Cooldown should be passed"
        );
        MAStorage.layout().lastUpnlSettlementTimestamp[msg.sender][partyB][partyA] = block.timestamp;
    }
    

Force Close Handling

In the code above, there's a check called isForceClose. What's that about? Well, until now in this document, I've only discussed scenarios where Party A doesn't have enough realized money for hedgers to fill their close requests. However, the reverse can also happen—a user might want to force-close a position, but the hedger doesn't have sufficient allocated balance. For these scenarios, we've added a method called settleAndForceClosePosition. As the name suggests, it first settles UPNLs for the hedger and then closes the position.

function settleAndForceClosePosition(
		uint256 quoteId,
		HighLowPriceSig memory highLowPriceSig,
		SettlementSig memory settleSig,
		uint256[] memory updatedPrices
) external notLiquidated(quoteId) whenNotPartyAActionsPaused {}

Reserve Vault for Hedgers

We've also introduced a reserve vault for hedgers. This vault allows hedgers to deposit funds that are not tied to any specific Party A and are used exclusively during force close operations when the hedger might become insolvent with the close. The reserve vault helps protect hedgers from liquidation in these scenarios.

Deposit and Withdraw Interface

/// @notice Transfers balance to the hedger's emergency reserve vault.
function depositToReserveVault(uint256 amount, address partyB) external whenNotPartyBActionsPaused notSuspended(msg.sender) notSuspended(partyB) {
    AccountFacetImpl.depositToReserveVault(amount, partyB);
    emit DepositToReserveVault(msg.sender, partyB, amount);
}

/// @notice Transfers balance from the hedger's emergency reserve vault.
function withdrawFromReserveVault(uint256 amount) external whenNotPartyBActionsPaused notSuspended(msg.sender) onlyPartyB {
    AccountFacetImpl.withdrawFromReserveVault(amount);
    emit WithdrawFromReserveVault(msg.sender, amount);
}

Detailed Example

Let's illustrate how the settleUpnl method works with a detailed example.

Scenario

Bob (Party A) has three open positions and zero allocated balance:

  1. Position 1 with Rasa Hedger (Party B1):

    • Unrealized Profit: $300

    • Quote ID: 1

  2. Position 2 with PerpsHub Hedger (Party B2):

    • Unrealized Profit: $100

    • Quote ID: 2

  3. Position 3 with PerpsHub Hedger (Party B2):

    • Unrealized Loss: $250

    • Quote ID: 3

Bob wants to close Position 3, which has an unrealized loss of $250. However, since Bob has zero allocated balance, he cannot cover this loss upon closing.

Problem

PerpsHub Hedger (Party B2) cannot fill Bob's close request for Position 3 because Bob doesn't have enough allocated balance to cover the $250 loss. Even though Bob has an overall positive UPNL ($150 net profit), his profits are unrealized and cannot be used to settle the loss directly.

Solution with settleUpnl

To enable the closing of Position 3, PerpsHub Hedger needs to settle Bob's unrealized profits from Positions 1 and 2, converting them into realized profits. This will increase Bob's allocated balance sufficiently to cover the loss.

  1. PerpsHub only needs to settle enough unrealized profit to cover the $250 loss. They can achieve this by settling Position 2 and part of Position 1. Hedgers should prioritize settling UPNLs from their own positions with users, as they face a cooldown period when settling positions involving other hedgers.

  2. PerpsHub initiates settleUpnl for Position1 and Position2:

SettlementSig memory settleSig = SettlementSig({
    reqId: bytes("uniqueRequestId"),
    timestamp: block.timestamp,
    quotesSettlementsData: [
        QuoteSettlementData({quoteId: 1, currentPrice: currentPricePos1, partyBUpnlIndex: 0}),
        QuoteSettlementData({quoteId: 2, currentPrice: currentPricePos2, partyBUpnlIndex: 1})
    ],
    upnlPartyBs: [-300, 150], // Rasa's upnl change, PerpsHub's upnl change
    upnlPartyA: 150, // Bob's total upnl being settled
    gatewaySignature: muonSignature,
    sigs: schnorrSignature
});

uint256[] memory updatedPrices = [newPricePos1, newPricePos2];

perpsHubHedger.settleUpnl(settleSig, updatedPrices, bobAddress);
  1. This call will:

    • Update the opened prices for Position1 and Position2

    • Realize $150 profit for Position1 and $100 profit for Position2

    • Adjust Bob's allocated balance: +$150 +$100 = $250 increase

    • Adjust Rasa's and PerpsHub's allocated balances accordingly

  2. After settlement, Bob's state:

    • Allocated balance: $250

    • Position2 has fully settled profit

    • Position1 has partially settled profit ($150 out of $300)

    • Position3 remains unchanged

  3. PerpsHub can now successfully fill the close request for Position3, as Bob has sufficient allocated balance ($250) to cover the loss ($250).

MultiAccount Changes

In the previous version of multiAccount, we introduced an option that allows users to delegate access to hedgers or any address to call functions on Symmio on their behalf. This feature laid the foundation for many powerful capabilities in Symmio, such as stopLoss, takeProfit, InstantOpen, and InstantClose. Users could revoke this access at any time. However, this freedom in revoking access created challenges for hedgers. For instance, hedgers might assume they could sendQuote on behalf of users during the instantOpen process, only to discover their access had been revoked after they had already hedged the position. In this update, we've implemented a cooldown period between when a user decides to revoke access and when that revocation takes effect. The process now requires users to first propose revoking their access using the following method:

function proposeToRevokeAccesses(address account, address target, bytes4[] memory selector) external onlyOwner(account, msg.sender)

After the revokeCooldown period has elapsed, users can then actually revoke their access by calling the following method:

function revokeAccesses(address account, address target, bytes4[] memory selector) external onlyOwner(account, msg.sender)

This two-step process allows hedgers to know in advance that they will lose their access, preventing potential complications later.

Internal Transfer For PartyBs

In the previous version, we introduced an option for users to transfer funds between their accounts without waiting for the deallocate cooldown. This function was initially limited to Party As only.

In this version, we've removed the notPartyB modifier on this method, allowing Party Bs to use it as well. Although Party Bs don't have multiple accounts to transfer between, they can use this method for a different purpose. Currently, Party Bs have balance manager processes that constantly monitor and allocate/deallocate funds for their users. This frequent activity resets their deallocate cooldown each time. With this new method, Party Bs can transfer some of their funds to a different account and let them remain there, allowing the cooldown period for that account to pass without interruption.

SymmioPartyB Changes

Given that the _multicastCall function allows hedgers to call any method from any contract, it was necessary to address potential reentrancy attacks. As a result, we've added the nonReentrant modifier to the _executeCall method. We chose to implement this logic ourselves rather than using OpenZeppelin's version because we needed to upgrade the existing contracts. Changing the inheritance structure would have prevented us from doing so, as it would have altered the contract's storage layout.

Other Code Improvements

Due to the growth of the PartyAFacet and PartyBFacet contracts in this version, they exceeded the size limit for on-chain deployment. To address this, we restructured the codebase:

  1. We divided PartyBFacet into three separate facets:

    • PartyBQuoteActionsFacet

    • PartyBPositionsActionsFacet

    • PartyBGroupActionsFacet

  2. All force actions of PartyA were moved to a new facet called ForceActionsFacet.

  3. The corresponding libraries were also split to maintain consistency with the new structure.

  4. Libraries like LibMuon were further divided into smaller components to manage contract size limitations.

Last updated

Logo

All rights to the people (c) 2023 Symmetry Labs A.G.