BTC
ETH
HTX
SOL
BNB
View Market
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

A Brief Talk on Curve: The Most Powerful Stablecoin Exchange Protocol

This article is about 6844 words, reading the full article takes about 10 minutes
Curve, an efficient and concise automatic market-making exchange pool protocol, is unique in the DeFi field with its extremely low slippage.
AI Summary
Expand
Curve, an efficient and concise automatic market-making exchange pool protocol, is unique in the DeFi field with its extremely low slippage.

Introduction

Introduction

StableSwap

first level title

StableSwap is a stable currency transaction model designed and proposed by Curve in the white paper, which can provide extremely low transaction slippage and unlimited liquidity.

secondary title

The constant function market maker curve (CFMM) of the StableSwap model is as follows:

The design concept is based on the fusion of two market-making models of constant sum and constant product, which has the characteristics of low slippage of constant sum and unlimited liquidity of constant product

secondary title

derive

Assuming that there are n stable coins in total, there are:

Combining the above two formulas to simplify can get the formula (1)

features

secondary title

Basepool

features

basepool is the basic exchange pool launched by Curve. The most common one is 3pool, which consists of three stable coins DAI, USDC, and USDT

The basepool contract implements the basic StableSwap transaction model

secondary title

@pure
@internal
def _get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256:
   """
  D invariant calculation in non-overflowing integer operations
  iteratively
  A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
  Converging solution:
  D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1)
  """
   S: uint256 = 0
   Dprev: uint256 = 0
   for _x in _xp:
       S += _x
   if S == 0:
       return 0
   D: uint256 = S
   Ann: uint256 = _amp * N_COINS
   for _i in range(255):
       D_P: uint256 = D
       for _x in _xp:
           D_P = D_P * D / (_x * N_COINS)  # If division by 0, this will be borked: only withdrawal will work. And that is good
       Dprev = D
       D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) *D_P)
       # Equality with the precision of 1
       if D > Dprev:
           if D - Dprev <= 1:
               return D
       else:
           if Dprev - D <= 1:
               return D
   # convergence typically occurs in 4 rounds or less, this should be unreachable!
   # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity`
   raise

Parameter calculation

The same algorithm is also used in the _get_y function to calculate the y value

@view
@internal
def _get_y(i: int128, j: int128, x: uint256, _xp: uint256[N_COINS]) -> uint256:
   """
  Calculate x[j] if one makes x[i] = x
  Done by solving quadratic equation iteratively.
  x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
  x_1**2 + b*x_1 = c
  x_1 = (x_1**2 + c) / (2*x_1 + b)
  """
   # x in the input is converted to the same price/precision
   assert i != j       # dev: same coin
   assert j >= 0       # dev: j below zero
   assert j < N_COINS  # dev: j above N_COINS
   # should be unreachable, but good for safety
   assert i >= 0
   assert i < N_COINS
   A: uint256 = self._A()
   D: uint256 = self._get_D(_xp, A)
   Ann: uint256 = A * N_COINS
   c: uint256 = D
   S: uint256 = 0
   _x: uint256 = 0
   y_prev: uint256 = 0
   for _i in range(N_COINS):
       if _i == i:
           _x = x
       elif _i != j:
           _x = _xp[_i]
       else:
           continue
       S += _x
       c = c * D / (_x * N_COINS)
   c = c * D * A_PRECISION / (Ann * N_COINS)
   b: uint256 = S + D * A_PRECISION / Ann  # - D
   y: uint256 = D
   for _i in range(255):
       y_prev = y
       y = (y*y + c) / (2 * y + b - D)
       # Equality with the precision of 1
       if y > y_prev:
           if y - y_prev <= 1:
               return y
       else:
           if y_prev - y <= 1:
               return y
   raise

fluidity

secondary title

