How stablecoins are swapped with minimal slippage
Understanding the smart contract implementation of the StableSwap curve
In the previous post, I explained what an AMM is and how the Uniswap curve (constant product) is used to exchange tokens in a pool.
However, this invariant can be inefficient for liquidity pools of stablecoins.
For example, if we have a pool of DAI and USDT, and you try to exchange 100,000 USDT, you would expect to receive 100,000 DAI or something very close to that amount. But you might receive only 98,000 DAI and lose $2000 because of slippage.
Slippage
To better understand slippage, imagine a pool of 100X and 100Y tokens.
Using the Uniswap curve, for 10X tokens, you should pay ~11,12Y tokens.
Now in the pool, there are 90X tokens and ~111,12Y tokens.
If you want to buy another 10X tokens, you should pay ~13,88Y tokens.
If you try to drain all the X tokens from the pool, the price will increase exponentially.
The bigger the liquidity in the pool, the less the swaps are affected by slippage.
CurveFi
Curve Finance implemented Stableswap, which improves the Uniswap invariant by minimizing slippage.
Practically, the StableSwap invariant flattens the area where the price should remain steady, making it perfect to be used with stablecoins or tokens with the same value, like ETH and wETH.
You can find more details about how the StableSwap invariant is obtained in the following ๐ article ๐.
Implementation and math behind
The following part is only for those who want to understand how this works and how StableSwap is implemented in smart contracts.
$$ An^n \sum_0^n x_i + D = ADn^n + \frac{D^{n+1}}{n^n \prod_0^n x_i} $$
The previous formula is the StableSwap equation, which establishes the price relation between tokens in the pool.
Factor A can be dynamic, but we will suppose it is a known constant in the demonstrations.
All math you should know
To understand this demonstration, you only need to know Newton's Method. This formula is used to approximate the solution of a non-linear equation:
$$ x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} $$
How StableSwap is implemented in smart contracts
The StableSwap curve is implemented in StableSwap3Pool.vy smart contract, and it is part of the CurveFi opensource project.
I will try to explain the main parts of this implementation, starting from the StableSwap equation:
$$ An^n \sum_0^n x_i + D = ADn^n + \frac{D^{n+1}}{n^n \prod_0^n x_i} $$
Compute D
By moving all the left side terms to the right side in the StableSwap equation, the following will be obtained:
$$ \frac{D^{n+1}}{n^n \prod_0^n x_i} + An^nD - D - An^n \sum_0^n x_i = 0 $$
Now, it is easy to write the f(D) and it's derivative f`(D):
$$ f(D) = \frac{D^{n+1}}{n^n \prod_0^n x_i} + D(An^n - 1) - An^n \sum_0^n x_i $$
$$ f'(D) = \frac{(n+1)D^n}{n^n \prod_0^n x_i} + An^n - 1 $$
For a better visibility, we will make the following notation:
$$ \alpha = \frac{D^{n+1}}{n^n \prod_0^n x_i} $$
Rewriting f(D) and f'(D) using the previous notation:
$$ f(D) = \alpha + D(An^n - 1) - An^n \sum_0^n x_i $$
$$ f'(D) = \frac{n+1}{D} \alpha + An^n - 1 $$
Applying Newton's Method, we obtain the recursive approximation of the solution: $$ D_{m+1} = D_m - \frac{f(D_m)}{f'(D_m)} $$
$$ D_{m+1} = D_m - \frac{\alpha + D_m(An^n - 1) - An^n \sum_0^n x_i}{\frac{n+1}{D_m} \alpha + An^n - 1} $$
Amplifying the fraction from the the equation with D_m:
$$ D_{m+1} = D_m - \frac{\alpha + D_m(An^n - 1) - An^n \sum_0^n x_i}{\frac{n+1}{D_m} \alpha + An^n - 1} \frac{D_m}{D_m} $$
Rewriting after amplification:
$$ D_{m+1} = D_m - \frac{\alpha+ D_m(An^n - 1) - An^n \sum_0^n x_i}{(n+1)\alpha + D_m(An^n - 1)} D_m $$
Taking out the common factor:
$$ D_{m+1} = D_m(1 - \frac{\alpha + D_m(An^n - 1) - An^n \sum_0^n x_i}{(n+1)\alpha + D_m(An^n - 1)}) $$
Amplifying the first term from the parathesis with the denominator of the fraction, then rewriting:
$$ D_{m+1} = D_m\frac{(n+1)\alpha + D_m(An^n - 1) - \alpha - D_m(An^n - 1) + An^n \sum_0^n x_i}{(n+1)\alpha + D_m(An^n - 1)} $$
Reducing the terms, resulting in the final expression:
$$ D_{m+1} = D_m\frac{n\alpha + An^n \sum_0^n x_i}{(n+1)\alpha + D_m(An^n - 1)} $$
Compute Y
By moving all the right side terms to the left side in the StableSwap equation, the following will be obtained:
$$ An^n \sum_0^n x_i - An^nD + D - \frac{D^{n+1}}{n^n \prod_0^n x_i} = 0 $$
In the liquidity pool, there are n types of tokens, but we will know the value of n-1 of the tokens, and the amount of the n-th token must be determined. Rewriting the equation so the n-th token noted with y can be visible:
$$ An^n (\sum_0^{n-1} x_i + y) - An^nD + D - \frac{D^{n+1}}{n^n \prod_0^{n-1} x_i y} = 0 $$
Rewriting the first parathesis:
$$ An^n \sum_0^{n-1} x_i + An^ny + D - An^nD - \frac{D^{n+1}}{n^n \prod_0^{n-1} x_i y} = 0 $$
Multiplying the whole equation with y, to reduce the y from the fraction denominator:
$$ An^ny^2 + y(An^n \sum_0^{n-1} x_i - An^nD + D) - \frac{D^{n+1}}{n^n \prod_0^{n-1} x_i} = 0 $$
At this point it is easy to write f(y) and compute its derivative f'(y):
$$ f(y) = An^ny^2 + y(An^n \sum_0^{n-1} x_i - An^nD + D) - \frac{D^{n+1}}{n^n \prod_0^{n-1} x_i} $$
$$ f'(y) = 2An^ny + An^n \sum_0^{n-1} x_i - An^nD + D $$
Applying Newton's Method, we obtain the recursive approximation of the solution:
$$ y_{m+1} = y_m - \frac{f(y_m)}{f'(y_m)} $$
$$ y_{m+1} = y_m - \frac{An^ny_m^2 + y_m(An^n \sum_0^{n-1} x_i - An^nD + D) - \frac{D^{n+1}}{n^n \prod_0^{n-1} x_i}}{2An^ny_m + An^n \sum_0^{n-1} x_i - An^nD + D} $$
Amplifying the first term with the denominator of the fraction:
$$ y_{m+1} = \frac{2An^ny_m^2 + y_m(An^n \sum_0^{n-1} x_i - An^nD + D) - An^ny_m^2 - y_m(An^n \sum_0^{n-1} x_i - An^nD + D) + \frac{D^{n+1}}{n^n \prod_0^{n-1} x_i}}{2An^ny_m + An^n \sum_0^{n-1} x_i - An^nD + D} $$
Reducing the terms in the nominator:
$$ y_{m+1} = \frac{An^ny_m^2 + \frac{D^{n+1}}{n^n \prod_0^{n-1} x_i}}{2An^ny_m + An^n \sum_0^{n-1} x_i - An^nD + D} $$
Rewriting the equation after reducing the amplification factor Ann:
$$ y_{m+1} = \frac{y_m^2 + \frac{D^{n+1}}{n^n \prod_0^{n-1} x_i An^n}}{2y_m + \sum_0^{n-1} x_i - D + \frac{D}{An^n}} $$
Smart contract implementation
In the previous paragraphs, I demonstrated how D and Y are computed in the smart contract implementation.
Those formulas are used to incrementally approximate solutions for D and Y.
The corresponding functions in the smart contract are called get_D and get_Y
Computation for D is done at line 210 and is exactly the formula for D_m+1, obtained previously.
Computation for Y is done at line 389 and is exactly the formula for y_m+1, obtained previously.
Both functions will use the following pattern to approximate the solution (line 212-217 and line 391-396):
x_prev = initial value
x_new = initial value
for i = 1..n
x_prev = x_new
x_new = compute_x(x_prev)
// x_prev and x_new are unsigned integers
// if the subtraction result is negative will result in a logic error
if x_prev > x_new
if x_prev - x_new > error_tolerance break
else
if x_new - x_prev > error_tolerance break
In the code above, multiple iterations are performed to approximate a solution for x. The solution is found if the difference between the last two solutions is lesser than a set tolerance.
Steps to calculate the received tokens
In a liquidity pool that implements the StableSwap invariant, to find out how many B tokens we would receive for 100 A tokens, the following steps should be completed:
- Compute D based on the current state of the pool with the corresponding amounts of A and B tokens
- Compute Y (the remaining amount of B tokens in the pool) using the D factor calculated in the previous step, and the number of A tokens increased with 100
- The received B tokens is the subtraction of the remaining amount of B tokens from the initial amount of B tokens