DAMM
ALEX AMM v3, also known as the Discrete Automated Market Maker (DAMM), is coming soon!
Abstract
The ALEX AMM v3 contract is an enhanced version of the Automated Market Making (AMM), optimized for concentrated liquidity. It utilizes the invariant function $(Vx + x) * (Vy + y) = K$, which, with appropriately chosen virtual liquidity values $Vx$ and $Vy$, allows the AMM to focus liquidity within a specific price range $[P_{start}, P_{end}]$.
Math
The invariant function is:
Where $Vx$ and $Vy$ ensure the pair is traded in the price range $[P_{start}, P_{end}]$.
In order to simply the math, we segregate the price range $(0, \infty)$ by introducing an integer parameter $tick$ and bin size $bs$, where for each $tick$ we have a price range $P_{start} = (1 + 0.01 * bs)^{tick}$ and $P_{end} = (1 + 0.01 * bs)^{tick + 1}$.
We define $t = \sqrt{1 + 0.01 * bs}$ and $p = P_{start}$, then we have:
The price of the pair is $P_{start}$ when balance x is swapped out (i.e. $\Delta x = -x$), and is $P_{end}$ when balance y is swapped out (i.e. $\Delta y = -y$). Hence:
Hence:
So:
Swap x for y util price hit $P_{max}$
This is similar to Immediate or Cancel (IOC) order in orderbook, given $\Delta x$, swap as much as possible until the price hit $P_{max}$, where $P_{max}$ is within the price range $[P_{start}, P_{end}]$.
Swap y for x util price hit $P_{min}$
Likewise, given $\Delta y$, swap as much as possible until the price hit $P_{min}$, where $P_{min}$ is within the price range $[P_{start}, P_{end}]$.
Add liquidity
After adding $\Delta x$ and $\Delta y$ to the pool, the pool size (i.e. liquidity token balance) and token pair price will both be affected.
The price should be checked to prevent unexpected deviation from other markets.
And the pool size increase proportionally to the virtual balances.
This is based on the fact that swapping does not change $V_x$, $V_y$, and the pool size. So when token y is depleted, i.e. $y = 0$, the balance of token x represents the total value of the pool:
The same when token x is depleted, i.e. $x = 0$, the balance of token y represents the total value of the pool:
Reduce liquidity
If we change the existing pool balance proportionally, we get the new invariant function:
Hence changing the existing pool balance proportionally does not affect the price of the pair. So when adjusting the liquidity, for a given $\Delta x$ we have:
Appendix
Implement pow-fixed in Clarity
The pow-fixed function needed for AMM v3 is defined as:
Where x is a fixed point number with precision $10^8$, and n is an integer.
To simplify the calculation in Clarity, we assume only a limited set of bin sizes are supported (e.g. 5%, 10%, and 20%), and the price range falls between 0.00000001 and 100000000. And then we can pre-calculate the result for $(1 + bs) ^ {(2 ^ n)}$.
So that for a given tick, we have:
This mathematical optimization reduce the calculation complexity exponentially.
Calculate Virtual Balances in Clarity
Since there's no float point numbers in clarity, we use fixed decimal to calculate values, specifically using uint128 to represent a decimal with 8 fixed decimal places. So the calculations should be handled very cautiously in order to prevent precision loss (with error rate less than roughly 1e-8) and arithmetic overflow.
This is how it looks like after we translate the formula of the virtual balances into code using float point numbers:
Then we translate it into code using fixed decimal with 8 decimal places:
Now we consider the value ranges of the input:
balanceX, balanceY cap at 1000T=$10^{15}$:
[0n, 10n ** (8n + 15n)]Allowed price range:
[10n ** 4n, 10n ** (8n + 7n)]U128_MAX:
2n ** 128n - 1n=340282366920938463463374607431768211455n, approximately3.4e38
When the values are small, there might be precision loss, for example:
When price and balanceY are small, e.g. price=1e4, balanceX=0, balanceY=1e3,
x_pty = 1e4 * 1e8 * 1e3 / 1e16 = 0which should be1e-9in float point number, in this casenumerator = 2 * x_pty = 0which should be2e-9in float point number, resultingvx = 0which should be1e-9 / (1.01 ** 0.5 - 1) = 20.0498756211e-8whenbin = 1When price is small, e.g. price=1e4,
denominator_vy = 2 * 1e4 * (1.01 - 1.01 ** 0.5) = 100, which should be100.2487577582
And when values are huge, there might be overflow, for example when balanceX > 2e19 then x_pty > 2e19, x_pty ** 2n > U128_MAX
To remediate that, we can scale balanceX and balanceY, to make max(balanceX, balanceY) be around 1e16. This is based on the fact that virtual balances and balances scale proportionally, which is proved before. And do not divide by 1e8 so fast when getting denominator_vy.
So we get the first revised version:
There are 2 issues with the first revision:
The max value of
x_ptycan be roughly1e16 + 1e8 * 1e16 * 1e15 / 1e16, which is1e23, this will causex_pty ** 2to overflow when calculating the numeratordenominator_vy_e8 * bx_scaled * by_scaled / ONE_16should be handled cautiously to prevent precision loss
For the first issue we use the same scaling method:
Since both denominator_vy_e8 and x_pty_shrink_factor scale proportionally to price, and x_pty_shrink_factor is greater than 1 when price is greater than approximately 1e8, hence denominator_vy_e8 / x_pty_shrink_factor is greater than 1e12. And also x_pty_shrink_factor cap at approximately 1e7 when by_scaled and price are with the maximum value.
Base on those, we can rewrite 2n * denominator_vy_e8 * bx_scaled * by_scaled / ONE_16 / (x_pty_shrink_factor ** 2) into 2n * (denominator_vy_e8 / x_pty_shrink_factor) * (bx_scaled * by_scaled / ONE_16 / x_pty_shrink_factor) to prevent precision loss.
Now we get final revision:
Last updated
Was this helpful?