From a77515acbf1c67365e938ae1df44fda01bd491ca Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Wed, 21 Jun 2023 15:25:24 +0200 Subject: [PATCH] Improve listing with invalid oracles (#620) * Stable Price: Reset on first price != 0 This helps when listing tokens or perp markets with an upcoming oracle. Currently the stable price would be 0 and would need to be manually reset by DAO proposal. With this change, the first non-zero value will be used as the starting point for the stable price instead. * Listing instructions: Succeed even if oracle is invalid But require the stable price to reset once it becomes valid. --- .../src/instructions/perp_create_market.rs | 14 +++++++++----- .../mango-v4/src/instructions/token_register.rs | 12 ++++++++---- .../src/instructions/token_register_trustless.rs | 12 ++++++++---- programs/mango-v4/src/state/stable_price.rs | 14 ++++++++++++-- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/programs/mango-v4/src/instructions/perp_create_market.rs b/programs/mango-v4/src/instructions/perp_create_market.rs index c56d0b7cc..67650325b 100644 --- a/programs/mango-v4/src/instructions/perp_create_market.rs +++ b/programs/mango-v4/src/instructions/perp_create_market.rs @@ -94,11 +94,15 @@ pub fn perp_create_market( reserved: [0; 1888], }; - let oracle_price = - perp_market.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None)?; - perp_market - .stable_price_model - .reset_to_price(oracle_price.to_num(), now_ts); + if let Ok(oracle_price) = + perp_market.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None) + { + perp_market + .stable_price_model + .reset_to_price(oracle_price.to_num(), now_ts); + } else { + perp_market.stable_price_model.reset_on_nonzero_price = 1; + } let mut orderbook = Orderbook { bids: ctx.accounts.bids.load_init()?, diff --git a/programs/mango-v4/src/instructions/token_register.rs b/programs/mango-v4/src/instructions/token_register.rs index dff98e00a..ad25f199b 100644 --- a/programs/mango-v4/src/instructions/token_register.rs +++ b/programs/mango-v4/src/instructions/token_register.rs @@ -93,10 +93,14 @@ pub fn token_register( }; require_gt!(bank.max_rate, MINIMUM_MAX_RATE); - let oracle_price = - bank.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None)?; - bank.stable_price_model - .reset_to_price(oracle_price.to_num(), now_ts); + if let Ok(oracle_price) = + bank.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None) + { + bank.stable_price_model + .reset_to_price(oracle_price.to_num(), now_ts); + } else { + bank.stable_price_model.reset_on_nonzero_price = 1; + } let mut mint_info = ctx.accounts.mint_info.load_init()?; *mint_info = MintInfo { diff --git a/programs/mango-v4/src/instructions/token_register_trustless.rs b/programs/mango-v4/src/instructions/token_register_trustless.rs index a198284aa..977cd1e0f 100644 --- a/programs/mango-v4/src/instructions/token_register_trustless.rs +++ b/programs/mango-v4/src/instructions/token_register_trustless.rs @@ -79,10 +79,14 @@ pub fn token_register_trustless( }; require_gt!(bank.max_rate, MINIMUM_MAX_RATE); - let oracle_price = - bank.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None)?; - bank.stable_price_model - .reset_to_price(oracle_price.to_num(), now_ts); + if let Ok(oracle_price) = + bank.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None) + { + bank.stable_price_model + .reset_to_price(oracle_price.to_num(), now_ts); + } else { + bank.stable_price_model.reset_on_nonzero_price = 1; + } let mut mint_info = ctx.accounts.mint_info.load_init()?; *mint_info = MintInfo { diff --git a/programs/mango-v4/src/state/stable_price.rs b/programs/mango-v4/src/state/stable_price.rs index 52283d72e..2538d04d5 100644 --- a/programs/mango-v4/src/state/stable_price.rs +++ b/programs/mango-v4/src/state/stable_price.rs @@ -49,15 +49,18 @@ pub struct StablePriceModel { /// The delay_interval_index that update() was last called on. pub last_delay_interval_index: u8, + /// If set to 1, the stable price will reset on the next non-zero price it sees. + pub reset_on_nonzero_price: u8, + #[derivative(Debug = "ignore")] - pub padding: [u8; 7], + pub padding: [u8; 6], #[derivative(Debug = "ignore")] pub reserved: [u8; 48], } const_assert_eq!( size_of::(), - 8 + 8 + 8 * 24 + 8 + 4 + 4 + 4 + 4 + 1 + 7 + 48 + 8 + 8 + 8 * 24 + 8 + 4 + 4 + 4 + 4 + 1 * 2 + 6 + 48 ); const_assert_eq!(size_of::(), 288); const_assert_eq!(size_of::() % 8, 0); @@ -74,6 +77,7 @@ impl Default for StablePriceModel { delay_growth_limit: 0.06, // 6% per hour, 400% per day stable_growth_limit: 0.0003, // 0.03% per second, 293% in 1h if updated every 10s, 281% in 1h if updated every 5min last_delay_interval_index: 0, + reset_on_nonzero_price: 0, padding: Default::default(), reserved: [0; 48], } @@ -87,6 +91,7 @@ impl StablePriceModel { self.delay_accumulator_price = 0.0; self.delay_accumulator_time = 0; self.last_update_timestamp = now_ts; + self.reset_on_nonzero_price = if oracle_price > 0.0 { 0 } else { 1 }; } pub fn delay_interval_index(&self, timestamp: u64) -> u8 { @@ -103,6 +108,11 @@ impl StablePriceModel { } pub fn update(&mut self, now_ts: u64, oracle_price: f64) { + // If a reset is requested (maybe there never was a non-zero price), jump to the current value + if self.reset_on_nonzero_price == 1 && oracle_price > 0.0 { + self.reset_to_price(oracle_price, now_ts); + } + let dt = now_ts.saturating_sub(self.last_update_timestamp); // Hardcoded. Requiring a minimum time between updates reduces the possible difference // between frequent updates and infrequent ones.