Technical Overview

At a high level, Perp v3 is a DEX system that uses a vault to hold user collateral, a router to choose from among several liquidity/pricing options in a liquidity framework, and a clearinghouse to record user account information.

For details about liquidity framework components, see Maker.

Contracts

The core Perp v3 contracts handle user deposits, trades, settlement and liquidation.

Vault contract

User deposits and withdrawals are made to/from the vault contract (called Funding Account for users). All collateral deposits are kept in the user's exchange funding account. All trades are backed by margin drawn from this collateral, and each position has its own margin. All user deposits remain in the vault at all times, and user accounts are updated according to the clearinghouse contract (below).

See Vault for details and code examples.

Order Gateway contract

You may choose to let the order gateway find the best price for you, or directly trade with the pool you want to use. This section assumes you want to trade using the order gateway.

The benefit of using the order gateway is easier setup via API, simplified quoting and automated price finding. The cons of using the order gateway are slightly slower execution and slightly increased potential for downtime due to an increased number of moving parts.

See Order Gateway for details and code examples.

Clearinghouse contract

Only one position of each asset can be open at a time, for a given account. If a second order for an asset with an existing position is created, the two resulting positions will be summed together. E.g. if you have a 1 ETH long and open a 1 ETH short, the two positions will sum to 0.

All trades and updates to account balance are handled by the clearinghouse contract.

Adding and removing position margin is controlled by the clearinghouse. Perp v3 uses isolated margin (each position has its own discrete margin). Rules regarding removal of margin are set and enforced by the clearinghouse.

See Clearinghouse for details and code examples.

Clearinghouse role in trade sequence

Quoter contract

Prices for trades (quotes) can be obtained using the Quoter contract.

Note: Quoter contract only considers onchain prices. Ie. it does not take live limit orders into account.

Maker contracts

Each maker has a contract housed under Maker. See each type for details.

Some maker types allow LPs to use leverage, such as Perp v3 oracle maker pools.

Fees

Borrowing fee

A fee is paid to maintain an open position in the same way you would pay interest on a loan from a Defi lending platform. All takers pay borrowing fees, and makers may pay in some circumstances.

The borrowing fee for each market is calculated based on the aggregate utilization ratio of all makers (LPs) in that market.

Details: Borrowing Fee

Funding fees / funding payments

Notes

  • Funding payments only apply to some markets. See Perp contract specs.

  • Funding payments relate to the ratio of long/short open interest, not the ratio of market and index prices. Regardless, longs still pay shorts when funding is positive, and vice versa.

Funding fees are designed to mitigate makers' exposure to price movement in cases where hedging is not possible (e.g. Oracle pools). The funding rate is based on the long/short skew of the basePool.

  • If the basePool has more shorts, takers (and possibly some makers) holding longs will pay funding fees.

  • If the basePool had more longs, takers (and possibly some makers) holding shorts will pay funding fees.

  • Non-basePool makers pay or receive funding based on their own long/short skew.

maxCapacity = basePool.totalDepositedAmount / basePool.minMarginRatio
imbalanceRatio = abs(basePool.openNotional)^fundingExponentFactor / maxCapacity
fundingRate = -1 * imbalanceRatio * fundingFactor
funding = trader.openNotional * fundingRate * deltaTimeInSeconds

Traders holding positions opposite to the base maker's direction need to pay a funding fee to traders holding positions in the same direction as the base maker.

Matcher fee

In general the matcher fee applies to UI users only. At launch this is a flat fee and in the future may be dynamic based on volume.

Account value

Account value is dependent on margin and unrealized PnL, and derived as follows.

pendingMargin:=borrowingFee+fundingFeemargin:=depositedMargin+unsettledPnl+pendingMarginpositionValue:=positionSize×oraclePriceunrealizedPnl:=openNotional+positionValueaccountValue:=margin+unrealizedPnl\begin{aligned} pendingMargin &:= borrowingFee + fundingFee \\ margin &:= depositedMargin + unsettledPnl + pendingMargin \\ positionValue &:= positionSize \times oraclePrice \\ unrealizedPnl &:= openNotional + positionValue \\ accountValue &:= margin + unrealizedPnl \end{aligned}

