Skip to main content

Both policies are written in Vyper 0.4.3 and import snekmate. YBTwocryptoPolicy is verified on Blockscout. The source of YBOraclizedPolicy is in curvefi/twocrypto-ng, branch oraclized-policy, file contracts/main/YBOraclizedPolicy.vy, commit 87dbb0d1a269e50f21757c0c1786e620fcaae5a2.

Pool policy

This page is for developers who integrate with or audit a YieldBasis pool. It describes the policy hook of a Twocrypto v3.0.0 pool and the YieldBasis pool policies. A pool policy is a contract that the pool calls to get its swap fee and its price_scale target. The pool also pushes its state to the policy after each operation. For the concept and the LP view, see Pool policies.

The pools of markets 7 to 10 run Twocrypto v3.0.0 and have this hook. The pools of markets 0 to 6 run older pool code without a policy hook. On those pools, a call to POLICY() reverts.

The YieldBasis pool policy is YBOraclizedPolicy, the oracle-report policy. It prices swaps against signed price reports. Some pools can still run the earlier YBTwocryptoPolicy, which uses only the pool's own prices.

To see which policy a pool runs, see Contract addresses or call POLICY() on the pool. A return of empty(address) means that the pool has no policy.

Pool side: the policy hook​

Interface the pool calls​

interface Policy:
def get_fee(xp: uint256[N_COINS]) -> uint256: view
def get_price_scale() -> uint256: view
def update_pool_state(xp: uint256[N_COINS],
price_scale: uint256,
price_oracle: uint256,
last_prices: uint256,
virtual_price: uint256,
xcp_profit: uint256,
D: uint256,
oracle_timestamp: uint256): nonpayable

The pool stores the policy in POLICY: public(Policy). The value is empty(address) at deployment. A pool that the factory deploys in initialization mode can attach a policy in its one-time initialize() call, before the first deposit. The pool deployer or factory.admin() can call initialize(), so the deployer can attach any policy without a vote. After that, only set_policy_contract changes the policy.

get_fee​

The pool fee function _fee(xp) calls POLICY.get_fee(xp) when the pool has a policy. A return of 0 selects the pool's native fee (mid_fee, out_fee, fee_gamma). The pool clamps any other return to [MIN_FEE, MAX_FEE] = [10^5, 10^10] in fee units (0.1 bp to 100%).

Swaps and liquidity operations use _fee. For add_liquidity and imbalanced removals, _calc_token_fee charges _fee(xp) / 2 × Sdiff / S of the LP amount, plus NOISE_FEE. Sdiff / S is 0 for a balanced operation and 1 for a one-coin operation. A deposit during the donation protection period also pays a spam penalty of at most _fee(xp) / 2.

Balanced remove_liquidity pays no fee. Donations pay only NOISE_FEE (0.1 bp). The view fee() evaluates _fee at the stored balances.

xp is the balance vector after the operation and before its fee, with coin1 scaled by price_scale.

get_price_scale​

tweak_price calls POLICY.get_price_scale() only when both conditions are true: vp_boosted > lp_xcp_profit (profit gate), and block.timestamp > last_timestamp. The pool reads last_timestamp before it stores the new timestamp, so only the first operation in a block that calls tweak_price can move price_scale. That operation can be a swap, an unbalanced liquidity operation, or a donation. If it fails the profit gate, price_scale does not move in that block.

target_price: uint256 = price_oracle
if p_policy > 0:
policy_bound: uint256 = unsafe_div(price_oracle, 5)
target_price = min(
max(p_policy, unsafe_sub(price_oracle, policy_bound)),
price_oracle + policy_bound,
)
norm = |target_price / price_scale - 1|
adjustment_step = min(norm / 5, adjustment_step_max)
# p_new moves price_scale by adjustment_step toward target_price,
# only if adjustment_step > adjustment_step_min

A return of 0 selects the native target, price_oracle. The pool moves one fifth of the gap, so both policies return price_scale ± 5 × move.

The pool moves price_scale only when all of these conditions are true:

  • The profit gate passes, and the call is the first tweak_price in the block.
  • The relative step (adjustment_step) is above adjustment_step_min.
  • The new virtual price stays above 10**18 and at or above lp_xcp_profit.

