Busy being born.

Inventing Curve AMM math

In this post, we'll invent Curve AMM math as if for the first time.


Here's the problem Curve wants to solve: how can we enable token trades [1] at a constant exchange rate [2] with almost no slippage?

For example, a trader wants to convert arbitrary amounts of USDC into DAI at the price of 1 USDC for 1 DAI.

Consider a liquidity pool containing 1,000 USDC and 1,000 DAI, for which trades must satisfy the Constant Sum invariant (CS):

x+y=D

cs-invariant

G.1: graph of the Constant Sum invariant with (x+y)=D=2000

We'll use x to represent the total USDC tokens in the pool and y to represent the total DAI tokens in the pool.

All trades in the pool must satisfy CS which means the sum of all tokens in the pool after a trade (x+dx)+(ydy) must remain the same as the sum before the trade x+y.

x+y=(x+dx)+(ydy)

dy=dx

So, we always get 1 DAI out of the pool in exchange for adding 1 USDC to the pool, and vice-versa. Although this gives us zero slippage on trades, this also means the pool can run out of tokens. For instance, you can buy up all of the 1,000 USDC in the pool for 1,000 DAI.

Pools in UniswapV2 don't suffer from this problem of running out of tokens because trades in those pools must satisfy the Constant Product invariant (CP):

x*y=k

cp-invariant

G.2: graph of the Constant Product invariant with (x*y)=D=2000

Consider the same liquidity pool containing 1,000 USDC and 1,000 DAI, for which trades must now satisfy CP. This means the product of the supply of both tokens in the pool after the trade must remain the same as before the trade.

(x+dx)(ydy)=xy

xyxdy+ydxdydx=xy

dy=y·dxx+dx

Now we cannot drain all of the pool's 1,000 DAI in exchange for 1,000 USDC. Adding 1,000 USDC (dx=1000) to the pool yields only 500 DAI (dy=500) out. Adding another 1,000 USDC returns only ~166.67 DAI.

The pool will never run out of DAI because the price of DAI in terms of USDC keeps increasing.

