Crypto-Pools are exchange contracts containing two volatile (non-pegged) assets.
These exchange contracts are deployed via the CryptoSwap Factory. Unlike newer Factory contracts, which utilize blueprint contracts, the earlier versions did not have this feature at the time of their deployments. Instead, in these earlier versions, the exchange contract is created using the Vyper built-in create_forwarder_to() function.
The pool is then initialized via the initialize() function of the pool implementation contract, which sets all the relevant variables, such as paired tokens, prices, and parameters.
Initializing the Pool
@externaldefinitialize(A:uint256,gamma:uint256,mid_fee:uint256,out_fee:uint256,allowed_extra_profit:uint256,fee_gamma:uint256,adjustment_step:uint256,admin_fee:uint256,ma_half_time:uint256,initial_price:uint256,_token:address,_coins:address[N_COINS],_precisions:uint256,):assertself.mid_fee==0# dev: check that we call it from factoryself.factory=msg.sender# Pack A and gamma:# shifted A + gammaA_gamma:uint256=shift(A,128)A_gamma=bitwise_or(A_gamma,gamma)self.initial_A_gamma=A_gammaself.future_A_gamma=A_gammaself.mid_fee=mid_feeself.out_fee=out_feeself.allowed_extra_profit=allowed_extra_profitself.fee_gamma=fee_gammaself.adjustment_step=adjustment_stepself.admin_fee=admin_feeself.price_scale=initial_priceself._price_oracle=initial_priceself.last_prices=initial_priceself.last_prices_timestamp=block.timestampself.ma_half_time=ma_half_timeself.xcp_profit_a=10**18self.token=_tokenself.coins=_coinsself.PRECISIONS=_precisions
Function to exchange dx amount of coin i for coin j and receive a minimum amount of min_dy.
Returns: output amount (uint256).
Emits: TokenExchange
Input
Type
Description
i
uint256
Index value for the input coin
j
uint256
Index value for the output coin
dx
uint256
Amount of input coin being swapped in
min_dy
uint256
Minimum amount of output coin to receive
use_eth
bool
whether to use plain ETH; defaults to False (uses wETH instead)
receiver
address
Address to send output coin to. Deafaults to msg.sender
Source code
eventTokenExchange:buyer:indexed(address)sold_id:uint256tokens_sold:uint256bought_id:uint256tokens_bought:uint256@payable@external@nonreentrant('lock')defexchange(i:uint256,j:uint256,dx:uint256,min_dy:uint256,use_eth:bool=False,receiver:address=msg.sender)->uint256:""" Exchange using WETH by default """returnself._exchange(msg.sender,msg.value,i,j,dx,min_dy,use_eth,receiver,ZERO_ADDRESS,EMPTY_BYTES32)@internaldef_exchange(sender:address,mvalue:uint256,i:uint256,j:uint256,dx:uint256,min_dy:uint256,use_eth:bool,receiver:address,callbacker:address,callback_sig:bytes32)->uint256:asserti!=j# dev: coin index out of rangeasserti<N_COINS# dev: coin index out of rangeassertj<N_COINS# dev: coin index out of rangeassertdx>0# dev: do not exchange 0 coinsA_gamma:uint256[2]=self._A_gamma()xp:uint256[N_COINS]=self.balancesp:uint256=0dy:uint256=0in_coin:address=self.coins[i]out_coin:address=self.coins[j]y:uint256=xp[j]x0:uint256=xp[i]xp[i]=x0+dxself.balances[i]=xp[i]price_scale:uint256=self.price_scaleprecisions:uint256[2]=self._get_precisions()xp=[xp[0]*precisions[0],xp[1]*price_scale*precisions[1]/PRECISION]prec_i:uint256=precisions[0]prec_j:uint256=precisions[1]ifi==1:prec_i=precisions[1]prec_j=precisions[0]# In case ramp is happeningt:uint256=self.future_A_gamma_timeift>0:x0*=prec_iifi>0:x0=x0*price_scale/PRECISIONx1:uint256=xp[i]# Back up old value in xpxp[i]=x0self.D=self.newton_D(A_gamma[0],A_gamma[1],xp)xp[i]=x1# And restoreifblock.timestamp>=t:self.future_A_gamma_time=1dy=xp[j]-self.newton_y(A_gamma[0],A_gamma[1],xp,self.D,j)# Not defining new "y" here to have less variables / make subsequent calls cheaperxp[j]-=dydy-=1ifj>0:dy=dy*PRECISION/price_scaledy/=prec_jdy-=self._fee(xp)*dy/10**10assertdy>=min_dy,"Slippage"y-=dyself.balances[j]=y# Do transfers in and out together# XXX coin vs ETHifuse_ethandin_coin==WETH20:assertmvalue==dx# dev: incorrect eth amountelse:assertmvalue==0# dev: nonzero eth amountifcallback_sig==EMPTY_BYTES32:response:Bytes[32]=raw_call(in_coin,_abi_encode(sender,self,dx,method_id=method_id("transferFrom(address,address,uint256)")),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)# dev: failed transferelse:b:uint256=ERC20(in_coin).balanceOf(self)raw_call(callbacker,concat(slice(callback_sig,0,4),_abi_encode(sender,receiver,in_coin,dx,dy)))assertERC20(in_coin).balanceOf(self)-b==dx# dev: callback didn't give us coinsifin_coin==WETH20:WETH(WETH20).withdraw(dx)ifuse_ethandout_coin==WETH20:raw_call(receiver,b"",value=dy)else:ifout_coin==WETH20:WETH(WETH20).deposit(value=dy)response:Bytes[32]=raw_call(out_coin,_abi_encode(receiver,dy,method_id=method_id("transfer(address,uint256)")),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)y*=prec_jifj>0:y=y*price_scale/PRECISIONxp[j]=y# Calculate priceifdx>10**5anddy>10**5:_dx:uint256=dx*prec_i_dy:uint256=dy*prec_jifi==0:p=_dx*10**18/_dyelse:# j == 0p=_dy*10**18/_dxself.tweak_price(A_gamma,xp,p,0)logTokenExchange(sender,i,dx,j,dy)returndy
exchange_underlying exchanges tokens by using the 'underlying' ETH instead of wETH.
Function to exchange dx amount of coin i for coin j and receive a minimum amount of min_dy.
Returns: output amount (uint256).
Emits: TokenExchange
Input
Type
Description
i
uint256
index value for the input coin
j
uint256
index value for the output coin
dx
uint256
amount of input coin being swapped in
min_dy
uint256
minimum amount of output coin to receive
use_eth
bool
whether to use plain ETH; defaults to False (uses wETH instead)
receiver
address
address to send output coin to; deafaults to msg.sender
Source code
eventTokenExchange:buyer:indexed(address)sold_id:uint256tokens_sold:uint256bought_id:uint256tokens_bought:uint256@payable@external@nonreentrant('lock')defexchange_underlying(i:uint256,j:uint256,dx:uint256,min_dy:uint256,receiver:address=msg.sender)->uint256:""" Exchange using ETH """returnself._exchange(msg.sender,msg.value,i,j,dx,min_dy,True,receiver,ZERO_ADDRESS,EMPTY_BYTES32)@internaldef_exchange(sender:address,mvalue:uint256,i:uint256,j:uint256,dx:uint256,min_dy:uint256,use_eth:bool,receiver:address,callbacker:address,callback_sig:bytes32)->uint256:asserti!=j# dev: coin index out of rangeasserti<N_COINS# dev: coin index out of rangeassertj<N_COINS# dev: coin index out of rangeassertdx>0# dev: do not exchange 0 coinsA_gamma:uint256[2]=self._A_gamma()xp:uint256[N_COINS]=self.balancesp:uint256=0dy:uint256=0in_coin:address=self.coins[i]out_coin:address=self.coins[j]y:uint256=xp[j]x0:uint256=xp[i]xp[i]=x0+dxself.balances[i]=xp[i]price_scale:uint256=self.price_scaleprecisions:uint256[2]=self._get_precisions()xp=[xp[0]*precisions[0],xp[1]*price_scale*precisions[1]/PRECISION]prec_i:uint256=precisions[0]prec_j:uint256=precisions[1]ifi==1:prec_i=precisions[1]prec_j=precisions[0]# In case ramp is happeningt:uint256=self.future_A_gamma_timeift>0:x0*=prec_iifi>0:x0=x0*price_scale/PRECISIONx1:uint256=xp[i]# Back up old value in xpxp[i]=x0self.D=self.newton_D(A_gamma[0],A_gamma[1],xp)xp[i]=x1# And restoreifblock.timestamp>=t:self.future_A_gamma_time=1dy=xp[j]-self.newton_y(A_gamma[0],A_gamma[1],xp,self.D,j)# Not defining new "y" here to have less variables / make subsequent calls cheaperxp[j]-=dydy-=1ifj>0:dy=dy*PRECISION/price_scaledy/=prec_jdy-=self._fee(xp)*dy/10**10assertdy>=min_dy,"Slippage"y-=dyself.balances[j]=y# Do transfers in and out together# XXX coin vs ETHifuse_ethandin_coin==WETH20:assertmvalue==dx# dev: incorrect eth amountelse:assertmvalue==0# dev: nonzero eth amountifcallback_sig==EMPTY_BYTES32:response:Bytes[32]=raw_call(in_coin,_abi_encode(sender,self,dx,method_id=method_id("transferFrom(address,address,uint256)")),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)# dev: failed transferelse:b:uint256=ERC20(in_coin).balanceOf(self)raw_call(callbacker,concat(slice(callback_sig,0,4),_abi_encode(sender,receiver,in_coin,dx,dy)))assertERC20(in_coin).balanceOf(self)-b==dx# dev: callback didn't give us coinsifin_coin==WETH20:WETH(WETH20).withdraw(dx)ifuse_ethandout_coin==WETH20:raw_call(receiver,b"",value=dy)else:ifout_coin==WETH20:WETH(WETH20).deposit(value=dy)response:Bytes[32]=raw_call(out_coin,_abi_encode(receiver,dy,method_id=method_id("transfer(address,uint256)")),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)y*=prec_jifj>0:y=y*price_scale/PRECISIONxp[j]=y# Calculate priceifdx>10**5anddy>10**5:_dx:uint256=dx*prec_i_dy:uint256=dy*prec_jifi==0:p=_dx*10**18/_dyelse:# j == 0p=_dy*10**18/_dxself.tweak_price(A_gamma,xp,p,0)logTokenExchange(sender,i,dx,j,dy)returndy
This method does not allow swapping in native token, but does allow swaps that transfer out native token from the pool.
Function to exchange dx amount of coin i for coin j and receive a minimum amount of min_dy with using a callback method.
Returns: output amount (uint256).
Emits: TokenExchange
Input
Type
Description
i
uint256
index value for the input coin
j
uint256
index value for the output coin
dx
uint256
amount of input coin being swapped in
min_dy
uint256
minimum amount of output coin to receive
use_eth
bool
whether to use plain ETH; defaults to False (uses wETH instead)
receiver
address
address to send output coin to; deafaults to msg.sender
cb
bytes32
callback signature
Source code
eventTokenExchange:buyer:indexed(address)sold_id:uint256tokens_sold:uint256bought_id:uint256tokens_bought:uint256@payable@external@nonreentrant('lock')defexchange_extended(i:uint256,j:uint256,dx:uint256,min_dy:uint256,use_eth:bool,sender:address,receiver:address,cb:bytes32)->uint256:assertcb!=EMPTY_BYTES32# dev: No callback specifiedreturnself._exchange(sender,msg.value,i,j,dx,min_dy,use_eth,receiver,msg.sender,cb)@internaldef_exchange(sender:address,mvalue:uint256,i:uint256,j:uint256,dx:uint256,min_dy:uint256,use_eth:bool,receiver:address,callbacker:address,callback_sig:bytes32)->uint256:asserti!=j# dev: coin index out of rangeasserti<N_COINS# dev: coin index out of rangeassertj<N_COINS# dev: coin index out of rangeassertdx>0# dev: do not exchange 0 coinsA_gamma:uint256[2]=self._A_gamma()xp:uint256[N_COINS]=self.balancesp:uint256=0dy:uint256=0in_coin:address=self.coins[i]out_coin:address=self.coins[j]y:uint256=xp[j]x0:uint256=xp[i]xp[i]=x0+dxself.balances[i]=xp[i]price_scale:uint256=self.price_scaleprecisions:uint256[2]=self._get_precisions()xp=[xp[0]*precisions[0],xp[1]*price_scale*precisions[1]/PRECISION]prec_i:uint256=precisions[0]prec_j:uint256=precisions[1]ifi==1:prec_i=precisions[1]prec_j=precisions[0]# In case ramp is happeningt:uint256=self.future_A_gamma_timeift>0:x0*=prec_iifi>0:x0=x0*price_scale/PRECISIONx1:uint256=xp[i]# Back up old value in xpxp[i]=x0self.D=self.newton_D(A_gamma[0],A_gamma[1],xp)xp[i]=x1# And restoreifblock.timestamp>=t:self.future_A_gamma_time=1dy=xp[j]-self.newton_y(A_gamma[0],A_gamma[1],xp,self.D,j)# Not defining new "y" here to have less variables / make subsequent calls cheaperxp[j]-=dydy-=1ifj>0:dy=dy*PRECISION/price_scaledy/=prec_jdy-=self._fee(xp)*dy/10**10assertdy>=min_dy,"Slippage"y-=dyself.balances[j]=y# Do transfers in and out together# XXX coin vs ETHifuse_ethandin_coin==WETH20:assertmvalue==dx# dev: incorrect eth amountelse:assertmvalue==0# dev: nonzero eth amountifcallback_sig==EMPTY_BYTES32:response:Bytes[32]=raw_call(in_coin,_abi_encode(sender,self,dx,method_id=method_id("transferFrom(address,address,uint256)")),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)# dev: failed transferelse:b:uint256=ERC20(in_coin).balanceOf(self)raw_call(callbacker,concat(slice(callback_sig,0,4),_abi_encode(sender,receiver,in_coin,dx,dy)))assertERC20(in_coin).balanceOf(self)-b==dx# dev: callback didn't give us coinsifin_coin==WETH20:WETH(WETH20).withdraw(dx)ifuse_ethandout_coin==WETH20:raw_call(receiver,b"",value=dy)else:ifout_coin==WETH20:WETH(WETH20).deposit(value=dy)response:Bytes[32]=raw_call(out_coin,_abi_encode(receiver,dy,method_id=method_id("transfer(address,uint256)")),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)y*=prec_jifj>0:y=y*price_scale/PRECISIONxp[j]=y# Calculate priceifdx>10**5anddy>10**5:_dx:uint256=dx*prec_i_dy:uint256=dy*prec_jifi==0:p=_dx*10**18/_dyelse:# j == 0p=_dy*10**18/_dxself.tweak_price(A_gamma,xp,p,0)logTokenExchange(sender,i,dx,j,dy)returndy
Getter for the received amount of coin j for swapping in dx amount of coin i.
Returns: output amount (uint256).
Input
Type
Description
i
uint256
index value for the input coin
j
uint256
index value for the output coin
dx
uint256
amount of input coin being swapped in
Note
This method takes fees into consideration.
Source code
@external@viewdefget_dy(i:uint256,j:uint256,dx:uint256)->uint256:asserti!=j# dev: same input and output coinasserti<N_COINS# dev: coin index out of rangeassertj<N_COINS# dev: coin index out of rangeprecisions:uint256[2]=self._get_precisions()price_scale:uint256=self.price_scale*precisions[1]xp:uint256[N_COINS]=self.balancesA_gamma:uint256[2]=self._A_gamma()D:uint256=self.Difself.future_A_gamma_time>0:D=self.newton_D(A_gamma[0],A_gamma[1],self.xp())xp[i]+=dxxp=[xp[0]*precisions[0],xp[1]*price_scale/PRECISION]y:uint256=self.newton_y(A_gamma[0],A_gamma[1],xp,D,j)dy:uint256=xp[j]-y-1xp[j]=yifj>0:dy=dy*PRECISION/price_scaleelse:dy/=precisions[0]dy-=self._fee(xp)*dy/10**10returndy
>>>CryptoSwap.get_dy(0,1,1e18)# get_dy: 1 ETH for dy PRISMA2244836869048665161301
Function to remove liquidity from the pool and burn the lp tokens. When removing liquidity via this function, no fees are charged as the coins are withdrawin in balanced proportions.
Emits: RemoveLiquidity
Input
Type
Description
_amount
uint256[N_COINS]
amount of lp tokens to burn
min_amounts
uint256[N_COINS]
minimum amounts of token to withdraw
use_eth
bool
True = withdraw ETH, False = withdraw wETH
receiver
address
receiver of the coins; defaults to msg.sender
Source code
eventRemoveLiquidity:provider:indexed(address)token_amounts:uint256[N_COINS]token_supply:uint256N_COINS:constant(int128)=2@external@nonreentrant('lock')defremove_liquidity(_amount:uint256,min_amounts:uint256[N_COINS],use_eth:bool=False,receiver:address=msg.sender):""" This withdrawal method is very safe, does no complex math """lp_token:address=self.tokentotal_supply:uint256=CurveToken(lp_token).totalSupply()CurveToken(lp_token).burnFrom(msg.sender,_amount)balances:uint256[N_COINS]=self.balancesamount:uint256=_amount-1# Make rounding errors favoring other LPs a tiny bitforiinrange(N_COINS):d_balance:uint256=balances[i]*amount/total_supplyassertd_balance>=min_amounts[i]self.balances[i]=balances[i]-d_balancebalances[i]=d_balance# now it's the amounts going outcoin:address=self.coins[i]ifuse_ethandcoin==WETH20:raw_call(receiver,b"",value=d_balance)else:ifcoin==WETH20:WETH(WETH20).deposit(value=d_balance)response:Bytes[32]=raw_call(coin,_abi_encode(receiver,d_balance,method_id=method_id("transfer(address,uint256)")),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)D:uint256=self.Dself.D=D-D*amount/total_supplylogRemoveLiquidity(msg.sender,balances,total_supply-_amount)
Method to calculate the amount of output token i when burning token_amount of lp tokens, taking fees into condsideration.
Returns: amount of token received (uint256).
Input
Type
Description
token_amount
uint256
amount of lp tokens burned
i
uint256
index of the coin to withdraw
Note
This method takes fees into consideration.
Source code
N_COINS:constant(int128)=2@view@externaldefcalc_withdraw_one_coin(token_amount:uint256,i:uint256)->uint256:returnself._calc_withdraw_one_coin(self._A_gamma(),token_amount,i,True,False)[0]@internal@viewdef_calc_withdraw_one_coin(A_gamma:uint256[2],token_amount:uint256,i:uint256,update_D:bool,calc_price:bool)->(uint256,uint256,uint256,uint256[N_COINS]):token_supply:uint256=CurveToken(self.token).totalSupply()asserttoken_amount<=token_supply# dev: token amount more than supplyasserti<N_COINS# dev: coin out of rangexx:uint256[N_COINS]=self.balancesD0:uint256=0precisions:uint256[2]=self._get_precisions()price_scale_i:uint256=self.price_scale*precisions[1]xp:uint256[N_COINS]=[xx[0]*precisions[0],xx[1]*price_scale_i/PRECISION]ifi==0:price_scale_i=PRECISION*precisions[0]ifupdate_D:D0=self.newton_D(A_gamma[0],A_gamma[1],xp)else:D0=self.DD:uint256=D0# Charge the fee on D, not on y, e.g. reducing invariant LESS than charging the userfee:uint256=self._fee(xp)dD:uint256=token_amount*D/token_supplyD-=(dD-(fee*dD/(2*10**10)+1))y:uint256=self.newton_y(A_gamma[0],A_gamma[1],xp,D,i)dy:uint256=(xp[i]-y)*PRECISION/price_scale_ixp[i]=y# Price calcp:uint256=0ifcalc_priceanddy>10**5andtoken_amount>10**5:# p_i = dD / D0 * sum'(p_k * x_k) / (dy - dD / D0 * y0)S:uint256=0precision:uint256=precisions[0]ifi==1:S=xx[0]*precisions[0]precision=precisions[1]else:S=xx[1]*precisions[1]S=S*dD/D0p=S*PRECISION/(dy*precision-dD*xx[i]*precision/D0)ifi==0:p=(10**18)**2/preturndy,p,D,xp
>>>CryptoSwap.calc_withdraw_one_coin(1000000000000000000,0)# withdraw 1 LP token in coin[0]43347133051647883
Oracle prices are updated whenever the tweak_price function is called. This occurs when any of the _exchange(), add_liquidity(), or remove_liquidity_one_coin() functions are called.
Getter for the last price of the coin at index k with regard to the coin at index 0. last_price stores the last price when calling the functions _exchange(), add_liquidity() or remove_liquitiy_one_coin().
Getter for the price scale of the coin at index k with regard to the coin at index 0. Price scale determines the price band around which liquidity is concentrated and is conditionally updated when calling the functions _exchange(), add_liquidity() or remove_liquitiy_one_coin().
Getter for the 'fee-gamma'. This parameter modifies the rate at which fees rise as imbalance intensifies. Smaller values result in rapid fee hikes with growing imbalances, while larger values lead to more gradual increments in fees as imbalance expands.
Function to claim admin fees from the pool and send them to the fee receiver. fee_receiver is set within the Factory.
Emits: ClaimAdminFee
Source code
eventClaimAdminFee:admin:indexed(address)tokens:uint256@external@nonreentrant('lock')defclaim_admin_fees():self._claim_admin_fees()@internaldef_claim_admin_fees():A_gamma:uint256[2]=self._A_gamma()xcp_profit:uint256=self.xcp_profitxcp_profit_a:uint256=self.xcp_profit_a# Gulp hereforiinrange(N_COINS):coin:address=self.coins[i]ifcoin==WETH20:self.balances[i]=self.balanceelse:self.balances[i]=ERC20(coin).balanceOf(self)vprice:uint256=self.virtual_priceifxcp_profit>xcp_profit_a:fees:uint256=(xcp_profit-xcp_profit_a)*self.admin_fee/(2*10**10)iffees>0:receiver:address=Factory(self.factory).fee_receiver()ifreceiver!=ZERO_ADDRESS:frac:uint256=vprice*10**18/(vprice-fees)-10**18claimed:uint256=CurveToken(self.token).mint_relative(receiver,frac)xcp_profit-=fees*2self.xcp_profit=xcp_profitlogClaimAdminFee(receiver,claimed)total_supply:uint256=CurveToken(self.token).totalSupply()# Recalculate D b/c we gulpedD:uint256=self.newton_D(A_gamma[0],A_gamma[1],self.xp())self.D=Dself.virtual_price=10**18*self.get_xcp(D)/total_supplyifxcp_profit>xcp_profit_a:self.xcp_profit_a=xcp_profit
Curve v2 pools adaptively adjust liquidity to optimize depth near prevailing market prices, thereby reducing slippage. This is achieved by maintaining a continuous EMA (exponential moving average) of the pool's recent exchange rates (termed "internal oracle"), and relocating liquidity around this EMA when it's economically sensible for LPs.
You can envision this mechanism as "resetting" the bonding curve to align the peak liquidity concentration (the curve's center) with the EMA. The price with the highest liquidity focus is termed the "price scale", while the ongoing EMA is labeled as the "price oracle."
The price scaling parameters can be adjusted by the admin of the pool, see here.
Similar to many AMMs, Curve v2 employs a bonding curve to determine asset prices based on the pool's availability of each asset. To centralize liquidity near the bonding curve's midpoint, Curve v2 utilizes an invariant that sits between the StableSwap (Curve v1) and the constant-product models (like Uniswap, Balancer, and others).
The bonding curve parameters can be adjusted by the admin of the pool, see here.
Function to find the D invariant using Newton method.
Returns: D invariant (uint256).
Input
Type
Description
AMN
uint256
ANN = A * N**N
gamma
uint256
AMM.gamma() value
x_unsorted
uint256[N_COINS]
unsorted array of coin balances
Source code
N_COINS:constant(int128)=2@internal@viewdefnewton_D(ANN:uint256,gamma:uint256,x_unsorted:uint256[N_COINS])->uint256:""" Finding the invariant using Newton method. ANN is higher by the factor A_MULTIPLIER ANN is already A * N**N Currently uses 60k gas """# Safety checksassertANN>MIN_A-1andANN<MAX_A+1# dev: unsafe values Aassertgamma>MIN_GAMMA-1andgamma<MAX_GAMMA+1# dev: unsafe values gamma# Initial value of invariant D is that for constant-product invariantx:uint256[N_COINS]=x_unsortedifx[0]<x[1]:x=[x_unsorted[1],x_unsorted[0]]assertx[0]>10**9-1andx[0]<10**15*10**18+1# dev: unsafe values x[0]assertx[1]*10**18/x[0]>10**14-1# dev: unsafe values x[i] (input)D:uint256=N_COINS*self.geometric_mean(x,False)S:uint256=x[0]+x[1]foriinrange(255):D_prev:uint256=D# K0: uint256 = 10**18# for _x in x:# K0 = K0 * _x * N_COINS / D# collapsed for 2 coinsK0:uint256=(10**18*N_COINS**2)*x[0]/D*x[1]/D_g1k0:uint256=gamma+10**18if_g1k0>K0:_g1k0=_g1k0-K0+1else:_g1k0=K0-_g1k0+1# D / (A * N**N) * _g1k0**2 / gamma**2mul1:uint256=10**18*D/gamma*_g1k0/gamma*_g1k0*A_MULTIPLIER/ANN# 2*N*K0 / _g1k0mul2:uint256=(2*10**18)*N_COINS*K0/_g1k0neg_fprime:uint256=(S+S*mul2/10**18)+mul1*N_COINS/K0-mul2*D/10**18# D -= f / fprimeD_plus:uint256=D*(neg_fprime+S)/neg_fprimeD_minus:uint256=D*D/neg_fprimeif10**18>K0:D_minus+=D*(mul1/neg_fprime)/10**18*(10**18-K0)/K0else:D_minus-=D*(mul1/neg_fprime)/10**18*(K0-10**18)/K0ifD_plus>D_minus:D=D_plus-D_minuselse:D=(D_minus-D_plus)/2diff:uint256=0ifD>D_prev:diff=D-D_prevelse:diff=D_prev-Difdiff*10**14<max(10**16,D):# Could reduce precision for gas efficiency here# Test that we are safe with the next newton_yfor_xinx:frac:uint256=_x*10**18/Dassert(frac>10**16-1)and(frac<10**20+1)# dev: unsafe values x[i]returnDraise"Did not converge"