The pool also limits the move in two ways. It clamps the target to price_oracle ± 20%, and it caps the relative step at adjustment_step_max. In these two cases, price_scale moves by less than move. move is a price amount, and the two step limits are fractions of price_scale.

update_pool_state​

The pool calls _update_policy_state after each state change. It ABI-encodes the arguments with self.last_timestamp as oracle_timestamp and makes a raw_call.

Call sitemust_succeedEffect of a policy revert
First add_liquidity into an empty pool (initialization)TrueOperation reverts
tweak_price (swaps, add_liquidity including donations, remove_liquidity_fixed_out, remove_liquidity_one_coin)TrueOperation reverts
set_policy_contract (attach, if D > 0)TrueAttach reverts
remove_liquidity (balanced, if amount > 0)False, 300,000 gasIgnored, if at least 500,000 gas remained before the call. Otherwise the pool reverts.

LT.withdraw uses remove_liquidity_fixed_out, so a reverting policy blocks it. LT.emergency_withdraw uses balanced remove_liquidity. A failed policy call does not block it if the transaction has at least 500,000 gas left before the policy call.

Attach, replace or remove​

@external
@nonreentrant
def set_policy_contract(policy: Policy)
  • Access: msg.sender == factory.admin() of the Curve Twocrypto factory, else revert only owner.
  • policy must not be coins[0] or coins[1] (dev revert).
  • empty(address) removes the policy.
  • If D > 0, the pool pushes its state with must_succeed = True.
  • Event: SetPolicyContract(policy).

For the YieldBasis pools, factory.admin() is the owner proxy 0x97aA696e37659Fb4f0B53824246d802Df40E980A. Its execute() accepts calls only from its dao, the Curve Ownership Agent 0x40907540d8a6C65c637785e8f8B742ae6b0b9968. After initialization, a policy change therefore needs a Curve ownership vote (voting contract 0xE478de485ad2fE566d49342Cbd03E49ed7DB3356).

The emergency DAO of the proxy can call only emergency_parameters. That function calls apply_new_parameters(mid_fee, out_fee, fee_gamma, adjustment_step_min, adjustment_step_max, ma_time) on the pool. The emergency DAO cannot call set_policy_contract. To read the two roles, call dao() and emergency_dao() on the proxy.

The YieldBasis Factory.add_market() does not check the pool code version or the policy.

YBTwocryptoPolicy​

YBTwocryptoPolicy, the earlier YieldBasis policy, is a price_scale controller with two exponential moving averages (EMAs), one fast and one slow. It reads no external price, has no owner, and has no setters.

Constants and immutables​

NameValue
N_COINS2
PRECISION10^18
TWEAK_PRICE_MULTIPLIER5
CAP_RAMP_SECONDS3,600
POOLimmutable address
FAST_HALF_LIFE, SLOW_HALF_LIFEimmutable, seconds
KAPPAimmutable, 1e18 = 1.0
DEADBAND, MIN_CAP, MAX_CAPimmutable, relative WAD (1e18 = 100%)

Storage​

struct State:
last_update_ts: uint256
last_prices: uint256
fast_ema: uint256
slow_ema: uint256
price_scale: uint256

state: public(State)

Constructor​

@deploy
def __init__(pool: address, fast_half_life: uint256, slow_half_life: uint256,
kappa: uint256, deadband: uint256, min_cap: uint256, max_cap: uint256)
CheckRevert string
pool != empty(address)pool=0
600 ≤ fast_half_life ≤ 604,800fast half-life
600 ≤ slow_half_life ≤ 604,800slow half-life
fast_half_life ≤ slow_half_lifehalf-life order
kappa ≤ 2e18kappa
deadband ≤ 60 bpdeadband
max_cap ≤ 60 bpmax cap
1 bp ≤ min_cap ≤ max_capmin cap

External functions​

SignatureMutabilityAccessReturns
get_fee(_xp: uint256[2]) -> uint256pureAnyoneAlways 0 (native pool fee)
get_emas() -> uint256[2]viewAnyoneProjected [fast, slow], or [0, 0] before the attach callback
get_price_scale() -> uint256viewAnyoneTarget for the pool, or 0 before the attach callback
update_pool_state(_xp, price_scale, price_oracle, last_prices, _virtual_price, _xcp_profit, _D, _oracle_timestamp)nonpayablePOOL only, else auth!—
state() -> StateviewAnyoneStored snapshot
POOL(), FAST_HALF_LIFE(), SLOW_HALF_LIFE(), KAPPA(), DEADBAND(), MIN_CAP(), MAX_CAP()viewAnyoneImmutables

