
PRICE_SIZE:256/(3-1) = 128
PRICE_MASK: 128 binary 1s, bitwise_and operations with any number x is still x
PRECISIONS: 1, 1, 1
packed_prices
In the initial state:
# Packing prices
packed_prices: uint256 = 0
for k in range(N_COINS-1):
packed_prices = shift(packed_prices, PRICE_SIZE)
p: uint256 = initial_prices[N_COINS-2 - k] # / PRICE_PRECISION_MUL
assert p < PRICE_MASK
packed_prices = bitwise_or(p, packed_prices)
self.price_scale_packed = packed_prices
self.price_oracle_packed = packed_prices
self.last_prices_packed = packed_prices
There are 3 types of price.
last_prices_packed
price_scale_packed: the current pegged price. “the internal set of coefficient p = (p0; p1; : : :)which we call price_scale in the code.”
price_oracle_packed
#Why does the curve use bitwise operations to pack prices instead of using a list? to save gas.
Update xp (xp[i]), calculate dy using Newton's equation
dy = xp[j] - Math(math).newton_y(A_gamma[0], A_gamma[1], xp, self.D, j)Here, dy and xp both refer to balance * price_scale. The input xp[i] += xi * price_scale(i), and dy is the change in xp[j].
Continue to update xp (xp[j])
xp[j] -= dyDeduct fees, continue to update xp (xp[j])
if j > 0: dy = dy * PRECISION / price_scale[j-1] dy /= prec_j dy -= self._fee(xp) * dy / 10**10 assert dy >= min_dy, "Slippage" y -= dy self.balances[j] = ySince dy is calculated when D is constant, if the pool is reduced by just dy, D will not change. However, now the pool is reduced by (dy-fee), which is smaller than dy,so the remaining y in the pool is larger than the y calculated when D is constant, so D will actually increase slightly.
*Dynamic Fee

def _fee(xp: uint256[N_COINS]) -> uint256:
f: uint256 = Math(math).reduction_coefficient(xp, self.fee_gamma)
return (self.mid_fee * f + self.out_fee * (10**18 - f)) / 10**18
f_mid: 4*10**6;f_out: 3*10**7
f range: [0.04%, 0.3%)
The more the pool is pushed off by a swap (the further away the price is from price_scale), the higher the fee.
3. After calculating the price with newton_y, the tweak_price method is called.
self.tweak_price(A_gamma, xp, ix, p, 0)
new_D == 0, D will be recalculated in the tweak_price method. The exchange fee will slightly change D, so D needs to be recalculated here.
Calculate P_oracle using the last recorded price first, note not using the current swap price.

if last_prices_timestamp < block.timestamp:
# MA update required
ma_half_time: uint256 = self.ma_half_time
alpha: uint256 = Math(math).halfpow((block.timestamp - last_prices_timestamp) * 10**18 / ma_half_time, 10**10)
packed_prices = 0
for k in range(N_COINS-1):
price_oracle[k] = (last_prices[k] * (10**18 - alpha) + price_oracle[k] * alpha) / 10**18
for k in range(N_COINS-1):
packed_prices = shift(packed_prices, PRICE_SIZE)
p: uint256 = price_oracle[N_COINS-2 - k] # / PRICE_PRECISION_MUL
assert p < PRICE_MASK
packed_prices = bitwise_or(p, packed_prices)
self.price_oracle_packed = packed_prices
self.last_prices_timestamp = block.timestamp
ma_half_time = 600
The time interval between the current time and the last recorded time is t. The shorter the interval, the closer alpha is to 1, and P* is closer to P_prev; the longer the interval, the closer alpha is to 0, and P* is closer to P_last; when the interval = ma_half_time, alpha = 0.5
2. Update last_prices
if p_i > 0:
# Save the last price
if i > 0:
last_prices[i-1] = p_i
3. Adjust pegged prices or not
“We allow the reduction in Xcp but only such that the loss of value of Xcp doesn’t exceed half the profit we’ve made (which we track by tracking the increase of Xcp).”
Equation: virtual_price-10**18 > (xcp_profit-10**18)/2 + self.allowed_extra_profit
Meaning: The value of lp must exceed half of the accumulated fee income.
Xcp: the value of the constant-product invariant at the equilibrium point.
At the equilibrium point (the values of each token in the pool are equal. for all i, j :x_i**p_i = x_j**p_j, which is the state of the uniswap pool), the constant product and the constant sum formulas are separately valid:

