Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 16 additions & 2 deletions packages/predict/simulations/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,20 @@ export function createMarketOracleWriterCapTx(recipient: string): Transaction {
return tx;
}

export function createMarketOracleLifecycleCapTx(
recipient: string,
oracleWriterCapId: string,
feedId: number,
): Transaction {
const tx = new Transaction();
const cap = tx.moveCall({
target: target("market_oracle", "create_lifecycle_cap"),
arguments: [tx.object(oracleWriterCapId), tx.pure.u32(feedId)],
});
tx.transferObjects([cap], tx.pure.address(recipient));
return tx;
}

export function createPythSourceTx(
feedId: number,
tickSize: bigint,
Expand Down Expand Up @@ -463,7 +477,7 @@ export async function seedPythSourceAndCreateExpiryMarketTx(params: {
poolVaultId: string;
protocolConfigId: string;
pythSourceId: string;
oracleCapId: string;
oracleLifecycleCapId: string;
expiry: bigint;
spot: bigint;
}): Promise<Transaction> {
Expand All @@ -482,7 +496,7 @@ export async function seedPythSourceAndCreateExpiryMarketTx(params: {
tx.object(params.poolVaultId),
tx.object(params.protocolConfigId),
tx.object(params.pythSourceId),
tx.object(params.oracleCapId),
tx.object(params.oracleLifecycleCapId),
tx.pure.u64(params.expiry),
tx.object(CLOCK_ID),
],
Expand Down
14 changes: 13 additions & 1 deletion packages/predict/simulations/src/sim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
address,
createManagerTx,
createMarketOracleWriterCapTx,
createMarketOracleLifecycleCapTx,
createPythSourceTx,
depositToManagerTx,
deriveManagerId,
Expand Down Expand Up @@ -780,6 +781,17 @@ async function setupSimulation(
const oracleCapId: string = oracleCapChange.objectId;
console.log(`[${ts()}] OracleWriterCap: ${oracleCapId}`);

result = await executeAndWait(
createMarketOracleLifecycleCapTx(address, oracleCapId, 1),
"create_oracle_lifecycle_cap",
);
const oracleLifecycleCapChange = result.objectChanges.find(
(change: any) =>
change.type === "created" && change.objectType.includes("MarketOracleLifecycleCap"),
);
const oracleLifecycleCapId: string = oracleLifecycleCapChange.objectId;
console.log(`[${ts()}] OracleLifecycleCap: ${oracleLifecycleCapId}`);

result = await executeAndWait(
createPythSourceTx(1, ORACLE_TICK_SIZE, expiryFeeWindowMs, expiryFeeMaxMultiplier),
"create_pyth_source",
Expand All @@ -805,7 +817,7 @@ async function setupSimulation(
poolVaultId,
protocolConfigId,
pythSourceId,
oracleCapId,
oracleLifecycleCapId,
expiry: EXPIRY_MS,
spot: initialPythSpot,
}),
Expand Down
6 changes: 3 additions & 3 deletions packages/predict/sources/expiry_market.move
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use deepbook_predict::{
ewma::{Self, EwmaState},
ewma_config::EwmaConfig,
expiry_cash::{Self, ExpiryCash},
market_oracle::{MarketOracle, MarketOracleWriterCap},
market_oracle::{MarketOracle, MarketOracleLifecycleCap},
order::{Self, Order},
order_events,
predict_manager::{PredictManager, PredictTradeProof},
Expand Down Expand Up @@ -281,12 +281,12 @@ public fun compact_storage(
market: &mut ExpiryMarket,
config: &ProtocolConfig,
market_oracle: &MarketOracle,
cap: &MarketOracleWriterCap,
cap: &MarketOracleLifecycleCap,
) {
market.assert_version_allowed();
config.assert_not_valuation_in_progress();
market.assert_market_oracle(market_oracle);
market_oracle.assert_authorized_writer_cap(cap);
market_oracle.assert_authorized_lifecycle_cap(cap);
market.materialize_settled_liability(market_oracle);
market.strike_exposure.destroy_live_indexes();
market.assert_cash_backing();
Expand Down
106 changes: 95 additions & 11 deletions packages/predict/sources/oracle/market_oracle.move
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const EPendingSettlement: u64 = 14;
const EMarketNotSettled: u64 = 15;
const EInvalidSettlementTimestamp: u64 = 16;
const EPackageVersionDisabled: u64 = 17;
const EInvalidMarketOracleLifecycleCap: u64 = 18;

const STATUS_ACTIVE: u8 = 1;
const STATUS_PENDING_SETTLEMENT: u8 = 2;
Expand All @@ -61,7 +62,7 @@ public struct SVIParams has copy, drop, store {
/// Shared per-expiry oracle object storing live source data and settlement state.
public struct MarketOracle has key {
id: UID,
/// MarketOracleWriterCap IDs authorized to write Block Scholes data.
/// MarketOracleWriterCap IDs authorized for high-privilege oracle writes/config.
authorized_writer_cap_ids: VecSet<ID>,
/// Mirror of `ProtocolConfig.allowed_versions`; synced permissionlessly.
allowed_versions: VecSet<u64>,
Expand All @@ -87,11 +88,19 @@ public struct MarketOracle has key {
settlement_update_timestamp_ms: u64,
}

/// Capability authorized to write Block Scholes data and tune this oracle.
/// High-privilege capability authorized to write Block Scholes data and tune this oracle.
public struct MarketOracleWriterCap has key, store {
id: UID,
}

/// Lifecycle capability authorized to create, settle, and compact markets without oracle writes.
public struct MarketOracleLifecycleCap has key, store {
id: UID,
writer_cap_id: ID,
pyth_lazer_feed_id: u32,
market_oracle_ids: VecSet<ID>,
}

// === Public Functions ===

/// Construct a Block Scholes SVI parameter set.
Expand All @@ -114,6 +123,16 @@ public fun cap_id(cap: &MarketOracleWriterCap): ID {
cap.id.to_inner()
}

/// Return the MarketOracleLifecycleCap object ID.
public fun lifecycle_cap_id(cap: &MarketOracleLifecycleCap): ID {
cap.id.to_inner()
}

/// Return the Pyth Lazer feed ID this lifecycle cap can create markets for.
public fun lifecycle_pyth_lazer_feed_id(cap: &MarketOracleLifecycleCap): u32 {
cap.pyth_lazer_feed_id
}

/// Return the Pyth source object bound to this oracle.
public fun pyth_source_id(market: &MarketOracle): ID {
market.pyth_source_id
Expand Down Expand Up @@ -283,11 +302,11 @@ public fun settle_if_possible(
market: &mut MarketOracle,
config: &ProtocolConfig,
pyth: &PythSource,
cap: &MarketOracleWriterCap,
cap: &MarketOracleLifecycleCap,
clock: &Clock,
): bool {
market.assert_version_allowed();
market.assert_authorized_writer_cap(cap);
market.assert_authorized_lifecycle_cap(cap);
config.assert_not_valuation_in_progress();
if (market.status(clock) != STATUS_PENDING_SETTLEMENT) return false;
market.assert_pyth_source(pyth);
Expand Down Expand Up @@ -371,18 +390,43 @@ public fun set_basis_bounds(
market.emit_bounds_updated();
}

/// Create a new oracle writer capability.
/// Create a new high-privilege oracle writer capability.
public fun create_writer_cap(_admin_cap: &AdminCap, ctx: &mut TxContext): MarketOracleWriterCap {
MarketOracleWriterCap { id: object::new(ctx) }
}

/// Create a new oracle lifecycle capability bound to one high-privilege oracle writer cap.
public fun create_lifecycle_cap(
oracle_writer_cap: &MarketOracleWriterCap,
pyth_lazer_feed_id: u32,
ctx: &mut TxContext,
): MarketOracleLifecycleCap {
MarketOracleLifecycleCap {
id: object::new(ctx),
writer_cap_id: oracle_writer_cap.cap_id(),
pyth_lazer_feed_id,
market_oracle_ids: vec_set::empty(),
}
}

/// Destroy a MarketOracleWriterCap the holder no longer needs.
public fun destroy_writer_cap(cap: MarketOracleWriterCap) {
let MarketOracleWriterCap { id } = cap;
id.delete();
}

/// Authorize an additional cap to write this market oracle.
/// Destroy a MarketOracleLifecycleCap the holder no longer needs.
public fun destroy_lifecycle_cap(cap: MarketOracleLifecycleCap) {
let MarketOracleLifecycleCap {
id,
writer_cap_id: _,
pyth_lazer_feed_id: _,
market_oracle_ids: _,
} = cap;
id.delete();
}

/// Authorize an additional writer cap to write and tune this market oracle.
public fun register_writer_cap(
market: &mut MarketOracle,
_admin_cap: &AdminCap,
Expand All @@ -391,7 +435,7 @@ public fun register_writer_cap(
market.register_writer_cap_internal(cap);
}

/// Remove a cap from this market oracle's writer set.
/// Remove a writer cap from this market oracle's authorized writer set.
public fun unregister_writer_cap(market: &mut MarketOracle, _admin_cap: &AdminCap, cap_id: ID) {
market.unregister_writer_cap_internal(cap_id);
}
Expand All @@ -401,6 +445,32 @@ public fun self_unregister_writer_cap(market: &mut MarketOracle, cap: &MarketOra
market.unregister_writer_cap_internal(cap.cap_id());
}

/// Authorize a lifecycle cap to settle and compact the paired expiry market.
public fun register_lifecycle_cap(
market: &MarketOracle,
_admin_cap: &AdminCap,
cap: &mut MarketOracleLifecycleCap,
) {
market.assert_version_allowed();
let market_oracle_id = market.id();
assert!(!cap.market_oracle_ids.contains(&market_oracle_id), EInvalidMarketOracleLifecycleCap);
cap.market_oracle_ids.insert(market_oracle_id);
}

/// Remove a market oracle from this lifecycle cap's authorized set.
public fun unregister_lifecycle_cap(
cap: &mut MarketOracleLifecycleCap,
_admin_cap: &AdminCap,
market_oracle_id: ID,
) {
unregister_lifecycle_cap_internal(cap, market_oracle_id);
}

/// Let a lifecycle cap holder remove one of its own market authorizations.
public fun self_unregister_lifecycle_cap(cap: &mut MarketOracleLifecycleCap, market_oracle_id: ID) {
unregister_lifecycle_cap_internal(cap, market_oracle_id);
}

// === Public-Package Functions ===

/// Overwrite this oracle's mirrored `allowed_versions`. The only authorized
Expand Down Expand Up @@ -440,14 +510,14 @@ public(package) fun settlement_price(market: &MarketOracle): u64 {
public(package) fun create_and_share(
pyth: &PythSource,
config: &MarketOracleConfig,
cap: &MarketOracleWriterCap,
cap: &mut MarketOracleLifecycleCap,
expiry: u64,
allowed_versions: VecSet<u64>,
ctx: &mut TxContext,
): ID {
let cap_id = cap.cap_id();
assert!(cap.pyth_lazer_feed_id == pyth.feed_id(), EInvalidMarketOracleLifecycleCap);
let mut authorized_writer_cap_ids = vec_set::empty();
authorized_writer_cap_ids.insert(cap_id);
authorized_writer_cap_ids.insert(cap.writer_cap_id);
let market = MarketOracle {
id: object::new(ctx),
authorized_writer_cap_ids,
Expand Down Expand Up @@ -479,6 +549,7 @@ public(package) fun create_and_share(
};

let market_oracle_id = market.id();
cap.market_oracle_ids.insert(market_oracle_id);
transfer::share_object(market);
market_oracle_id
}
Expand Down Expand Up @@ -506,7 +577,7 @@ public(package) fun assert_not_pending_settlement(market: &MarketOracle, clock:
assert!(market.status(clock) != STATUS_PENDING_SETTLEMENT, EPendingSettlement);
}

/// Abort unless the cap is authorized for this oracle.
/// Abort unless the writer cap is authorized for this oracle.
public(package) fun assert_authorized_writer_cap(
market: &MarketOracle,
cap: &MarketOracleWriterCap,
Expand All @@ -517,6 +588,14 @@ public(package) fun assert_authorized_writer_cap(
);
}

/// Abort unless the lifecycle cap is authorized for this oracle.
public(package) fun assert_authorized_lifecycle_cap(
market: &MarketOracle,
cap: &MarketOracleLifecycleCap,
) {
assert!(cap.market_oracle_ids.contains(&market.id()), EInvalidMarketOracleLifecycleCap);
}

// === Private Functions ===

fun register_writer_cap_internal(market: &mut MarketOracle, cap: &MarketOracleWriterCap) {
Expand All @@ -532,6 +611,11 @@ fun unregister_writer_cap_internal(market: &mut MarketOracle, cap_id: ID) {
market.authorized_writer_cap_ids.remove(&cap_id);
}

fun unregister_lifecycle_cap_internal(cap: &mut MarketOracleLifecycleCap, market_oracle_id: ID) {
assert!(cap.market_oracle_ids.contains(&market_oracle_id), EInvalidMarketOracleLifecycleCap);
cap.market_oracle_ids.remove(&market_oracle_id);
}

fun apply_block_scholes_prices(
market: &mut MarketOracle,
spot: u64,
Expand Down
4 changes: 2 additions & 2 deletions packages/predict/sources/registry.move
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use deepbook_predict::{
config_events,
constants,
expiry_market::{Self, ExpiryMarket},
market_oracle::{Self, MarketOracle, MarketOracleWriterCap},
market_oracle::{Self, MarketOracle, MarketOracleLifecycleCap},
plp::PoolVault,
predict_manager::{Self, PredictDepositCap, PredictManager, PredictTradeCap, PredictWithdrawCap},
pricing,
Expand Down Expand Up @@ -410,7 +410,7 @@ public fun create_expiry_market(
pool_vault: &mut PoolVault,
config: &ProtocolConfig,
pyth: &PythSource,
cap: &MarketOracleWriterCap,
cap: &mut MarketOracleLifecycleCap,
expiry: u64,
clock: &Clock,
ctx: &mut TxContext,
Expand Down
Loading
Loading