Liquidity Provision

There are 2 possible liquidity provider (LP) actions on Increment:

  1. Provide liquidity

  2. Remove liquidity

  3. Claim rewards

LPs earn trading fees and potentially additional rewards while taking the opposing side of the aggregated price movements of the traders in one market.

A liquidity provider may provide liquidity multiple times (e.g. first 1000, then again 1000 for a new total of 2000) and remove liquidity multiple times (e.g. remove 500 then 300 for a new balance of 1200).

When the liquidity provider provides liquidity, the system will use half of the total funds to mint vBase and the other half to mint vQuote and then deposit both into the pool.

There is a short lock period when liquidity provided, currently 1 hour, but can be changed through governance. This lock period is meant to prevent flashloans and liquidity manipulation.

Providing liquidity

Anyone can provide liquidity. The sequence of actions for depositing liquidity is as follows:

  1. Deposit one of the supported allowlisted collateral into the Vault with the deposit function

  2. Call the provideLiquidity function with 1) the market in which you'd like to provide liquidity to, 2) the amounts of virtual tokens to provide and 3) a minimum amount of LP tokens the you wish to receive (use 0 if you do not know what constitutes a reasonable value).

Liquidity providers can provide liquidity in a ratio different than that of the pool they provide liquidity to, with a limit of 10% deviation compared to the current index price of the pool.

Here's a more fine-grained version of what's happening when a user provides liquidity:

Removing liquidity

