Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a06d5a1
predict: refresh NAV per market and flush from stored marks (DBU-557)
0xaslan Jul 14, 2026
4f65463
predict: plain-language doc for the drift-epsilon knob
0xaslan Jul 14, 2026
1ffacb4
predict: take Pricer directly in refresh_expiry_nav and collect_expir…
0xaslan Jul 14, 2026
6a17030
predict: net NAV at the pool, hook liquidation write-through, extract…
0xaslan Jul 14, 2026
4c3924d
predict: correct the drift-bound doc claim to the measured ~0.8*epsilon
0xaslan Jul 14, 2026
83b4cc7
predict: third drift-guard leg — surface probes close the wing-reshap…
0xaslan Jul 14, 2026
c97b962
predict: API review cleanups — drop dead liquidate returns, split flu…
0xaslan Jul 14, 2026
dcf6da0
predict: drift guard probes prices, not variances — one leg, face-uni…
0xaslan Jul 14, 2026
231f781
predict: drift becomes a measured fact; acceptance moves to the pool …
0xaslan Jul 14, 2026
f845351
predict: fill in drift(pricer) — closed-form oracle-drift envelope
0xaslan Jul 14, 2026
8003060
predict: charge the skew correction in the drift envelope; complete t…
0xaslan Jul 14, 2026
9399623
predict: dollarize drift by live face quantity; enforce the aggregate…
0xaslan Jul 14, 2026
6d57200
predict: price drift into the flush mark as a bid/ask spread; kill th…
0xaslan Jul 14, 2026
88c1bf6
predict: fix review Criticals — anchor-priced write-through, band und…
0xaslan Jul 14, 2026
963d0cf
Revert "predict: fix review Criticals — anchor-priced write-through, …
0xaslan Jul 14, 2026
b37a7f1
predict: fail closed when band rounding swallows the strike band
0xaslan Jul 14, 2026
947739c
predict: guard the flush potato against interleaved cash moves
0xaslan Jul 14, 2026
da83644
predict: anchor-price the valuation-mark write-through, move the mark…
0xaslan Jul 14, 2026
737f455
predict: drift-envelope edge guards and stale flush comments
0xaslan Jul 14, 2026
5c630e5
predict: fold FlushMark executability into the value's type
0xaslan Jul 14, 2026
7688941
predict: review cleanup — stale references, dead code, comment de-dup…
0xaslan Jul 14, 2026
b3b3ff9
predict: drift-replica parity vectors (M0 gate for the DBU-557 measur…
0xaslan Jul 14, 2026
dc030f0
predict: pin the 15 measured drift-envelope violations in the parity …
0xaslan Jul 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/predict/sources/config/config_constants.move
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const EInvalidBackingBufferLambda: u64 = 19;
const EInvalidMaxAdmissionLeverage: u64 = 20;
const EInvalidCadenceWindowSize: u64 = 21;
const EMarketTickSizeTooLarge: u64 = 22;
const EInvalidNavMarkFreshnessMs: u64 = 23;

// === Fees ===

Expand Down Expand Up @@ -359,3 +360,14 @@ public(package) fun assert_upper_benefit_power(value: u64) {
EInvalidUpperBenefitPower,
);
}

public(package) macro fun default_nav_mark_freshness_ms(): u64 { 60_000 }
public(package) macro fun min_nav_mark_freshness_ms(): u64 { 1_000 }
public(package) macro fun max_nav_mark_freshness_ms(): u64 { 600_000 }

public(package) fun assert_nav_mark_freshness_ms(value: u64) {
assert!(
value >= min_nav_mark_freshness_ms!() && value <= max_nav_mark_freshness_ms!(),
EInvalidNavMarkFreshnessMs,
);
}
62 changes: 25 additions & 37 deletions packages/predict/sources/config/protocol_config.move
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

/// Protocol-wide configuration and flow gates for Predict.
///
/// This shared object owns the admin-tunable config structs, the trading pause
/// gate, and the transaction-local full-pool valuation lock. Flow modules decide
/// which gates apply before they mutate expiry, oracle, pool, or account state.
/// This shared object owns the admin-tunable config structs and the trading pause
/// gate. Flow modules decide which gates apply before they mutate expiry, oracle,
/// pool, or account state.
module deepbook_predict::protocol_config;