The contract emits no events.

EMA and target​

Each update_pool_state settles both EMAs toward the last_prices stored at the previous callback, then stores the snapshot of this callback. The attach callback seeds both EMAs with price_oracle.

p=clamp⁡(last_prices, price_scale2, 2⋅price_scale),α=e−ln⁡2⋅Δt/half_life,ema′=p(1−α)+ema⋅αp = \operatorname{clamp}\left(\text{last\_prices},\ \tfrac{\text{price\_scale}}{2},\ 2\cdot\text{price\_scale}\right),\qquad \alpha = e^{-\ln 2 \cdot \Delta t / \text{half\_life}},\qquad \text{ema}' = p(1-\alpha) + \text{ema}\cdot\alpha

get_price_scale():

  1. raw_target = slow + KAPPA · (fast − slow), floored at 0.
  2. gap = |raw_target − price_scale|. If gap ≤ DEADBAND · price_scale, return price_scale (hold).
  3. cap = MIN_CAP + (MAX_CAP − MIN_CAP) · min(t − last_update_ts, 3600) / 3600.
  4. move = min(gap, price_scale · cap).
  5. Return price_scale ± 5 · move.

Parameters of the deployed policies​

The YBTwocryptoPolicy contracts that Curve vote 1494 attached use the same immutable parameters:

ParameterValue
FAST_HALF_LIFE8,100 s
SLOW_HALF_LIFE57,600 s
KAPPA1.30
DEADBAND8 bp
MIN_CAP12 bp
MAX_CAP54 bp

Governance history:

  • Curve votes 1486 (ended 17 August 2026, executed) and 1491 (ended 29 August 2026, executed) attached the first YBTwocryptoPolicy contracts.
  • These first policies used fast 7,500 s, slow 54,000 s, KAPPA 1.27, DEADBAND 2.8 bp, MIN_CAP 12 bp, and MAX_CAP 48 bp.
  • Curve vote 1494 (ended 7 September 2026, executed) replaced them with policies that use the parameters above.

YBOraclizedPolicy​

YBOraclizedPolicy is the YieldBasis pool policy. The team's forum post of 21 October 2026 explains the design and its backtest.

YBOraclizedPolicy prices swap fees from signed Pyth Pro (Pyth Lazer) price reports. It sets a trend-gated dual-EMA price_scale target. It also checks deposits into LT (the yb-LP vault) against the debt ratio of the leveraged AMM (LEVAMM).

Units​

  • Fees: FEE_PRECISION = 10^10 = 100%.
  • Prices and EMAs: 18 decimals. A price report prices coin1 in USD (1e18 = $1). coin0 (crvUSD) counts as $1.
  • Relative values: WAD, 1e18 = 100%.
  • Pyth times: microseconds.

Constants​

NameValueMeaning
N_COINS2
PRECISION10^18
FEE_PRECISION10^10
MIN_FEE10^50.1 bp, the pool's minimum
A_MULTIPLIER10,000
TWEAK_PRICE_MULTIPLIER5
PYTH_PAYLOAD_MAGIC2,479,346,549 (0x93c7d375)Pyth Lazer payload format
MIN_REPORT_PRICE10^6$1e-12
MAX_REPORT_PRICE10^30$1e12
MAX_PAST_WINDOW_US12,000,00012 s
MAX_FUTURE_WINDOW_US6,000,0006 s

Immutables​

NameTypeExample: policy 0xAEa9…8A7d (WETH market)
POOLaddress0x656341Ef90b622c6634e0573772FfB7f3669b9f3
AMMaddress0x5f8D24F33Cc5a1D5D1bf012261E6A2214C92233c (LEVAMM)
FEED_IDuint322 (ETH/USD)
EMERGENCY_ADMINaddress0x467947EE34aF926cF1DCac093870f613C96B1E0c (Safe)

Config structs​

struct OracleConfig:
verifier: address # Pyth Lazer verifier
channel: uint8 # 1 real_time, 2 fixed_rate@50ms, 3 fixed_rate@200ms, 4 fixed_rate@1000ms
past_window_us: uint256 # oldest admissible observation, before block.timestamp
future_window_us: uint256 # newest admissible message, after block.timestamp
min_price: uint256 # 1e18 = $1
max_price: uint256

struct FeeConfig:
base_fee: uint256 # FEE_PRECISION
capture: uint256 # PRECISION
fallback_fee: uint256 # FEE_PRECISION
relax_time: uint256 # seconds

struct DriverConfig:
fast_half_life: uint256 # seconds
slow_half_life: uint256 # seconds
trend_gate: uint256 # WAD
step_cap: uint256 # WAD
report_lifetime: uint256 # seconds; 0 = never observe reports

struct PolicyConfig:
fee: FeeConfig
driver: DriverConfig
ratio_tolerance: uint256 # WAD

struct Report:
asset_price: uint256 # 0 before any report and after a kill
observation_us: uint256 # replay watermark, kept on a kill

struct State:
last_update_ts: uint256
fast_ema: uint256
slow_ema: uint256
price_scale: uint256
xp: uint256[2]
D: uint256
last_prices: uint256 # pool spot after the last callback's operation

Public storage: config, oracle, is_killed, report, state, cached_A, cached_supply, amm_surplus.

Deployment configuration (example)​

The deployer gave the policy 0xAEa95d551154443E73565bE724d59D63d72f8A7d of the WETH market this configuration. The owner can change it. To read the current values, call config() and oracle().

FieldRawMeaning
fee.base_fee50,000,0000.50%
fee.capture0.675e1867.5%
fee.fallback_fee175,000,0001.75%
fee.relax_time300300 s
driver.fast_half_life4,80080 minutes
driver.slow_half_life9,600160 minutes
driver.trend_gate0.007e180.70%
driver.step_cap0.005e180.50%
driver.report_lifetime5,40090 minutes
ratio_tolerance1e141 bp
oracle.verifier0xACeA761c27A909d4D3895128EBe6370FDE2dF481Pyth Lazer verifier
oracle.channel4fixed_rate@1000ms
oracle.past_window_us12,000,00012 s
oracle.future_window_us6,000,0006 s
oracle.min_price / max_price100e18 / 100,000e18$100 / $100,000

The verifier 0xACeA…F481 is an ERC-1967 proxy for the Pyth PythLazer contract. To find its implementation, read the ERC-1967 implementation slot of the proxy.

Constructor​

@deploy
def __init__(pool: address, amm: address, feed_id: uint32, emergency_admin: address,
oracle: OracleConfig, config: PolicyConfig)
CheckRevert string
pool != empty(address)pool=0
feed_id > 0feed id
emergency_admin != empty(address)emergency admin=0
LevAMM(amm).COLLATERAL() == poolamm collateral
LevAMM(amm).STABLECOIN() == pool.coins(0)amm stablecoin
pool.precisions()[0] == 1pool precision
pool.admin_fee() == 0admin fee

The constructor caches pool.A(), makes the deployer the owner, then applies oracle and config with the setter checks below.

External functions​

SignatureMutabilityAccess
get_fee(xp: uint256[2]) -> uint256viewAnyone (called by the pool)
get_fee_with_report(xp: uint256[2], report_price: uint256, report_age_us: uint256) -> uint256viewAnyone
get_price_scale() -> uint256viewAnyone (called by the pool)
get_emas() -> uint256[2]viewAnyone
update_pool_state(xp: uint256[2], price_scale: uint256, price_oracle: uint256, last_prices: uint256, _virtual_price: uint256, _xcp_profit: uint256, D: uint256, _oracle_timestamp: uint256)nonpayablePOOL only, else auth!
sync()nonpayableAnyone
deliver_oracle_report(report_data: Bytes[112])payable, nonreentrantAnyone
verification_fee() -> uint256viewAnyone
set_policy_config(config: PolicyConfig)nonpayableOwner
set_oracle_config(oracle: OracleConfig)nonpayableOwner
set_killed(is_killed: bool)nonpayableOwner or EMERGENCY_ADMIN, else access
get_dy_with_report(i: uint256, j: uint256, dx: uint256, report_data: Bytes[112], block_timestamp: uint256) -> uint256viewAnyone
get_dy_with_report_age(i: uint256, j: uint256, dx: uint256, report_price: uint256, report_age_us: uint256) -> uint256viewAnyone
owner() -> address, transfer_ownership(new_owner: address), renounce_ownership()snekmate ownableOwner only, for transfer_ownership and renounce_ownership
config(), oracle(), is_killed(), report(), state(), cached_A(), cached_supply(), amm_surplus(), POOL(), AMM(), FEED_ID(), EMERGENCY_ADMIN()viewAnyone