Margin & Leverage

Each market may have separate parameters for initial and maintenance margin ratios (and corresponding leverage values). See values in Perp contract specs.

leverage:=Abs(openNotional)accountValue\begin{aligned} leverage &:= \cfrac{Abs(openNotional)}{accountValue} \end{aligned}

Initial Margin Ratio

initailMarginRequirement:=Abs(openNotional)×initialMarginRatioinitailMarginRequirement:=Abs(openNotional) \times initialMarginRatio

Positions must have a margin ration equal to or higher than the initial margin ratio when opened. The open position transaction will revert if the margin ratio is lower than the initial margin ratio. Initial Margin Ratio applies to

  • Open position (including when trading in reverse direction, e.g. opening a short with an existing long)

  • Increase position size*

Maintenance Margin Ratio

maintenanceMarginRequirement:=Abs(openNotional)×maintenanceMarginRatiomaintenanceMarginRequirement:=Abs(openNotional) \times maintenanceMarginRatio

Positions must have a margin ratio higher than the maintenance margin ratio. Positions with a lower margin ratio may be liquidated. Maintenance margin ratio applies to

  • Decrease position size* (includes close position and partial close)

  • Liquidation conditions (margin ratio must be equal to or lower than MMR for a liquidation transaction to succeed)

Reduce vs Close Position

Reducing a position is done by calling openingPosition in the opposite direction of the existing position. The clearinghouse will evaluate the position's margin according to positionSizeBefore and positionSizeAfter as well as taking into account position direction (long or short).

Depending on the result of the trade, either maintenanceMarginRatio or initialMarginRatio will be used to evaluate the validity of the transaction.

// evaluate maintenanceMarginRatio
positionSizeAfter < positionSizeBefore & direction is same
positionSizeAfter = 0

// evaluate initialMarginRatio
positionSizeAfter > positionSizeBefore & direction is same
positionSizeAfter < positionSizeBefore & direction is not same

Close or partial close will always use maintenanceMarginRatio.

Free collateral

The vault (funding account) contract checks the user's free collateral when trades and withdrawals. Depending on the action, the method for calculating free collateral is as follows.

Collateral needed for untriggered limit orders, etc., will cause orders to fail if removed.

For trades

freeCollateralopen:=min(margin,accountValue)initialMarginRequirementfreeCollateralreduce:=min(margin,accountValue)maintenanceMarginRequirement\begin{aligned} freeCollateral_{open} &:= min(margin, accountValue) - initialMarginRequirement \\ freeCollateral_{reduce} &:= min(margin, accountValue) - maintenanceMarginRequirement \end{aligned}

For withdrawals

freeMargin=max(depositedMargin+min(unsettledMargin+pendingMargin,pnlPoolBalance),0)freeCollateralwithdraw=min(freeMargin,accountValue)initialMarginRequirement\begin{aligned} freeMargin &= max(depositedMargin + min(unsettledMargin+pendingMargin, pnlPoolBalance), 0) \\ freeCollateral_{withdraw} &= min(freeMargin, accountValue) - initialMarginRequirement \end{aligned}

Liquidation

When the account value falls below the maintenance margin requirement, the position will start to undergo liquidation. When a position is liquidated, it will be transferred to the liquidator at the current oracle price. This means that the liquidator must hold sufficient margin to take over the liquidated position. The margin amount will determine how many positions they can take over.

Maximum Liquidatable Position Size

When the account margin ratio falls below the maintenance margin requirement (MMR) but remains above 0.5*MMR, the liquidator may only liquidate up to half of the position size. If the margin ratio < 0.5*MMR, liquidators may liquidate the entire position.

Liquidation Penalty & Liquidation Fee

