description |
---|
Automating Tried And Tested Procedures |
Pool unlocking/initialization
Overview
No action (minting / burning / swaps) can be performed prior to pool initialization.
In addition to setting the initial sqrt price, a small amount of token0 and token1 is required to be seeded for the initialization of reinvestL
to the value of MIN_LIQUIDITY
. This is required to prevent division by zero in the calcRMintQty()
function when swaps are performed. MIN_LIQUIDITY
was chosen to be reasonably small enough to avoid calculation inaccuracies for swaps, and from taking unreasonably large capital amounts from the caller.
As part of this anti-spam feature, Elastic allocates
In cases where the token has a low decimal value and the per unit value of the token high, the amount taken might be of significant value (i.e. a token is created with
Minting and burning (add/remove liquidity)
Overview
Adding and removing liquidity have very similar flows. One of the main differences is that mint()
is possibly a permissioned function, but burn()
is not. More information relating to the requirement for this can be found in this section on whitelisting position managers.
Implementation details
- A simple check is performed to ensure that the requested liquidity amount to mint / burn is non-zero
_tweakPosition()
is called, which does the following:- Load the pool state into memory
poolData
(current price, tick and liquidity values) - Call
_syncFeeGrowth()
to update fee growth data. Mints reinvestment tokens if necessary - Call
_syncSecondsPerLiquidity()
to update seconds per liquidity data - The updated global values and
poolData
is passed into_updatePosition()
- Updates (initializes) the lower and upper position ticks. Will insert or remove the tick from the linked list whenever necessary
- Calculates feeGrowthInside and returns the amount of reinvestment tokens claimable by the position
- Transfers the claimable reinvestment tokens to the position owner, if any
- Calculates the token0 and token1 quantity required to be collected from (add liquidity) or sent to (remove liquidity)
msg.sender
. Will apply liquidity changes to pool liquidity if the specified position is active
- Load the pool state into memory
- In the case of adding liquidity, a callback is made to collect the tokens
- Emit event
Overview
Like KyberSwap Classic
, there are 4 different types of swaps available that a user can specify.
- Swap from a specified amount of token 0 (exactInput0)
- Swap from a specified amount of token 1 (exactInput1)
- Swap to a specified amount of token 0 (exactOutput0)
- Swap to a specified amount of token 1 (exactOutput1)
Swapping token 0 for token 1 (cases 1 and 4) cause the pool price and tick to move downwards, while swapping token 1 for token 0 (cases 2 and 3) cause the pool price and tick to move upwards.
In addition, the user can specify a price limit that the swap can reach. The minimum and maximum price limits a user can specify is MIN_SQRT_RATIO + 1
and MAX_SQRT_RATIO - 1
.
The algorithm exits when either the specified amount has been fully used, or if the price limit has been reached.
Implementation details
The swap amount is a int256
to implicitly suggest whether it is exact input (> 0) or exact output (< 0).
- Fetch the initial pool state
-
$$L_{base}$$ := pool.baseL (liquidity provided by positions) -
$$L_{reinvest}$$ := pool.reinvestL (liquidity from fees collected) -
$$\sqrt{P_{current}}$$ := pool.sqrtP (current sqrt price of token1/token0) -
$$t_c$$ := pool.currentTick (tick associated with pool price) -
$$t_n$$ := pool.nextTick (next initialized tick from current tick)
-
- Verify specified price limit
$$\sqrt{P_{lim}}$$ - Cases 1 & 4:
MIN_SQRT_RATIO
<$$\sqrt{P_{lim}}$$ <$$\sqrt{P_{current}}$$ - Cases 2 & 3:
$$\sqrt{P_{current}}$$ <$$\sqrt{P_{lim}}$$ <MAX_SQRT_RATIO
- Cases 1 & 4:
- While specified amount
$$delta_{remaining}$$ not used up or price limit not reached,- Calculate temp next tick
$$t_{tmp}$$ and next sqrt price$$\sqrt{P_{next}}$$ . The temporary next tick is to ensure that the next tick does not exceed the MAX_TICK_DISTANCE cap from the current tick, so as not to violate the 5% price difference requirement.-
$$\sqrt{P_{next}}$$ = TickMath.getSqrtRatioAtTick($$t_{tmp}$$ )
-
- Check if
$$\sqrt{P_{next}}$$ exceeds$$\sqrt{P_{lim}}$$ ,- If true then
$$\sqrt{P_{target}}$$ =$$\sqrt{P_{lim}}$$ - If false then
$$\sqrt{P_{target}}$$ =$$\sqrt{P_{next}}$$
- If true then
- Call
SwapMath.computeSwapStep()
to calculate the actual swap input and output amounts to be used, swap fee amount and next pool price- Subtract amount to be used (
usedAmount
) toswapData.specifiedAmount
- Add amount to be sent to user (
returnedAmount
) toswapData.returnedAmount
- Add collected swap fee
$$\Delta{L}$$ to$$L_{reinvest}$$
- Subtract amount to be used (
- Check if swap will reach next tick
- If true, set
swapData.currentTick = willUpTick ? tempNextTick : tempNextTick - 1
and continue - If false, recalculate the current tick based on current price and break the loop
- If true, set
- If
$$t_{tmp}$$ ==$$t_n$$ , we are crossing tick$$t_n$$ :- Load variables (if not loaded already) that are initialized when crossing ticks
- Calculate amount of reinvestment tokens to be minted for fees to be sent to government and to for LP contributions, and update feeGrowthGlobal
- Cross tick
$$t_n$$ : updates the tick outside values and apply tick.liquidityNet to pool liquidity whilst fetching the next tick$$t_n$$
- Calculate temp next tick
- Perform actual minting of reinvestment tokens if necessary
- Update pool state (price, ticks, liquidity, feeGrowth, reinvestment variables)
- Send token to caller, execute swap callback to collect token
- Negative quantity = transfer to caller
- Positive quantity = collect from caller
computeSwapStep()
Flow
Inputs
Field | Type | Explanation |
---|---|---|
liquidity |
uint256 |
active base liquidity + reinvestment liquidity |
currentSqrtP |
uint160 |
current sqrt price |
targetSqrtP |
uint160 |
sqrt price limit nextSqrtP can take |
feeInBps |
uint256 |
swap fee in basis points |
specifiedAmount |
int256 |
amount remaining to be used for the swap |
isExactInput |
bool |
true if specifiedAmount refers to input amount, false if specifiedAmount refers to output amount |
isToken0 |
bool |
true if specifiedAmount is in token0, false if specifiedAmount is in token1 |
Outputs
Field | Type | Explanation |
---|---|---|
usedAmount |
int256 |
actual amount to be used for the swap. >= 0 if isExactInput = true, <= 0 if isExactInput = false |
returnedAmount |
int256 |
output qty (<= 0) to be accumulated if isExactInput = true, input qty (>= 0) if isExactInput = false |
deltaL |
uint256 |
collected swap fee, to be incremented to reinvest liquidity |
nextSqrtP |
uint160 |
new sqrt price after the computed swap step |
- Calculate the amount required to reach
targetSqrtP
fromcurrentSqrtP
by callingcalcReachAmount()
. - If amount required exceeds
specifiedAmount
, then the targetPrice will not be reached, and we expect the resulting pricenextSqrtP
to not exceedtargetSqrtP
.usedAmount := specifiedAmount
- Estimate
$$\Delta{L}$$ , the swap fee to be collected by callingestimateIncrementalLiquidity()
- Calculate the final price
nextSqrtP
by callingcalcFinalPrice()
- Otherwise, the temporary next tick will be crossed.
-
usedAmount
will be the amount calculated in step 1 - calculate
$$\Delta{L}$$ by callingcalcIncrementalLiquidity()
- set the resulting price
nextSqrtP
=targetSqrtP
-
- Finally, calculate
returnedAmount
by callingcalcReturnedAmount()
.
Swapping formula
Assume that:
- x1, x2: the amount of token0 before/after swap
- y1, y2: the amount of token1 before/after swap
- L1, L2: the liquidity before/after swap
- p1, p2: the price before/after swap
Swap exact input from token0 -> token1
- Given L1, p1, fee and
$$\Delta y$$ , calculate$$\Delta L$$ and p2
Finally calculate new
- Given L1, p1 and p2 calculate the
$$\Delta L$$ and$$\Delta y$$
From (2) we have:$$\sqrt p_2 * (L1 + \Delta x * \sqrt p_1) = \sqrt p_1 * (L1 + \Delta L)$$ combine with (1)
=>
=> (3)
Swap exact input from token1 -> token0
- Given L1, p1, fee and
$$\Delta y$$ , calculate$$\Delta L$$ and p2
Finally calculate new
- Given L1, p1 and p2 calculate the
$$\Delta L$$ and$$\Delta y$$
From (1) and (2)
=>
=> (3)
Swap exact output from token0 -> token1 (isExactInput = false, isToken0 = false)
- Given L1, p1 and p2, calculate the
$$\Delta y$$
=>
=>
=>
=>
- Given L1, p1 and
$$\Delta y$$ , calculate$$\Delta L$$
=>
=>
=>
This can be transformed into
Swap exact output token1 -> token0 (isExactInput = false, isToken0 = true)
- Given L1, p1 and p2, calculate the
$$\Delta x$$
=>
=>
=>
=>
=>
- Given L1, p1 and
$$\Delta x$$ , calculate$$\Delta L$$
=>
=>
=>