In snekmate ownable, a call to an owner function from another address reverts with ownable: caller is not the owner. transfer_ownership(new_owner) reverts with ownable: new owner is the zero address if new_owner is the zero address.

Fee​

get_fee(xp) evaluates _get_fee against the stored report and its age. get_fee_with_report does the same with a caller-supplied price and age, without verification.

_get_fee(xp, report_price, report_age_us):

  1. If killed, return 0 (native pool fee).
  2. If neither balance fell, or neither balance rose, against state.xp, return fallback_fee. This covers every liquidity operation.
  3. If report_price == 0 or report_age_us ≥ relax_time · 10^6, return fallback_fee.
  4. If state.D == 0 or pool.D() != state.D, return fallback_fee. This happens before the attach callback, after a failed best-effort removal callback, and usually during an A ramp.
  5. Compute the edge. If it is 0, return fallback_fee.
  6. Compute the reported fee. Return it only if it is below fallback_fee and the swap is corrective. Else return fallback_fee.

Edge. With coin_in the coin whose balance rose:

edge=1−input valueoutput value\text{edge} = 1 - \frac{\text{input value}}{\text{output value}}

The policy values both legs in coin0. It multiplies a coin1 delta in xp by report_price / price_scale. The edge is 0 when the input is worth at least the output.

Reported fee.

fresh=min⁡(fallback, base+capture⋅max⁡(edge−base,0))\text{fresh} = \min\left(\text{fallback},\ \text{base} + \text{capture}\cdot\max(\text{edge}-\text{base},0)\right) fee=fresh+⌊(fallback−fresh)2⋅agerelax_time⌋+1\text{fee} = \text{fresh} + \left\lfloor\sqrt{(\text{fallback}-\text{fresh})^2 \cdot \frac{\text{age}}{\text{relax\_time}}}\right\rfloor + 1

The + 1 fee unit applies only when age > 0 and fresh < fallback.

Corrective test. The marginal price get_p(xp, D, [cached_A, 0]) · price_scale must move toward report_price and not pass it. A swap that empties a balance is not corrective.

The constructor and sync() set cached_A. During an A ramp, quotes and swaps can disagree on the fee. The source comment gives two options: kill the policy for the ramp, or call sync() after the pool checkpoints its final A.

price_scale driver​

Observation (_observation):

