Desmond
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 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$

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully