owned this note
owned this note
Published
Linked with GitHub
# Kyber Elastic v2 smart contract walkthrough
## General:
### Concentrated liquidity
In early version, Kyber Elastic allows people to add liquidity in a speicific price range. However, this approach has a draw back: liquidity provider has to add liquidity to pre-defined range in a pool or create a pool with high gas cost.
Uniswap v3 later show how to concat these pools into 1 by defining the concept of **ticks**. A tick is the price that is equals to the power of 1.0001.
> $\sqrt{p(i)} = 1.0001^i$
Liquidity providers can specified which price range they want to add liquidity to. However, to avoid too many range being crossed (resulting in high gas cost), the prices is quantized into ticks and liquidity providers has to specify the tick range instead of the price range.
Whenever you add/remove liquidity to a tick range (including a lowerTick and upperTick), the pool has to record how much virtual liquidity is kicked in/out when crossing these ticks and how many tokens are used to active the liquidity within the range.
| ![Concentrated liquidity](https://i.imgur.com/I3Y57hd.png)|
|:--:|
| Concentrated liquidity |
### Compounding fee
In Uniswap v3, the fees are taken in the form of token0 & token1 separately, which do not contribute to the pool's liquidity. This limit makes Uniswap v3 not directly compoundable inside the protocol. The v2 of Kyber Elastic inherits the liquidity concentration of Kyber Elastic v1 and achieves customizability of Uniswap v3 while preserving the compound feature. Eventually, the protocol pours the collected fee into a reinvestment curve, which is aggregated with the pool.
The reinvestment curve is a compoundable constant product curve that supports the price range from 0 to infinity. The reinvestment curve is aggregated with the pool so that they maintain a common price while behaving differently. Price ranges of the pool have different amounts of liquidity, which are not affected by exchange activities. Meanwhile, the reinvestment curve's liquidity remains the same on all ticks and increases based on the accumulating fee after each swap.
### Reinvestment curve
Assume that:
- x, y: reserve of token in/out
- $\Delta x, \Delta y$: amount in/out
- L: liquidity
Base formula:
**(1.1)** $x*y=L^2$
Currently, the fee of a trade will be calculated as a proportion of amountIn = ($\Delta x * fee$).
This amount will be reinvest into the pool as an addition of liquidity:
**(1.2)** $(x + \Delta x * fee) * y = (L + \Delta L)^2$
This can be approximate to
**(1.3)** $\Delta L =\Large \frac{L * fee * \Delta x}{2 * x}$
with the assumption that
**(1.4)** $\Delta x * fee << x$
the equation after swap:
**(1.5)** $(x + \Delta x) * (y - \Delta y) = (L + \Delta L)^2$
By 1.3, we calculate the incremental liquidity based on the amount in and fee; and then by 1.5 we calculate the amont out based on the amount in and incremental liquidity.
After the swap, the constant product curve in 1.1 formula is unchanged except that a small proportion of liquidity is added into the curve, we called this as reinvetment liquidity. The pool contract will needs to keep track of 2 variables: `baseL` is the liquidity from lp provider and `reinvestL` is the liquidity from the fee. The liquidity L is the sum of these 2 variables. After each swap, the fee will be added into `reinvestL`.
### Approximation condition
Assume that if fee = 1% and the swap within a range of 5% change to the price, we have:
${\Large \frac{\Delta x * fee}{x} } < 0.0005$
Or we can always use this approximation formula for a swap within 5% change of price (equals to **487** ticks).
With a swap that is larger than 5% change of price, we can divide it into multiple steps, each step only change up to 5% price.
### Reinvestment token
- User can still earn fee from `reinvestL`, so we create **reinvestment token** to represent the proportion of user in `reinvestL`.
- Each time, users add/remove liquidity or cross a tick, we have to mint the reinvestment token equivalent to the increment of `reinvestL` by `baseL`. The formula to calculate mintQty is at `ReinvestmentMath.calcrMintQty`
- reinvestment token: after London hardfork, it is [costly](https://hackmd.io/@fvictorio/gas-costs-after-berlin) to warm up another contract. Therfore, we decided to merge the reinvestment token and pool contract and the pool contract will be inherited from ERC20.
## Ticks concentrated liquidity - LinkedList
As mentioned above, we need to store information about each tick to track the amount of net liquidity that should be added and removed when the tick is crossed. As a result, the tick's variables must contain the net liquidity of liquidity providers and the fee outside of the tick.
All information are stored in the **TickData** struct
struct TickData
uint128 liquidityGross;
int128 liquidityNet;
uint256 feeGrowthOutside;
uint128 secondsPerLiquidityOutside;
Indeed, when swapping, the total amount of liquidity that is added or removed when the tick is crossed goes left and right. So we need to track the **liquidityNet**, which is the total amount of liquidity that is added and removed when the tick is crossed. This value is one signed integer: the amount of liquidity added when price moves rightward, and if we're moving leftward, we interpret **liquidityNet** as the opposite sign.
Between several ticks, we will not have any liquidity. As a result, to optimize, we only initialize a tick if and only if it has liquidity referencing the tick. The **liquidityGross** ensures that if net liquidity at a tick is 0, we can still know if a tick is referenced by at least one underlying position, which tells us whenever to initialize the tick.
We use **feeGrowthOutside** to track how many fees were accumulated within a given range. **feeGrowthOutside** needs to be updated each time the tick is crossed.
There could be several ticks that are not initialized and we only access/process data of initalized ticks, we need a data structure to store all initialized ticks with ability of moving from one initialized tick to its 2 adjacent initialized ticks optimally. To do so, we use a doubly linked list to store all initialized ticks.
The doubly linked list of initialized ticks is stored in the **initializedTicks** from the **PoolStorage**, each Linkedlist data consists of 2 variables:
struct Data
int24 previous; // the previous data in the doubly linked list
int24 next; // the next data in the doubly linked list
| Field | Type | Explanation |
| ----- | ---- | ------------ |
| `previous` | `int24` | the previous data in the doubly linked list |
| `next` | `int24` | the next data in the doubly linked list |
Initiallly, its **HEAD** is **MIN_TICK** and its **TAIL** is **MAX_TICK**, they will never be removed from the **initializedTicks**.
##### Add new tick to the linked list
When adding a new tick **X**, to save gas consumption, user needs to specify the tick **Y** that is initialized, nearest and lower than the added tick **X**. However, due to on-chain delay/human error, there are few cases could happen:
- **X** has been initialized, ignore this case.
- **Y** has been removed, the transaction will be reverted.
- There are several ticks have been initialized/added between **X** and **Y**, to avoid reverting, we allow to jump at most **MAX_TICK_TRAVEL** from **Y** to the right to find the nearest initialized tick that is lower than **X** in the current linked list, i.e: **Y < X < Y.next**.
Logic
if Y is removed -> revert
While (Y.next <= X && iteration <= MAX_TICK_TRAVEL):
Y = Y.next
iteration++;
if Y >= X or Y.next <= X -> revert - invalid lower value
// Add new tick X between Y and Y.next
X.next = Y.next
X.previous = Y
Y.next.previous = X
Y.next = X
##### Remove a tick to the linked list
When removing a tick **X**, there are 3 cases:
- **X** is **MIN_TICK** or **MAX_TICK**, ignore this case.
- **X** has been removed, the transaction will be reverted.
- Otherwise, remove **X** by linking its 2 adjacent ticks to each other, and return the previous tick.
Logic:
if X == X.previous -> return X; // X is the lowest tick
if X == X.next -> return X.previous; // X is the highest tick
// Remove tick X by linking its 2 adjacent ticks
X.previous.next = X.next;
X.next.previous = X.previous;
delete X
## Range Mechanism
### Objective
The goal is to accurately account a position's accured fees and duration for which it is active. A position is defined to be active if the current pool tick lies within the lower and upper ticks of the position.
### Global value
The `feeGrowthGlobal` and `secondsPerLiquidityGlobal` variables represent the total amount for 1 unit of unbounded liquidity.
![](https://i.imgur.com/DoPB569.png)
### Outside value
The outside value (`feeGrowthOutside` and `secondsPerLiquidityOutside`) for a tick represents the accumulated value relative to the currentTick. By definition, we can visually represent it to be as such:
- tick <= currentTick ![](https://i.imgur.com/IW8y1Gd.png)
- tick > currentTick ![](https://i.imgur.com/X0uXxvB.png)
#### Initialization
When a tick is initialized, all growth is assumed to happen below it. Hence, the outside value is initialized to the following values under these cases:
- tick <= currentTick: `outside value := global value`
- tick > currentTick: `outside value := 0`
### Crossing ticks
Due to the definition of the outside value, a tick's outside value is reversed whenever the pool tick crosses it. Specifically, `outside value := global value - outside value`.
#### Note
When swapping downtick, the current tick is further decremented by 1. This is to ensure a tick's outside value is interpreted correctly.
`swapData.currentTick = willUpTick ? tempNextTick : tempNextTick - 1;`
### Calculating value inside ticks
The current tick can be
1. below a position (currentTick < lowerTick)
![](https://i.imgur.com/s3jWPgr.png)
The value inside can therefore be calculated as `tickLower's outside value - tickUpper's outside value`
2. within a position (lowerTick <= currentTick < upperTick)
![](https://i.imgur.com/pO9HPYb.png)
The value inside can therefore be calculated as `global value - (lower + upper tick's outside values)`
3. above a position (upperTick <= currentTick)
![](https://i.imgur.com/Yi8iwu0.png)
The value inside can therefore be calculated as `tickUpper's outside value - tickLower's outside value`
## 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.
## Minting (adding liquidity) And Burning (removing liquidity) Flow
### 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](#Whitelisting-PositionManager) 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
- In the case of adding liquidity, a callback is made to collect the tokens
- Emit event
## Tweak Position With Zero Liquidity Flow
### Overview
Sync fee growth for position with tickLower and tickUpper, return feeGrowthInsideLast.
### Implementation Details
- Simpe check valid tick lower and tick upper from params
- Check sender need to be whitelisted from factory
- Query position key with sender and tick input and check valid
- Call `_synFeeGrowth()` to update fee growth data and ReinvestLLast
- Call `_syncSecondsPerLiquidity()` to update seconds per liquidity data
- Call `_updatePosition()` with tick input and not adding liquidity
- Transfer fee to sender if `feeClaimable = true`
## Swap Flow
### Overview
Like Elastic v1, there are 4 different types of swaps available that a user can specify.
1. Swap *from* a specified amount of token 0 (exactInput0)
2. Swap *from* a specified amount of token 1 (exactInput1)
3. Swap *to* a specified amount of token 0 (exactOutput0)
4. 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).
1. 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)
2. 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`
3. 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}}$
- 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`) to `swapData.specifiedAmount`
- Add amount to be sent to user (`returnedAmount`) to `swapData.returnedAmount`
- Add collected swap fee $\Delta{L}$ to $L_{reinvest}$
- 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 $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$
4. Perform actual minting of reinvestment tokens if necessary
5. Update pool state (price, ticks, liquidity, feeGrowth, reinvestment variables)
6. 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 |
1. Calculate the amount required to reach `targetSqrtP` from `currentSqrtP` by calling `calcReachAmount()`.
2. If amount required exceeds `specifiedAmount`, then the targetPrice will not be reached, and we expect the resulting price `nextSqrtP` to not exceed `targetSqrtP`.
- `usedAmount := specifiedAmount`
- Estimate $\Delta{L}$, the swap fee to be collected by calling `estimateIncrementalLiquidity()`
- Calculate the final price `nextSqrtP` by calling `calcFinalPrice()`
3. Otherwise, the temporary next tick will be crossed.
- `usedAmount` will be the amount calculated in step 1
- calculate $\Delta{L}$ by calling `calcIncrementalLiquidity()`
- set the resulting price `nextSqrtP` = `targetSqrtP`
4. Finally, calculate `returnedAmount` by calling `calcReturnedAmount()`.
## AntiSnipping Attack
### Motivation
New AMM, which compounds different positions such as Uniswap v3, provides tools for liquidity providers to add and remove liquidity easily in specific ranges. The feature leads to a novel attack, called snipping, where the attacker tries to jump in front of normal liquidity providers by adding and removing liquidity just before and right after a huge swap.
We have collected some [data](https://docs.google.com/document/d/1F50RWQRRyaNxnW5RvKgw09fN2FofIVLVccijgcOt-Iw/edit#heading=h.f4dlsc528bn4) to show the impact of the attack.
The sandwich attack can be effectively restrained on the taker's side thanks to parameters limiting how much slippage can be tolerated. However, there is no similar anti-snipping attack mechanism for liquidity providers. Hence, the protocol should introduce a feature that protects liquidity providers from this type of attack.
**DMMv2's Anti-Snipping Feature** is introduced as a lock of reward, which is vested based on the duration of liquidity contribution. The principal difference between attacks and normal activities of liquidity providers is their contributing duration. When liquidity providers supply their funds to the protocol, they take the risk of impermanent loss. However, in the case of an attacker who withdraws their fund immediately, the impermanent loss can be pre-calculated so that their profit is guaranteed.
### Mechanism
#### Struct: Data
| Field | Type | Explanation |
| ----- | ---- | ------------ |
| `lastActionTime` | `uint32` | timestamp of last action performed |
| `lockTime` | `uint32` | average start time of lock schedule |
| `unlockTime` | `uint32` | average unlock time of locked fees |
| `feesLocked` | `uint32` | locked rToken qty since last update |
#### Adding liquidity
When adding liquidity to a position, it updates [Data](#Struct-Data1) values for that position and calculates the amount of claimable reinvestment tokens to be sent to the user.
Claimable_fees = claimable_amount_of_collected_fees + claimable_amount_of_locked_fees
The values are calculates based on the **unlockTime**, **lockedTime**, **lastActionTime**, **currentTime**, **feesLocked** and **feeSinceLastAction**, the formula can be found [here](https://hackmd.io/sgADNlGNS8eSGU_8mZYqDQ?view#update)
#### Removing liquidity
When removing liquidity from a position, it updates [Data](#Struct-Data1) values for that position and calculates the amount of claimable reinvestment tokens, as well as the amount of reinvestment tokens to be burnt.
After calculating the claimable amount of fees, the burnt fees is calculated based on the amount of liquidity that user is withdrawing.
Burnt_fees = Claimable_fees * liquidity_delta / current_liquidity.
Claimable_fees = Claimable_fees - Burnt_fees
### Whitelisting PositionManager
As we implement the [Anti-Snipping Attack](#AntiSnipping-Attack) mechanism in the **PositionMananger** contract, we need to prevent users/contracts from interacting directly with **Pool** contracts. To do so, we allow only whitelisted **PositionManager** contracts to call the **mint** function.
We don't have the whitelisted check in the **burn** function because:
- Only the **PositionManager** holds positions in the **Pool** contracts, thus, users must interact with **PositionManager** to burn their positions.
- In case we upgrade to a new **PositionManager** contract, all previous **PositionManager** contracts should still be able to remove liquidity.
The **Factory** contract stores all whitelisted **PositionManager** contracts, and have a function called **isWhitelistedNFTManager(address)** for **Pool** contracts to check if an address is whitelisted.
To make it flexible, the **Factory** can disable the whitelisting feature, i.e: the **isWhitelistedNFTManager** function will always return **true**.
---
## Appendix about 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 x$, calculate $\Delta L$ and p2
$\Delta L = {L_1 * {\Large {\Delta x * fee \over 2 * x1}}}$
**(1)** $\Delta L = {\Large {\Delta x * fee * \sqrt{p_1} \over 2}}$
Finally calculate new $\sqrt{p_2}$
$\sqrt{p_2} = \Large {L_2 \over x_1 + \Delta x}$
**(2)** $\sqrt{p_2} = \Large {(L_1 + \Delta L) * \sqrt{p_1}\over L_1 + \Delta X * \sqrt{p_1}}$
- Given L1, p1 and p2 calculate the $\Delta L$ and $\Delta x$
From (2) we have
$\sqrt p_2 * (L1 + \Delta x * \sqrt p_1) = \sqrt p_1 * (L1 + \Delta L)$
combine with (1)
$2 * \sqrt p_2 * (L1 + \Delta x * \sqrt p_1) = \sqrt p_1 * ( 2 * L1 + \Delta x * fee * \sqrt p_1)$
=> $\Delta x * \sqrt p1 * (2 * \sqrt p_2 - fee * \sqrt p_1) = 2 * L1 * (\sqrt p_1 - \sqrt p_2)$
=> **(3)** $\Delta x = \Large {\frac{2 * L1 * (\sqrt p_1 - \sqrt p_2)}{\sqrt p_1 * (2 * \sqrt p_2 - fee * \sqrt p_1)} }$
### Swap exact input from token1 -> token0
- Given L1, p1, fee and $\Delta y$, calculate $\Delta L$ and p2
$\Delta L = {L_1 * {\Large {\Delta y * fee \over 2 * y1}}}$
**(1)** $\Delta L = {\Large {\Delta y * fee \over 2 * \sqrt p_1}}$
Finally calculate new $\sqrt{p_2}$
$\sqrt{p_2} = \Large {y_1 + \Delta y \over L_2}$
**(2)** $\sqrt{p_2} = \Large {L1 * \sqrt p1 + \Delta y \over L1 + \Delta L}$
- Given L1, p1 and p2 calculate the $\Delta L$ and $\Delta y$
From (1) and (2)
$\sqrt p_2 * (L1 + {\Large {\Delta y * fee \over 2 * \sqrt p_1}}) = L1 * \sqrt p1 + \Delta y$
=> $\Delta y * (2 * \sqrt p_1 - fee * \sqrt p_2) = 2 * \sqrt p_1 * L1 * (\sqrt p_2 - \sqrt p_1)$
=> **(3)** $\Delta y = \Large {2 * \sqrt p_1 * L1 * (\sqrt p_2 - \sqrt p_1) \over (2 * \sqrt p_1 - fee * \sqrt p_2)}$
### Swap exact output from token0 -> token1 (isExactInput = false, isToken0 = false)
- Given L1, p1 and p2, calculate the $\Delta y$
$y1 - \Delta y = L2 * \sqrt p_2$
=> $\Delta y = \Delta L * \sqrt p_2 + L1 * (\sqrt p_2 - \sqrt p_1)$
=> $\Delta y = {\Large \frac{\Delta x * fee * \sqrt p_1}{2}} * \sqrt p_2 + L1 * (\sqrt p_2 - \sqrt p_1)$
=> $\Delta y = {\Large \frac{fee * \sqrt p_1 * L1 * (\sqrt p_1 - \sqrt p_2)}{\sqrt p_1 * (2 * \sqrt p_2 - fee * \sqrt p_1)}} * \sqrt p_2 + L1 * (\sqrt p_2 - \sqrt p_1)$
=> $\Delta y = {\Large \frac{L1 (\sqrt p_1 - \sqrt p_2) (2 * \sqrt p_2 - fee * \sqrt p_1 - fee * \sqrt p_2)}{2 * \sqrt p_2 - fee * \sqrt p_1}}$
- Given L1, p1 and $\Delta y$, calculate $\Delta L$
$(L1 + \Delta L)^2 = (y1 - \Delta y) * (x1 + \Delta x)$
=> $(L1 + \Delta L)^2 = (y1 - \Delta y) * (x1 + {\Large \frac{2 * \Delta L}{\sqrt p_1 * fee}})$
=> $(L1 + \Delta L)^2 * fee = L1^2 * fee - fee * \Delta y * L1 / \sqrt p_1 + {\Large \frac{2 * \Delta L}{\sqrt p_1}} * (L1 / \sqrt p_1 - \Delta y)$
=> $fee * \Delta L^2 - 2 * (L1 - fee * L1 - \Delta y / \sqrt p_1) * \Delta L + L1 * fee * \Delta y / \sqrt p_1 = 0$
This can be transformed into
$a * \Delta L ^2 - 2 * b * \Delta L + c = 0$
$\Delta L$ will be the smaller solution of this equation
### Swap exact output token1 -> token0 (isExactInput = false, isToken0 = true)
- Given L1, p1 and p2, calculate the $\Delta x$
$x1 - \Delta x = L2 / \sqrt p_2$
=> $\Delta x = {\Large \frac{L1}{\sqrt p_1} - \frac{\Delta L}{\sqrt p_2} - \frac{L1}{\sqrt p_2}}$
=> $\Delta x = {\Large \frac{L1}{\sqrt p_1} - \frac{L1}{\sqrt p_2} - \frac{\Delta y * fee}{ 2 * \sqrt p_1 * \sqrt p_2}}$
=> $\Delta x = {\Large \frac{L1}{\sqrt p_1} - \frac{L1}{\sqrt p_2} - {2 * \sqrt p_1 * L1 * (\sqrt p_2 - \sqrt p_1) \over (2 * \sqrt p_1 - fee * \sqrt p_2)} * \frac{fee}{2 * \sqrt p_1 * \sqrt p_2}}$
=> $\Delta x = {\Large \frac{L1 * (\sqrt p_2 - \sqrt p_1)}{\sqrt p_2 * \sqrt p_1} * (1 - \frac{fee * \sqrt p_1}{2 * \sqrt p_1 - fee * \sqrt p_2})}$
=> $\Delta x = {\Large \frac{L1 * (\sqrt p_2 - \sqrt p_1) * (2 * \sqrt p_1 - fee * \sqrt p_2 - fee * \sqrt p_1)}{\sqrt p_2 * \sqrt p_1 * (2 * \sqrt p_1 - fee * \sqrt p_2)}}$
- Given L1, p1 and $\Delta x$, calculate $\Delta L$
$(L1 + \Delta L)^2 = (x1 - \Delta x)(y1 + \Delta y)$
=> $(L1 + \Delta L)^2 = L1 ^ 2 - \Delta x * L1 * \sqrt p_1 + \Delta y * (L1 / \sqrt p_1 - \Delta x)$
=> $(L1 + \Delta L)^2 = L1 ^ 2 - \Delta x * L1 * \sqrt p_1 + \Delta L * 2 * \sqrt p_1 /fee * (L1 / \sqrt p_1 - \Delta x)$
=> $fee * \Delta L^2 - 2 * (L1 * (1 - fee) - \Delta x * \sqrt p_1) * \Delta L + \Delta x * L1 * \sqrt p_1 * fee=0$