Generalized CPMM

Unlike other Automated Market Makers, our Constant-Product AMM can handle multiple tokens simultaneously, whether their amounts are specified or not. This extends to LP tokens as well, which means we don't differentiate between LP join/exit and swap actions.

This article delves into the advanced workings of constant-product automated market makers (AMMs) with a focus on understanding the calculations involved. The explanations are provided with the presumption that the reader is already familiar with the basic concept of an CPMM.

Mathematical framework

Imagine that we have a pool consisting of N tokens. We can represent the states of this pool as a point within a vector space that has (N+1) dimensions. Specifically, (D,x0,x1,x2,...)(D, x_0, x_1, x_2, ...), where each xix_i represents a token balance in the pool, and D represents the invariant. changes in invariant is credited as changes in LP token.

Any interaction with the pool, which we term as an "operation", will result in a change in the state of the pool, moving it from one point to another within this vector space. However, not every point within this space is legitimate. Only those points that satisfy the pool's equation can be considered as valid states. Therefore, any operation must result in a valid state.

Mathematically, this condition can be expressed as: xwD(Σw)=1 \frac{\prod x^w}{D^{(\Sigma w)}} = 1

This implies that a valid state can be calculated if a user specifies the desired change (delta) for certain dimensions (tokens), leaving at least one dimension unknown. The core of this article revolves around figuring out these valid states, filling in the unknown dimensions based on the known ones.

The state of the pool before an operation is represented as vector a\vec{a}, while the state after the operation is denoted as vector b\vec{b}.

a\vec{a} is fully known, and our goal is to calculate the values for b\vec{b}.

Calculating unknown dimensions in a zero-fee scenario

The computation becomes straightforward when there is no fee. Given the equation xwD(Σw)=1\frac{\prod x^w}{D^{(\Sigma w)}} = 1, we can simplify it to xw=1\prod x^w = 1 considering D as just another token with its weight being the negative sum of other tokens' weights.

The product of the ratio of balance changes raised to their respective weights (biai)wi\prod{(\frac{b_i}{a_i})}^{w_i} has to remain 1, as is the case for both a\vec{a} and b\vec{b}. aiwi=biwi=1\prod {a_i}^{w_i} = \prod {b_i}^{w_i} = 1. This allows us to think in terms of balance change ratios.

Within this structure, we can separate the unknown ratios from the known ones as:

unknown(biai)wi×known(biai)wi=1\prod_{unknown} {(\frac{b_i}{a_i})}^{w_i} \times \prod_{known} {(\frac{b_i}{a_i})}^{w_i}=1

Assuming all the unknown ratios to be equal (bunknownaunknown\frac{b_{unknown}}{a_{unknown}})

unknown(biai)wi=(bunknownaunknown)(unknownwi)\prod_{unknown} {(\frac{b_i}{a_i})}^{w_i}=(\frac{b_{unknown}}{a_{unknown}})^{(\sum_{unknown} w_i)}

we can derive them with ease:

(bunknownaunknown)=(known(biai)wi)(1unknownwi)(\frac{b_{unknown}}{a_{unknown}}) = {(\prod_{known} {(\frac{b_i}{a_i})^{w_i}})}^{(-\frac{1}{\sum_{unknown} w_i})}

Thus we can calculate b.

Calculating unknown dimensions with fees

To incorporate fees into the calculation of unknown dimensions, we have to adapt our approach. Unlike the case without fees, the computation becomes a bit more intricate.

When LP Token Balance Remains Unchanged (ΔD = 0)

In scenarios where the balance of the LP token stays constant, the fee applies to the increase in balance of tokens. The equation for this fee structure is as follows:

(bifee(biai)ai)wi=1\prod{(\frac{b_i - fee(b_i - a_i)}{a_i})}^{w_i}=1

where fee(x)=max(0,xfeeFactor)fee(x) = max(0, x * feeFactor).

in other words, the taker must deposit additional fee(ba)fee(\vec{b}-\vec{a}) in addition to fair trade delta.

This approach mirrors traditional constant-product market makers, where fees are deducted based on a fixed rate from input tokens.

solving the above equation the same way, yields the equation below, which is the way the calculation is performed.

bunknownfee(bunknownaunknown)aunknown=(known(bifee(biai)ai)wi)(1unknownwi)\frac{b_{unknown} - fee(b_{unknown} - a_{unknown})}{a_{unknown}} = {(\prod_{known} {(\frac{b_i - fee(b_i - a_i)}{a_i})^{w_i}})}^{(-\frac{1}{\sum_{unknown} w_i})}

When LP Token Balance Changes (ΔD ≠ 0)

In the case when the LP token balance changes, our objective is to apply the usual fee structure to token swaps, while not applying it to proportional deposits and withdrawals. To do this, we divide the requested vector change, denoted as r=ba\vec{r}=\vec{b}-\vec{a}, into two parts: one subjected to full fees (taxable) and another not subjected to fees (non-taxable).

The taxable component must maintain the LP token balance (ΔD = 0), and the non-taxable component must correspond to scalar multiplication, with a coefficient we call kk. So the request vector could be split as:

r=ba=(1kba)+k1kb\vec{r}=\vec{b}-\vec{a}=(\frac{1}{k}\vec{b} - \vec{a}) + \frac{k-1}{k}\vec{b}, where k=DbDak=\frac{D_b}{D_a}.

With this division of the request vector, we can then compute our desired result, by solving for (bifee(1kbiai)ai)wi=1\prod{(\frac{b_i - fee(\frac{1}{k}b_i - a_i)}{a_i})}^{w_i}=1

It's crucial to note that there are numerous ways to divide the request vector.

For example, we can also split it as:

r=ba=(bka)+(k1)a\vec{r}=\vec{b}-\vec{a}=(\vec{b} - k \vec{a}) + (k-1)\vec{a}.

When 'k' is more significant than one, representing a deposit scenario, the form (1kba)(\frac{1}{k}\vec{b} - \vec{a}) will yield a lower fee. Conversely, when 'k' is less than one, implying a withdrawal, the form (bka)(\vec{b} - k \vec{a}) will yield a lower fee. The pool will automatically select the version that results in the least fee.

in other words, we solve for (bifee(1kbiai)ai)wi=1\prod{(\frac{b_i - fee(\frac{1}{k}b_i - a_i)}{a_i})}^{w_i}=1 or (bifee(bikai)ai)wi=1\prod{(\frac{b_i - fee(b_i - ka_i)}{a_i})}^{w_i}=1, depending on kk

Last updated