use deepbook_predict::{
Expand All @@ -21,10 +21,8 @@ use deepbook_predict::{
};

const ETradingPaused: u64 = 0;
const EValuationInProgress: u64 = 1;
const EValuationNotInProgress: u64 = 2;
const EPackageVersionDisabled: u64 = 3;
const EVersionWatermarkNotAdvanced: u64 = 4;
const EPackageVersionDisabled: u64 = 1;
const EVersionWatermarkNotAdvanced: u64 = 2;

/// Shared protocol policy and config state.
public struct ProtocolConfig has key {
Expand All @@ -47,9 +45,10 @@ public struct ProtocolConfig has key {
version_watermark: u64,
/// Blocks new risk creation while true.
trading_paused: bool,
/// Transaction-local lock held while a full-pool valuation is assembled, so no
/// NAV-changing op can interleave between per-market value steps in the PTB.
valuation_in_progress: bool,
/// Hard ceiling on stored valuation-mark age at the pool flush, in
/// milliseconds — the backstop against a stalled feed, which shows zero
/// measured drift and so cannot be caught by the drift spread.
nav_mark_freshness_ms: u64,
}

// === Public Functions ===
Expand Down Expand Up @@ -165,7 +164,6 @@ public fun set_pyth_spot_freshness_ms(
value: u64,
) {
config.assert_version();
config.assert_not_valuation_in_progress();
config.pricing_config.set_pyth_spot_freshness_ms(value);
}

Expand All @@ -176,7 +174,6 @@ public fun set_block_scholes_price_freshness_ms(
value: u64,
) {
config.assert_version();
config.assert_not_valuation_in_progress();
config.pricing_config.set_block_scholes_price_freshness_ms(value);
}

Expand All @@ -187,10 +184,20 @@ public fun set_block_scholes_svi_freshness_ms(
value: u64,
) {
config.assert_version();
config.assert_not_valuation_in_progress();
config.pricing_config.set_block_scholes_svi_freshness_ms(value);
}

/// Set the hard freshness ceiling on stored valuation marks at the pool flush.
public fun set_nav_mark_freshness_ms(
config: &mut ProtocolConfig,
_admin_cap: &AdminCap,
value: u64,
) {
config.assert_version();
config_constants::assert_nav_mark_freshness_ms(value);
config.nav_mark_freshness_ms = value;
}

/// Set the trading loss rebate rate template used by future expiry markets.
public fun set_template_trading_loss_rebate_rate(
config: &mut ProtocolConfig,
Expand Down Expand Up @@ -260,7 +267,6 @@ public fun set_protocol_reserve_profit_share(
protocol_reserve_profit_share: u64,
) {
config.assert_version();
config.assert_not_valuation_in_progress();
config_constants::assert_protocol_reserve_profit_share(protocol_reserve_profit_share);
config.protocol_reserve_profit_share = protocol_reserve_profit_share;
}
Expand Down Expand Up @@ -305,6 +311,10 @@ public(package) fun ewma_config(config: &ProtocolConfig): &EwmaConfig {
&config.ewma_config
}

public(package) fun nav_mark_freshness_ms(config: &ProtocolConfig): u64 {
config.nav_mark_freshness_ms
}

/// Abort unless the running package version is at or above the watermark floor.
///
/// The single version gate for the package: every version-gated flow threads the
Expand All @@ -322,16 +332,6 @@ public(package) fun assert_trading_allowed(config: &ProtocolConfig) {
config.assert_not_trading_paused();
}

/// Abort unless a valuation lock is currently active.
public(package) fun assert_valuation_in_progress(config: &ProtocolConfig) {
assert!(config.valuation_in_progress, EValuationNotInProgress);
}

/// Abort unless no valuation lock is currently active.
public(package) fun assert_not_valuation_in_progress(config: &ProtocolConfig) {
assert!(!config.valuation_in_progress, EValuationInProgress);
}

/// Create and share the protocol-wide configuration object.
public(package) fun create_and_share(ctx: &mut TxContext): ID {
let config = new(ctx);
Expand All @@ -346,18 +346,6 @@ public(package) fun pause_trading(config: &mut ProtocolConfig) {
config.set_trading_paused_internal(true);
}

/// Begin a transaction-local full-pool valuation lock.
public(package) fun begin_valuation(config: &mut ProtocolConfig) {
config.assert_not_valuation_in_progress();
config.valuation_in_progress = true;
}

/// End a transaction-local full-pool valuation lock.
public(package) fun end_valuation(config: &mut ProtocolConfig) {
config.assert_valuation_in_progress();
config.valuation_in_progress = false;
}

fun set_trading_paused_internal(config: &mut ProtocolConfig, paused: bool) {
config.trading_paused = paused;
config_events::emit_trading_paused_updated(config.id(), paused);
Expand All @@ -380,7 +368,7 @@ fun new(ctx: &mut TxContext): ProtocolConfig {
ewma_config: ewma_config::new(),
version_watermark: constants::current_version!(),
trading_paused: false,
valuation_in_progress: false,
nav_mark_freshness_ms: config_constants::default_nav_mark_freshness_ms!(),
}
}

Expand Down
13 changes: 8 additions & 5 deletions packages/predict/sources/constants.move
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ public(package) macro fun max_executable_plp_price(): u64 { 100_000_000 }
public(package) macro fun max_live_expiry_markets(): u64 { 24 }

/// Maximum finite payout-tree boundary nodes one expiry market may carry into NAV.
public(package) macro fun max_payout_tree_nodes(): u64 { 1_000 }
/// Held below Sui's ~1,000 cached-object per-transaction wall (measured single-market
/// crossing ~982 nodes, `predeploy/evidence/c1-object-cache-flush-2026-07-07.md`) so
/// one market's refresh walk always fits its own transaction.
public(package) macro fun max_payout_tree_nodes(): u64 { 950 }

/// Maximum active leveraged orders one expiry market may carry into NAV.
public(package) macro fun max_active_leveraged_orders(): u64 { 5_000 }
Expand Down Expand Up @@ -157,10 +160,10 @@ public(package) macro fun min_fee_incentive_sponsorship(): u64 { 10_000_000 }
/// resolution relayer sources that key from Pyth Lazer's exact-timestamp
/// resolution endpoints and inserts it on this millisecond grid.
/// Cadence periods are multiples of this value, so cadence-created expiries stay
/// on a settling key the relayer can produce. An off-grid expiry could never settle
/// and would block the pool flush indefinitely
/// (`plp::value_expiry` aborts on a past-expiry market that has no settling
/// observation yet).
/// on a settling key the relayer can produce. An off-grid expiry could never
/// settle and would block the pool flush indefinitely (a past-expiry market
/// cannot refresh its valuation mark — `pricing::load_live_pricer` rejects it —
/// so `plp::collect_expiry_nav` aborts stale until settlement sweeps it).
public(package) macro fun resolution_period_ms(): u64 { one_minute_ms!() }

// === Strike Tick Domain ===
Expand Down
58 changes: 47 additions & 11 deletions packages/predict/sources/events/vault_events.move
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,39 @@ public struct WithdrawFilled has copy, drop, store {
dusdc_amount: u64,
}

/// Emitted when a live market's valuation mark is refreshed: the exact
/// per-order liability stored for the pool flush to read, at its landing time.
public struct NavRefreshed has copy, drop, store {
pool_vault_id: ID,
expiry_market_id: ID,
liability: u64,
computed_at_ms: u64,
}

/// Emitted once per flush after both queues drain. The flush IS the full-pool
/// valuation, so this single event carries the frozen mark every fill was priced at
/// (`pool_value` over `total_supply`), its valuation breakdown (`idle_balance_before`
/// plus `active_market_nav` over `market_count` active markets), how many of each
/// kind filled, how many queue heads spent per-flush budget (filled,
/// protocol-refunded, or live limit-missed), and the idle balance after the drain.
/// valuation, so this single event carries the mid mark every fill was spread
/// around (`pool_value` over `total_supply`, with `total_drift` as the bid/ask
/// half-spread), its raw valuation breakdown (`idle_balance_before` plus
/// `total_free_cash` owing `total_liability` over `market_count` active
/// markets), how many of each kind filled, how many queue heads spent
/// per-flush budget (filled, protocol-refunded, or live limit-missed), and the
/// idle balance after the drain.
public struct FlushExecuted has copy, drop, store {
pool_vault_id: ID,
epoch: u64,
/// LP-attributable pool NAV every fill was priced at: idle plus
/// `active_market_nav`, excluding unrealized and pending protocol profit.
/// LP-attributable pool NAV every fill was priced at: idle plus the raw
/// market sums below, excluding unrealized and pending protocol profit,
/// floored once at zero.
pool_value: u64,
total_supply: u64,
/// Σ of each active market's exact NAV at valuation (settled markets contribute 0).
active_market_nav: u64,
/// Σ of each counted market's live free cash at the flush (raw, unfloored;
/// with `total_liability` this exposes aggregate-underwater conditions).
total_free_cash: u64,
/// Σ of each counted market's marked liability at the flush (raw, unfloored).
total_liability: u64,
/// Σ of each counted market's worst-case dollar drift, applied at
/// `finish_flush` as the flush mark's bid/ask half-spread.
total_drift: u64,
/// Number of active markets valued for this flush.
market_count: u64,
/// Idle DUSDC held by the pool at valuation time, before the drain.
Expand Down Expand Up @@ -425,12 +443,28 @@ public(package) fun emit_withdraw_filled(
});
}

public(package) fun emit_nav_refreshed(
pool_vault_id: ID,
expiry_market_id: ID,
liability: u64,
computed_at_ms: u64,
) {
event::emit(NavRefreshed {
pool_vault_id,
expiry_market_id,
liability,
computed_at_ms,
});
}

public(package) fun emit_flush_executed(
pool_vault_id: ID,
epoch: u64,
pool_value: u64,
total_supply: u64,
active_market_nav: u64,
total_free_cash: u64,
total_liability: u64,
total_drift: u64,
market_count: u64,
idle_balance_before: u64,
supplies_filled: u64,
Expand All @@ -443,7 +477,9 @@ public(package) fun emit_flush_executed(
epoch,
pool_value,
total_supply,
active_market_nav,
total_free_cash,
total_liability,
total_drift,
market_count,
idle_balance_before,
supplies_filled,
Expand Down
Loading