obs=clamp⁡({report.asset_priceif age<report_lifetimestate.last_pricesotherwise, price_scale2, 2⋅price_scale)\text{obs} = \operatorname{clamp}\left(\begin{cases}\text{report.asset\_price} & \text{if age} < \text{report\_lifetime}\\ \text{state.last\_prices} & \text{otherwise}\end{cases},\ \tfrac{\text{price\_scale}}{2},\ 2\cdot\text{price\_scale}\right)

state.last_prices is the pool spot stored at the previous callback, so an operation cannot steer the EMAs with its own trade.

EMAs (_project_emas, _ema): the same true half-life EMA as YBTwocryptoPolicy, with dt = block.timestamp − state.last_update_ts. If dt == 0, the function returns the stored EMAs.

Target (get_price_scale):

trend=∣fast−slow∣slow,w=min⁡(1, trendtrend_gate),target=fast+w⋅(obs−fast)\text{trend} = \frac{|\text{fast}-\text{slow}|}{\text{slow}},\qquad w = \min\left(1,\ \frac{\text{trend}}{\text{trend\_gate}}\right),\qquad \text{target} = \text{fast} + w\cdot(\text{obs}-\text{fast})

If trend_gate == 0, the target is fast.

move=min⁡(∣target−price_scale∣2, step_cap⋅price_scale),return price_scale±5⋅move\text{move} = \min\left(\frac{|\text{target}-\text{price\_scale}|}{2},\ \text{step\_cap}\cdot\text{price\_scale}\right),\qquad \text{return } \text{price\_scale} \pm 5\cdot\text{move}

The function returns 0 while killed or before the attach callback. YBOraclizedPolicy has no deadband, no KAPPA and no cap ramp.

Pool callback and LT deviation check​

update_pool_state (POOL only):

  1. If not killed, run the LT deviation check against the previous snapshot. A revert unwinds the operation.
  2. Settle the EMAs with _observation(snapshot). The attach callback seeds both EMAs with price_oracle.
  3. Store State(block.timestamp, fast, slow, price_scale, xp, D, last_prices).

LT deviation check (_update_lt_gate(stable, old_stable)):

  • If pool.user_supply() is unchanged (and not 0), return. Swaps and donations do not change user supply.
  • Store the supply in cached_supply. If supply fell (an exit), return.
  • surplus = pool.balanceOf(AMM) − AMM.collateral_amount(). This is LP that the LEVAMM holds beyond its booked collateral.
  • If old_supply == 0, record only. If the LEVAMM book is empty, the policy resets the surplus to 0.
  • Else, if surplus ≥ amm_surplus + added_lp, the add is an LT deposit. If collateral != 0, the check below must pass, else the call reverts with LT deviation.
proportional=added_lp⋅AMM.get_debt()collateral,∣added_debt−proportional∣≤proportional⋅ratio_tolerance\text{proportional} = \frac{\text{added\_lp}\cdot\text{AMM.get\_debt()}}{\text{collateral}},\qquad |\text{added\_debt}-\text{proportional}| \le \text{proportional}\cdot\text{ratio\_tolerance}

added_lp is the growth of pool.user_supply(). added_debt is the growth of xp[0] since the previous callback.

The check is exact only while the pool admin_fee is 0. The constructor checks this. If governance later sets a pool admin fee, an exact deposit can fail.

The check applies to each LT.deposit into the market of the pool while the policy is not killed and the LEVAMM has collateral. HybridVault.deposit and LTMigrator pass a debt that the caller or the migration chooses. They revert with LT deviation if that debt is more than ratio_tolerance off the LEVAMM ratio.

sync() re-reads A, the snapshot geometry (xp, D, price_scale), cached_supply, and amm_surplus. It does not settle the EMAs. Before the attach callback, it updates only cached_A and amm_surplus.

sync() reverts inside a pool operation, because pool.price_scale() is nonreentrant. It reverts with pool uninitialized if the pool price_scale is 0. Call it after an A ramp ends, after a failed balanced-removal callback, or after an LP gift to the LEVAMM.

Price report admission​

deliver_oracle_report(report_data). Send verification_fee() as msg.value.

  1. Revert killed while killed.
  2. Revert pool uninitialized if pool.price_scale() == 0. This call also blocks delivery from inside a pool operation.
  3. len(report_data) == 112, else report length.
  4. Call verifier.verifyUpdate(report_data) with msg.value. The signer must not be 0 (signer). The payload must be 41 bytes (payload length).
  5. Decode the payload (below).
  6. Do the time checks:
    • 0 < observation ≤ message ≤ block_time + future_window, else report time.
    • observation + past_window ≥ block_time, else report too old.
    • observation > report.observation_us, else observation not newer.
  7. min_price ≤ price ≤ max_price (price bounds).
  8. Store the report and emit ReportAccepted.

Payload layout (big-endian):

BytesFieldRequired valueRevert
[0:4]magic0x93c7d375payload magic
[4:12]message time, µs——
[12]channeloracle.channelchannel
[13]feed count1one feed
[14:18]feed idFEED_IDfeed
[18:20]property count, property id3, 0 (price)price property
[20:28]price mantissa, int64> 0price/exponent
[28]property id4 (exponent)exponent property
[29:31]exponent, int16−18 to 0price/exponent
[31:33]property id, value present12 (feed update time), 1observation property
[33:41]observation time, µs——

Price = mantissa · 10^(18 + exponent).

Report age is block_time − observation, or 0 if the observation is after the block time.

Send the exact fee: read verification_fee() in the same transaction. With the current verifier implementation, the verifier refunds any excess over verification_fee to msg.sender (the policy) with transfer. The policy cannot receive ETH, so the refund fails. An overpayment therefore reverts the whole delivery without a reason string. The verifier is an upgradeable proxy, so a later implementation can behave differently.

Quote views​

get_dy_with_report and get_dy_with_report_age return pool.get_dy(i, j, dx) as if the policy stored the given report. They decode the report but do not verify the signature or admit it. get_dy_with_report takes the report age at block_timestamp, so pass the timestamp of the block you target.

_quote reverts with coin index out of range, not attached (when pool.POLICY() != self), or unsafe value for y. It re-solves D during an A ramp. While the policy is killed, it uses pool.fee_calc(xp).

Governance​

set_policy_config(config) (owner) replaces the whole config. It does not checkpoint the EMAs. Changed half-lives apply to the whole interval since the last pool operation. A higher relax_time or report_lifetime can revive a stored report.

FieldBoundRevert
base_feeMIN_FEE ≤ base_fee ≤ fallback_feebase fee
fallback_fee≤ 10^9 (10%)fallback fee
capture≤ 1e18capture
relax_time1 to 3,600 srelax time
fast_half_life≥ 600 s and ≤ slow_half_lifefast half-life
slow_half_life≤ 604,800 sslow half-life
trend_gate≤ 0.1e18 (10%)trend gate
step_cap> 0 and ≤ 60 bpstep cap
report_lifetime≤ 14,400 sreport lifetime
ratio_tolerance≤ 1e15 (10 bp)ratio tolerance

set_oracle_config(oracle) (owner) sets the verifier, channel, windows, and price bounds. Each call sets report.asset_price to 0 and keeps report.observation_us as the replay watermark.

FieldBoundRevert
verifierHas codeverifier
channel1 to 4channel
past_window_us≤ 12,000,000past window
future_window_us≤ 6,000,000future window
min_price, max_price10^6 ≤ min ≤ max ≤ 10^30price bounds

The verifier decides which prices are authentic. The owner can set any contract as verifier, so the owner holds that trust.

set_killed(is_killed) (owner or EMERGENCY_ADMIN):

  • Kill: sets report.asset_price to 0 and keeps the watermark. While killed, get_fee and get_price_scale return 0, reports revert with killed, and the LT deviation check is off. Callbacks still settle the EMAs.
  • Revive: calls sync().
  • Emits SetKilled.

The owner of the policy 0xAEa9…8A7d is the YB DAO 0x42F2A41A0D0e65A440813190880c8a65124895Fa. To check, call owner().

Events​

EventFieldsEmitted by
ReportAcceptedasset_price: uint256, observation_us: uint256, message_us: uint256deliver_oracle_report
SetPolicyConfigconfig: PolicyConfigConstructor, set_policy_config
SetOracleConfigoracle: OracleConfigConstructor, set_oracle_config
SetKilledis_killed: boolset_killed

snekmate ownable also emits its ownership-transfer event.

Revert strings​

StringFunction
auth!update_pool_state
LT deviationupdate_pool_state (LT deviation check)
pool uninitializedsync, set_killed(False), deliver_oracle_report
killeddeliver_oracle_report
report length, signer, payload lengthdeliver_oracle_report
payload magic, channel, one feed, feed, price property, exponent property, observation property, price/exponentdeliver_oracle_report, get_dy_with_report
report time, report too old, observation not newer, price boundsdeliver_oracle_report
accessset_killed
base fee, fallback fee, capture, relax time, fast half-life, slow half-life, trend gate, step cap, report lifetime, ratio toleranceConstructor, set_policy_config
verifier, channel, past window, future window, price boundsConstructor, set_oracle_config
pool=0, feed id, emergency admin=0, amm collateral, amm stablecoin, pool precision, admin feeConstructor
coin index out of range, not attached, unsafe value for yget_dy_with_report, get_dy_with_report_age

The Pyth verifier can revert with its own strings: Insufficient fee provided, input too short, invalid evm magic, invalid signature, and invalid signer.

Earlier deployment​

0xAEa95d551154443E73565bE724d59D63d72f8A7d supersedes an earlier YBOraclizedPolicy for the YB-WETH pool, 0x508356890B1AB013b2f184604f403e435C794Cca. The earlier policy has the same configuration and owner. It differs from 0xAEa9…8A7d in these points:

  • Its EMAs and target observe the pool's current last_prices, not the spot stored at the previous callback.
  • It records the LP of an LT deposit into an empty pool as a gift.
  • It has no quote views (get_dy_with_report*).

The fee formula, corrective test, admission rules, bounds, and kill switch are the same in both. For the addresses of all policy contracts, see Contract addresses.