Although CP creates infinite liquidity for trades, it creates great slippage in trades (i.e. large deviations from the constant price at which we'd like to trade tokens such as USDC and DAI).

The ideal invariant for our pool would give the best of both worlds:

  1. Constant exchange rate when the pool is fairly balanced (i.e. around 1:1 supply ratio of tokens in the pool)
  2. Slippage as the pool becomes imbalanced, creating infinite liquidity for trades.

The graph of this ideal invariant would, therefore, look something like this:

ideal-invariant

G.3: graph of the ideal invariant which is like CS in the center and CP at the axes

The graph is perfectly flat at its center where the pool is perfectly balanced at x=y=1000. It is largely flat around the center and only curves sharply near the axes where the pool is imbalanced, to create infinite liquidity.

In the remainder of the post, we'll come up with the invariant for this graph.

Let's look at the Constant Sum CS and Constant Product CP invariants again.

x+y=D

x.y=(D/2)2

Here, D stands for the total number of tokens in the pool when they have equal price. For example, D=2000 for a pool with 1,000 USDC and 1,000 DAI. The CP and CS curves intersect at this point where the pool is perfectly balanced.

cp-cs-invariant

G.4: graph of the CS and CP together with D=2000

We're looking for a curve that sits right between the CP and CS curves. As we've noted, ideally our curve overlaps the CS curve almost exactly from the center out until near the axes where it would begin deviating asymptotically like the CP curve.

Let's start with an invariant that will guarantee a curve between CS and CP, and then modify this invariant.

Invariant v1

(x+y)+x.y=D+(D/2)2

The v1 invariant simply adds the CS and CP invariants. We can verify algebraically that this invariant guarantees a curve between CS and CP.

Assume some (a,b)CP or the region above the curve. So, a.b(D/2)2 and a+b>D. Therefore, (a,b) doesn't satisfy the v1 invariant since

(a+b)+a.b>D+(D/2)2

Now assume some (p,q)CS or the region below the curve. So, p.q<(D/2)2 and p+qD. Therefore, (p,q) doesn't satisfy the v1 invariant since

(p+q)+p.q<D+(D/2)2

Therefore, only points in the region between the CS and CP curves can possibly satisfy the v1 invariant.

Note that the point where the pool is perfectly balanced lies on both CS and CP and does satisfy the v1 invariant.

v1-invariant

G.5: graph of the v1 invariant

The v1 invariant does not achieve what we set out to do - create a curve that is flat like CS in the middle and curves like CP near the axes. Currently, it closely tracks CP.

At a high level, we can expect this since the v1 invariant simply adds CS and CP and the multiplicative nature of CP is dominant over the additive nature of CS.

However, this is also the clue for how we should modify our invariant.

Invariant v2:

χ.(x+y)+x.y=χ.D+(D/2)2

In the v2 invariant, we multiply the CS part by some positive factor χ to control the dominance of the CS part over the CP part. In other words, χ controls the flatness of the curve.

v2-invariant-chi-100

G.6: graph of the v2 invariant with χ=100

v2-invariant-chi-500

G.7: graph of the v2 invariant with χ=500

As χ the curve approaches CS and as χ0 the curve approaches CP. We can re-arrange the v2 invariant equation to concretely see why:

Let c>0

(χ+c).(x+y)+x.y=(χ.D)+(D\2)2

χ.(x+y)+x.y=χ.D+(D/2)2+c(D(x+y))

We know c(D(x+y)) is some negative quantity since x+y>D for all (x,y) above CS. So, increasing the value of χ has led to the same v2 invariant except with a smaller R.H.S. This means the L.H.S. must also become smaller for the invariant to be satisfied.

Therefore, for any fixed value of x, the corresponding value of y becomes smaller when we increase χ, and so, the curve flattens.

At this point, the factor χ affects the flatness of our curve differently for different pool sizes i.e. different values of D.

v2-invariant-D-200

G.8: graph of the v2 invariant with χ=500 and D=200

v2-invariant-D-2000

G.9: graph of the v2 invariant with χ=500 and D=2000

Therefore, e.g. χ=10 by itself does not communicate anything meaningful about the flatness of the curve, since the overall effect of χ depends on the size of the pool in consideration.

Invariant v3:

χD.(x+y)+x.y=χ.D2+(D/2)2

In the updated v3 invariant, we multiply χ with the size of the pool D. Now, e.g. χ=10 communicates the same level of flatness of the invariant curve for a pool of any size. We can verify this algebraically by re-arranging the v3 equation in terms of y:

χD.(x+y)+x.y=χ.D2+(D/2)2

χDx+χDy+xy=χ.D2+(D/2)2

y=[χ.D2+(D/2)2χDx]/[χD+x]

Now we scale D by some factor c and calculate the corresponding y value for the proportionally scaled x value:

ynew=[χ.D2c2+(Dc/2)2χDxc2]/[χDc+xc]

ynew=c2[χ.D2+(D/2)2χDx]/c[χD+x]

ynew=c.[χ.D2+(D/2)2χDx]/[χD+x]=c.y

So, when we scale D by some factor c, every point on the curve scales exactly by the same factor. This means the shape of the v3 invariant curve does not change with the changing size of a pool.

v3-invariant-D-200

G.10: graph of the v3 invariant with χ=1 and D=200

v3-invariant-D-2000

G.11: graph of the v3 invariant with χ=1 and D=2000

The final update to our invariant makes the value of χ depend on how balanced a pool is. Since the value of χ is directly proportional to how flat our curve is, we want the χ to be greatest at the center i.e. the point of a perfectly balanced pool, and approach zero near the axes where the pool is most imbalanced.

Invariant v4:

χ=(A·xy)/(D/2)2

χD.(x+y)+x.y=χ.D2+(D/2)2

Here, A is any positive constant. When the pool is perfectly balanced, x2=y2=xy=(D/2)2 which means χ=A and so, our invariant's curve behaves like CS around the center -- for how far about the center does it tend to be flat depends on the value of A.

When the pool starts to become imbalanced xy we have that xy/(D/2)2<1. Therefore, as the imbalance increases i.e. xyx we have that xy/(D/2)20 and so χ0. The pool is most imbalanced near the axes, where our invariant's curve starts behaving like CP.

v4-invariant

G.12: graph of the v4 invariant with A=10 and D=2000

Finally, the Curve invariant can be generalized for a pool of n tokens instead of fixing n=2 as we have done in this post.

χ=A·i=1nxi(Dn)n

χ·Dn1·i=1nxi+i=1nxi=χ·Dn+(Dn)n


Sources: