The CurveRegistryExchange contract serves as a router for Curve liquidity pools. Users can utilize this contract to find pools, query exchange rates, and execute swaps directly.
Outdated: Does not consider all liquidity pools
The CurveRegistryExchange contract was deployed in December 2022 at 0x99a58482bd75cbab83b27ec03ca68ff489b5788f on the Ethereum Mainnet. This contract does not support all pools due to newer versions of exchange contracts.
A new and updated version is available here: CurveRouterNG.
This contract only considers liquidity sources that have been added to it. These sources are primarily the liquidity pools registered in factory_registry and crypto_registry.
The contract offers three distinct functions to facilitate token exchanges:
exchange: Conducts a regular exchange using a specified pool.
exchange_with_best_rate: Executes a token exchange using the pool that offers the best rate.
exchange_multiple: Allows up to four token exchanges in a single transaction.
Function to perform a token exchange using a specific pool. Prior to calling this function, the caller must approve this contract to transfer _amount of coins from _from.
Returns: amount received (uint256)
Emits: TokenExchange
Input
Type
Description
_pool
address
Address of the pool to use for the token exchange.
_from
address
Address of the coin being sent.
_to
address
Address of the coin being received.
_amount
uint256
Amount of coins to exchange.
_expected
uint256
Minimum amount of coins to receive.
_receiver
address
Receiver of the tokens. Defaults to msg.sender.
Source code
eventTokenExchange:buyer:indexed(address)receiver:indexed(address)pool:indexed(address)token_sold:addresstoken_bought:addressamount_sold:uint256amount_bought:uint256@payable@external@nonreentrant("lock")defexchange(_pool:address,_from:address,_to:address,_amount:uint256,_expected:uint256,_receiver:address=msg.sender,)->uint256:""" @notice Perform an exchange using a specific pool @dev Prior to calling this function, the caller must approve this contract to transfer `_amount` coins from `_from` Works for both regular and factory-deployed pools @param _pool Address of the pool to use for the swap @param _from Address of coin being sent @param _to Address of coin being received @param _amount Quantity of `_from` being sent @param _expected Minimum quantity of `_from` received in order for the transaction to succeed @param _receiver Address to transfer the received tokens to @return uint256 Amount received """if_from==ETH_ADDRESS:assert_amount==msg.value,"Incorrect ETH amount"else:assertmsg.value==0,"Incorrect ETH amount"ifRegistry(self.crypto_registry).get_lp_token(_pool)!=ZERO_ADDRESS:returnself._crypto_exchange(_pool,_from,_to,_amount,_expected,msg.sender,_receiver)registry:address=self.registryifRegistry(registry).get_lp_token(_pool)==ZERO_ADDRESS:registry=self.factory_registryreturnself._exchange(registry,_pool,_from,_to,_amount,_expected,msg.sender,_receiver)@internaldef_crypto_exchange(_pool:address,_from:address,_to:address,_amount:uint256,_expected:uint256,_sender:address,_receiver:address,)->uint256:assertnotself.is_killedinitial:address=_fromtarget:address=_toif_from==ETH_ADDRESS:initial=WETH_ADDRESSif_to==ETH_ADDRESS:target=WETH_ADDRESSeth_amount:uint256=0received_amount:uint256=0i:uint256=0j:uint256=0i,j=CryptoRegistry(self.crypto_registry).get_coin_indices(_pool,initial,target)# dev: no market# perform / verify input transferif_from==ETH_ADDRESS:eth_amount=_amountelse:response:Bytes[32]=raw_call(_from,_abi_encode(_sender,self,_amount,method_id=method_id("transferFrom(address,address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)# approve input tokenifnotself.is_approved[_from][_pool]:response:Bytes[32]=raw_call(_from,_abi_encode(_pool,MAX_UINT256,method_id=method_id("approve(address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)self.is_approved[_from][_pool]=True# perform coin exchangeifETH_ADDRESSin[_from,_to]:CryptoPoolETH(_pool).exchange(i,j,_amount,_expected,True,value=eth_amount)else:CryptoPool(_pool).exchange(i,j,_amount,_expected)# perform output transferif_to==ETH_ADDRESS:received_amount=self.balanceraw_call(_receiver,b"",value=self.balance)else:received_amount=ERC20(_to).balanceOf(self)response:Bytes[32]=raw_call(_to,_abi_encode(_receiver,received_amount,method_id=method_id("transfer(address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)logTokenExchange(_sender,_receiver,_pool,_from,_to,_amount,received_amount)returnreceived_amount@internaldef_exchange(_registry:address,_pool:address,_from:address,_to:address,_amount:uint256,_expected:uint256,_sender:address,_receiver:address,)->uint256:assertnotself.is_killedeth_amount:uint256=0received_amount:uint256=0i:int128=0j:int128=0is_underlying:bool=Falsei,j,is_underlying=Registry(_registry).get_coin_indices(_pool,_from,_to)# dev: no marketifis_underlyingand_registry==self.factory_registry:ifRegistry(_registry).is_meta(_pool):base_coins:address[2]=self.base_coins[_pool]ifbase_coins[0]==empty(address)andbase_coins[1]==empty(address):base_coins=[CurvePool(_pool).coins(0),CurvePool(_pool).coins(1)]self.base_coins[_pool]=base_coins# we only need to use exchange underlying if the input or output is not in the base coinsis_underlying=_fromnotinbase_coinsor_tonotinbase_coinselse:# not a metapool so no underlying exchange methodis_underlying=False# perform / verify input transferif_from==ETH_ADDRESS:eth_amount=_amountelse:response:Bytes[32]=raw_call(_from,_abi_encode(_sender,self,_amount,method_id=method_id("transferFrom(address,address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)# approve input tokenif_from!=ETH_ADDRESSandnotself.is_approved[_from][_pool]:response:Bytes[32]=raw_call(_from,_abi_encode(_pool,MAX_UINT256,method_id=method_id("approve(address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)self.is_approved[_from][_pool]=True# perform coin exchangeifis_underlying:CurvePool(_pool).exchange_underlying(i,j,_amount,_expected,value=eth_amount)else:CurvePool(_pool).exchange(i,j,_amount,_expected,value=eth_amount)# perform output transferif_to==ETH_ADDRESS:received_amount=self.balanceraw_call(_receiver,b"",value=self.balance)else:received_amount=ERC20(_to).balanceOf(self)response:Bytes[32]=raw_call(_to,_abi_encode(_receiver,received_amount,method_id=method_id("transfer(address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)logTokenExchange(_sender,_receiver,_pool,_from,_to,_amount,received_amount)returnreceived_amount
This method does not check rates in factory-deployed pools.
Function to perform a token exchange using the pool that offers the best rate. Prior to calling this function, the caller must approve this contract to transfer _amount of coins from _from.
Returns: received amount of the output token (uint256).
Emits: TokenExchange
Input
Type
Description
_from
address
Address of the coin being sent.
_to
address
Address of the coin being received.
_amount
uint256
Amount of coins to exchange.
_expected
uint256
Minimum amount of coins to receive.
_receiver
address
Receiver of the tokens. Defaults to msg.sender.
Source code
eventTokenExchange:buyer:indexed(address)receiver:indexed(address)pool:indexed(address)token_sold:addresstoken_bought:addressamount_sold:uint256amount_bought:uint256@payable@external@nonreentrant("lock")defexchange_with_best_rate(_from:address,_to:address,_amount:uint256,_expected:uint256,_receiver:address=msg.sender,)->uint256:""" @notice Perform an exchange using the pool that offers the best rate @dev Prior to calling this function, the caller must approve this contract to transfer `_amount` coins from `_from` Does NOT check rates in factory-deployed pools @param _from Address of coin being sent @param _to Address of coin being received @param _amount Quantity of `_from` being sent @param _expected Minimum quantity of `_from` received in order for the transaction to succeed @param _receiver Address to transfer the received tokens to @return uint256 Amount received """if_from==ETH_ADDRESS:assert_amount==msg.value,"Incorrect ETH amount"else:assertmsg.value==0,"Incorrect ETH amount"registry:address=self.registrybest_pool:address=ZERO_ADDRESSmax_dy:uint256=0foriinrange(65536):pool:address=Registry(registry).find_pool_for_coins(_from,_to,i)ifpool==ZERO_ADDRESS:breakdy:uint256=self._get_exchange_amount(registry,pool,_from,_to,_amount)ifdy>max_dy:best_pool=poolmax_dy=dyreturnself._exchange(registry,best_pool,_from,_to,_amount,_expected,msg.sender,_receiver)@view@internaldef_get_exchange_amount(_registry:address,_pool:address,_from:address,_to:address,_amount:uint256)->uint256:""" @notice Get the current number of coins received in an exchange @param _registry Registry address @param _pool Pool address @param _from Address of coin to be sent @param _to Address of coin to be received @param _amount Quantity of `_from` to be sent @return Quantity of `_to` to be received """i:int128=0j:int128=0is_underlying:bool=Falsei,j,is_underlying=Registry(_registry).get_coin_indices(_pool,_from,_to)# dev: no marketifis_underlyingand(_registry==self.registryorRegistry(_registry).is_meta(_pool)):returnCurvePool(_pool).get_dy_underlying(i,j,_amount)returnCurvePool(_pool).get_dy(i,j,_amount)@internaldef_exchange(_registry:address,_pool:address,_from:address,_to:address,_amount:uint256,_expected:uint256,_sender:address,_receiver:address,)->uint256:assertnotself.is_killedeth_amount:uint256=0received_amount:uint256=0i:int128=0j:int128=0is_underlying:bool=Falsei,j,is_underlying=Registry(_registry).get_coin_indices(_pool,_from,_to)# dev: no marketifis_underlyingand_registry==self.factory_registry:ifRegistry(_registry).is_meta(_pool):base_coins:address[2]=self.base_coins[_pool]ifbase_coins[0]==empty(address)andbase_coins[1]==empty(address):base_coins=[CurvePool(_pool).coins(0),CurvePool(_pool).coins(1)]self.base_coins[_pool]=base_coins# we only need to use exchange underlying if the input or output is not in the base coinsis_underlying=_fromnotinbase_coinsor_tonotinbase_coinselse:# not a metapool so no underlying exchange methodis_underlying=False# perform / verify input transferif_from==ETH_ADDRESS:eth_amount=_amountelse:response:Bytes[32]=raw_call(_from,_abi_encode(_sender,self,_amount,method_id=method_id("transferFrom(address,address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)# approve input tokenif_from!=ETH_ADDRESSandnotself.is_approved[_from][_pool]:response:Bytes[32]=raw_call(_from,_abi_encode(_pool,MAX_UINT256,method_id=method_id("approve(address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)self.is_approved[_from][_pool]=True# perform coin exchangeifis_underlying:CurvePool(_pool).exchange_underlying(i,j,_amount,_expected,value=eth_amount)else:CurvePool(_pool).exchange(i,j,_amount,_expected,value=eth_amount)# perform output transferif_to==ETH_ADDRESS:received_amount=self.balanceraw_call(_receiver,b"",value=self.balance)else:received_amount=ERC20(_to).balanceOf(self)response:Bytes[32]=raw_call(_to,_abi_encode(_receiver,received_amount,method_id=method_id("transfer(address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)logTokenExchange(_sender,_receiver,_pool,_from,_to,_amount,received_amount)returnreceived_amount
Function to perform up to four token exchanges in a single transaction. Prior to calling this function, the caller must approve this contract to transfer _amount coins from _from.
Returns: expected amount of the final output token (uint256).
Emits: ExchangeMultiple
Swap Type
Description
1
Stableswap exchange
2
Stableswap exchange_underlying
3
Cryptoswap exchange
4
Cryptoswap exchange_underlying
5
Factory metapools with lending base pool: exchange_underlying
6
Factory crypto-meta pools underlying exchange (exchange method in zap)
7 - 11
For wrapped coin (underlying for lending pool) -> LP token "exchange" (actually add_liquidity)
12 - 14
For LP token -> wrapped coin (underlying for lending or fake pool) "exchange" (actually remove_liquidity_one_coin)
15
For WETH -> ETH "exchange" (actually deposit/withdraw)
Input
Type
Description
_route
address[9]
Array of the route consisting of [initial token, pool, token, pool, token, ...].
_swap_params
uint256[3][4]
Multidimensional array of [i, j, swap_type] where i and j are the correct values for the n'th pool in _route.
_amount
uint256
Amount of initial tokens (_route[0]) to be exchanged.
_expected
uint256
Minimum amount of coins to receive.
_pools
address[4]
Array of pools for swaps via zap contracts. This parameter is only needed for Polygon meta-factories underlying swaps.
_receiver
address
Receiver of the tokens. Defaults to msg.sender.
Source code
eventExchangeMultiple:buyer:indexed(address)receiver:indexed(address)route:address[9]swap_params:uint256[3][4]pools:address[4]amount_sold:uint256amount_bought:uint256@external@payabledefexchange_multiple(_route:address[9],_swap_params:uint256[3][4],_amount:uint256,_expected:uint256,_pools:address[4]=[ZERO_ADDRESS,ZERO_ADDRESS,ZERO_ADDRESS,ZERO_ADDRESS],_receiver:address=msg.sender)->uint256:""" @notice Perform up to four swaps in a single transaction @dev Routing and swap params must be determined off-chain. This functionality is designed for gas efficiency over ease-of-use. @param _route Array of [initial token, pool, token, pool, token, ...] The array is iterated until a pool address of 0x00, then the last given token is transferred to `_receiver` @param _swap_params Multidimensional array of [i, j, swap type] where i and j are the correct values for the n'th pool in `_route`. The swap type should be 1 for a stableswap `exchange`, 2 for stableswap `exchange_underlying`, 3 for a cryptoswap `exchange`, 4 for a cryptoswap `exchange_underlying`, 5 for factory metapools with lending base pool `exchange_underlying`, 6 for factory crypto-meta pools underlying exchange (`exchange` method in zap), 7-11 for wrapped coin (underlying for lending or fake pool) -> LP token "exchange" (actually `add_liquidity`), 12-14 for LP token -> wrapped coin (underlying for lending pool) "exchange" (actually `remove_liquidity_one_coin`) 15 for WETH -> ETH "exchange" (actually deposit/withdraw) @param _amount The amount of `_route[0]` token being sent. @param _expected The minimum amount received after the final swap. @param _pools Array of pools for swaps via zap contracts. This parameter is only needed for Polygon meta-factories underlying swaps. @param _receiver Address to transfer the final output token to. @return Received amount of the final output token """input_token:address=_route[0]amount:uint256=_amountoutput_token:address=ZERO_ADDRESS# validate / transfer initial tokenifinput_token==ETH_ADDRESS:assertmsg.value==amountelse:assertmsg.value==0response:Bytes[32]=raw_call(input_token,_abi_encode(msg.sender,self,amount,method_id=method_id("transferFrom(address,address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)foriinrange(1,5):# 4 rounds of iteration to perform up to 4 swapsswap:address=_route[i*2-1]pool:address=_pools[i-1]# Only for Polygon meta-factories underlying swap (swap_type == 4)output_token=_route[i*2]params:uint256[3]=_swap_params[i-1]# i, j, swap typeifnotself.is_approved[input_token][swap]:# approve the pool to transfer the input tokenresponse:Bytes[32]=raw_call(input_token,_abi_encode(swap,MAX_UINT256,method_id=method_id("approve(address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)self.is_approved[input_token][swap]=Trueeth_amount:uint256=0ifinput_token==ETH_ADDRESS:eth_amount=amount# perform the swap according to the swap typeifparams[2]==1:CurvePool(swap).exchange(convert(params[0],int128),convert(params[1],int128),amount,0,value=eth_amount)elifparams[2]==2:CurvePool(swap).exchange_underlying(convert(params[0],int128),convert(params[1],int128),amount,0,value=eth_amount)elifparams[2]==3:ifinput_token==ETH_ADDRESSoroutput_token==ETH_ADDRESS:CryptoPoolETH(swap).exchange(params[0],params[1],amount,0,True,value=eth_amount)else:CryptoPool(swap).exchange(params[0],params[1],amount,0)elifparams[2]==4:CryptoPool(swap).exchange_underlying(params[0],params[1],amount,0,value=eth_amount)elifparams[2]==5:LendingBasePoolMetaZap(swap).exchange_underlying(pool,convert(params[0],int128),convert(params[1],int128),amount,0)elifparams[2]==6:use_eth:bool=input_token==ETH_ADDRESSoroutput_token==ETH_ADDRESSCryptoMetaZap(swap).exchange(pool,params[0],params[1],amount,0,use_eth)elifparams[2]==7:_amounts:uint256[2]=[0,0]_amounts[params[0]]=amountBasePool2Coins(swap).add_liquidity(_amounts,0)elifparams[2]==8:_amounts:uint256[3]=[0,0,0]_amounts[params[0]]=amountBasePool3Coins(swap).add_liquidity(_amounts,0)elifparams[2]==9:_amounts:uint256[3]=[0,0,0]_amounts[params[0]]=amountLendingBasePool3Coins(swap).add_liquidity(_amounts,0,True)# example: aave on Polygonelifparams[2]==10:_amounts:uint256[4]=[0,0,0,0]_amounts[params[0]]=amountBasePool4Coins(swap).add_liquidity(_amounts,0)elifparams[2]==11:_amounts:uint256[5]=[0,0,0,0,0]_amounts[params[0]]=amountBasePool5Coins(swap).add_liquidity(_amounts,0)elifparams[2]==12:# The number of coins doesn't matter hereBasePool3Coins(swap).remove_liquidity_one_coin(amount,convert(params[1],int128),0)elifparams[2]==13:# The number of coins doesn't matter hereLendingBasePool3Coins(swap).remove_liquidity_one_coin(amount,convert(params[1],int128),0,True)# example: aave on Polygonelifparams[2]==14:# The number of coins doesn't matter hereCryptoBasePool3Coins(swap).remove_liquidity_one_coin(amount,params[1],0)# example: atricrypto3 on Polygonelifparams[2]==15:ifinput_token==ETH_ADDRESS:wETH(swap).deposit(value=amount)elifoutput_token==ETH_ADDRESS:wETH(swap).withdraw(amount)else:raise"One of the coins must be ETH for swap type 15"else:raise"Bad swap type"# update the amount receivedifoutput_token==ETH_ADDRESS:amount=self.balanceelse:amount=ERC20(output_token).balanceOf(self)# sanity check, if the routing data is incorrect we will have a 0 balance and that is badassertamount!=0,"Received nothing"# check if this was the last swapifi==4or_route[i*2+1]==ZERO_ADDRESS:break# if there is another swap, the output token becomes the input for the next roundinput_token=output_token# validate the final amount receivedassertamount>=_expected# transfer the final token to the receiverifoutput_token==ETH_ADDRESS:raw_call(_receiver,b"",value=amount)else:response:Bytes[32]=raw_call(output_token,_abi_encode(_receiver,amount,method_id=method_id("transfer(address,uint256)"),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)logExchangeMultiple(msg.sender,_receiver,_route,_swap_params,_pools,_amount,amount)returnamount
Getter for the current number of coins received in an exchange.
Returns: amount of tokens received (uint256).
Input
Type
Description
_pool
address
Pool address.
_from
address
Address of the coin being sent.
_to
address
Address of the coin being received.
_amount
uint256
Amount of coins to exchange.
Source code
@view@externaldefget_exchange_amount(_pool:address,_from:address,_to:address,_amount:uint256)->uint256:""" @notice Get the current number of coins received in an exchange @dev Works for both regular and factory-deployed pools @param _pool Pool address @param _from Address of coin to be sent @param _to Address of coin to be received @param _amount Quantity of `_from` to be sent @return Quantity of `_to` to be received """registry:address=self.crypto_registryifRegistry(registry).get_lp_token(_pool)!=ZERO_ADDRESS:initial:address=_fromtarget:address=_toif_from==ETH_ADDRESS:initial=WETH_ADDRESSif_to==ETH_ADDRESS:target=WETH_ADDRESSreturnself._get_crypto_exchange_amount(registry,_pool,initial,target,_amount)registry=self.registryifRegistry(registry).get_lp_token(_pool)==ZERO_ADDRESS:registry=self.factory_registryreturnself._get_exchange_amount(registry,_pool,_from,_to,_amount)@view@internaldef_get_exchange_amount(_registry:address,_pool:address,_from:address,_to:address,_amount:uint256)->uint256:""" @notice Get the current number of coins received in an exchange @param _registry Registry address @param _pool Pool address @param _from Address of coin to be sent @param _to Address of coin to be received @param _amount Quantity of `_from` to be sent @return Quantity of `_to` to be received """i:int128=0j:int128=0is_underlying:bool=Falsei,j,is_underlying=Registry(_registry).get_coin_indices(_pool,_from,_to)# dev: no marketifis_underlyingand(_registry==self.registryorRegistry(_registry).is_meta(_pool)):returnCurvePool(_pool).get_dy_underlying(i,j,_amount)returnCurvePool(_pool).get_dy(i,j,_amount)@view@internaldef_get_crypto_exchange_amount(_registry:address,_pool:address,_from:address,_to:address,_amount:uint256)->uint256:""" @notice Get the current number of coins received in an exchange @param _registry Registry address @param _pool Pool address @param _from Address of coin to be sent @param _to Address of coin to be received @param _amount Quantity of `_from` to be sent @return Quantity of `_to` to be received """i:uint256=0j:uint256=0i,j=CryptoRegistry(_registry).get_coin_indices(_pool,_from,_to)# dev: no marketreturnCryptoPool(_pool).get_dy(i,j,_amount)
>>>CurveRegistryExchange.get_exchange_amount('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7','0x6b175474e89094c44da98b954eedeac495271d0f','0xdac17f958d2ee523a2206206994597c13d831ec7',1000000000000000000000000)1000242275074# swapping DAI for USDT using threepool>>>CurveRegistryExchange.get_exchange_amount('0xd51a44d3fae010294c616388b506acda1bfaae46','0xdac17f958d2ee523a2206206994597c13d831ec7','0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',10000000000)3315937652359468906# swapping USDT for ETH using tricrypto2
Function to find the pool offering the best rate for a given swap.
Returns: pool address and amount received (address, uint256).
Input
Type
Description
_from
address
Address of the coin being sent.
_to
address
Address of the coin being received.
_amount
uint256
Amount of coins to send.
_exclude_pools
address[8]
List of up to 8 pools which should not be returned.
Source code
@view@externaldefget_best_rate(_from:address,_to:address,_amount:uint256,_exclude_pools:address[8]=EMPTY_POOL_LIST)->(address,uint256):""" @notice Find the pool offering the best rate for a given swap. @dev Checks rates for regular and factory pools @param _from Address of coin being sent @param _to Address of coin being received @param _amount Quantity of `_from` being sent @param _exclude_pools A list of up to 8 addresses which shouldn't be returned @return Pool address, amount received """best_pool:address=ZERO_ADDRESSmax_dy:uint256=0initial:address=_fromtarget:address=_toif_from==ETH_ADDRESS:initial=WETH_ADDRESSif_to==ETH_ADDRESS:target=WETH_ADDRESSregistry:address=self.crypto_registryforiinrange(65536):pool:address=Registry(registry).find_pool_for_coins(initial,target,i)ifpool==ZERO_ADDRESS:ifi==0:# we only check for stableswap pools if we did not find any crypto poolsbreakreturnbest_pool,max_dyelifpoolin_exclude_pools:continuedy:uint256=self._get_crypto_exchange_amount(registry,pool,initial,target,_amount)ifdy>max_dy:best_pool=poolmax_dy=dyregistry=self.registryforiinrange(65536):pool:address=Registry(registry).find_pool_for_coins(_from,_to,i)ifpool==ZERO_ADDRESS:breakelifpoolin_exclude_pools:continuedy:uint256=self._get_exchange_amount(registry,pool,_from,_to,_amount)ifdy>max_dy:best_pool=poolmax_dy=dyregistry=self.factory_registryforiinrange(65536):pool:address=Registry(registry).find_pool_for_coins(_from,_to,i)ifpool==ZERO_ADDRESS:breakelifpoolin_exclude_pools:continueifERC20(pool).totalSupply()==0:# ignore pools without TVL as the call to `get_dy` will revertcontinuedy:uint256=self._get_exchange_amount(registry,pool,_from,_to,_amount)ifdy>max_dy:best_pool=poolmax_dy=dyreturnbest_pool,max_dy@view@internaldef_get_exchange_amount(_registry:address,_pool:address,_from:address,_to:address,_amount:uint256)->uint256:""" @notice Get the current number of coins received in an exchange @param _registry Registry address @param _pool Pool address @param _from Address of coin to be sent @param _to Address of coin to be received @param _amount Quantity of `_from` to be sent @return Quantity of `_to` to be received """i:int128=0j:int128=0is_underlying:bool=Falsei,j,is_underlying=Registry(_registry).get_coin_indices(_pool,_from,_to)# dev: no marketifis_underlyingand(_registry==self.registryorRegistry(_registry).is_meta(_pool)):returnCurvePool(_pool).get_dy_underlying(i,j,_amount)returnCurvePool(_pool).get_dy(i,j,_amount)@view@internaldef_get_crypto_exchange_amount(_registry:address,_pool:address,_from:address,_to:address,_amount:uint256)->uint256:""" @notice Get the current number of coins received in an exchange @param _registry Registry address @param _pool Pool address @param _from Address of coin to be sent @param _to Address of coin to be received @param _amount Quantity of `_from` to be sent @return Quantity of `_to` to be received """i:uint256=0j:uint256=0i,j=CryptoRegistry(_registry).get_coin_indices(_pool,_from,_to)# dev: no marketreturnCryptoPool(_pool).get_dy(i,j,_amount)
Getter for the amount of output tokens when performing a swap with multiple steps. The function iterates over the route addresses and, once the ZERO_ADDRESS is reached, the last given token is transferred to _receiver.
Swap Type
Description
1
Stableswap exchange
2
Stableswap exchange_underlying
3
Cryptoswap: exchange
4
Cryptoswap: exchange_underlying
5
Factory metapools with lending base pool: exchange_underlying
6
Factory crypto-meta pools underlying exchange (exchange method in zap)
7 - 11
For wrapped coin (underlying for lending pool) -> LP token "exchange" (actually add_liquidity)
12 - 14
For LP token -> wrapped coin (underlying for lending or fake pool) "exchange" (actually remove_liquidity_one_coin)
15
For WETH -> ETH "exchange" (actually deposit/withdraw)
Returns: expected amount of the final output token (uint256).
Input
Type
Description
_route
address[9]
Array of the route consisting of [initial token, pool, token, pool, token, ...].
_swap_params
uint256[3][4]
Multidimensional array of [i, j, swap_type] where i and j are the correct values for the n'th pool in _route.
_amount
uint256
Amount of initial tokens (_route[0]) to exchange.
_pools
address[4]
Array of pools for swaps via zap contracts. This parameter is only needed for Polygon meta-factories underlying swaps.
Source code
@view@externaldefget_exchange_multiple_amount(_route:address[9],_swap_params:uint256[3][4],_amount:uint256,_pools:address[4]=[ZERO_ADDRESS,ZERO_ADDRESS,ZERO_ADDRESS,ZERO_ADDRESS])->uint256:""" @notice Get the current number the final output tokens received in an exchange @dev Routing and swap params must be determined off-chain. This functionality is designed for gas efficiency over ease-of-use. @param _route Array of [initial token, pool, token, pool, token, ...] The array is iterated until a pool address of 0x00, then the last given token is transferred to `_receiver` @param _swap_params Multidimensional array of [i, j, swap type] where i and j are the correct values for the n'th pool in `_route`. The swap type should be 1 for a stableswap `exchange`, 2 for stableswap `exchange_underlying`, 3 for a cryptoswap `exchange`, 4 for a cryptoswap `exchange_underlying`, 5 for factory metapools with lending base pool `exchange_underlying`, 6 for factory crypto-meta pools underlying exchange (`exchange` method in zap), 7-11 for wrapped coin (underlying for lending pool) -> LP token "exchange" (actually `add_liquidity`), 12-14 for LP token -> wrapped coin (underlying for lending or fake pool) "exchange" (actually `remove_liquidity_one_coin`) 15 for WETH -> ETH "exchange" (actually deposit/withdraw) @param _amount The amount of `_route[0]` token to be sent. @param _pools Array of pools for swaps via zap contracts. This parameter is only needed for Polygon meta-factories underlying swaps. @return Expected amount of the final output token """amount:uint256=_amountforiinrange(1,5):# 4 rounds of iteration to perform up to 4 swapsswap:address=_route[i*2-1]pool:address=_pools[i-1]# Only for Polygon meta-factories underlying swap (swap_type == 4)params:uint256[3]=_swap_params[i-1]# i, j, swap type# Calc output amount according to the swap typeifparams[2]==1:amount=CurvePool(swap).get_dy(convert(params[0],int128),convert(params[1],int128),amount)elifparams[2]==2:amount=CurvePool(swap).get_dy_underlying(convert(params[0],int128),convert(params[1],int128),amount)elifparams[2]==3:amount=CryptoPool(swap).get_dy(params[0],params[1],amount)elifparams[2]==4:amount=CryptoPool(swap).get_dy_underlying(params[0],params[1],amount)elifparams[2]==5:amount=CurvePool(pool).get_dy_underlying(convert(params[0],int128),convert(params[1],int128),amount)elifparams[2]==6:amount=CryptoMetaZap(swap).get_dy(pool,params[0],params[1],amount)elifparams[2]==7:_amounts:uint256[2]=[0,0]_amounts[params[0]]=amountamount=BasePool2Coins(swap).calc_token_amount(_amounts,True)elifparams[2]in[8,9]:_amounts:uint256[3]=[0,0,0]_amounts[params[0]]=amountamount=BasePool3Coins(swap).calc_token_amount(_amounts,True)elifparams[2]==10:_amounts:uint256[4]=[0,0,0,0]_amounts[params[0]]=amountamount=BasePool4Coins(swap).calc_token_amount(_amounts,True)elifparams[2]==11:_amounts:uint256[5]=[0,0,0,0,0]_amounts[params[0]]=amountamount=BasePool5Coins(swap).calc_token_amount(_amounts,True)elifparams[2]in[12,13]:# The number of coins doesn't matter hereamount=BasePool3Coins(swap).calc_withdraw_one_coin(amount,convert(params[1],int128))elifparams[2]==14:# The number of coins doesn't matter hereamount=CryptoBasePool3Coins(swap).calc_withdraw_one_coin(amount,params[1])elifparams[2]==15:# ETH <--> WETH rate is 1:1passelse:raise"Bad swap type"# check if this was the last swapifi==4or_route[i*2+1]==ZERO_ADDRESS:breakreturnamount
The contract retrieves pool data from various registries sourced from the AddressProvider. Since its deployment, a new AddressProvider along with additional registries have been deployed.
registry:public(address)@externaldef__init__(_address_provider:address,_calculator:address,_weth:address):""" @notice Constructor function """self.address_provider=AddressProvider(_address_provider)self.registry=AddressProvider(_address_provider).get_registry()self.factory_registry=AddressProvider(_address_provider).get_address(3)self.crypto_registry=AddressProvider(_address_provider).get_address(5)self.default_calculator=_calculatorWETH_ADDRESS=_weth
factory_registry:public(address)@externaldef__init__(_address_provider:address,_calculator:address,_weth:address):""" @notice Constructor function """self.address_provider=AddressProvider(_address_provider)self.registry=AddressProvider(_address_provider).get_registry()self.factory_registry=AddressProvider(_address_provider).get_address(3)self.crypto_registry=AddressProvider(_address_provider).get_address(5)self.default_calculator=_calculatorWETH_ADDRESS=_weth
crypto_registry:public(address)@externaldef__init__(_address_provider:address,_calculator:address,_weth:address):""" @notice Constructor function """self.address_provider=AddressProvider(_address_provider)self.registry=AddressProvider(_address_provider).get_registry()self.factory_registry=AddressProvider(_address_provider).get_address(3)self.crypto_registry=AddressProvider(_address_provider).get_address(5)self.default_calculator=_calculatorWETH_ADDRESS=_weth
Function to update registry, factory_registry and crypto_registry. This function is callable by anyone and sets the variables to the current vaules in the AddressProvider contract.
Returns: True (bool).
Source code
address_provider:AddressProviderregistry:public(address)factory_registry:public(address)crypto_registry:public(address)@externaldefupdate_registry_address()->bool:""" @notice Update registry address @dev The registry address is kept in storage to reduce gas costs. If a new registry is deployed this function should be called to update the local address from the address provider. @return bool success """address_provider:address=self.address_provider.addressself.registry=AddressProvider(address_provider).get_registry()self.factory_registry=AddressProvider(address_provider).get_address(3)self.crypto_registry=AddressProvider(address_provider).get_address(5)returnTrue
Getter for the default calculator. The default calculator can be set by the admin of the AddressProvider contract using the set_default_calculator function.
Returns: calculator contract (address).
Source code
default_calculator:public(address)@externaldef__init__(_address_provider:address,_calculator:address,_weth:address):""" @notice Constructor function """self.address_provider=AddressProvider(_address_provider)self.registry=AddressProvider(_address_provider).get_registry()self.factory_registry=AddressProvider(_address_provider).get_address(3)self.crypto_registry=AddressProvider(_address_provider).get_address(5)self.default_calculator=_calculatorWETH_ADDRESS=_weth
Getter for the calculator contract of _pool. The calculator of pool can be set by the admin of the AddressProvider contract using the set_calculator function.
Returns: calculator contract (address).
Input
Type
Description
_pool
address
Liquidity pool address.
Source code
@view@externaldefget_calculator(_pool:address)->address:""" @notice Set calculator contract @dev Used to calculate `get_dy` for a pool @param _pool Pool address @return `CurveCalc` address """calculator:address=self.pool_calculator[_pool]ifcalculator==ZERO_ADDRESS:returnself.default_calculatorelse:returncalculator
This function is only callable by the admin of the AddressProvider contract.
Function to set a calculator contract for a pool.
Returns: True (bool).
Input
Type
Description
_pool
address
Liquidity pool to set the calculator for.
_calculator
address
Calculator contract.
Source code
pool_calculator:HashMap[address,address]@externaldefset_calculator(_pool:address,_calculator:address)->bool:""" @notice Set calculator contract @dev Used to calculate `get_dy` for a pool @param _pool Pool address @param _calculator `CurveCalc` address @return bool success """assertmsg.sender==self.address_provider.admin()# dev: admin-only functionself.pool_calculator[_pool]=_calculatorreturnTrue
This function is only callable by the admin of the AddressProvider contract.
Function to set the default calculator contract.
Returns: True (bool).
Input
Type
Description
_calculator
address
Calculator address.
Source code
default_calculator:public(address)@externaldefset_default_calculator(_calculator:address)->bool:""" @notice Set default calculator contract @dev Used to calculate `get_dy` for a pool @param _calculator `CurveCalc` address @return bool success """assertmsg.sender==self.address_provider.admin()# dev: admin-only functionself.default_calculator=_calculatorreturnTrue
The admin of the AddressProvider contract has the ability to set the is_killed status of the CurveRegistryExchange contract via the set_killed function. Setting this status to true disables all exchanges in the contract. The status can be reversed, or "unkilled," to allow token exchanges to resume.
This function is only callable by the admin of the AddressProvider contract.
Function to set the is_killed status for the contract.
Returns: True (bool).
Input
Type
Description
_is_killed
bool
true or false.
Source code
@externaldefset_killed(_is_killed:bool)->bool:""" @notice Kill or unkill the contract @param _is_killed Killed status of the contract @return bool success """assertmsg.sender==self.address_provider.admin()# dev: admin-only functionself.is_killed=_is_killedreturnTrue
In the event that the contract holds an ERC20 or ETH balance, these tokens can be claimed by the admin of the AddressProvider contract. Although this should not occur at all, a possible scenario in which this could happen is when users mistakenly send their tokens directly to the contract address.
This function is only callable by the admin of the AddressProvider contract.
Function to transfer an ERC20 or ETH balance held by this contract. When calling this function, the entire balance is transfered to the admin of the AddressProvider. This method can be used when tokens are mistakenly sent to the contract. Other than that, the contract does not hold any user assets.
Returns: True (bool).
Input
Type
Description
_token
address
Token to transfer.
Source code
@externaldefclaim_balance(_token:address)->bool:""" @notice Transfer an ERC20 or ETH balance held by this contract @dev The entire balance is transferred to the owner @param _token Token address @return bool success """assertmsg.sender==self.address_provider.admin()# dev: admin-only functionif_token==ETH_ADDRESS:raw_call(msg.sender,b"",value=self.balance)else:amount:uint256=ERC20(_token).balanceOf(self)response:Bytes[32]=raw_call(_token,concat(method_id("transfer(address,uint256)"),convert(msg.sender,bytes32),convert(amount,bytes32),),max_outsize=32,)iflen(response)!=0:assertconvert(response,bool)returnTrue