The AggregatorStablePrice contract is designed to aggregate the prices of crvUSD based on multiple Curve Stableswap pools. This price is primarily used as an oracle for calculating the interest rate, but also for PegKeepers to determine whether to mint and deposit or withdraw and burn.
The internal _ema_tvl() calculates the Exponential Moving Average (EMA) of the Total Value Locked (TVL) for multiple Curve StableSwap pools. There is a maximum of 20 pairs to consider, and each price pair (pool) must have at least 100k TVL.
last_tvl:public(uint256[MAX_PAIRS])@internal@viewdef_ema_tvl()->DynArray[uint256,MAX_PAIRS]:tvls:DynArray[uint256,MAX_PAIRS]=[]last_timestamp:uint256=self.last_timestampalpha:uint256=10**18iflast_timestamp<block.timestamp:alpha=self.exp(-convert((block.timestamp-last_timestamp)*10**18/TVL_MA_TIME,int256))n_price_pairs:uint256=self.n_price_pairsforiinrange(MAX_PAIRS):ifi==n_price_pairs:breaktvl:uint256=self.last_tvl[i]ifalpha!=10**18:# alpha = 1.0 when dt = 0# alpha = 0.0 when dt = infnew_tvl:uint256=self.price_pairs[i].pool.totalSupply()# We don't do virtual price here to save on gastvl=(new_tvl*(10**18-alpha)+tvl*alpha)/10**18tvls.append(tvl)returntvls
Getter for the exponential moving average of the TVL in price_pairs.
Returns: array of ema tvls (DynArray[uint256, MAX_PAIRS]).
Source code
TVL_MA_TIME:public(constant(uint256))=50000# slast_tvl:public(uint256[MAX_PAIRS])@external@viewdefema_tvl()->DynArray[uint256,MAX_PAIRS]:returnself._ema_tvl()@internal@viewdef_ema_tvl()->DynArray[uint256,MAX_PAIRS]:tvls:DynArray[uint256,MAX_PAIRS]=[]last_timestamp:uint256=self.last_timestampalpha:uint256=10**18iflast_timestamp<block.timestamp:alpha=self.exp(-convert((block.timestamp-last_timestamp)*10**18/TVL_MA_TIME,int256))n_price_pairs:uint256=self.n_price_pairsforiinrange(MAX_PAIRS):ifi==n_price_pairs:breaktvl:uint256=self.last_tvl[i]ifalpha!=10**18:# alpha = 1.0 when dt = 0# alpha = 0.0 when dt = infnew_tvl:uint256=self.price_pairs[i].pool.totalSupply()# We don't do virtual price here to save on gastvl=(new_tvl*(10**18-alpha)+tvl*alpha)/10**18tvls.append(tvl)returntvls
Function to calculate the weighted price of crvUSD.
Returns: price (uint256).
Source code
interfaceStableswap:defprice_oracle()->uint256:viewdefcoins(i:uint256)->address:viewdefget_virtual_price()->uint256:viewdeftotalSupply()->uint256:viewMAX_PAIRS:constant(uint256)=20MIN_LIQUIDITY:constant(uint256)=100_000*10**18# Only take into account pools with enough liquidityprice_pairs:public(PricePair[MAX_PAIRS])n_price_pairs:uint256@external@viewdefprice()->uint256:returnself._price(self._ema_tvl())@internal@viewdef_price(tvls:DynArray[uint256,MAX_PAIRS])->uint256:n:uint256=self.n_price_pairsprices:uint256[MAX_PAIRS]=empty(uint256[MAX_PAIRS])D:uint256[MAX_PAIRS]=empty(uint256[MAX_PAIRS])Dsum:uint256=0DPsum:uint256=0foriinrange(MAX_PAIRS):ifi==n:breakprice_pair:PricePair=self.price_pairs[i]pool_supply:uint256=tvls[i]ifpool_supply>=MIN_LIQUIDITY:p:uint256=price_pair.pool.price_oracle()ifprice_pair.is_inverse:p=10**36/pprices[i]=pD[i]=pool_supplyDsum+=pool_supplyDPsum+=pool_supply*pifDsum==0:return10**18# Placeholder for no active poolsp_avg:uint256=DPsum/Dsume:uint256[MAX_PAIRS]=empty(uint256[MAX_PAIRS])e_min:uint256=max_value(uint256)foriinrange(MAX_PAIRS):ifi==n:breakp:uint256=prices[i]e[i]=(max(p,p_avg)-min(p,p_avg))**2/(SIGMA**2/10**18)e_min=min(e[i],e_min)wp_sum:uint256=0w_sum:uint256=0foriinrange(MAX_PAIRS):ifi==n:breakw:uint256=D[i]*self.exp(-convert(e[i]-e_min,int256))/10**18w_sum+=wwp_sum+=w*prices[i]returnwp_sum/w_sum
This function is only callable by the admin of the contract.
Function to add a price pair to the PriceAggregator.
Emits: AddPricePair
Input
Type
Description
_pool
address
Price pair to add
Source code
eventAddPricePair:n:uint256pool:Stableswapis_inverse:bool@externaldefadd_price_pair(_pool:Stableswap):assertmsg.sender==self.adminprice_pair:PricePair=empty(PricePair)price_pair.pool=_poolcoins:address[2]=[_pool.coins(0),_pool.coins(1)]ifcoins[0]==STABLECOIN:price_pair.is_inverse=Trueelse:assertcoins[1]==STABLECOINn:uint256=self.n_price_pairsself.price_pairs[n]=price_pair# Should revert if too many pairsself.last_tvl[n]=_pool.totalSupply()self.n_price_pairs=n+1logAddPricePair(n,_pool,price_pair.is_inverse)
This function is only callable by the admin of the contract.
Function to remove a price pair from the contract. If a prior pool than the latest added one gets removed, the function will move the latest added price pair to the removed pair pairs index to not mess up price_pairs.
Getter for the admin of the contract, which is the Curve DAO OwnershipAgent.
Returns: admin (address).
Source code
admin:public(address)@externaldef__init__(stablecoin:address,sigma:uint256,admin:address):STABLECOIN=stablecoinSIGMA=sigma# The change is so rare that we can change the whole thing altogetherself.admin=admin
This function is only callable by the admin of the contract.
Function to set a new admin.
Emits: SetAdmin
Input
Type
Description
_admin
address
new admin address
Source code
eventSetAdmin:admin:addressadmin:public(address)@externaldefset_admin(_admin:address):# We are not doing commit / apply because the owner will be a voting DAO anyway# which has vote delaysassertmsg.sender==self.adminself.admin=_adminlogSetAdmin(_admin)
Getter for the last price. This variable was set to (1.00) when initializing the contract and is now updated every time calling price_w.
Returns: last price (uint256).
Source code
last_price:public(uint256)@externaldef__init__(stablecoin:address,sigma:uint256,admin:address):STABLECOIN=stablecoinSIGMA=sigma# The change is so rare that we can change the whole thing altogetherself.admin=adminself.last_price=10**18self.last_timestamp=block.timestamp@externaldefprice_w()->uint256:ifself.last_timestamp==block.timestamp:returnself.last_priceelse:ema_tvl:DynArray[uint256,MAX_PAIRS]=self._ema_tvl()self.last_timestamp=block.timestampforiinrange(MAX_PAIRS):ifi==len(ema_tvl):breakself.last_tvl[i]=ema_tvl[i]p:uint256=self._price(ema_tvl)self.last_price=preturnp
Function to calculate and write the price. If called successfully, updates last_tvl, last_price and last_timestamp.
Returns: price (uint256).
Source code
@externaldefprice_w()->uint256:ifself.last_timestamp==block.timestamp:returnself.last_priceelse:ema_tvl:DynArray[uint256,MAX_PAIRS]=self._ema_tvl()self.last_timestamp=block.timestampforiinrange(MAX_PAIRS):ifi==len(ema_tvl):breakself.last_tvl[i]=ema_tvl[i]p:uint256=self._price(ema_tvl)self.last_price=preturnp@internal@viewdef_price(tvls:DynArray[uint256,MAX_PAIRS])->uint256:n:uint256=self.n_price_pairsprices:uint256[MAX_PAIRS]=empty(uint256[MAX_PAIRS])D:uint256[MAX_PAIRS]=empty(uint256[MAX_PAIRS])Dsum:uint256=0DPsum:uint256=0foriinrange(MAX_PAIRS):ifi==n:breakprice_pair:PricePair=self.price_pairs[i]pool_supply:uint256=tvls[i]ifpool_supply>=MIN_LIQUIDITY:p:uint256=price_pair.pool.price_oracle()ifprice_pair.is_inverse:p=10**36/pprices[i]=pD[i]=pool_supplyDsum+=pool_supplyDPsum+=pool_supply*pifDsum==0:return10**18# Placeholder for no active poolsp_avg:uint256=DPsum/Dsume:uint256[MAX_PAIRS]=empty(uint256[MAX_PAIRS])e_min:uint256=max_value(uint256)foriinrange(MAX_PAIRS):ifi==n:breakp:uint256=prices[i]e[i]=(max(p,p_avg)-min(p,p_avg))**2/(SIGMA**2/10**18)e_min=min(e[i],e_min)wp_sum:uint256=0w_sum:uint256=0foriinrange(MAX_PAIRS):ifi==n:breakw:uint256=D[i]*self.exp(-convert(e[i]-e_min,int256))/10**18w_sum+=wwp_sum+=w*prices[i]returnwp_sum/w_sum