The sequence of actions for removing liquidity is as follows:

  1. Get the number of LP tokens the user owns using the removeLiquidity function, i.e. liquidityBalance.

  2. Remove the liquidity by calling the removeLiquidity function with 1) the index of the market in which you'd like to remove liquidity from, 2) the amount of LP tokens you'd like to remove from the pool (i.e. something lower or equal to liquidityBalance), 3) the minimum amount of vQuote and vBase tokens to withdraw from the pool (use [0, 0] if you don't know what constitute a reasonable value given the current market conditions), 4) a proposed vQuote or vBase amount at which to sell the active position created after removing liquidity (see to find out how to determine the right proposedAmount) and 5) the minimum amount of vQuote (if the active position looks like a SHORT) or vBase (if the active position looks like a LONG) you'd accept when closing the position.

It's worth noting that reducing or closing a liquidity position involves 2 distinct steps.

The first step is to swap some LP tokens in exchange for some vQuote and vBase tokens in the Curve pool of the market. The 2nd and 3rd arguments of the removeLiquidity function are used in this step.

In a high likelihood where the ratio in the market pool has shifted between the moment the LP provided liquidity and the moment the user withdraws, the LP will end up with an active position which is the opposite of the direction in which the market moved. For example, if vBase became stronger during the time a LP provided liquidity, then their active position will be negative on vBase. From the protocol's standpoint, active positions are similar to trading positions.

The second step is about closing the active position. The 4th and 5th arguments of the removeLiquidity function are used at this step.

Here's a more fine-grained view of what is happening when LP removes liquidity:

Lifecycle example: provide liquidity, trades happnen in the pool, and remove liquidity

Before liquidity is provided, we assume the following protocol and pool states:

  • totalvETH in the pool = 1

  • totalvUSD in the pool = 2000

  • total USDC locked in the vault = 4000

  • total liquidity tokens = 4000

  • totalTradingFeesGrowth = 2%

  • totalBaseFeesGrowth = 1%

  • totalQuoteFeesGrowth = 1%

Market price = 2000 / 1 = 2000 USD per ETH or 2000 ETHUSD

Tom decides to provide liquidity to the ETHUSD trading pair with the provideLiquidity function. He wants to provide 100 USDC. So he first deposits this USDC amount in the Vault, then he calls the provideLiquidity to deposit the equivalent of 100 USDC in the ETHUSD pool.

Assuming a market price of ETH at 2000 USD and assuming Tom wants to deposit at the same ratio as the current market price, he passes [50, 0.025] as amounts to mint 100 vUSD and 0.025 vETH. These vETH and vUSD tokens are added to the ETHUSD pool. Upon receiving these tokens, the Curve pool returns some newly minted LP tokens that Perpetual immediately assigns to Tom.

At the end of this operation, Tom has a LP position that looks like the following:

  • liquidityBalance (i.e. LP tokens owned by Tom) = 97.56. We assume Curve returned 97.56 LP tokens for the virtual tokens Tom provided.

  • positionSize = -0.025

  • notionalAmount = -50

  • totalTradingFeesGrowth = 2%

  • totalBaseFeesGrowth = 1%

  • totalQuoteFeesGrowth = 1%

After Tom deposits liquidity, the protocol pool states are the following:

  • totalvETH = 1 + 0.025 = 1.025 (0.01 are fees or 1%)

  • totalvUSD = 2000 + 50 = 2050 (20 are fees or 1%)

  • total USDC locked in the vault = 4100

  • total liquidity tokens = 4097.56

  • totalTradingFeesGrowth = 2%

  • totalBaseFeesGrowth = 1%

  • totalQuoteFeesGrowth = 1%

Let's imagine that a few trades happen so that the ratio between the vETH and vUSD tokens in the pool change and the amount of fee increases.

Alice opens a LONG position (with the changePosition function) Alice opens a long position and buys 0.047674 vETH with 100 USDC (100 vUSD) at 1x leverage and pays 2 in fees. Math: 1.025 - (1.025 * 2050)/(2050 + 100) = 0.047674 vETH

We use the constant product formula here (x \* y = k) for simplicity but the math of the CryptoSwap pool we rely on is more complex. The market price changes after every trade with the pool, because every trade generates a shift in the token ratio in the pool.

In Curve, Alice receives 0.04762633 vETH instead of the 0.047674 vETH she would receive otherwise, because of the trading fees incurred in the pool for swapping tokens. That's where the tricky part comes in because Increment cancels out Curve's fees in exchange for an harmonized fee in vQuote only. So even though, in this example, Alice receives 0.04762633 vETH from Curve, Increment gives 0.047674 vETH to Alice (i.e. ignores the 0.00004767 vETH trading fee that Curve charges) then it charges its own trading fee in vQuote (which is still somewhat close to the trading fee Curve charges).

In addition, Alice pays an insurance fee of 1% on the notional of the trade. That accounts for 1% \* 100 vUSD = 1 USDC

At this point, the protocol and pool states look like the following:

  • totalvETH = 1.025 - 0.04762633 = 0.97737 (0.00004767 are fees for Curve or 0.1% -> Increment cancels out Curve's fees in exchange for an harmonized fee in vQuote only)

  • totalvUSD = 2050 + 100 = 2150 (Out of the 100, 2 are fees or 2%)

  • total UA locked in the vault = 4200

  • total liquidity tokens = 4097.56

  • totalTradingFeesGrowth = 2% + 2/4097.56 = 2.04881%

  • totalBaseFeesGrowth = 1% + 0.00004767/0.97737 = 1.004877%

  • totalQuoteFeesGrowth = 1%

Market price = vUSD / vETH = 2150/0.97737 = 2199.78 USD per ETH or 2199.78 ETHUSD

Tom now decides to withdraw all his liquidity from the liquidity pool of the ETHUSD pair with the removeLiquidity function. Putting aside some checks and verifications, the way Increment processes this request proceeds as follows, with 2 main steps:

  1. The first step is to remove the liquidity from the pool. More explicitly, Increment burns the 97.56 LP tokens from Tom in exchange for:

  • 97.56 / 4097.56 * 2150 = 51.19 vUSD

  • 97.56 / 4097.56 * 0.97737 = 0.02327 vETH

The market state on the vAMM is now:

  • totalvETH = 0.97737 - 0.02327 = 0.9541

  • totalvUSD = 2150 - 51.19 = 2098.81

We need to find the amount of fees to burn in the Curve pool. In this case, the amount of vUSD fees (in the Curve pool) hasn't moved but the amount of vETH fees changed. Indeed, we know that Tom did not earn any additional fees on the vUSD he provided to the pool. But, inside of Curve, he earned some fees on the vETH provided to the pool. The fee (per unit of token) grew from 1% to 1.004877%. So, 0.004877%% of the tokens withdrawn by Tom are fees earned.

We define the amount of vETH returned, after the vETH Curve fees are disregarded, as the amount:

  • 0.02327 / (1 + (1.004877% - 1%)) = 0.023269 vETH

Liquidity providers earned an additional 0.04881% fees per unit of quote provided. So, he receives:

  • 51.19 + (2.04881% - 2%) * 97.56 = 51.2376 vUSD

Tom's position is now:

  • liquidityBalance (i.e. LP tokens owned by Tom) = 0

  • positionSize = -0.025 + 0.023269 = -0.001731

  • notionalAmount = -50 + 51.19 + 0.0476 = 1.2376

  1. The second step of withdrawing liquidity consists of simulating a sale of the LP accounting position in the market to determine if the movement of the market between the moment he provided liquidity and now has played in his favor or not. A LP is taking the opposing side of the aggregated movements of traders in a specific market.

In this case, Tom position looks like an active SHORT since positionSize < 0 and notionalAmount > 0.

Tom submits a number of vUSD to sell which is large enough to pay back his 0.001731 vETH debt in full. It can be shown that the LP will have to sell ~3.81 vUSD.

Math: 0.954 - (0.954 * 2098.81 / (2098.81 + x)) = 0.001731 x = 3.81

Tom LP position is now:

  • positionSize = -0.001731 + 0.001731 = 0

  • notionalAmount = 1.2376 - 3.81 = -2.5724

In this example where liquidity was limited and price impact was magnified, Tom made a loss of 2.57 USDC. LP can withdraw up to 97.43 USDC.

Claim rewards

Because liquidity providers are one of the most important actors on the protocol, it is possible for liquidity providers to earn additional rewards on top of the trading fees that they earn by calling claimRewardsFor function in the RewardDistributor contract. The amount of rewards depends on the amount of reward tokens in the EcosystemReserve on zkSync Era.

The system can support more than one reward token. The speed of reward accrual for stakers is dependent on the reward distribution parameters set by governance.

There is an early withdrawal threshold implemented to ensure that rewards are distributed to liquidity providers fairly. The initial threshold is proposed to be 10 days but can be changed in the future through governance.

If the LP withdraws from the pool during the early withdrawal threshold, they will be penalized and lose rewards proportional to how long they have held their deposit in the pool.

The early withdrawal threshold will restart whenever a deposit or withdraw action is made. This threshold will not have any effect on the fees earned by LPs.

The sequence of actions for claiming additional rewards is as follows:

  1. Check whether the early withdrawal threshold has passed. If not, revert the transaction. The early threshold represents the amount of time after which LPs can remove liquidity without penalties.

  2. Check whether the current LP position is registered for rewards. If so, for each reward token, update the reward accumulator for this market. Then compute new rewards since the last accrual to the user based on their liquidity position and add the rewards accrued by the user.

  3. Check whether the Ecosystem Reserve has enough reward tokens. If so, then transfer all the user's rewards. If not, transfer any remaining reward tokens to user.

  4. Subtract transferred amount from user's accrued rewards

Here is a flowchart on the sequence of actions described above:

Last updated