Traders pay a liquidation penalty after liquidation, and the liquidator will receive a portion of the liquidation penalty as fee for triggering the liquidation. The remaining penalty is reserved by the protocol. To prevent bad debt exploits, both the liquidation penalty and liquidation fee are settled through the Pnl Pool. Traders pay liquidation penalties to Pnl Pool, and liquidators and the protocol receive liquidation fees from the Pnl Pool.

Liqudation Penalty Calculation

liquidationPenaltyRatio:=the percentage of the open notional that the trader needs to pay as a liquidation penaltyliquidaitonRatio:=liquidatedPositionSizepositionSizeliquidationPenalty:=the actual amount that the trader needs to pay to the PnlPool=openNotional×liquidaitonRatio×liquidationPenaltyRatio\begin{aligned} liquidationPenaltyRatio &:= \text{the percentage of the open notional that the trader needs to pay as a liquidation penalty} \\ liquidaitonRatio&:= \cfrac{liquidatedPositionSize}{positionSize} \\ liquidationPenalty &:= \text{the actual amount that the trader needs to pay to the PnlPool} \\ \\&= openNotional \times liquidaitonRatio \times liquidationPenaltyRatio \end{aligned}

Liquidation Fee Calculation

liquidationFeeRatio:=the percentage of the liquidation penalty that the liquidator can receive as a liquidation fee.liquidationFeeliquidator:=the actual amount that the liquidator can receive from the PnlPool=liquidatinPenalty×liquidaitonFeeRatioliquidationFeeprotocol:=the actual amount that the protocol can receive from the PnlPool=liquidatinPenaltyliquidationFeeliquidator\begin{aligned} liquidationFeeRatio &:= \text{the percentage of the liquidation penalty that the liquidator can receive as a liquidation fee.} \\ liquidationFee_{liquidator} &:= \text{the actual amount that the liquidator can receive from the PnlPool} \\ \\&= liquidatinPenalty \times liquidaitonFeeRatio \\ liquidationFee_{protocol} &:= \text{the actual amount that the protocol can receive from the PnlPool} \\ \\&= liquidatinPenalty - liquidationFee_{liquidator} \\ \end{aligned}

PnL Pool

PnL pool is pending confirmation

Each market has a separate PnL pool

All PnL, positive or negative, passes through the market's PnL pool before being withdrawn to the user's funding account (vault). The purpose of the PnL pool is to settle PnL and ensuring all losses match profits for the given market before profits can be withdrawn. Negative PnL is retained by the pool to pay to accounts that settle positive PnL.

If settlement is not possible (settled positive PnL > PnL pool balance), withdrawal will be delayed until positions with negative PnL (unrealized losses) are settled, adding funds to the PnL pool. Withdrawal delays could occur for example when there is a large amount of realized profit and unrealized loss within one market. Traders in profit would need to wait for some of the unrealized losses to be realized before being able to withdraw fully. In general conditions when withdrawals would be blocked are considered an edge case.

This architecture allows the protocol trading engine to be less restrictive without compromising the safety of user funds. Using the PnL pool, the protocol ensures that PnL always balances and user funds are safe from exploitation. The PnL Pool replaces the Insurance Fund in more traditional leverage trading.

PnL Pool Actions

  • Open position

    • no action needed

  • Reduce, close or liquidate position

    • If PnL > 0 →

      • Withdraw only if PnL Pool has enough funds

      • When positive PnL is settled, the PnL pool balance decreases

    • If PnL < 0 →

      • Withdraw only if trader.margin has enough funds

      • When negative PnL is settled, the PnL pool balance increases

  • _settlePnl()

    • What is it

    • Deposit: Should _settlePnl() as much as possible to clear the trader’s existing unsettled PnL.

    • Withdraw: Should _settlePnl() as much as possible before calculating free collateral so it could maximize withdrawal limit.

Bad debt

The PnL pool is intended to serve in place of an insurance fund, which means it will have to handle cases where bad debt occurs.

Price band

A price band is in place to ensure trades happen within a safe range compared to the underlying price mechanism (e.g. oracle price). This guards against exploits and extreme volatility. Query priceBandRatio using Config.