if price_scale doesn’t change, D controls the shape of the curve, and X_cp is the square root of the rectangle formed by the intersection of the xy curve and the y=price_scale*x line with the origin.
@internal
@view
def get_xcp(D: uint256) -> uint256:
x: uint256[N_COINS] = empty(uint256[N_COINS])
x[0] = D / N_COINS
packed_prices: uint256 = self.price_scale_packed
# No precisions here because we don't switch to "real" units
for i in range(1, N_COINS):
x[i] = D * 10**18 / (N_COINS * bitwise_and(packed_prices, PRICE_MASK)) # ... * PRICE_PRECISION_MUL)
packed_prices = shift(packed_prices, -PRICE_SIZE)
return Math(math).geometric_mean(x)
3-1: Calculate D
if new_D == 0:
# We will need this a few times (35k gas)
D_unadjusted = Math(math).newton_D(A_gamma[0], A_gamma[1], _xp)
3-2: Calculate virtual_price and xcp_profit under the current price_scale
# Update profit numbers without price adjustment first
xp[0] = D_unadjusted / N_COINS
for k in range(N_COINS-1):
xp[k+1] = D_unadjusted * 10**18 / (N_COINS * price_scale[k])
xcp_profit: uint256 = 10**18
virtual_price: uint256 = 10**18
if old_virtual_price > 0:
xcp: uint256 = Math(math).geometric_mean(xp)
**virtual_price = 10**18 * xcp / total_supply**
xcp_profit = old_xcp_profit * virtual_price / old_virtual_price
In exchange, the total_supply quantity does not change, only xcp will cause virtual_price to change, and price_scale has not changed at this point, so only the change in D will cause virtual_price to change. As analyzed earlier, the increase in D here is entirely due to the fee, so the increase in virtual_price is also entirely due to the fee. Therefore, virtual_price is used to calculate xcp_profit here, which is the accumulated fee income.
3-3: Determine whether to repeg.
After repeg, X_cp will definitely decrease. If the new X_cp can satisfy > xcp_profit/2, the current X_cp can definitely satisfy it.
if not needs_adjustment and (virtual_price * 2 - 10**18 > xcp_profit + 2*self.allowed_extra_profit):
needs_adjustment = True
self.not_adjusted = True
3-4: Repeg: move price_scale towards price_oracle

if needs_adjustment:
adjustment_step: uint256 = self.adjustment_step
norm: uint256 = 0
for k in range(N_COINS-1):
ratio: uint256 = price_oracle[k] * 10**18 / price_scale[k]
if ratio > 10**18:
ratio -= 10**18
else:
ratio = 10**18 - ratio
norm += ratio**2
if norm > adjustment_step ** 2 and old_virtual_price > 0:
norm = Math(math).sqrt_int(norm / 10**18) # Need to convert to 1e18 units!
for k in range(N_COINS-1):
p_new[k] = (price_scale[k] * (norm - adjustment_step) + adjustment_step * price_oracle[k]) / norm

norm looks like standard deviation, measuring the deviation of P_oracle relative to the current pegged P_scale.
The equation in the whitepaper is actually a weighted average. The rate of change of P_scale = s/norm * the change rate from P_scale to P_oracle.
s: adjustment_step = 2 * 10**15 → 0.002
No matter how far P_oracle deviates from the current P_scale, P_scale always moves forward by 0.2% with each step.
3-5: Determine whether the new price_scale meets the adjustment criteria.
Price Transmission Order:P_scale < P_oracle < P_last < P
Uniswap, The user's "l" never changes, as long as the price returns to "mint", there will be no impermanent loss.
Curve V2: As the Xcp of the user is constantly re-pegged, it will continuously decrease. Even if the price is restored to mint, there will still be impermanent loss, but this impermanent loss will not exceed half of the accumulated handling fee. What is the impact?
Equilibrium State:
Uniswap: Always in equilibrium state. Curve: Only one point is in equilibrium state.
references:
Curve V2 whitepaper
Curve V2 pool code
