Curve pools may contain lending functionality, whereby the underlying tokens are lent out on other protocols (e.g., Compound or Yearn). Hence, the main difference to a plain pool is that a lending pool does not hold the underlying token itself, but a wrapped representation of it.
Currently, Curve supports the following lending pools:
An example of a Curve lending pool is Compound Pool, which contains the wrapped tokens cDAI and cUSDC, while the underlying tokens DAI and USDC are lent out on Compound. Liquidity providers of the Compound Pool therefore receive interest generated on Compound in addition to fees from token swaps in the pool.
Implementation of lending pools may differ with respect to how wrapped tokens accrue interest. There are two main types of wrapped tokens that are used by lending pools:
cToken-style tokens: These are tokens, such as interest-bearing cTokens on Compound (e.g., cDAI) or on yTokens on Yearn, where interest accrues as the rate of the token increases.
aToken-style tokens: These are tokens, such as aTokens on AAVE (e.g., aDAI), where interest accrues as the balance of the token increases.
The template source code for lending pools may be viewed on GitHub.
Note
Lending pools also implement the ABI from plain pools. Refer to the plan pools documentation for overlapping methods.
Like plain pools, lending pools have the exchange method. However, in the case of lending pools, calling exchange performs a swap between two wrapped tokens in the pool.
For example, calling exchange on the Compound Pool, would result in a swap between the wrapped tokens cDAI and cUSDC.
Perform an exchange between two underlying tokens. Index values can be found via the underlying_coins public getter method. Returns the actual amount of coin j received.
Input
Type
Description
i
int128
Index value for the underlying coin to send
j
int128
Index value of the underlying coin to receive
_dx
uint256
Amount of i being exchanged
_min_dy
uint256
Minimum amount of j to receive
Emits: TokenExchangeUnderlying
Source code
@external@nonreentrant('lock')defexchange_underlying(i:int128,j:int128,dx:uint256,min_dy:uint256)->uint256:""" @notice Perform an exchange between two underlying coins @dev Index values can be found via the `underlying_coins` public getter method @param i Index value for the underlying coin to send @param j Index valie of the underlying coin to recieve @param dx Amount of `i` being exchanged @param min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """dy:uint256=self._exchange(i,j,dx)assertdy>=min_dy,"Exchange resulted in fewer coins than expected"u_coin_i:address=self.underlying_coins[i]lending_pool:address=self.aave_lending_pool# transfer underlying coin from msg.sender to self_response:Bytes[32]=raw_call(u_coin_i,concat(method_id("transferFrom(address,address,uint256)"),convert(msg.sender,bytes32),convert(self,bytes32),convert(dx,bytes32)),max_outsize=32)iflen(_response)!=0:assertconvert(_response,bool)# deposit to aave lending poolraw_call(lending_pool,concat(method_id("deposit(address,uint256,address,uint16)"),convert(u_coin_i,bytes32),convert(dx,bytes32),convert(self,bytes32),convert(self.aave_referral,bytes32),))# withdraw `j` underlying from lending pool and transfer to callerLendingPool(lending_pool).withdraw(self.underlying_coins[j],dy,msg.sender)logTokenExchangeUnderlying(msg.sender,i,dx,j,dy)returndy
Older Curve lending pools may not implement the same signature for exchange_underlying. For instance, Compound pool does not return anything for exchange_underlying and therefore costs more in terms of gas.
The function signatures for adding and removing liquidity to a lending pool are mostly the same as for a plain pool. However, for lending pools, liquidity is added and removed in the wrapped token, not the underlying.
In order to be able to add and remove liquidity in the underlying token (e.g., remove DAI from Compound Pool instead of cDAI) there exists a Deposit<POOL>.vy contract (e.g., (DepositCompound.vy).
Warning
Older Curve lending pools (e.g., Compound Pool) do not implement all plain pool methods for adding and removing liquidity. For instance, remove_liquidity_one_coin is not implemented by Compound Pool).
Some newer pools (e.g., IB) have a modified signature for add_liquidity and allow the caller to specify whether the deposited liquidity is in the wrapped or underlying token.
Perform an exchange between two underlying tokens. Index values can be found via the underlying_coins public getter method. Returns amount of LP tokens received in exchange for the deposited tokens.
Input
Type
Description
_amounts
uint256[N_COINS]
List of amounts of coins to deposit
_min_mint_amount
uint256
Minimum amount of LP tokens to mint from the deposit
_use_underlying
bool
If True, deposit underlying assets instead of wrapped assets
Emits: AddLiquidity
Source code
@external@nonreentrant('lock')defadd_liquidity(_amounts:uint256[N_COINS],_min_mint_amount:uint256,_use_underlying:bool=False)->uint256:""" @notice Deposit coins into the pool @param _amounts List of amounts of coins to deposit @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit @param _use_underlying If True, deposit underlying assets instead of aTokens @return Amount of LP tokens received by depositing """assertnotself.is_killed# dev: is killed# Initial invariantamp:uint256=self._A()old_balances:uint256[N_COINS]=self._balances()lp_token:address=self.lp_tokentoken_supply:uint256=ERC20(lp_token).totalSupply()D0:uint256=0iftoken_supply!=0:D0=self.get_D_precisions(old_balances,amp)new_balances:uint256[N_COINS]=old_balancesforiinrange(N_COINS):iftoken_supply==0:assert_amounts[i]!=0# dev: initial deposit requires all coinsnew_balances[i]+=_amounts[i]# Invariant after changeD1:uint256=self.get_D_precisions(new_balances,amp)assertD1>D0# We need to recalculate the invariant accounting for fees# to calculate fair user's sharefees:uint256[N_COINS]=empty(uint256[N_COINS])mint_amount:uint256=0iftoken_supply!=0:# Only account for fees if we are not the first to depositys:uint256=(D0+D1)/N_COINS_fee:uint256=self.fee*N_COINS/(4*(N_COINS-1))_feemul:uint256=self.offpeg_fee_multiplier_admin_fee:uint256=self.admin_feedifference:uint256=0foriinrange(N_COINS):ideal_balance:uint256=D1*old_balances[i]/D0new_balance:uint256=new_balances[i]ifideal_balance>new_balance:difference=ideal_balance-new_balanceelse:difference=new_balance-ideal_balancexs:uint256=old_balances[i]+new_balancefees[i]=self._dynamic_fee(xs,ys,_fee,_feemul)*difference/FEE_DENOMINATORif_admin_fee!=0:self.admin_balances[i]+=fees[i]*_admin_fee/FEE_DENOMINATORnew_balances[i]=new_balance-fees[i]D2:uint256=self.get_D_precisions(new_balances,amp)mint_amount=token_supply*(D2-D0)/D0else:mint_amount=D1# Take the dust if there was anyassertmint_amount>=_min_mint_amount,"Slippage screwed you"# Take coins from the senderif_use_underlying:lending_pool:address=self.aave_lending_poolaave_referral:bytes32=convert(self.aave_referral,bytes32)# Take coins from the senderforiinrange(N_COINS):amount:uint256=_amounts[i]ifamount!=0:coin:address=self.underlying_coins[i]# transfer underlying coin from msg.sender to self_response:Bytes[32]=raw_call(coin,concat(method_id("transferFrom(address,address,uint256)"),convert(msg.sender,bytes32),convert(self,bytes32),convert(amount,bytes32)),max_outsize=32)iflen(_response)!=0:assertconvert(_response,bool)# deposit to aave lending poolraw_call(lending_pool,concat(method_id("deposit(address,uint256,address,uint16)"),convert(coin,bytes32),convert(amount,bytes32),convert(self,bytes32),aave_referral,))else:foriinrange(N_COINS):amount:uint256=_amounts[i]ifamount!=0:assertERC20(self.coins[i]).transferFrom(msg.sender,self,amount)# dev: failed transfer# Mint pool tokensCurveToken(lp_token).mint(msg.sender,mint_amount)logAddLiquidity(msg.sender,_amounts,fees,D1,token_supply+mint_amount)returnmint_amount