Skip to main content

Net Pressure (PID)

A feedback-controlled incentive system: measure aggregate crvUSD net pressure across YB markets, stream crvUSD to a FastGauge over the Curve crvUSD/pyUSD sink pool at a PID-set rate, funded by a DAO-set slice of LT admin fees.

Source code lives in contracts/net_pressure/ in yb-core. All contracts are written with Vyper 0.4.3.

PID stands for proportional–integral–derivative: the controller's output combines a term proportional to the current error, a term integrating past error, and a term tracking how fast the error is rising.

The system was activated by DAO vote: the proposal recovered the deprecated BTC markets' fee balances from the FeeDistributor (recover_token), converted them into the PID reserve via LTSwapZap.convert, transferred a crvUSD headstart to the MerklPIDDriver, and installed the FeeSplitter as Factory.fee_receiver().

Components

ContractRole
YBNetPressure.vyPressure oracle. Per market: AMM debt minus the crvUSD inside the Curve LP the AMM holds. Positive = crvUSD must be bought to repay (buy pressure). Aggregates over a DAO-selected set of LTs.
PID.vyThe controller. Converts incoming LT fees to a crvUSD reserve, runs the control loop on aggregate pressure, and sets the FastGauge's reward_rate.
FastGauge.vyERC-4626 staking gauge over the sink pool's LP token. Streams a single reward (crvUSD), pulled from the PID's reserve at every checkpoint. Includes add_liquidity / remove_liquidity / remove_liquidity_one_coin zaps for one-call entry and exit.
FeeSplitter.vyInstalled as Factory.fee_receiver(). Routes split_fraction (1e18-scaled, DAO-set) of each incoming LT fee balance to the PID and forwards the rest to the FeeDistributor, then pokes both. Reads the LT token set from the FeeDistributor.
MerklPIDDriver.vyRead-only mirror of the PID's parameters and inputs so Merkl can run the same control loop off-chain and post campaigns at the resulting target APR.
MarketRateGetter.vyThe comparator rate the PID prices its bonus against. Current implementation reads the Sky Savings Rate (sUSDS); swappable by the DAO — only rate() is part of the interface.

The control loop

Per step (dt in years, all values 1e18 fixed point), from the PID.vy specification:

pressure     = max(0, sum of net pressure) / sum of half-TVL   (oracle-priced)
sink = sink-pool TVL / sum of half-TVL
error = pressure - sink (coverage gap)
integral += error * dt clamped to [0, max_integral]
d_pressure = max(0, d(pressure)/dt) (rising edge only)
target = min(feedforward_gain*pressure + kp*error
+ ki*integral + kd*d_pressure, sink_cap)
offer = max(1, dead_band + target / sink_per_offer) (APR multiple, floored at 1x)
bonus_apr = (offer - 1) * market_rate (0 when no sink is wanted)
rate = bonus_apr * staked_value / seconds_per_year (crvUSD/sec)

Gains and parameters are DAO-settable storage, not constants (set_gains, set_execution_params, set_sources, set_pressure_lts, set_gauge). The reserve is the PID contract's crvUSD balance.

Behaviour worth knowing

  • trigger() is permissionless. Anyone can advance the loop; there is no keeper dependency for liveness.
  • preview_signals() exposes the full signal set (pressure, sink, error, integral, derivative, offer, rate) as a view for monitoring and integration.
  • Graceful depletion. The FastGauge pulls owed crvUSD from the PID at each checkpoint, capped by the PID's balance. An empty reserve slows and stops the stream; nothing reverts.
  • Manipulation resistance. The PID reads the sink size via the FastGauge's tvl_ema(), an EMA over staked value, so flash deposits cannot spike the measured sink inside one block. Fee conversion into crvUSD happens via bounded, oracle-checked swaps (swap_fee_multiplier, dust_floor).
  • FastGauge is not a LiquidityGauge. No CRV emissions, no boost, single reward token, ERC-4626 accounting mirroring Curve's reward logic.
  • Fee flow. With the FeeSplitter installed as fee_receiver, veYB distributions receive 1 − split_fraction of admin fees. The PID share is DAO-set; it is 15% at activation.