Architecture

Overview

This project features multiple market contracts centered around PredyPool. The market contracts define financial products and order types. Markets can leverage positions by utilizing PredyPool for token lending and borrowing. This architecture is highly scalable. For example, developers can create new futures exchanges with minimal code and gain leverage by connecting to PredyPool.

Functional Diagram

Actors

Operator/PoolCreator

Currently, the Operator and PoolCreator are the same and hold the admin role in the Predy protocol. Operator can register pairs and PoolCreator can update the parameters of registered pairs.

Lender

A Lender is a user who provides tokens to Predy. These provided tokens are borrowed by traders when they open positions. Traders pay interest to the Lender, and in return, the Lender provides leverage to the traders.

Trader

The Trader deposits margin and trades perps and Squarts. The Trader signs the order and passes the order and signature to the Filler. The Filler is responsible for executing the actual transaction.

Filler

The Filler is responsible for actually submitting and executing the Trader's orders on the blockchain. The Filler can determine the token exchange routes.

Liquidator

When a trader's position meets the conditions for liquidation, the Liquidator forcibly closes the position. Liquidators can earn profits from the differences in the liquidation process.

Reallocator

The Reallocator is responsible for reallocating the Uniswap V3 LP positions held by PredyPool to maintain Squart. Reallocation can be performed by anyone. The protocol does not incur any losses from the reallocation process.

PredyPool

RegisterPair

Register a pair of quoteToken and baseToken. In practice, register the corresponding Uniswap pool with the quoteToken. The token in the Uniswap pool that is not the quoteToken will become the baseToken.

For example, when specifying the address of USDC as the quoteToken and the ETH/USDC pool as the uniswapPool, the baseToken is automatically set to ETH, and a new pair is registered in Predy.

Let's look at the parameters in detail. We have already explained quoteToken and uniswapPool. quoteIrmParams and baseIrmParams define the interest rate curves for the deposited tokens. For specific usage, please refer to the Interest Rate Model. poolOwner specifies the address that can update the pool's settings. priceFeed specifies the contract that can obtain the oracle price for the pair. If a zero address is specified, the TWAP from the UniswapPool will be used as the oracle price. The fee indicates the percentage of the fee. it`s the same as the feeRatio. When whitelistEnabled is set to true, only specific addresses can trade in this pool. In assetRiskParams, risk parameters that determine the Min. Value and the range size of Uniswap LP position are specified.

struct AddPairParams {
    address quoteToken;
    address poolOwner;
    address uniswapPool;
    address priceFeed;
    bool whitelistEnabled;
    uint8 fee;
    Perp.AssetRiskParams assetRiskParams;
    InterestRateModel.IRMParams quoteIrmParams;
    InterestRateModel.IRMParams baseIrmParams;
}

updateAssetRiskParams

The minSlippage and maxSlippage parameters define the slippage tolerance of the execution price from the oracle price during liquidation. The slippage tolerance is determined within the range of minSlippage and maxSlippage, based on the value of vaultValue / minMargin.

struct AssetRiskParams {
    uint128 riskRatio;
    uint128 debtRiskRatio;
    int24 rangeSize;
    int24 rebalanceThreshold;
    uint64 minSlippage;
    uint64 maxSlippage;
}

updateFeeRatio

The feeRatio indicates the percentage of the fee. For example, if the feeRatio is 10%, 10% of the borrowing interest is collected as a fee. Half of this, 5%, goes to the protocol, and the other half, 5%, goes to the pool creator.

Supply

It is a function to provide either the quoteToken or the baseToken to the lending pool. In return, the lender receives bond tokens.

Withdraw

It is a function to withdraw either the quoteToken or the baseToken from the lending pool. In return, the lender burns the bond tokens.

Trade

It is a function to trade perp and Squart using quoteToken as collateral. It is intended to be called by the market contract.

function trade(TradeParams memory tradeParams, bytes memory settlementData)
    external
    returns (TradeResult memory tradeResult);

In tradeParams, specify pairId, vaultId, and the amounts of perp and Squart. If vaultId is 0, a new vault is created. settlementData is passed directly to predySettlementCallback, and extraData will be used inpredyTradeAfterCallback.

struct TradeParams {
    uint256 pairId;
    uint256 vaultId;
    int256 tradeAmount;
    int256 tradeAmountSqrt;
    bytes extraData;
}

Liquidation

If the Vault Value becomes smaller than the Min. Margin, the vault can be forcefully closed. The liquidation price is determined through a Dutch auction, based on the ratio of Vault Value to Min Margin.

Calculating Slippage Tolerance

The value of slippageTolerance is determined by the ratio of vaultValue to minMargin. The smaller the vaultValue/minMargin ratio, the larger the slippageTolerance. As slippageTolerance increases, the difference between the market price and the liquidation price also grows, thereby providing a greater incentive for the liquidator. https://github.com/predyprotocol/predyx/blob/main/src/libraries/logic/LiquidationLogic.sol#L159

Reallocation

The Reallocate function repositions the LP (Liquidity Provider) funds within the specified range as described in Squart. This function can be called by anyone.

This section explains how to allocate the LP range effectively. The relevant parameters for this process are rangeSize and rebalanceThreshold.

  • rangeSize specifies the width of the LP range.

  • rebalanceThreshold specifies the threshold at which reallocation occurs.

For example, if the current tick of the UniswapPool is 100, the rangeSize is 200, and the rebalanceThreshold is 100, the initial range is (-100, 300), and the reallocation threshold is set at (0, 200). If the tick exceeds 200, the LP position is reallocated around the current tick. If the tick becomes 210, the new range will be (10, 410).

For specific reallocation methods, please refer to Squart and the activity flow.

Take

The take function is explained here.

Market Contracts

The MarketContract defines the specifics of a market. For example, it implements an intent-based approach (signature verification with permit2, swaps on Uniswap, delta hedging, etc.). The MarketContract needs to have the following functions:

  • predySettlementCallback: defines the method for exchanging tokens.

  • predyTradeAfterCallback: defines the method for adding collateral.

interface IHooks {
    function predySettlementCallback(
        address quoteToken,
        address baseToken,
        bytes memory settlementData,
        int256 baseAmountDelta
    ) external;

    function predyTradeAfterCallback(
        IPredyPool.TradeParams memory tradeParams,
        IPredyPool.TradeResult memory tradeResult
    ) external;

Interact with the pool and manage funds

Within the callback, the MarketContract can call the take function of the PredyPool. The take function allows any amount of tokens to be sent from the PredyPool to any destination. After the callback finishes, the PredyPool verifies that there are no issues with the token amount difference.

Specifically, in the case of predySettlementCallback, the difference in quoteToken is reflected in the entryValue (i.e., the entry price). In the case of predyTradeAfterCallback, the difference in quoteToken is reflected in the collateral. The system then checks the safety of the vault and verifies that there are no issues with the trade.

Example Sequence of a Trade

PriceFeedFactory

PriceFeedFactory is the factory contract for the PriceFeed contract. The PriceFeed contract can be used as the priceFeed parameter when registering a pair. In quotePrice, specify the Chainlink address corresponding to the USD-denominated price of the quoteToken. In priceId, specify the Pyth address corresponding to the USD-denominated price of the baseToken. The decimalsDiff parameter adjusts the decimal places. As a return value, the address of the PriceFeed contract is returned.

function createPriceFeed(
  address quotePrice,
  bytes32 priceId,
  uint256 decimalsDiff
) external returns (address)

PriceFeed

getSqrtPrice

It returns the square root value of the baseToken price denominated in quoteToken. Similar to Uniswap's sqrtPriceX96, the value is scaled by 2^96.

Last updated