DEV Community

MY ZT
MY ZT

Posted on • Edited on

Curve V1 notes

Curve V1 equation

Image description

a curve that is in between constant sum and constant product.

  1. A parameter controls how flat this curve B1 curb is (like the constant sum).
  2. Usually the a parameter is a fixed number however an admin can decide to change to a new A parameter, this change will take overtime.
  3. to put this point back on the curb what we had to do is find the new liquidity D, this process is not done all at once, but instead passes in the gradually changing value of A each time get_D is called.
  4. every time token balances changed, we will recalculate D.
#@ztmy
# What is D?
# Each token balance is D / N when the pool is perfectly balanced
# D is repreeent ideal liquidity (The sum of the balance is the actual liquidity), 
# It is the optimal balance point that Curve thinks the pool should reach.

# D=f(balances,A)
# everytime calculate case of we need to use D, it update A by the way.

# it's going to perform the Newton's method to calculate the value D
# xp: each token balance
# amp: self._A() to put in a gradually changing value of A 
def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256:
Enter fullscreen mode Exit fullscreen mode

Contract overview

all of the stable swap contracts will have five functions that are common:

  1. exchange
  2. add_liquidity
  3. remove_liquidity
  4. remove_liquidity_imbalance
  5. remove liquidity_one_coin

Code walkthrough :

  • ramp_A
  • stop_ramp_A

get_D:

D=f(balances,A): everytime calculate case of we need to use D, it update A by the way.

used at def get_y, add_liquidity

get_y:

calculate the amounts y of j is changed by the input x of asset i.

used at def get_dy, exchange

get_y_D:

calculate when D0 -> D1, How many the only one token y amount changed.

used at def _calc_withdraw_one_coin

Exchange

Image description

@ztmy
# i and j represent the Token index of the transaction
def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256):
    # Handling an unexpected charge of a fee on transfer (USDT)
    dx_w_fee: uint256 = dx
    input_coin: address = self.coins[i]

    # @ztmy
    # FEE_INDEX: constant(int128) = 2  # Which coin may potentially have fees (USDT)
    # like DAI that does not have fee on transfer, dx_w_fee == dx
    # however for tokens with transfer fee we need to 
    # balance of the token before transfer from - after transfer from = actual amount of tokens that came in
    if i == FEE_INDEX:
        dx_w_fee = ERC20(input_coin).balanceOf(self)
...
Enter fullscreen mode Exit fullscreen mode

Some ERC20 tokens automatically charge a transfer fee when they are transferred.This fee is not charged by Curve itself, is deducted automatically by certain ERC20 token contracts when they do transferFrom.

# "safeTransferFrom" which works for ERC20s which return bool or not
# @ztmy
# using a low level call to avoid the problem of 
# calling the function transferFrom() from both for tokens like DAI that returns a bool 
# and tokens like usdt does not return a bool
# through whether the response length is greater than 0
_response: Bytes[32] = raw_call(
    input_coin,
    concat(
        method_id("transferFrom(address,address,uint256)"),
        convert(msg.sender, bytes32),
        convert(self, bytes32),
        convert(dx, bytes32),
    ),
    max_outsize=32,
)  # dev: failed transfer
if len(_response) > 0:
    assert convert(_response, bool)  # dev: failed transfer
Enter fullscreen mode Exit fullscreen mode
  • get_dy: Quotation stage, Check estimated prices. used for intra-contract.
  • get_dy_underlying: used for user. Return Real token unit.
  • exchange: Execute a trade.

add liquidity

Image description

to discourage users from imbalancing the pools there is an imbalance fee.

for i in range(N_COINS):
    #@ztmy this is the ideal balance increment for each token
    ideal_balance: uint256 = D1 * old_balances[i] / D0
    difference: uint256 = 0 
    if ideal_balance > new_balances[i]:
        difference = ideal_balance - new_balances[i]
    else:
        difference = new_balances[i] - ideal_balance
    # this is the imbalance fee that will be charged for each token
    fees[i] = _fee * difference / FEE_DENOMINATOR
    self.balances[i] = new_balances[i] - (fees[i] * _admin_fee / FEE_DENOMINATOR)
    new_balances[i] -= fees[i]
D2 = self.get_D_mem(new_balances, amp)
Enter fullscreen mode Exit fullscreen mode

the ideal balances are calculated by taking this ratio of the changing liquidity and then multiplying it by the previous token balances.

remove_liquidity

Image description

remove_liquidity_one_coin

Image description

Image description

Remove liquidity, D decreases. Just take out y, and x doesn't change, dy is the amount of y take out.

# @ztmy for the output it's going to calculate the amount the token that you will receive and the fees.
def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256):

    amp: uint256 = self._A()
    _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
    precisions: uint256[N_COINS] = PRECISION_MUL
    total_supply: uint256 = self.token.totalSupply()
xp: uint256[N_COINS] = self._xp()

D0: uint256 = self.get_D(xp, amp)
D1: uint256 = D0 - _token_amount * D0 / total_supply
xp_reduced: uint256[N_COINS] = xp
# @ztmy calculate when D0 -> D1, and only one token amount changes y0
new_y: uint256 = self.get_y_D(amp, i, xp, D1)
dy_0: uint256 = (xp[i] - new_y) / precisions[i]  # w/o fees

for j in range(N_COINS):
    dx_expected: uint256 = 0
    # @ztmy
    # taking the the difference this will give us 
    # the difference from The ideal Balance of j after removing liquidity (xp[j] * D1 / D0)
    # and the actual amount after removing liquidity
    if j == i:
        dx_expected = xp[j] * D1 / D0 - new_y
    else:
        dx_expected = xp[j] - xp[j] * D1 / D0
        #@ztmy the Imbalance Fee is paid only in the currency of withdrawal, 
        # and the Imbalance Fee in other currencies is deducted directly from the pool
    xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR

dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

👋 Kindness is contagious

Please consider leaving a ❤️ or a kind comment on this post if it was useful to you!

Thanks!