bias:=oraclePrice×priceBandRatiooraclePricebiastradePriceoraclePrice+bias\begin{aligned} &bias := oraclePrice \times priceBandRatio \\ &{oraclePrice - bias} \le{tradePrice}\le{oraclePrice + bias} \end{aligned}

baseMaker

The baseMaker is used when calculating funding rates. A base maker is a whitelisted liquidity provider that meets the following conditions:

  • Cannot increase/decrease positions actively (may increase/decrease margin)

  • Must always accept taker orders

  • Must use index price as main pricing mechanism

  • Only one baseMaker per market

  • The baseMaker always receives funding, and other makers and takers that have positions with the same direction as the baseMaker also receive funding

Updating baseMaker

🏗️ Admin function - how this is updated is a future decision.

Circuit Breaker

Perp v3 employs the ERC-7265 circuit breaker in order to rate limit withdrawals. The withdrawal rate is limited to a set percentage of TVL withdrawn per time period. The circuit breaker is controlled by a multisig that is controlled by the team and/or protocol governance.

Rate Limit Procedure

The rate limit will revert any operation that exceeds the set limit. Operations within the limit can continue to execute, e.g. if rate limit is 1000 and current rate is 900, a withdrawal of 200 will be limited but a subsequent withdrawal of 50 will be permitted.

  1. When rate limit is triggered:

    1. Vault withdrawal transactions will revert

    2. Some withdrawals from maker (LP) positions will revert

    3. Spot hedge maker fillOrder() may also revert because it withdraws from vault

  2. Multisig owners check if there’s a hack or just a whale withdrawing or trading against spot hedge maker.

  3. Next steps

    1. It's a hack: Multisig is used to call markAsNotOperational(). Withdrawals are permanently locked and funds can only be removed by calling migrateFundsAfterExploit().

    2. It's not a hack:

      1. Take no action - withdrawals will be limited until the _rateLimitCooldownPeriod has passed. After cooldown, anyone can call overrideExpiredRateLimit() to remove rate limiting once _rateLimitCooldownPeriod has passed.

      2. If it’s not a hack, the multisig owners can choose to call overrideRateLimit() to establish a grace period. Rate limit will be suspended until gracePeriodEndTimestamp.

Oracle System

In oracle price markets, Perp v3 relies on oracle prices to calculate account value and for liquidations. The Pyth Network is currently used as the source for oracle prices. To avoid oracle front-running issues, most actions require two steps separated by a delay to execute, or actions are handled via a trusted off-chain service to relay the tx:

  • Send transactions in two steps, first to update the oracle price and second to execute the transaction, with a min. timestamp delay of 3 seconds (applies to add/remove margin, open/close positions, and liquidations)

  • Bundle the two transactions using the multicaller contract

(Trades performed using the UI use multicaller.)

Limit orders

Limit orders are held in an offchain database (orderbook) and will be matched against orders (market or limit orders) or filled using liquidity framework makers when the trigger conditions are met.

Key considerations

  • Limit order creation via contract or API is not currently supported but may be in the future.

  • Limit orders are treated like market orders once they are triggered; all fees are paid the same as market orders.

  • Order expiry is currently fixed and will be updatable in a future release.

  • A min. order size (openNotional, currently 10 USDT) applies to limit orders.

  • Market orders can be matched directly against open limit orders, if the price is preferential.

  • Order will be cancelled if user's futures account does not hold enough funds to back the order.

  • Limit orders are filled FCFS (lower timestamps fill first; if timestamp is the same, order may be determined according to the database specs.)

Fees

Limit orders are subject to a relayer fee:

  • Fee is paid one time, no matter how many times order is filled

  • Fee is paid from the user's futures account

  • Fee is paid at the time of the first fill for each order

Multicaller Library

Perp v3 uses the Multicaller library to "efficiently call multiple contracts in a single transaction." See the library here: https://github.com/vectorized/multicaller

Last updated