@external
@nonreentrant('lock')
def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256) -> uint256:
   """
  @notice Deposit coins into the pool
  @param _amounts List of amounts of coins to deposit
  @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
  @return Amount of LP tokens received by depositing
  """
   assert not self.is_killed  # dev: is killed
   amp: uint256 = self._A()
   old_balances: uint256[N_COINS] = self.balances
   # Initial invariant
   D0: uint256 = self._get_D_mem(old_balances,fluidity
   lp_token: address = self.lp_token
   token_supply: uint256 = CurveToken(lp_token).totalSupply()
   new_balances: uint256[N_COINS] = old_balances
   for i in range(N_COINS):
       if token_supply == 0:
           assert _amounts[i] > 0  # dev: initial deposit requires all coins
       # balances store amounts of c-tokens
       new_balances[i] += _amounts[i]
   # Invariant after change
   D1: uint256 = self._get_D_mem(new_balances,The liquidity addition of the Curve pool is different from the two assets that Uniswap must add trading pairs, and the basepool can only add a certain asset in the pool
   assert D1 > D0
   # We need to recalculate the invariant accounting for fees
   # to calculate fair user's share
   D2: uint256 = D1
   fees: uint256[N_COINS] = empty(uint256[N_COINS])
   mint_amount: uint256 = 0
   if token_supply >amp) #hunya# initial state D0
       # Only account for fees if we are not the first to deposit
       fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
       admin_fee: uint256 = self.admin_fee
       for i in range(N_COINS):
           ideal_balance: uint256 = D1 * old_balances[i] / D0
           difference: uint256 = 0
           new_balance: uint256 = new_balances[i]
           if ideal_balance > new_balance:
               difference = ideal_balance - new_balance
           else:
               difference = new_balance - ideal_balance
           fees[i] = fee * difference / FEE_DENOMINATOR
           self.balances[i] = new_balance - (fees[i] * admin_fee / FEE_DENOMINATOR)
           new_balances[i] -= fees[i]
       D2 = self._get_D_mem(new_balances,amp) #hunya# theory D1 after adding liquidity
0: #hunya# It is not the first time to add liquidity to deduct the handling fee
   else:
       self.balances = new_balances
       mint_amount = D1  # Take the dust if there was any
   assert mint_amount >= _min_mint_amount, "Slippage screwed you"
   # Take coins from the sender
   for i in range(N_COINS):
       if _amounts[i] > 0:
           # "safeTransferFrom" which works for ERC20s which return bool or not
           _response: Bytes[32] = raw_call(
               self.coins[i],
               concat(
                   method_id("transferFrom(address,address,uint256)"),
                   convert(msg.sender, bytes32),
                   convert(self, bytes32),
                   convert(_amounts[i], bytes32),
              ),
               max_outsize=32,
          )
           if len(_response) > 0:
               assert convert(_response, bool)  # dev: failed transfer
           # end "safeTransferFrom"
   # Mint pool tokens
   CurveToken(lp_token).mint(msg.sender, mint_amount)
   log AddLiquidity(msg.sender, _amounts, fees, D1, token_supply + mint_amount)
   return mint_amount

Metapool

amp) #hunya# D2 after deducting the handling fee

Metapool is a new pool derived from the expansion of basepool, which is used to anchor the new stable currency to the LP token of basepool. The liquidity provider of the basepool pool can add the LP token of the basepool to the metapool to add liquidity again, and further earn additional transaction fee income.

In the metapool contract, the new stable currency and the stable currency of the basic pool are indirectly coupled. While providing the exchange function between different stable coins, it also separates the risks of the new stable currency to a certain extent.

secondary title

@external
@nonreentrant('lock')
def exchange_underlying(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256:
  ....
   # Use base_i or base_j if they are >= 0
   base_i: int128 = i - MAX_COIN
   base_j: int128 = j - MAX_COIN
   meta_i: int128 = MAX_COIN
   meta_j: int128 = MAX_COIN
   if base_i < 0:
       meta_i = i
   if base_j < 0:
       meta_j = j
   dy: uint256 = 0
  ...
Stablecoin exchange
       old_balances: uint256[N_COINS] = self.balances
       xp: uint256[N_COINS] = self._xp_mem(rates[MAX_COIN], old_balances)
       x: uint256 = 0
The exchange_underlying function in the metapool contract realizes the mutual exchange function between stable coins
           x = xp[i] + dx_w_fee * rates[i] / PRECISION
if base_i < 0 or base_j < 0: #hunya# swap includes meta pool tokens
           # i is from BasePool
           # At first, get the amount of pool tokens
           base_inputs: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])
           base_inputs[base_i] = dx_w_fee
           coin_i: address = self.coins[MAX_COIN]
           # Deposit and measure delta
           x = ERC20(coin_i).balanceOf(self)
           Curve(base_pool).add_liquidity(base_inputs,if base_i < 0: #hunya# input token is meta pool token
           # Need to convert pool token to "virtual" units using rates
           # dx is also different now
           dx_w_fee = ERC20(coin_i).balanceOf(self) - x
           x = dx_w_fee * rates[MAX_COIN] / PRECISION
           # Adding number of pool tokens
           x += xp[MAX_COIN]
      ...
       # Withdraw from the base pool if needed
       if base_j >else: #hunya# The input token is the base pool token
           out_amount: uint256 = ERC20(output_coin).balanceOf(self)
           Curve(base_pool).remove_liquidity_one_coin(dy, base_j,0) #hunya# base pool adds liquidity
           dy = ERC20(output_coin).balanceOf(self) - out_amount
       assert dy >= _min_dy, "Too few coins in result"
= 0: #hunya# output tokens are base pool tokens
       # If both are from the base pool
       dy = ERC20(output_coin).balanceOf(self)
       Curve(base_pool).exchange(base_i, base_j, dx_w_fee, _min_dy)
       dy = ERC20(output_coin).balanceOf(self) - dy
  ...
   log TokenExchangeUnderlying(msg.sender, i, _dx, j, dy)
   return dy

else: #hunya# Pure base pool token exchange, directly call the base pool exchange function

Summarize

If the exchange involves basepool tokens and metapool pool tokens, the intermediate process will be done by adding or removing liquidity through the LP of the basepool; if it is only the exchange of basepool tokens, the exchange function of the basepool will be called directly for exchange.


Curve
stable currency
Welcome to Join Odaily Official Community