Skip to main content

Oracle Design

The oracle returns the USD value of one Curve LP token at the EMA-smoothed external market price (EMA = exponential moving average, a rolling price average that weights recent observations more than old ones, so a single flash-loan swap cannot move the feed in one block because it averages across many prior blocks). Valuation reads cannot be moved in-block. The Factory validates the crvUSD aggregator price within (0.90,1.10)(0.90, 1.10) when the aggregator is set; routine operations rely on the last-validated aggregator without re-checking.

Before you read this

This page assumes some upstream knowledge. If any of the below is unfamiliar, follow the link before continuing. For the basics in one place, see Glossary.

  • What an oracle is. An on-chain price feed, external to any single pool, used by the protocol to value positions. See Glossary.
  • Exponential moving average (EMA). A rolling average weighted toward recent observations; a single new price barely moves a long-history EMA in one block.
  • Curve Cryptoswap's price_scale and virtual_price. price_scale is the pool's internal target price (slow-moving, only updated when rebalance conditions trigger). virtual_price is the pool's per-LP-token growth accounting, net of slippage.
  • Why flash-loan manipulation matters for AMM pricing. A flash loan can push a pool's spot price far from market for one block. A naive oracle reading spot price would be fooled; an EMA-smoothed or aggregator-based oracle is not.

LP price formula

The LP token price, denominated in USD, is

lp_price=2vpprice_scalepagg\mathrm{lp\_price} = 2 \cdot v_p \cdot \sqrt{\mathrm{price\_scale}} \cdot p_{\text{agg}}

  • vpv_p: Cryptoswap's virtual_price, the pool's internal accounting of per-LP-token growth net of slippage.
  • price_scale\mathrm{price\_scale}: the pool's internal target price, updated only when Cryptoswap's rebalance conditions are satisfied.
  • paggp_{\text{agg}}: the normalised crvUSD/USD price read from Curve's off-pool price aggregator.

This is the formula implemented by CryptopoolLPOracle.vy.

Factor-by-factor meaning

  • 2vpprice_scale2 \cdot v_p \cdot \sqrt{\mathrm{price\_scale}} gives the USD value of one LP token under the balanced-pool assumption.
  • Multiplying by paggp_{\text{agg}} re-anchors to external crvUSD value. A flash-loan swap can move the pool's instantaneous price away from market, but it cannot move the external aggregator (which pulls from multiple DEX quotes via EMA). Multiplying by paggp_{\text{agg}} normalises any temporary pool-versus-market imbalance. The oracle price tracks where the market is, not where a flash-loan attacker pushed the pool for one block.
  • Price_scale is EMA-smoothed. A flash-loan swap changes virtual_price and balances in-block, but price_scale only updates when the Cryptoswap internal rebalance conditions are met (fee-funded, gated). Over normal timescales price_scale tracks market; within one block it does not. Combined with the aggregator, this gives sandwich-resistance.

Derivation of the balanced-pool form

At balance, the pool holds equal USD value on each side. Let reserves be xx (stable) and yy (volatile), satisfying xy=kpoolxy = k_{\text{pool}}. The pool's internal price price_scale\mathrm{price\_scale} tracks the ratio, so at balance x=kpoolprice_scalex = \sqrt{k_{\text{pool}} \cdot \mathrm{price\_scale}} and the total USD pool value is 2kpoolprice_scale2\sqrt{k_{\text{pool}} \cdot \mathrm{price\_scale}}. Dividing by LP supply and expressing kpoolk_{\text{pool}} in virtual-price terms yields vp2price_scalev_p \cdot 2 \cdot \sqrt{\mathrm{price\_scale}} per LP token.

Safety band on the crvUSD aggregator

The Factory validates the crvUSD aggregator price within (0.90,1.10)(0.90, 1.10) (open interval on both ends) when the aggregator is initialized at deploy and when governance updates it via set_agg. Outside that band, the aggregator setter reverts and the new aggregator is not adopted; the previously valid aggregator continues to feed the oracle. Routine deposit, withdraw, and exchange operations do not re-validate the band on every call — they trust whatever aggregator was last validated. The band is deliberately wider than ordinary intraday volatility and tighter than any flash-loan-class swing.

Lending oracle wrapper

YBLendingOracle.vy wraps CryptopoolLPOracle.vy for use by LEVAMM's rebalancing invariant. It exposes price() for view-only reads and price_w() as the writing variant that advances the EMA state.