diff --git a/.gitignore b/.gitignore index 9f253318..1897cb58 100644 --- a/.gitignore +++ b/.gitignore @@ -5,12 +5,9 @@ target cmake-build-* /venv -# CMake files -features.h - # IntelliJ / CLion configuration .idea *.iml -# Clang format -.clang-format \ No newline at end of file +# CMake files +features.h \ No newline at end of file diff --git a/program/c/src/oracle/oracle.h b/program/c/src/oracle/oracle.h index 61fbc7b5..05c62338 100644 --- a/program/c/src/oracle/oracle.h +++ b/program/c/src/oracle/oracle.h @@ -23,7 +23,7 @@ extern "C" { #define PC_MAP_TABLE_SIZE 640 // Total price component slots available -#define PC_NUM_COMP_PYTHNET 127 +#define PC_NUM_COMP_PYTHNET 128 // PC_NUM_COMP - number of price components in use // Not whole PC_NUM_COMP_PYTHNET because of stack issues appearing in upd_aggregate() diff --git a/program/c/src/oracle/upd_aggregate.h b/program/c/src/oracle/upd_aggregate.h index 94179940..21ae8e45 100644 --- a/program/c/src/oracle/upd_aggregate.h +++ b/program/c/src/oracle/upd_aggregate.h @@ -134,6 +134,14 @@ static inline void upd_twap( // update aggregate price static inline bool upd_aggregate( pc_price_t *ptr, uint64_t slot, int64_t timestamp ) { + // Update the value of the previous price, if it had TRADING status. + if ( ptr->agg_.status_ == PC_STATUS_TRADING ) { + ptr->prev_slot_ = ptr->agg_.pub_slot_; + ptr->prev_price_ = ptr->agg_.price_; + ptr->prev_conf_ = ptr->agg_.conf_; + ptr->prev_timestamp_ = ptr->timestamp_; + } + // update aggregate details ready for next slot ptr->valid_slot_ = ptr->agg_.pub_slot_;// valid slot-time of agg. price ptr->agg_.pub_slot_ = slot; // publish slot-time of agg. price diff --git a/program/rust/src/accounts/price.rs b/program/rust/src/accounts/price.rs index bcc76a59..421cc827 100644 --- a/program/rust/src/accounts/price.rs +++ b/program/rust/src/accounts/price.rs @@ -31,113 +31,62 @@ mod price_pythnet { }, error::OracleError, }, - std::ops::{ - Deref, - DerefMut, - Index, - IndexMut, - }, }; - #[repr(C)] - #[derive(Copy, Clone)] - pub struct PriceComponentArrayWrapper([PriceComponent; PC_NUM_COMP_PYTHNET as usize]); - - // Implementing Index and IndexMut allows PriceComponentArrayWrapper to use array indexing directly, - // such as price_account.comp_[i], making it behave more like a native array or slice. - impl Index for PriceComponentArrayWrapper { - type Output = PriceComponent; - - fn index(&self, index: usize) -> &Self::Output { - &self.0[index] - } - } - impl IndexMut for PriceComponentArrayWrapper { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.0[index] - } - } - - // Implementing Deref and DerefMut allows PriceComponentArrayWrapper to use slice methods directly, - // such as len(), making it behave more like a native array or slice. - impl Deref for PriceComponentArrayWrapper { - type Target = [PriceComponent]; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl DerefMut for PriceComponentArrayWrapper { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - - unsafe impl Pod for PriceComponentArrayWrapper { - } - unsafe impl Zeroable for PriceComponentArrayWrapper { - } - /// Pythnet-only extended price account format. This extension is /// an append-only change that adds extra publisher slots and /// PriceCumulative for TWAP processing. #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] pub struct PriceAccountPythnet { - pub header: AccountHeader, + pub header: AccountHeader, /// Type of the price account - pub price_type: u32, + pub price_type: u32, /// Exponent for the published prices - pub exponent: i32, + pub exponent: i32, /// Current number of authorized publishers - pub num_: u32, + pub num_: u32, /// Number of valid quotes for the last aggregation - pub num_qt_: u32, + pub num_qt_: u32, /// Last slot with a succesful aggregation (status : TRADING) - pub last_slot_: u64, + pub last_slot_: u64, /// Second to last slot where aggregation was attempted - pub valid_slot_: u64, + pub valid_slot_: u64, /// Ema for price - pub twap_: PriceEma, + pub twap_: PriceEma, /// Ema for confidence - pub twac_: PriceEma, + pub twac_: PriceEma, /// Last time aggregation was attempted - pub timestamp_: i64, + pub timestamp_: i64, /// Minimum valid publisher quotes for a succesful aggregation - pub min_pub_: u8, - pub message_sent_: u8, + pub min_pub_: u8, + pub message_sent_: u8, /// Configurable max latency in slots between send and receive - pub max_latency_: u8, + pub max_latency_: u8, /// Unused placeholder for alignment - pub unused_2_: i8, - pub unused_3_: i32, + pub unused_2_: i8, + pub unused_3_: i32, /// Corresponding product account - pub product_account: Pubkey, + pub product_account: Pubkey, /// Next price account in the list - pub next_price_account: Pubkey, + pub next_price_account: Pubkey, /// Second to last slot where aggregation was succesful (i.e. status : TRADING) - pub prev_slot_: u64, + pub prev_slot_: u64, /// Aggregate price at prev_slot_ - pub prev_price_: i64, + pub prev_price_: i64, /// Confidence interval at prev_slot_ - pub prev_conf_: u64, + pub prev_conf_: u64, /// Timestamp of prev_slot_ - pub prev_timestamp_: i64, + pub prev_timestamp_: i64, /// Last attempted aggregate results - pub agg_: PriceInfo, + pub agg_: PriceInfo, /// Publishers' price components. NOTE(2023-10-06): On Pythnet, not all /// PC_NUM_COMP_PYTHNET slots are used due to stack size /// issues in the C code. For iterating over price components, /// PC_NUM_COMP must be used. - pub comp_: PriceComponentArrayWrapper, - /// Previous EMA for price and confidence - pub prev_twap_: PriceEma, - pub prev_twac_: PriceEma, - /// Previous TWAP cumulative values - pub prev_price_cumulative: PriceCumulative, + pub comp_: [PriceComponent; PC_NUM_COMP_PYTHNET as usize], /// Cumulative sums of aggregative price and confidence used to compute arithmetic moving averages - pub price_cumulative: PriceCumulative, + pub price_cumulative: PriceCumulative, } impl PriceAccountPythnet { diff --git a/program/rust/src/processor/upd_price.rs b/program/rust/src/processor/upd_price.rs index e1440ad6..5e62f241 100644 --- a/program/rust/src/processor/upd_price.rs +++ b/program/rust/src/processor/upd_price.rs @@ -2,10 +2,10 @@ use { crate::{ accounts::{ PriceAccount, + PriceInfo, PythOracleSerialize, UPD_PRICE_WRITE_SEED, }, - c_oracle_header::PC_STATUS_TRADING, deserialize::{ load, load_checked, @@ -128,8 +128,7 @@ pub fn upd_price( let clock = Clock::from_account_info(clock_account)?; let mut publisher_index: usize = 0; - let slots_since_last_successful_aggregate: u64; - let noninitial_price_update_after_program_upgrade: bool; + let latest_aggregate_price: PriceInfo; // The price_data borrow happens in a scope because it must be // dropped before we borrow again as raw data pointer for the C @@ -150,16 +149,7 @@ pub fn upd_price( OracleError::PermissionViolation.into(), )?; - // We use last_slot_ to calculate slots_since_last_successful_aggregate. This is because last_slot_ is updated after the aggregate price is updated successfully. - slots_since_last_successful_aggregate = clock.slot - price_data.last_slot_; - - // Check if the program upgrade has happened in the current slot and aggregate price has been updated, if so, use the old logic to update twap/twac/price_cumulative. - // This is to ensure that twap/twac/price_cumulative are calculated correctly during the migration. - // We check if prev_twap_.denom_ is == 0 because when the program upgrade has happened, denom_ is initialized to 0 and it can only stay the same or increase while numer_ can be negative if prices are negative. - // And we check if slots_since_last_successful_aggregate == 0 to check if the aggregate price has been updated in the current slot. - noninitial_price_update_after_program_upgrade = - price_data.prev_twap_.denom_ == 0 && slots_since_last_successful_aggregate == 0; - + latest_aggregate_price = price_data.agg_; let latest_publisher_price = price_data.comp_[publisher_index].latest_; // Check that publisher is publishing a more recent price @@ -171,38 +161,10 @@ pub fn upd_price( )?; } - // Extend the scope of the mutable borrow of price_data - { - let mut price_data = load_checked::(price_account, cmd_args.header.version)?; - - // Update the publisher's price - if is_component_update(cmd_args)? { - let status: u32 = get_status_for_conf_price_ratio( - cmd_args.price, - cmd_args.confidence, - cmd_args.status, - )?; - let publisher_price = &mut price_data.comp_[publisher_index].latest_; - publisher_price.price_ = cmd_args.price; - publisher_price.conf_ = cmd_args.confidence; - publisher_price.status_ = status; - publisher_price.pub_slot_ = cmd_args.publishing_slot; - } - - // If the price update is the first in the slot and the aggregate is trading, update the previous slot, price, conf, and timestamp. - if slots_since_last_successful_aggregate > 0 && price_data.agg_.status_ == PC_STATUS_TRADING - { - price_data.prev_slot_ = price_data.agg_.pub_slot_; - price_data.prev_price_ = price_data.agg_.price_; - price_data.prev_conf_ = price_data.agg_.conf_; - price_data.prev_timestamp_ = price_data.timestamp_; - } - } - - let updated = unsafe { - if noninitial_price_update_after_program_upgrade { - false - } else { + // Try to update the aggregate + #[allow(unused_variables)] + if clock.slot > latest_aggregate_price.pub_slot_ { + let updated = unsafe { // NOTE: c_upd_aggregate must use a raw pointer to price // data. Solana's `.borrow_*` methods require exclusive // access, i.e. no other borrow can exist for the account. @@ -211,41 +173,25 @@ pub fn upd_price( clock.slot, clock.unix_timestamp, ) - } - }; - - // If the aggregate was successfully updated, calculate the difference and update TWAP. - if updated { - { + }; + + // If the aggregate was successfully updated, calculate the difference and update TWAP. + if updated { + let agg_diff = (clock.slot as i64) + - load_checked::(price_account, cmd_args.header.version)?.prev_slot_ + as i64; + // Encapsulate TWAP update logic in a function to minimize unsafe block scope. + unsafe { + c_upd_twap(price_account.try_borrow_mut_data()?.as_mut_ptr(), agg_diff); + } let mut price_data = load_checked::(price_account, cmd_args.header.version)?; - - // Multiple price updates may occur within the same slot. Updates within the same slot will - // use the previously calculated values (prev_twap, prev_twac, and prev_price_cumulative) - // from the last successful aggregated price update as their basis for recalculation. This - // ensures that each update within a slot builds upon the last and not the twap/twac/price_cumulative - // that is calculated right after the publishers' individual price updates. - if slots_since_last_successful_aggregate > 0 { - price_data.prev_twap_ = price_data.twap_; - price_data.prev_twac_ = price_data.twac_; - price_data.prev_price_cumulative = price_data.price_cumulative; - } - price_data.twap_ = price_data.prev_twap_; - price_data.twac_ = price_data.prev_twac_; - price_data.price_cumulative = price_data.prev_price_cumulative; - - price_data.update_price_cumulative()?; // We want to send a message every time the aggregate price updates. However, during the migration, // not every publisher will necessarily provide the accumulator accounts. The message_sent_ flag // ensures that after every aggregate update, the next publisher who provides the accumulator accounts // will send the message. price_data.message_sent_ = 0; - } - let agg_diff = (clock.slot as i64) - - load_checked::(price_account, cmd_args.header.version)?.prev_slot_ - as i64; - unsafe { - c_upd_twap(price_account.try_borrow_mut_data()?.as_mut_ptr(), agg_diff); + price_data.update_price_cumulative()?; } } @@ -315,6 +261,23 @@ pub fn upd_price( } } + // Try to update the publisher's price + if is_component_update(cmd_args)? { + // IMPORTANT: If the publisher does not meet the price/conf + // ratio condition, its price will not count for the next + // aggregate. + let status: u32 = + get_status_for_conf_price_ratio(cmd_args.price, cmd_args.confidence, cmd_args.status)?; + + { + let publisher_price = &mut price_data.comp_[publisher_index].latest_; + publisher_price.price_ = cmd_args.price; + publisher_price.conf_ = cmd_args.confidence; + publisher_price.status_ = status; + publisher_price.pub_slot_ = cmd_args.publishing_slot; + } + } + Ok(()) } diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index fdfa8e70..8208d153 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -19,10 +19,13 @@ mod test_publish_batch; mod test_set_max_latency; mod test_set_min_pub; mod test_sizes; -mod test_twap; mod test_upd_aggregate; mod test_upd_permissions; mod test_upd_price; mod test_upd_price_no_fail_on_error; mod test_upd_product; mod test_utils; + + +mod test_twap; +mod test_upd_price_v2; diff --git a/program/rust/src/tests/test_ema.rs b/program/rust/src/tests/test_ema.rs index 4973ec59..1a1cae10 100644 --- a/program/rust/src/tests/test_ema.rs +++ b/program/rust/src/tests/test_ema.rs @@ -1,31 +1,12 @@ extern crate test_generator; use { - super::test_utils::update_clock_slot, crate::{ - accounts::{ - PriceAccount, - PythAccount, - }, - c_oracle_header::{ - PC_STATUS_TRADING, - PC_STATUS_UNKNOWN, - PC_VERSION, - }, - deserialize::{ - load_checked, - load_mut, - }, - instruction::{ - OracleCommand, - UpdPriceArgs, - }, + accounts::PriceAccount, processor::{ c_upd_aggregate, c_upd_twap, - process_instruction, }, - tests::test_utils::AccountSetup, }, bytemuck::Zeroable, csv::ReaderBuilder, @@ -33,161 +14,10 @@ use { Deserialize, Serialize, }, - solana_program::pubkey::Pubkey, - solana_sdk::account_info::AccountInfo, - std::{ - fs::File, - mem::size_of, - }, + std::fs::File, test_generator::test_resources, }; -#[test] -fn test_ema_multiple_publishers_same_slot() -> Result<(), Box> { - let mut instruction_data = [0u8; size_of::()]; - - let program_id = Pubkey::new_unique(); - - let mut funding_setup = AccountSetup::new_funding(); - let funding_account = funding_setup.as_account_info(); - - let mut price_setup = AccountSetup::new::(&program_id); - let mut price_account = price_setup.as_account_info(); - price_account.is_signer = false; - PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); - - add_publisher(&mut price_account, funding_account.key); - - let mut clock_setup = AccountSetup::new_clock(); - let mut clock_account = clock_setup.as_account_info(); - clock_account.is_signer = false; - clock_account.is_writable = false; - - update_clock_slot(&mut clock_account, 1); - - populate_instruction(&mut instruction_data, 10, 1, 1); - process_instruction( - &program_id, - &[ - funding_account.clone(), - price_account.clone(), - clock_account.clone(), - ], - &instruction_data, - )?; - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.prev_twap_.val_, 0); - assert_eq!(price_data.prev_twac_.val_, 0); - assert_eq!(price_data.twap_.val_, 10); - assert_eq!(price_data.twac_.val_, 1); - } - - // add new test for multiple publishers and ensure that ema is updated correctly - let mut funding_setup_two = AccountSetup::new_funding(); - let funding_account_two = funding_setup_two.as_account_info(); - - add_publisher(&mut price_account, funding_account_two.key); - - update_clock_slot(&mut clock_account, 2); - - populate_instruction(&mut instruction_data, 20, 1, 2); - process_instruction( - &program_id, - &[ - funding_account.clone(), - price_account.clone(), - clock_account.clone(), - ], - &instruction_data, - )?; - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.prev_twap_.val_, 10); - assert_eq!(price_data.prev_twac_.val_, 1); - assert_eq!(price_data.twap_.val_, 15); - assert_eq!(price_data.twac_.val_, 1); - } - - populate_instruction(&mut instruction_data, 30, 1, 2); - process_instruction( - &program_id, - &[ - funding_account_two.clone(), - price_account.clone(), - clock_account.clone(), - ], - &instruction_data, - )?; - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.prev_twap_.val_, 10); - assert_eq!(price_data.prev_twac_.val_, 1); - // The EMA value decreases to 12 despite an increase in the aggregate price due to the higher confidence associated with the new price. - // The EMA calculation considers the weight of 1/confidence for the new price, leading to a lower weighted average when the confidence is high. - assert_eq!(price_data.twap_.val_, 12); - assert_eq!(price_data.twac_.val_, 1); - } - - // add test for multiple publishers where the first publisher causes the aggregate to be unknown and second publisher causes it to be trading - { - let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - price_data.min_pub_ = 2; - price_data.num_ = 1; - } - - update_clock_slot(&mut clock_account, 3); - - populate_instruction(&mut instruction_data, 20, 1, 3); - process_instruction( - &program_id, - &[ - funding_account.clone(), - price_account.clone(), - clock_account.clone(), - ], - &instruction_data, - )?; - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); - assert_eq!(price_data.prev_twap_.val_, 10); - assert_eq!(price_data.prev_twac_.val_, 1); - assert_eq!(price_data.twap_.val_, 12); - assert_eq!(price_data.twac_.val_, 1); - } - - { - let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - price_data.min_pub_ = 2; - price_data.num_ = 2; - } - - populate_instruction(&mut instruction_data, 40, 1, 3); - process_instruction( - &program_id, - &[ - funding_account_two.clone(), - price_account.clone(), - clock_account.clone(), - ], - &instruction_data, - )?; - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_twap_.val_, 12); - assert_eq!(price_data.prev_twac_.val_, 1); - assert_eq!(price_data.twap_.val_, 13); - assert_eq!(price_data.twac_.val_, 2); - } - Ok(()) -} #[test_resources("program/rust/test_data/ema/*.csv")] fn test_ema(input_path_raw: &str) { @@ -280,6 +110,7 @@ fn run_ema_test(inputs: &[InputRecord], expected_outputs: &[OutputRecord]) { } } + // TODO: put these functions somewhere more accessible pub fn upd_aggregate( price_account: &mut PriceAccount, @@ -299,6 +130,7 @@ pub fn upd_twap(price_account: &mut PriceAccount, nslots: i64) { unsafe { c_upd_twap((price_account as *mut PriceAccount) as *mut u8, nslots) } } + #[derive(Serialize, Deserialize, Debug)] struct InputRecord { price: i64, @@ -316,20 +148,3 @@ struct OutputRecord { twap: i64, twac: i64, } - -fn populate_instruction(instruction_data: &mut [u8], price: i64, conf: u64, pub_slot: u64) { - let mut cmd = load_mut::(instruction_data).unwrap(); - cmd.header = OracleCommand::UpdPrice.into(); - cmd.status = PC_STATUS_TRADING; - cmd.price = price; - cmd.confidence = conf; - cmd.publishing_slot = pub_slot; - cmd.unused_ = 0; -} - -fn add_publisher(price_account: &mut AccountInfo, publisher_key: &Pubkey) { - let mut price_data = load_checked::(price_account, PC_VERSION).unwrap(); - let index = price_data.num_ as usize; - price_data.comp_[index].pub_ = *publisher_key; - price_data.num_ += 1; -} diff --git a/program/rust/src/tests/test_full_publisher_set.rs b/program/rust/src/tests/test_full_publisher_set.rs index 87f48b71..bc4cfd13 100644 --- a/program/rust/src/tests/test_full_publisher_set.rs +++ b/program/rust/src/tests/test_full_publisher_set.rs @@ -36,6 +36,7 @@ async fn test_full_publisher_set() -> Result<(), Box> { .await; let price = price_accounts["LTC"]; + let n_pubs = pub_keypairs.len(); // Divide publishers into two even parts (assuming the max PC_NUM_COMP size is even) @@ -60,6 +61,18 @@ async fn test_full_publisher_set() -> Result<(), Box> { sim.upd_price(second_kp, price, second_quote).await?; } + // Advance slot once from 1 to 2 + sim.warp_to_slot(2).await?; + + // Final price update to trigger aggregation + let first_kp = pub_keypairs.first().unwrap(); + let first_quote = Quote { + price: 100, + confidence: 30, + status: PC_STATUS_TRADING, + }; + sim.upd_price(first_kp, price, first_quote).await?; + { let price_data = sim .get_account_data_as::(price) diff --git a/program/rust/src/tests/test_publish.rs b/program/rust/src/tests/test_publish.rs index f10489d8..208642a4 100644 --- a/program/rust/src/tests/test_publish.rs +++ b/program/rust/src/tests/test_publish.rs @@ -73,9 +73,10 @@ async fn test_publish() { assert_eq!(price_data.comp_[0].latest_.price_, 150); assert_eq!(price_data.comp_[0].latest_.conf_, 7); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.comp_[0].agg_.price_, 150); - assert_eq!(price_data.comp_[0].agg_.conf_, 7); - assert_eq!(price_data.comp_[0].agg_.status_, PC_STATUS_TRADING); + + assert_eq!(price_data.comp_[0].agg_.price_, 0); + assert_eq!(price_data.comp_[0].agg_.conf_, 0); + assert_eq!(price_data.comp_[0].agg_.status_, PC_STATUS_UNKNOWN); } sim.warp_to_slot(2).await.unwrap(); @@ -101,8 +102,8 @@ async fn test_publish() { assert_eq!(price_data.comp_[0].latest_.conf_, 0); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_UNKNOWN); - assert_eq!(price_data.comp_[0].agg_.price_, 0); - assert_eq!(price_data.comp_[0].agg_.conf_, 0); - assert_eq!(price_data.comp_[0].agg_.status_, PC_STATUS_UNKNOWN); + assert_eq!(price_data.comp_[0].agg_.price_, 150); + assert_eq!(price_data.comp_[0].agg_.conf_, 7); + assert_eq!(price_data.comp_[0].agg_.status_, PC_STATUS_TRADING); } } diff --git a/program/rust/src/tests/test_publish_batch.rs b/program/rust/src/tests/test_publish_batch.rs index f40f97b5..f1c5424b 100644 --- a/program/rust/src/tests/test_publish_batch.rs +++ b/program/rust/src/tests/test_publish_batch.rs @@ -84,12 +84,9 @@ async fn test_publish_batch() { price_data.comp_[0].latest_.status_, get_status_for_conf_price_ratio(quote.price, quote.confidence, quote.status).unwrap() ); - assert_eq!(price_data.comp_[0].agg_.price_, quote.price); - assert_eq!(price_data.comp_[0].agg_.conf_, quote.confidence); - assert_eq!( - price_data.comp_[0].agg_.status_, - get_status_for_conf_price_ratio(quote.price, quote.confidence, quote.status).unwrap() - ); + assert_eq!(price_data.comp_[0].agg_.price_, 0); + assert_eq!(price_data.comp_[0].agg_.conf_, 0); + assert_eq!(price_data.comp_[0].agg_.status_, PC_STATUS_UNKNOWN); } sim.warp_to_slot(2).await.unwrap(); @@ -114,6 +111,7 @@ async fn test_publish_batch() { .get_account_data_as::(*price) .await .unwrap(); + let quote = quotes.get(key).unwrap(); let new_quote = new_quotes.get(key).unwrap(); assert_eq!(price_data.comp_[0].latest_.price_, new_quote.price); @@ -127,16 +125,11 @@ async fn test_publish_batch() { ) .unwrap() ); - assert_eq!(price_data.comp_[0].agg_.price_, new_quote.price); - assert_eq!(price_data.comp_[0].agg_.conf_, new_quote.confidence); + assert_eq!(price_data.comp_[0].agg_.price_, quote.price); + assert_eq!(price_data.comp_[0].agg_.conf_, quote.confidence); assert_eq!( price_data.comp_[0].agg_.status_, - get_status_for_conf_price_ratio( - new_quote.price, - new_quote.confidence, - new_quote.status - ) - .unwrap() + get_status_for_conf_price_ratio(quote.price, quote.confidence, quote.status).unwrap() ); } } diff --git a/program/rust/src/tests/test_sizes.rs b/program/rust/src/tests/test_sizes.rs index 50e68341..9ec1e37b 100644 --- a/program/rust/src/tests/test_sizes.rs +++ b/program/rust/src/tests/test_sizes.rs @@ -6,7 +6,6 @@ use { PermissionAccount, PriceAccount, PriceComponent, - PriceCumulative, PriceEma, PriceInfo, ProductAccount, @@ -15,7 +14,6 @@ use { c_oracle_header::{ PC_MAP_TABLE_SIZE, PC_NUM_COMP, - PC_NUM_COMP_PYTHNET, PC_VERSION, ZSTD_UPPER_BOUND, }, @@ -54,22 +52,30 @@ fn test_sizes() { size_of::(), size_of::() + 2 * size_of::() ); - // Sanity-check the Pythnet PC_NUM_COMP - assert_eq!(PC_NUM_COMP, 64); - assert_eq!( - size_of::(), - 48 + u64::BITS as usize - + 3 * size_of::() - + size_of::() - + (PC_NUM_COMP_PYTHNET as usize) * size_of::() - + size_of::() - + size_of::() - + size_of::() - + size_of::() - ); - assert_eq!(size_of::(), 12576); - assert!(size_of::() == try_convert::<_, usize>(ZSTD_UPPER_BOUND).unwrap()); - assert_eq!(size_of::(), 48); + + { + use crate::{ + accounts::PriceCumulative, + c_oracle_header::PC_NUM_COMP_PYTHNET, + }; + + // Sanity-check the Pythnet PC_NUM_COMP + assert_eq!(PC_NUM_COMP, 64); + + assert_eq!( + size_of::(), + 48 + u64::BITS as usize + + 3 * size_of::() + + size_of::() + + (PC_NUM_COMP_PYTHNET as usize) * size_of::() + + size_of::() + ); + assert_eq!(size_of::(), 12576); + assert!(size_of::() == try_convert::<_, usize>(ZSTD_UPPER_BOUND).unwrap()); + + assert_eq!(size_of::(), 48); + } + assert_eq!(size_of::(), 8); assert_eq!(size_of::(), 16); assert_eq!(size_of::(), 16); diff --git a/program/rust/src/tests/test_upd_aggregate.rs b/program/rust/src/tests/test_upd_aggregate.rs index 657c6cd6..c9e962cf 100644 --- a/program/rust/src/tests/test_upd_aggregate.rs +++ b/program/rust/src/tests/test_upd_aggregate.rs @@ -67,6 +67,7 @@ fn test_upd_aggregate() { corp_act_status_: 0, }; + let mut instruction_data = [0u8; size_of::()]; populate_instruction(&mut instruction_data, 42, 2, 1); @@ -77,42 +78,11 @@ fn test_upd_aggregate() { price_account.is_signer = false; PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); - // test same slot aggregation, aggregate price and conf should be updated - { - let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - price_data.num_ = 1; - price_data.last_slot_ = 1000; - price_data.agg_.pub_slot_ = 1000; - price_data.comp_[0].latest_ = p1; - } - unsafe { - assert!(c_upd_aggregate( - price_account.try_borrow_mut_data().unwrap().as_mut_ptr(), - 1000, - 1, - )); - } - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - - assert_eq!(price_data.agg_.price_, 100); - assert_eq!(price_data.agg_.conf_, 10); - assert_eq!(price_data.num_qt_, 1); - assert_eq!(price_data.timestamp_, 1); - assert_eq!(price_data.prev_slot_, 0); - assert_eq!(price_data.prev_price_, 0); - assert_eq!(price_data.prev_conf_, 0); - assert_eq!(price_data.prev_timestamp_, 0); - } - // single publisher { let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); price_data.num_ = 1; - price_data.num_qt_ = 0; price_data.last_slot_ = 1000; - price_data.timestamp_ = 0; price_data.agg_.pub_slot_ = 1000; price_data.comp_[0].latest_ = p1; } @@ -164,6 +134,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.agg_.conf_, 55); assert_eq!(price_data.num_qt_, 2); assert_eq!(price_data.timestamp_, 2); + assert_eq!(price_data.prev_slot_, 1000); + assert_eq!(price_data.prev_price_, 100); + assert_eq!(price_data.prev_conf_, 10); + assert_eq!(price_data.prev_timestamp_, 1); } // three publishers @@ -193,6 +167,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.agg_.conf_, 90); assert_eq!(price_data.num_qt_, 3); assert_eq!(price_data.timestamp_, 3); + assert_eq!(price_data.prev_slot_, 1000); + assert_eq!(price_data.prev_price_, 145); + assert_eq!(price_data.prev_conf_, 55); + assert_eq!(price_data.prev_timestamp_, 2); } // four publishers @@ -224,6 +202,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.num_qt_, 4); assert_eq!(price_data.timestamp_, 4); assert_eq!(price_data.last_slot_, 1001); + assert_eq!(price_data.prev_slot_, 1000); + assert_eq!(price_data.prev_price_, 200); + assert_eq!(price_data.prev_conf_, 90); + assert_eq!(price_data.prev_timestamp_, 3); } unsafe { @@ -241,6 +223,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.last_slot_, 1025); assert_eq!(price_data.num_qt_, 4); assert_eq!(price_data.timestamp_, 5); + assert_eq!(price_data.prev_slot_, 1001); + assert_eq!(price_data.prev_price_, 245); + assert_eq!(price_data.prev_conf_, 85); + assert_eq!(price_data.prev_timestamp_, 4); } // check what happens when nothing publishes for a while @@ -259,6 +245,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.last_slot_, 1025); assert_eq!(price_data.num_qt_, 0); assert_eq!(price_data.timestamp_, 10); + assert_eq!(price_data.prev_slot_, 1025); + assert_eq!(price_data.prev_price_, 245); + assert_eq!(price_data.prev_conf_, 85); + assert_eq!(price_data.prev_timestamp_, 5); } unsafe { @@ -276,6 +266,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.last_slot_, 1025); assert_eq!(price_data.num_qt_, 0); assert_eq!(price_data.timestamp_, 12); + assert_eq!(price_data.prev_slot_, 1025); + assert_eq!(price_data.prev_price_, 245); + assert_eq!(price_data.prev_conf_, 85); + assert_eq!(price_data.prev_timestamp_, 5); } // ensure the update occurs within the PC_MAX_SEND_LATENCY limit of 25 slots, allowing the aggregated price to reflect both p4 and p5 contributions @@ -304,6 +298,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.agg_.conf_, 55); assert_eq!(price_data.num_qt_, 2); assert_eq!(price_data.timestamp_, 13); + assert_eq!(price_data.prev_slot_, 1025); + assert_eq!(price_data.prev_price_, 245); + assert_eq!(price_data.prev_conf_, 85); + assert_eq!(price_data.prev_timestamp_, 5); } // verify behavior when publishing halts for 1 slot, causing the slot difference from p5 to exceed the PC_MAX_SEND_LATENCY threshold of 25. @@ -323,6 +321,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.agg_.conf_, 50); assert_eq!(price_data.num_qt_, 1); assert_eq!(price_data.timestamp_, 14); + assert_eq!(price_data.prev_slot_, 1025); + assert_eq!(price_data.prev_price_, 445); + assert_eq!(price_data.prev_conf_, 55); + assert_eq!(price_data.prev_timestamp_, 13); } // verify behavior when max_latency_ is set to 5, and all components pub_slot_ gap is more than 5, this should result in PC_STATUS_UNKNOWN status @@ -354,6 +356,10 @@ fn test_upd_aggregate() { assert_eq!(price_data.agg_.conf_, 50); assert_eq!(price_data.num_qt_, 0); assert_eq!(price_data.timestamp_, 15); + assert_eq!(price_data.prev_slot_, 1000); + assert_eq!(price_data.prev_price_, 500); + assert_eq!(price_data.prev_conf_, 50); + assert_eq!(price_data.prev_timestamp_, 14); } } diff --git a/program/rust/src/tests/test_upd_price.rs b/program/rust/src/tests/test_upd_price.rs index d370030f..9726cda7 100644 --- a/program/rust/src/tests/test_upd_price.rs +++ b/program/rust/src/tests/test_upd_price.rs @@ -21,7 +21,6 @@ use { processor::process_instruction, tests::test_utils::{ update_clock_slot, - update_clock_timestamp, AccountSetup, }, }, @@ -29,7 +28,6 @@ use { program_error::ProgramError, pubkey::Pubkey, }, - solana_sdk::account_info::AccountInfo, std::mem::size_of, }; @@ -60,7 +58,6 @@ fn test_upd_price() { clock_account.is_writable = false; update_clock_slot(&mut clock_account, 1); - update_clock_timestamp(&mut clock_account, 1); assert!(process_instruction( &program_id, @@ -80,17 +77,9 @@ fn test_upd_price() { assert_eq!(price_data.comp_[0].latest_.pub_slot_, 1); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); assert_eq!(price_data.valid_slot_, 0); - assert_eq!(price_data.last_slot_, 1); assert_eq!(price_data.agg_.pub_slot_, 1); - assert_eq!(price_data.agg_.price_, 42); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 0); - assert_eq!(price_data.prev_price_, 0); - assert_eq!(price_data.prev_conf_, 0); - assert_eq!(price_data.prev_timestamp_, 0); - assert_eq!(price_data.price_cumulative.price, 42); - assert_eq!(price_data.price_cumulative.conf, 2); - assert_eq!(price_data.price_cumulative.num_down_slots, 0); + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); } // add some prices for current slot - get rejected @@ -116,20 +105,12 @@ fn test_upd_price() { assert_eq!(price_data.comp_[0].latest_.pub_slot_, 1); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); assert_eq!(price_data.valid_slot_, 0); - assert_eq!(price_data.last_slot_, 1); assert_eq!(price_data.agg_.pub_slot_, 1); - assert_eq!(price_data.agg_.price_, 42); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 0); - assert_eq!(price_data.prev_price_, 0); - assert_eq!(price_data.prev_conf_, 0); - assert_eq!(price_data.prev_timestamp_, 0); - assert_eq!(price_data.price_cumulative.price, 42); - assert_eq!(price_data.price_cumulative.conf, 2); - assert_eq!(price_data.price_cumulative.num_down_slots, 0); + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); } - // update new price in new slot, aggregate should be updated and prev values should be updated + // add next price in new slot triggering snapshot and aggregate calc populate_instruction(&mut instruction_data, 81, 2, 2); update_clock_slot(&mut clock_account, 3); @@ -151,23 +132,14 @@ fn test_upd_price() { assert_eq!(price_data.comp_[0].latest_.pub_slot_, 2); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); assert_eq!(price_data.valid_slot_, 1); - assert_eq!(price_data.last_slot_, 3); assert_eq!(price_data.agg_.pub_slot_, 3); - assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.price_, 42); assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 1); - assert_eq!(price_data.prev_price_, 42); - assert_eq!(price_data.prev_conf_, 2); - assert_eq!(price_data.prev_timestamp_, 1); - assert_eq!(price_data.price_cumulative.price, 42 + 2 * 81); - assert_eq!(price_data.price_cumulative.conf, 3 * 2); - assert_eq!(price_data.price_cumulative.num_down_slots, 0); } - // next price doesn't change but slot and timestamp does + // next price doesn't change but slot does populate_instruction(&mut instruction_data, 81, 2, 3); update_clock_slot(&mut clock_account, 4); - update_clock_timestamp(&mut clock_account, 4); assert!(process_instruction( &program_id, &[ @@ -186,18 +158,9 @@ fn test_upd_price() { assert_eq!(price_data.comp_[0].latest_.pub_slot_, 3); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); assert_eq!(price_data.valid_slot_, 3); - assert_eq!(price_data.last_slot_, 4); assert_eq!(price_data.agg_.pub_slot_, 4); assert_eq!(price_data.agg_.price_, 81); assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 3); - assert_eq!(price_data.prev_price_, 81); - assert_eq!(price_data.prev_conf_, 2); - assert_eq!(price_data.timestamp_, 4); // We only check for timestamp_ here because test_upd_price doesn't directly update timestamp_, this is updated through c_upd_aggregate which is tested in test_upd_aggregate, but we assert here to show that in subsequent asserts for prev_timestamp_ the value should be updated to this value - assert_eq!(price_data.prev_timestamp_, 1); - assert_eq!(price_data.price_cumulative.price, 42 + 3 * 81); - assert_eq!(price_data.price_cumulative.conf, 4 * 2); - assert_eq!(price_data.price_cumulative.num_down_slots, 0); } // next price doesn't change and neither does aggregate but slot does @@ -221,17 +184,9 @@ fn test_upd_price() { assert_eq!(price_data.comp_[0].latest_.pub_slot_, 4); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); assert_eq!(price_data.valid_slot_, 4); - assert_eq!(price_data.last_slot_, 5); assert_eq!(price_data.agg_.pub_slot_, 5); assert_eq!(price_data.agg_.price_, 81); assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 4); - assert_eq!(price_data.prev_price_, 81); - assert_eq!(price_data.prev_conf_, 2); - assert_eq!(price_data.prev_timestamp_, 4); - assert_eq!(price_data.price_cumulative.price, 42 + 81 * 4); - assert_eq!(price_data.price_cumulative.conf, 5 * 2); - assert_eq!(price_data.price_cumulative.num_down_slots, 0); } // try to publish back-in-time @@ -257,17 +212,9 @@ fn test_upd_price() { assert_eq!(price_data.comp_[0].latest_.pub_slot_, 4); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); assert_eq!(price_data.valid_slot_, 4); - assert_eq!(price_data.last_slot_, 5); assert_eq!(price_data.agg_.pub_slot_, 5); assert_eq!(price_data.agg_.price_, 81); assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 4); - assert_eq!(price_data.prev_price_, 81); - assert_eq!(price_data.prev_conf_, 2); - assert_eq!(price_data.prev_timestamp_, 4); - assert_eq!(price_data.price_cumulative.price, 42 + 81 * 4); - assert_eq!(price_data.price_cumulative.conf, 5 * 2); - assert_eq!(price_data.price_cumulative.num_down_slots, 0); } populate_instruction(&mut instruction_data, 50, 20, 5); @@ -299,22 +246,14 @@ fn test_upd_price() { assert_eq!(price_data.comp_[0].latest_.pub_slot_, 5); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_IGNORED); assert_eq!(price_data.valid_slot_, 5); - assert_eq!(price_data.last_slot_, 5); assert_eq!(price_data.agg_.pub_slot_, 6); assert_eq!(price_data.agg_.price_, 81); - assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); - assert_eq!(price_data.prev_slot_, 5); - assert_eq!(price_data.prev_price_, 81); - assert_eq!(price_data.prev_conf_, 2); - assert_eq!(price_data.prev_timestamp_, 4); - assert_eq!(price_data.price_cumulative.price, 42 + 81 * 4); - assert_eq!(price_data.price_cumulative.conf, 2 * 5); - assert_eq!(price_data.price_cumulative.num_down_slots, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); } - // Negative prices are accepted - populate_instruction(&mut instruction_data, -100, 1, 7); - update_clock_slot(&mut clock_account, 8); + // Crank one more time and aggregate should be unknown + populate_instruction(&mut instruction_data, 50, 20, 6); + update_clock_slot(&mut clock_account, 7); assert!(process_instruction( &program_id, @@ -326,34 +265,22 @@ fn test_upd_price() { &instruction_data ) .is_ok()); + { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.comp_[0].latest_.price_, -100); - assert_eq!(price_data.comp_[0].latest_.conf_, 1); - assert_eq!(price_data.comp_[0].latest_.pub_slot_, 7); - assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.comp_[0].latest_.price_, 50); + assert_eq!(price_data.comp_[0].latest_.conf_, 20); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 6); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_IGNORED); assert_eq!(price_data.valid_slot_, 6); - assert_eq!(price_data.last_slot_, 8); - assert_eq!(price_data.agg_.pub_slot_, 8); - assert_eq!(price_data.agg_.price_, -100); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 5); - assert_eq!(price_data.prev_price_, 81); - assert_eq!(price_data.prev_conf_, 2); - assert_eq!(price_data.prev_timestamp_, 4); - assert_eq!(price_data.price_cumulative.price, 42 + 81 * 4 - 100 * 3); - assert_eq!(price_data.price_cumulative.conf, 2 * 5 + 3); // 2 * 5 + 1 * 3 - assert_eq!(price_data.price_cumulative.num_down_slots, 0); + assert_eq!(price_data.agg_.pub_slot_, 7); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); } - // add new test for multiple publishers and ensure that agg price is not updated multiple times when program upgrade happens in the same slot after the first update - let mut funding_setup_two = AccountSetup::new_funding(); - let funding_account_two = funding_setup_two.as_account_info(); - - add_publisher(&mut price_account, funding_account_two.key); - - populate_instruction(&mut instruction_data, 10, 1, 10); - update_clock_slot(&mut clock_account, 10); + // Negative prices are accepted + populate_instruction(&mut instruction_data, -100, 1, 7); + update_clock_slot(&mut clock_account, 8); assert!(process_instruction( &program_id, @@ -368,77 +295,19 @@ fn test_upd_price() { { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.comp_[0].latest_.price_, 10); + assert_eq!(price_data.comp_[0].latest_.price_, -100); assert_eq!(price_data.comp_[0].latest_.conf_, 1); - assert_eq!(price_data.comp_[0].latest_.pub_slot_, 10); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 7); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.valid_slot_, 8); - assert_eq!(price_data.last_slot_, 10); - assert_eq!(price_data.agg_.pub_slot_, 10); - assert_eq!(price_data.agg_.price_, 10); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 8); - assert_eq!(price_data.prev_price_, -100); - assert_eq!(price_data.prev_conf_, 1); - assert_eq!(price_data.prev_timestamp_, 4); - assert_eq!(price_data.twap_.numer_, 1677311098); - assert_eq!(price_data.twap_.denom_, 1279419481); - assert_eq!( - price_data.price_cumulative.price, - 42 + 81 * 4 - 100 * 3 + 10 * 2 - ); - assert_eq!(price_data.price_cumulative.conf, 2 * 5 + 5); // 2 * 5 + 1 * 5 - assert_eq!(price_data.price_cumulative.num_down_slots, 0); - } - - // reset twap_.denom_ to 0 to simulate program upgrade in the same slot and make sure agg_.price_ is not updated again - { - let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - price_data.prev_twap_.denom_ = 0; - } - populate_instruction(&mut instruction_data, 20, 1, 10); - assert!(process_instruction( - &program_id, - &[ - funding_account_two.clone(), - price_account.clone(), - clock_account.clone() - ], - &instruction_data - ) - .is_ok()); - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.comp_[1].latest_.price_, 20); - assert_eq!(price_data.comp_[1].latest_.conf_, 1); - assert_eq!(price_data.comp_[1].latest_.pub_slot_, 10); - assert_eq!(price_data.comp_[1].latest_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.valid_slot_, 8); - assert_eq!(price_data.last_slot_, 10); - assert_eq!(price_data.agg_.pub_slot_, 10); - assert_eq!(price_data.agg_.price_, 10); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 8); - assert_eq!(price_data.prev_price_, -100); - assert_eq!(price_data.prev_conf_, 1); - assert_eq!(price_data.prev_timestamp_, 4); - assert_eq!(price_data.twap_.numer_, 1677311098); // twap_.numer_ should not be updated - assert_eq!(price_data.twap_.denom_, 1279419481); // twap_.denom_ should not be updated - - // price_cumulative should not be updated - assert_eq!( - price_data.price_cumulative.price, - 42 + 81 * 4 - 100 * 3 + 10 * 2 - ); - assert_eq!(price_data.price_cumulative.conf, 2 * 5 + 5); - assert_eq!(price_data.price_cumulative.num_down_slots, 0); + assert_eq!(price_data.valid_slot_, 7); + assert_eq!(price_data.agg_.pub_slot_, 8); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); } - remove_publisher(&mut price_account); - // Big gap - populate_instruction(&mut instruction_data, 60, 4, 50); - update_clock_slot(&mut clock_account, 50); + // Crank again for aggregate + populate_instruction(&mut instruction_data, -100, 1, 8); + update_clock_slot(&mut clock_account, 9); assert!(process_instruction( &program_id, @@ -453,111 +322,14 @@ fn test_upd_price() { { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.comp_[0].latest_.price_, 60); - assert_eq!(price_data.comp_[0].latest_.conf_, 4); - assert_eq!(price_data.comp_[0].latest_.pub_slot_, 50); - assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.valid_slot_, 10); - assert_eq!(price_data.agg_.pub_slot_, 50); - assert_eq!(price_data.agg_.price_, 60); - assert_eq!(price_data.agg_.conf_, 4); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!( - price_data.price_cumulative.price, - 42 + 81 * 4 - 100 * 3 + 10 * 2 + 60 * 40 - ); - assert_eq!(price_data.price_cumulative.conf, 2 * 5 + 5 + 40 * 4); - assert_eq!(price_data.price_cumulative.num_down_slots, 15); - } - - // add new test for multiple publishers and ensure that price_cumulative is updated correctly - add_publisher(&mut price_account, funding_account_two.key); - populate_instruction(&mut instruction_data, 10, 1, 100); - update_clock_slot(&mut clock_account, 100); - assert!(process_instruction( - &program_id, - &[ - funding_account.clone(), - price_account.clone(), - clock_account.clone() - ], - &instruction_data - ) - .is_ok()); - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.comp_[0].latest_.price_, 10); - assert_eq!(price_data.comp_[0].latest_.conf_, 1); - assert_eq!(price_data.comp_[0].latest_.pub_slot_, 100); - assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.valid_slot_, 50); - assert_eq!(price_data.agg_.pub_slot_, 100); - assert_eq!(price_data.agg_.price_, 10); - assert_eq!(price_data.agg_.conf_, 1); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 50); - assert_eq!(price_data.prev_price_, 60); - assert_eq!(price_data.prev_conf_, 4); - assert_eq!( - price_data.prev_price_cumulative.price, - 42 + 81 * 4 - 100 * 3 + 10 * 2 + 60 * 40 - ); - assert_eq!(price_data.prev_price_cumulative.conf, 2 * 5 + 5 + 40 * 4); - assert_eq!(price_data.prev_price_cumulative.num_down_slots, 15); - assert_eq!( - price_data.price_cumulative.price, - 42 + 81 * 4 - 100 * 3 + 10 * 2 + 60 * 40 + 10 * 50 - ); // (42 + 81 * 4 - 100 * 3 + 10 * 2 + 60 * 40) is the price cumulative from the previous test and 10 * 50 (slot_gap) is the price cumulative from this test - assert_eq!(price_data.price_cumulative.conf, 2 * 5 + 5 + 40 * 4 + 50); - assert_eq!(price_data.price_cumulative.num_down_slots, 40); // prev num_down_slots was 15 and since pub slot is 100 and last pub slot was 50, slot_gap is 50 and default latency is 25, so num_down_slots = 50 - 25 = 25, so total num_down_slots = 15 + 25 = 40 - } - - populate_instruction(&mut instruction_data, 20, 2, 100); - assert!(process_instruction( - &program_id, - &[ - funding_account_two.clone(), - price_account.clone(), - clock_account.clone() - ], - &instruction_data - ) - .is_ok()); - - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert_eq!(price_data.comp_[0].latest_.price_, 10); + assert_eq!(price_data.comp_[0].latest_.price_, -100); assert_eq!(price_data.comp_[0].latest_.conf_, 1); - assert_eq!(price_data.comp_[0].latest_.pub_slot_, 100); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 8); assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.comp_[1].latest_.price_, 20); - assert_eq!(price_data.comp_[1].latest_.conf_, 2); - assert_eq!(price_data.comp_[1].latest_.pub_slot_, 100); - assert_eq!(price_data.comp_[1].latest_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.valid_slot_, 100); - assert_eq!(price_data.agg_.pub_slot_, 100); - assert_eq!(price_data.agg_.price_, 14); - assert_eq!(price_data.agg_.conf_, 6); + assert_eq!(price_data.valid_slot_, 8); + assert_eq!(price_data.agg_.pub_slot_, 9); + assert_eq!(price_data.agg_.price_, -100); assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - assert_eq!(price_data.prev_slot_, 50); - assert_eq!(price_data.prev_price_, 60); - assert_eq!(price_data.prev_conf_, 4); - assert_eq!( - price_data.prev_price_cumulative.price, - 42 + 81 * 4 - 100 * 3 + 10 * 2 + 60 * 40 - ); - assert_eq!(price_data.prev_price_cumulative.conf, 2 * 5 + 5 + 40 * 4); - assert_eq!(price_data.prev_price_cumulative.num_down_slots, 15); - assert_eq!( - price_data.price_cumulative.price, - 42 + 81 * 4 - 100 * 3 + 10 * 2 + 60 * 40 + 14 * 50 - ); // (42 + 81 * 4 - 100 * 3 + 10 * 2 + 60 * 40) is the price cumulative from the previous test and 14 * 50 (slot_gap) is the price cumulative from this test - assert_eq!( - price_data.price_cumulative.conf, - 2 * 5 + 5 + 40 * 4 + 6 * 50 - ); - assert_eq!(price_data.price_cumulative.num_down_slots, 40); // prev num_down_slots was 15 and since pub slot is 100 and last pub slot was 50, slot_gap is 50 and default latency is 25, so num_down_slots = 50 - 25 = 25, so total num_down_slots = 15 + 25 = 40 } } @@ -571,17 +343,3 @@ fn populate_instruction(instruction_data: &mut [u8], price: i64, conf: u64, pub_ cmd.publishing_slot = pub_slot; cmd.unused_ = 0; } - -fn add_publisher(price_account: &mut AccountInfo, publisher_key: &Pubkey) { - let mut price_data = load_checked::(price_account, PC_VERSION).unwrap(); - let index = price_data.num_ as usize; - price_data.comp_[index].pub_ = *publisher_key; - price_data.num_ += 1; -} - -fn remove_publisher(price_account: &mut AccountInfo) { - let mut price_data = load_checked::(price_account, PC_VERSION).unwrap(); - if price_data.num_ > 0 { - price_data.num_ -= 1; - } -} diff --git a/program/rust/src/tests/test_upd_price_no_fail_on_error.rs b/program/rust/src/tests/test_upd_price_no_fail_on_error.rs index 1d175d8b..3483ad1d 100644 --- a/program/rust/src/tests/test_upd_price_no_fail_on_error.rs +++ b/program/rust/src/tests/test_upd_price_no_fail_on_error.rs @@ -52,6 +52,7 @@ fn test_upd_price_no_fail_on_error_no_fail_on_error() { update_clock_slot(&mut clock_account, 1); + // Check that the normal upd_price fails populate_instruction(&mut instruction_data, 42, 9, 1, true); @@ -68,6 +69,7 @@ fn test_upd_price_no_fail_on_error_no_fail_on_error() { Err(OracleError::PermissionViolation.into()) ); + populate_instruction(&mut instruction_data, 42, 9, 1, false); // We haven't permissioned the publish account for the price account // yet, so any update should fail silently and have no effect. The @@ -83,6 +85,7 @@ fn test_upd_price_no_fail_on_error_no_fail_on_error() { ) .is_ok()); + { let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); assert_eq!(price_data.comp_[0].latest_.price_, 0); @@ -119,8 +122,8 @@ fn test_upd_price_no_fail_on_error_no_fail_on_error() { assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); assert_eq!(price_data.valid_slot_, 0); assert_eq!(price_data.agg_.pub_slot_, 1); - assert_eq!(price_data.agg_.price_, 42); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); } // Invalid updates, such as publishing an update for the current slot, @@ -161,11 +164,12 @@ fn test_upd_price_no_fail_on_error_no_fail_on_error() { assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); assert_eq!(price_data.valid_slot_, 0); assert_eq!(price_data.agg_.pub_slot_, 1); - assert_eq!(price_data.agg_.price_, 42); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); } } + // Create an upd_price_no_fail_on_error or upd_price instruction with the provided parameters fn populate_instruction( instruction_data: &mut [u8], diff --git a/program/rust/src/tests/test_upd_price_v2.rs b/program/rust/src/tests/test_upd_price_v2.rs new file mode 100644 index 00000000..0e0c1f4b --- /dev/null +++ b/program/rust/src/tests/test_upd_price_v2.rs @@ -0,0 +1,456 @@ +use { + crate::{ + accounts::{ + PriceAccount, + PythAccount, + }, + c_oracle_header::{ + PC_STATUS_IGNORED, + PC_STATUS_TRADING, + PC_STATUS_UNKNOWN, + PC_VERSION, + }, + deserialize::{ + load_checked, + load_mut, + }, + instruction::{ + OracleCommand, + UpdPriceArgs, + }, + processor::process_instruction, + tests::test_utils::{ + update_clock_slot, + AccountSetup, + }, + }, + solana_program::{ + program_error::ProgramError, + pubkey::Pubkey, + }, + std::mem::size_of, +}; + +#[test] +fn test_upd_price_v2() -> Result<(), Box> { + let mut instruction_data = [0u8; size_of::()]; + populate_instruction(&mut instruction_data, 42, 2, 1); + + let program_id = Pubkey::new_unique(); + + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.as_account_info(); + + let mut price_setup = AccountSetup::new::(&program_id); + let mut price_account = price_setup.as_account_info(); + price_account.is_signer = false; + PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); + + { + let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + price_data.num_ = 1; + price_data.comp_[0].pub_ = *funding_account.key; + } + + let mut clock_setup = AccountSetup::new_clock(); + let mut clock_account = clock_setup.as_account_info(); + clock_account.is_signer = false; + clock_account.is_writable = false; + + update_clock_slot(&mut clock_account, 1); + + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 42); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 1); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 0); + assert_eq!(price_data.agg_.pub_slot_, 1); + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.conf_, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + + assert_eq!(price_data.price_cumulative.price, 0); + assert_eq!(price_data.price_cumulative.conf, 0); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // add some prices for current slot - get rejected + populate_instruction(&mut instruction_data, 43, 2, 1); + + assert_eq!( + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 42); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 1); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 0); + assert_eq!(price_data.agg_.pub_slot_, 1); + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.conf_, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + + assert_eq!(price_data.price_cumulative.price, 0); + assert_eq!(price_data.price_cumulative.conf, 0); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // add next price in new slot triggering snapshot and aggregate calc + populate_instruction(&mut instruction_data, 81, 2, 2); + update_clock_slot(&mut clock_account, 3); + + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 81); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 2); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 1); + assert_eq!(price_data.agg_.pub_slot_, 3); + assert_eq!(price_data.agg_.price_, 42); + assert_eq!(price_data.agg_.conf_, 2); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + + assert_eq!(price_data.price_cumulative.price, 3 * 42); + assert_eq!(price_data.price_cumulative.conf, 3 * 2); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // next price doesn't change but slot does + populate_instruction(&mut instruction_data, 81, 2, 3); + update_clock_slot(&mut clock_account, 4); + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 81); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 3); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 3); + assert_eq!(price_data.agg_.pub_slot_, 4); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.conf_, 2); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + + assert_eq!(price_data.price_cumulative.price, 3 * 42 + 81); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // next price doesn't change and neither does aggregate but slot does + populate_instruction(&mut instruction_data, 81, 2, 4); + update_clock_slot(&mut clock_account, 5); + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 81); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 4); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 4); + assert_eq!(price_data.agg_.pub_slot_, 5); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.conf_, 2); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + + assert_eq!(price_data.price_cumulative.price, 3 * 42 + 81 * 2); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2 * 2); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // try to publish back-in-time + populate_instruction(&mut instruction_data, 81, 2, 1); + update_clock_slot(&mut clock_account, 5); + assert_eq!( + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 81); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 4); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 4); + assert_eq!(price_data.agg_.pub_slot_, 5); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.conf_, 2); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + + assert_eq!(price_data.price_cumulative.price, 3 * 42 + 81 * 2); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2 * 2); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + populate_instruction(&mut instruction_data, 50, 20, 5); + update_clock_slot(&mut clock_account, 6); + + // Publishing a wide CI results in a status of unknown. + + // check that someone doesn't accidentally break the test. + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + } + + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 50); + assert_eq!(price_data.comp_[0].latest_.conf_, 20); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 5); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_IGNORED); + assert_eq!(price_data.valid_slot_, 5); + assert_eq!(price_data.agg_.pub_slot_, 6); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.conf_, 2); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + + assert_eq!(price_data.price_cumulative.price, 3 * 42 + 81 * 3); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2 * 3); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // Crank one more time and aggregate should be unknown + populate_instruction(&mut instruction_data, 50, 20, 6); + update_clock_slot(&mut clock_account, 7); + + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 50); + assert_eq!(price_data.comp_[0].latest_.conf_, 20); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 6); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_IGNORED); + assert_eq!(price_data.valid_slot_, 6); + assert_eq!(price_data.agg_.pub_slot_, 7); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.conf_, 2); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + + assert_eq!(price_data.price_cumulative.price, 3 * 42 + 81 * 3); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2 * 3); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // Negative prices are accepted + populate_instruction(&mut instruction_data, -100, 1, 7); + update_clock_slot(&mut clock_account, 8); + + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, -100); + assert_eq!(price_data.comp_[0].latest_.conf_, 1); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 7); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 7); + assert_eq!(price_data.agg_.pub_slot_, 8); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.conf_, 2); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + + assert_eq!(price_data.price_cumulative.price, 3 * 42 + 81 * 3); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2 * 3); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // Crank again for aggregate + populate_instruction(&mut instruction_data, -100, 1, 8); + update_clock_slot(&mut clock_account, 9); + + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, -100); + assert_eq!(price_data.comp_[0].latest_.conf_, 1); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 8); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 8); + assert_eq!(price_data.agg_.pub_slot_, 9); + assert_eq!(price_data.agg_.price_, -100); + assert_eq!(price_data.agg_.conf_, 1); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + + assert_eq!(price_data.price_cumulative.price, 3 * 42 + 81 * 3 - 100 * 3); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2 * 3 + 3); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // Big gap + + populate_instruction(&mut instruction_data, 60, 4, 50); + update_clock_slot(&mut clock_account, 50); + + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 60); + assert_eq!(price_data.comp_[0].latest_.conf_, 4); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 50); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 9); + assert_eq!(price_data.agg_.pub_slot_, 50); + assert_eq!(price_data.agg_.price_, -100); + assert_eq!(price_data.agg_.conf_, 1); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + + assert_eq!(price_data.price_cumulative.price, 3 * 42 + 81 * 3 - 100 * 3); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2 * 3 + 3); + assert_eq!(price_data.price_cumulative.num_down_slots, 0); + } + + // Crank again for aggregate + + populate_instruction(&mut instruction_data, 55, 5, 51); + update_clock_slot(&mut clock_account, 51); + + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone(), + ], + &instruction_data, + )?; + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 55); + assert_eq!(price_data.comp_[0].latest_.conf_, 5); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 51); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 50); + assert_eq!(price_data.agg_.pub_slot_, 51); + assert_eq!(price_data.agg_.price_, 60); + assert_eq!(price_data.agg_.conf_, 4); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + + assert_eq!( + price_data.price_cumulative.price, + 3 * 42 + 81 * 3 - 100 * 3 + 42 * 60 + ); + assert_eq!(price_data.price_cumulative.conf, 3 * 2 + 2 * 3 + 3 + 42 * 4); + assert_eq!(price_data.price_cumulative.num_down_slots, 17); + } + + Ok(()) +} + +// Create an upd_price instruction with the provided parameters +fn populate_instruction(instruction_data: &mut [u8], price: i64, conf: u64, pub_slot: u64) { + let mut cmd = load_mut::(instruction_data).unwrap(); + cmd.header = OracleCommand::UpdPrice.into(); + cmd.status = PC_STATUS_TRADING; + cmd.price = price; + cmd.confidence = conf; + cmd.publishing_slot = pub_slot; + cmd.unused_ = 0; +} diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index b5f02582..853fdcd6 100644 --- a/program/rust/src/tests/test_utils.rs +++ b/program/rust/src/tests/test_utils.rs @@ -134,12 +134,6 @@ pub fn update_clock_slot(clock_account: &mut AccountInfo, slot: u64) { clock_data.to_account_info(clock_account); } -pub fn update_clock_timestamp(clock_account: &mut AccountInfo, timestamp: i64) { - let mut clock_data = clock::Clock::from_account_info(clock_account).unwrap(); - clock_data.unix_timestamp = timestamp; - clock_data.to_account_info(clock_account); -} - impl From for CommandHeader { fn from(val: OracleCommand) -> Self { CommandHeader {