Curve V1 equation
a curve that is in between constant sum and constant product.
- A parameter controls how flat this curve B1 curb is (like the constant sum).
- 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.
- 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.
- 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:
Contract overview
all of the stable swap contracts will have five functions that are common:
- exchange
- add_liquidity
- remove_liquidity
- remove_liquidity_imbalance
- 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
@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)
...
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
- 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
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)
the ideal balances are calculated by taking this ratio of the changing liquidity and then multiplying it by the previous token balances.
remove_liquidity
remove_liquidity_one_coin
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)
Top comments (0)