From fe2abc42372547ce024f163f2e8646471cbaadee Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Wed, 24 Jul 2024 10:01:33 +0100 Subject: [PATCH 1/9] batch publish wip --- program/rust/src/lib.rs | 7 ++++++- program/rust/src/processor.rs | 1 + program/rust/src/validator.rs | 5 ++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/program/rust/src/lib.rs b/program/rust/src/lib.rs index eb1881ef..687410c3 100644 --- a/program/rust/src/lib.rs +++ b/program/rust/src/lib.rs @@ -45,8 +45,13 @@ pub use accounts::{ PythAccount, PythOracleSerialize, }; -use { +#[cfg(feature = "library")] +pub use { crate::error::OracleError, + processor::find_publisher_index, + utils::get_status_for_conf_price_ratio, +}; +use { processor::process_instruction, solana_program::entrypoint, }; diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index b4104fc1..8a2f557a 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -47,6 +47,7 @@ pub use { upd_price::{ c_upd_aggregate, c_upd_twap, + find_publisher_index, upd_price, upd_price_no_fail_on_error, }, diff --git a/program/rust/src/validator.rs b/program/rust/src/validator.rs index 17a3aa10..186e8118 100644 --- a/program/rust/src/validator.rs +++ b/program/rust/src/validator.rs @@ -50,7 +50,7 @@ fn check_price_account_header(price_account_info: &[u8]) -> Result<(), ProgramEr } // Attempts to validate and access the contents of an account as a PriceAccount. -fn validate_price_account( +pub fn validate_price_account( price_account_info: &mut [u8], ) -> Result<&mut PriceAccount, AggregationError> { check_price_account_header(price_account_info) @@ -123,9 +123,8 @@ pub fn aggregate_price( slot: u64, timestamp: i64, price_account_pubkey: &Pubkey, - price_account_data: &mut [u8], + price_account: &mut PriceAccount, ) -> Result<[Vec; 2], AggregationError> { - let price_account = validate_price_account(price_account_data)?; if price_account.agg_.pub_slot_ == slot { // Avoid v2 aggregation if v1 aggregation has happened in the same slot // (this should normally happen only in the slot that contains the v1->v2 transition). From 70912fddc8fba6f0d0d03681123680dc58981514 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Thu, 1 Aug 2024 10:21:59 +0100 Subject: [PATCH 2/9] refactor: separate header checks and account data casts --- program/rust/src/validator.rs | 52 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/program/rust/src/validator.rs b/program/rust/src/validator.rs index 186e8118..bc78fe46 100644 --- a/program/rust/src/validator.rs +++ b/program/rust/src/validator.rs @@ -45,35 +45,9 @@ fn check_price_account_header(price_account_info: &[u8]) -> Result<(), ProgramEr && account_header.account_type == PriceAccount::ACCOUNT_TYPE, OracleError::InvalidAccountHeader.into(), )?; - Ok(()) } -// Attempts to validate and access the contents of an account as a PriceAccount. -pub fn validate_price_account( - price_account_info: &mut [u8], -) -> Result<&mut PriceAccount, AggregationError> { - check_price_account_header(price_account_info) - .map_err(|_| AggregationError::NotPriceFeedAccount)?; - - let data = bytemuck::from_bytes_mut::( - &mut price_account_info[0..size_of::()], - ); - if !data.flags.contains(PriceAccountFlags::ACCUMULATOR_V2) { - return Err(AggregationError::V1AggregationMode); - } - if !data - .flags - .contains(PriceAccountFlags::MESSAGE_BUFFER_CLEARED) - { - // We make sure that we don't generate v2 messages while v1 messages are still - // in the message buffer. - return Err(AggregationError::V1AggregationMode); - } - - Ok(data) -} - fn update_aggregate(slot: u64, timestamp: i64, price_account: &mut PriceAccount) { // NOTE: c_upd_aggregate must use a raw pointer to price data. We already // have the exclusive mut reference so we can simply cast before calling @@ -125,6 +99,21 @@ pub fn aggregate_price( price_account_pubkey: &Pubkey, price_account: &mut PriceAccount, ) -> Result<[Vec; 2], AggregationError> { + if !price_account + .flags + .contains(PriceAccountFlags::ACCUMULATOR_V2) + { + return Err(AggregationError::V1AggregationMode); + } + if !price_account + .flags + .contains(PriceAccountFlags::MESSAGE_BUFFER_CLEARED) + { + // We make sure that we don't generate v2 messages while v1 messages are still + // in the message buffer. + return Err(AggregationError::V1AggregationMode); + } + if price_account.agg_.pub_slot_ == slot { // Avoid v2 aggregation if v1 aggregation has happened in the same slot // (this should normally happen only in the slot that contains the v1->v2 transition). @@ -142,13 +131,22 @@ pub fn aggregate_price( } /// Load a price account as read-only, returning `None` if it isn't a valid price account. -fn checked_load_price_account(price_account_info: &[u8]) -> Option<&PriceAccount> { +pub fn checked_load_price_account(price_account_info: &[u8]) -> Option<&PriceAccount> { check_price_account_header(price_account_info).ok()?; Some(bytemuck::from_bytes::( &price_account_info[0..size_of::()], )) } +pub fn checked_load_price_account_mut( + price_account_info: &mut [u8], +) -> Result<&mut PriceAccount, ProgramError> { + check_price_account_header(price_account_info)?; + Ok(bytemuck::from_bytes_mut::( + &mut price_account_info[0..size_of::()], + )) +} + /// Computes the stake caps for each publisher based on the oracle program accounts provided /// - `account_datas` - the account datas of the oracle program accounts /// - `timestamp` - the timestamp to include in the message From 44bf1257e14d3ecf06516bc2520d9358e58b1ed7 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Thu, 1 Aug 2024 10:28:37 +0100 Subject: [PATCH 3/9] feat: feed index (wip) --- program/rust/src/accounts/price.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/program/rust/src/accounts/price.rs b/program/rust/src/accounts/price.rs index a569d054..f1d21b33 100644 --- a/program/rust/src/accounts/price.rs +++ b/program/rust/src/accounts/price.rs @@ -69,8 +69,9 @@ mod price_pythnet { pub max_latency_: u8, /// Various flags pub flags: PriceAccountFlags, - /// Unused placeholder for alignment - pub unused_3_: i32, + /// Globally unique price feed index used for publishing. + /// Limited to 28 bites. + pub feed_index: i32, /// Corresponding product account pub product_account: Pubkey, /// Next price account in the list From 951298101fc1dac986ecf24c7440e1a8bc348a33 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Fri, 2 Aug 2024 11:03:59 +0100 Subject: [PATCH 4/9] feat: assign price feed indexes to old accounts --- program/rust/build.rs | 4 +- program/rust/src/accounts/permission.rs | 30 +++++- program/rust/src/accounts/price.rs | 2 +- program/rust/src/error.rs | 2 + program/rust/src/instruction.rs | 6 ++ program/rust/src/lib.rs | 3 +- program/rust/src/processor.rs | 3 + .../src/processor/init_price_feed_index.rs | 100 ++++++++++++++++++ .../tests/test_upd_price_with_validator.rs | 79 ++++++++++---- 9 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 program/rust/src/processor/init_price_feed_index.rs diff --git a/program/rust/build.rs b/program/rust/build.rs index b33d8dd7..8738479a 100644 --- a/program/rust/build.rs +++ b/program/rust/build.rs @@ -91,8 +91,8 @@ fn do_make_build(targets: Vec<&str>, out_dir: &Path) { "C oracle make build did not exit with 0 (code ({:?}).\n\nstdout:\n{}\n\nstderr:\n{}", make_output.status.code(), - String::from_utf8(make_output.stdout).unwrap_or("".to_owned()), - String::from_utf8(make_output.stderr).unwrap_or("".to_owned()) + String::from_utf8_lossy(&make_output.stdout), + String::from_utf8_lossy(&make_output.stderr) ); } } diff --git a/program/rust/src/accounts/permission.rs b/program/rust/src/accounts/permission.rs index 990faeb4..fc0b9c81 100644 --- a/program/rust/src/accounts/permission.rs +++ b/program/rust/src/accounts/permission.rs @@ -11,8 +11,15 @@ use { Pod, Zeroable, }, - solana_program::pubkey::Pubkey, - std::mem::size_of, + solana_program::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::Pubkey, + }, + std::{ + cell::RefMut, + mem::size_of, + }, }; /// This account stores the pubkeys that can execute administrative instructions in the Pyth @@ -40,6 +47,9 @@ pub struct PermissionAccount { } impl PermissionAccount { + pub const MIN_SIZE_WITH_LAST_FEED_INDEX: usize = + size_of::() + size_of::(); + pub fn is_authorized(&self, key: &Pubkey, command: OracleCommand) -> bool { #[allow(clippy::match_like_matches_macro)] match (*key, command) { @@ -50,9 +60,25 @@ impl PermissionAccount { _ => false, } } + + pub fn load_last_feed_index_mut<'a>( + account: &'a AccountInfo, + ) -> Result, ProgramError> { + let start = size_of::(); + let end = start + size_of::(); + assert_eq!(Self::MIN_SIZE_WITH_LAST_FEED_INDEX, end); + if account.data_len() < end { + return Err(ProgramError::AccountDataTooSmall); + } + Ok(RefMut::map(account.try_borrow_mut_data()?, |data| { + bytemuck::from_bytes_mut(&mut data[start..end]) + })) + } } impl PythAccount for PermissionAccount { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS; + // TODO: change? + // TODO: add feed_index when creating account const INITIAL_SIZE: u32 = size_of::() as u32; } diff --git a/program/rust/src/accounts/price.rs b/program/rust/src/accounts/price.rs index f1d21b33..309a9889 100644 --- a/program/rust/src/accounts/price.rs +++ b/program/rust/src/accounts/price.rs @@ -71,7 +71,7 @@ mod price_pythnet { pub flags: PriceAccountFlags, /// Globally unique price feed index used for publishing. /// Limited to 28 bites. - pub feed_index: i32, + pub feed_index: u32, /// Corresponding product account pub product_account: Pubkey, /// Next price account in the list diff --git a/program/rust/src/error.rs b/program/rust/src/error.rs index 22fb27e5..9eed3450 100644 --- a/program/rust/src/error.rs +++ b/program/rust/src/error.rs @@ -52,6 +52,8 @@ pub enum OracleError { PermissionViolation = 619, #[error("NeedsSuccesfulAggregation")] NeedsSuccesfulAggregation = 620, + #[error("MaxLastFeedIndexReached")] + MaxLastFeedIndexReached = 621, } impl From for ProgramError { diff --git a/program/rust/src/instruction.rs b/program/rust/src/instruction.rs index c3d8a3de..33d60d0b 100644 --- a/program/rust/src/instruction.rs +++ b/program/rust/src/instruction.rs @@ -103,6 +103,12 @@ pub enum OracleCommand { // account[0] funding account [signer writable] // account[1] price account [signer writable] SetMaxLatency = 18, + /// Init price feed index + // account[0] funding account [signer writable] + // account[1] price account [writable] + // account[2] permissions account [writable] + // account[3] system program account [] + InitPriceFeedIndex = 19, } #[repr(C)] diff --git a/program/rust/src/lib.rs b/program/rust/src/lib.rs index 687410c3..acc66fbf 100644 --- a/program/rust/src/lib.rs +++ b/program/rust/src/lib.rs @@ -1,4 +1,3 @@ -#![deny(warnings)] // Allow non upper case globals from C #![allow(non_upper_case_globals)] @@ -29,6 +28,7 @@ mod log; // While we have `pyth-sdk-rs` which exposes a more friendly interface, this is still useful when a // downstream user wants to confirm for example that they can compile against the binary interface // of this program for their specific solana version. +pub use crate::error::OracleError; #[cfg(feature = "strum")] pub use accounts::MessageType; #[cfg(feature = "library")] @@ -47,7 +47,6 @@ pub use accounts::{ }; #[cfg(feature = "library")] pub use { - crate::error::OracleError, processor::find_publisher_index, utils::get_status_for_conf_price_ratio, }; diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index 8a2f557a..66c1a619 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -21,6 +21,7 @@ mod del_product; mod del_publisher; mod init_mapping; mod init_price; +mod init_price_feed_index; mod set_max_latency; mod set_min_pub; mod upd_permissions; @@ -32,6 +33,7 @@ pub use add_publisher::{ DISABLE_ACCUMULATOR_V2, ENABLE_ACCUMULATOR_V2, }; +use init_price_feed_index::init_price_feed_index; pub use { add_price::add_price, add_product::add_product, @@ -85,5 +87,6 @@ pub fn process_instruction( DelProduct => del_product(program_id, accounts, instruction_data), UpdPermissions => upd_permissions(program_id, accounts, instruction_data), SetMaxLatency => set_max_latency(program_id, accounts, instruction_data), + InitPriceFeedIndex => init_price_feed_index(program_id, accounts, instruction_data), } } diff --git a/program/rust/src/processor/init_price_feed_index.rs b/program/rust/src/processor/init_price_feed_index.rs new file mode 100644 index 00000000..11238f99 --- /dev/null +++ b/program/rust/src/processor/init_price_feed_index.rs @@ -0,0 +1,100 @@ +use { + crate::{ + accounts::{ + PermissionAccount, + PriceAccount, + }, + deserialize::{ + load, + load_checked, + }, + instruction::CommandHeader, + utils::{ + check_permissioned_funding_account, + check_valid_funding_account, + pyth_assert, + }, + OracleError, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + program::invoke, + program_error::ProgramError, + pubkey::Pubkey, + rent::Rent, + system_instruction, + sysvar::Sysvar, + }, + std::mem::size_of, +}; + +/// Init price feed index +// account[0] funding account [signer writable] +// account[1] price account [writable] +// account[2] permissions account [writable] +// account[3] system program account +pub fn init_price_feed_index( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let cmd = load::(instruction_data)?; + + pyth_assert( + instruction_data.len() == size_of::(), + ProgramError::InvalidArgument, + )?; + + let (funding_account, price_account, permissions_account, system_program) = match accounts { + [x, y, p, z] => Ok((x, y, p, z)), + _ => Err(OracleError::InvalidNumberOfAccounts), + }?; + + check_valid_funding_account(funding_account)?; + check_permissioned_funding_account( + program_id, + price_account, + funding_account, + permissions_account, + cmd, + )?; + pyth_assert( + solana_program::system_program::check_id(system_program.key), + OracleError::InvalidSystemAccount.into(), + )?; + + if permissions_account.data_len() < PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX { + let new_size = PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX; + let rent = Rent::get()?; + let new_minimum_balance = rent.minimum_balance(new_size); + let lamports_diff = new_minimum_balance.saturating_sub(permissions_account.lamports()); + if lamports_diff > 0 { + invoke( + &system_instruction::transfer( + funding_account.key, + permissions_account.key, + lamports_diff, + ), + &[ + funding_account.clone(), + permissions_account.clone(), + system_program.clone(), + ], + )?; + } + + permissions_account.realloc(new_size, true)?; + } + let mut last_feed_index = PermissionAccount::load_last_feed_index_mut(permissions_account)?; + *last_feed_index += 1; + pyth_assert( + *last_feed_index < (1 << 28), + OracleError::MaxLastFeedIndexReached.into(), + )?; + + let mut price_account_data = load_checked::(price_account, cmd.version)?; + price_account_data.feed_index = *last_feed_index; + + Ok(()) +} diff --git a/program/rust/src/tests/test_upd_price_with_validator.rs b/program/rust/src/tests/test_upd_price_with_validator.rs index 815a8c91..0c6cb204 100644 --- a/program/rust/src/tests/test_upd_price_with_validator.rs +++ b/program/rust/src/tests/test_upd_price_with_validator.rs @@ -25,7 +25,10 @@ use { update_clock_slot, AccountSetup, }, - validator, + validator::{ + self, + checked_load_price_account_mut, + }, }, pythnet_sdk::messages::{ PriceFeedMessage, @@ -125,9 +128,13 @@ fn test_upd_price_with_validator() { } // We aggregate the price at the end of each slot now. - let messages1 = - validator::aggregate_price(1, 101, price_account.key, *price_account.data.borrow_mut()) - .unwrap(); + let messages1 = validator::aggregate_price( + 1, + 101, + price_account.key, + checked_load_price_account_mut(*price_account.data.borrow_mut()).unwrap(), + ) + .unwrap(); let expected_messages1 = [ PriceFeedMessage { feed_id: price_account.key.to_bytes(), @@ -155,9 +162,13 @@ fn test_upd_price_with_validator() { assert_eq!(messages1, expected_messages1); update_clock_slot(&mut clock_account, 2); - let messages2 = - validator::aggregate_price(2, 102, price_account.key, *price_account.data.borrow_mut()) - .unwrap(); + let messages2 = validator::aggregate_price( + 2, + 102, + price_account.key, + checked_load_price_account_mut(*price_account.data.borrow_mut()).unwrap(), + ) + .unwrap(); let expected_messages2 = [ PriceFeedMessage { @@ -214,8 +225,13 @@ fn test_upd_price_with_validator() { // next price doesn't change but slot does populate_instruction(&mut instruction_data, 81, 2, 3); - validator::aggregate_price(3, 103, price_account.key, *price_account.data.borrow_mut()) - .unwrap(); + validator::aggregate_price( + 3, + 103, + price_account.key, + checked_load_price_account_mut(*price_account.data.borrow_mut()).unwrap(), + ) + .unwrap(); update_clock_slot(&mut clock_account, 4); assert!(process_instruction( &program_id, @@ -242,8 +258,13 @@ fn test_upd_price_with_validator() { // next price doesn't change and neither does aggregate but slot does populate_instruction(&mut instruction_data, 81, 2, 4); - validator::aggregate_price(4, 104, price_account.key, *price_account.data.borrow_mut()) - .unwrap(); + validator::aggregate_price( + 4, + 104, + price_account.key, + checked_load_price_account_mut(*price_account.data.borrow_mut()).unwrap(), + ) + .unwrap(); update_clock_slot(&mut clock_account, 5); assert!(process_instruction( @@ -299,8 +320,13 @@ fn test_upd_price_with_validator() { } populate_instruction(&mut instruction_data, 50, 20, 5); - validator::aggregate_price(5, 105, price_account.key, *price_account.data.borrow_mut()) - .unwrap(); + validator::aggregate_price( + 5, + 105, + price_account.key, + checked_load_price_account_mut(*price_account.data.borrow_mut()).unwrap(), + ) + .unwrap(); update_clock_slot(&mut clock_account, 6); // Publishing a wide CI results in a status of unknown. @@ -337,8 +363,13 @@ fn test_upd_price_with_validator() { // Crank one more time and aggregate should be unknown populate_instruction(&mut instruction_data, 50, 20, 6); - validator::aggregate_price(6, 106, price_account.key, *price_account.data.borrow_mut()) - .unwrap(); + validator::aggregate_price( + 6, + 106, + price_account.key, + checked_load_price_account_mut(*price_account.data.borrow_mut()).unwrap(), + ) + .unwrap(); update_clock_slot(&mut clock_account, 7); @@ -367,8 +398,13 @@ fn test_upd_price_with_validator() { // Negative prices are accepted populate_instruction(&mut instruction_data, -100, 1, 7); - validator::aggregate_price(7, 107, price_account.key, *price_account.data.borrow_mut()) - .unwrap(); + validator::aggregate_price( + 7, + 107, + price_account.key, + checked_load_price_account_mut(*price_account.data.borrow_mut()).unwrap(), + ) + .unwrap(); update_clock_slot(&mut clock_account, 8); @@ -397,8 +433,13 @@ fn test_upd_price_with_validator() { // Crank again for aggregate populate_instruction(&mut instruction_data, -100, 1, 8); - validator::aggregate_price(8, 108, price_account.key, *price_account.data.borrow_mut()) - .unwrap(); + validator::aggregate_price( + 8, + 108, + price_account.key, + checked_load_price_account_mut(*price_account.data.borrow_mut()).unwrap(), + ) + .unwrap(); update_clock_slot(&mut clock_account, 9); From 38ada8a5ff10ed894c3dc2ca553469a4162aa209 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Mon, 5 Aug 2024 12:27:32 +0100 Subject: [PATCH 5/9] feat: init feed index when creating prices and init permission account --- program/rust/src/accounts.rs | 9 ++- program/rust/src/accounts/permission.rs | 5 +- program/rust/src/error.rs | 2 + program/rust/src/processor.rs | 58 ++++++++++++++++++- program/rust/src/processor/add_price.rs | 27 ++++++--- .../src/processor/init_price_feed_index.rs | 54 +++++------------ program/rust/src/tests/pyth_simulator.rs | 1 + program/rust/src/tests/test_add_price.rs | 21 +++++-- .../src/tests/test_permission_migration.rs | 7 ++- .../rust/src/tests/test_upd_permissions.rs | 7 +-- program/rust/src/tests/test_utils.rs | 18 +++++- 11 files changed, 141 insertions(+), 68 deletions(-) diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 8bc83ce5..609efd79 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -105,6 +105,11 @@ pub trait PythAccount: Pod { /// have. `INITIAL_SIZE` <= `minimum_size()` const MINIMUM_SIZE: usize = size_of::(); + /// Size of the account data on creation. Usually this is the same as `MINIMUM_SIZE` but it's + /// different for `PermissionAccount` because we've added new fields to it. In this case + /// we cannot increase `MINIMUM_SIZE` because that would break reading the account. + const NEW_ACCOUNT_SPACE: usize = Self::MINIMUM_SIZE; + /// Given an `AccountInfo`, verify it is sufficiently large and has the correct discriminator. fn initialize<'a>( account: &'a AccountInfo, @@ -139,7 +144,7 @@ pub trait PythAccount: Pod { seeds: &[&[u8]], version: u32, ) -> Result<(), ProgramError> { - let target_rent = get_rent()?.minimum_balance(Self::MINIMUM_SIZE); + let target_rent = get_rent()?.minimum_balance(Self::NEW_ACCOUNT_SPACE); if account.data_len() == 0 { create( @@ -147,7 +152,7 @@ pub trait PythAccount: Pod { account, system_program, program_id, - Self::MINIMUM_SIZE, + Self::NEW_ACCOUNT_SPACE, target_rent, seeds, )?; diff --git a/program/rust/src/accounts/permission.rs b/program/rust/src/accounts/permission.rs index fc0b9c81..46c4b209 100644 --- a/program/rust/src/accounts/permission.rs +++ b/program/rust/src/accounts/permission.rs @@ -78,7 +78,6 @@ impl PermissionAccount { impl PythAccount for PermissionAccount { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS; - // TODO: change? - // TODO: add feed_index when creating account - const INITIAL_SIZE: u32 = size_of::() as u32; + const INITIAL_SIZE: u32 = Self::MIN_SIZE_WITH_LAST_FEED_INDEX as u32; + const NEW_ACCOUNT_SPACE: usize = Self::MIN_SIZE_WITH_LAST_FEED_INDEX; } diff --git a/program/rust/src/error.rs b/program/rust/src/error.rs index 9eed3450..b4eb642b 100644 --- a/program/rust/src/error.rs +++ b/program/rust/src/error.rs @@ -54,6 +54,8 @@ pub enum OracleError { NeedsSuccesfulAggregation = 620, #[error("MaxLastFeedIndexReached")] MaxLastFeedIndexReached = 621, + #[error("FeedIndexAlreadyInitialized")] + FeedIndexAlreadyInitialized = 622, } impl From for ProgramError { diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index 66c1a619..b1cf8cb3 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -1,14 +1,25 @@ use { crate::{ + accounts::{ + AccountHeader, + PermissionAccount, + }, + deserialize::load_account_as_mut, error::OracleError, instruction::{ load_command_header_checked, OracleCommand, }, + utils::{ + pyth_assert, + try_convert, + }, }, solana_program::{ entrypoint::ProgramResult, + program::invoke, pubkey::Pubkey, + system_instruction, sysvar::slot_history::AccountInfo, }, }; @@ -33,7 +44,6 @@ pub use add_publisher::{ DISABLE_ACCUMULATOR_V2, ENABLE_ACCUMULATOR_V2, }; -use init_price_feed_index::init_price_feed_index; pub use { add_price::add_price, add_product::add_product, @@ -55,6 +65,14 @@ pub use { }, upd_product::upd_product, }; +use { + init_price_feed_index::init_price_feed_index, + solana_program::{ + program_error::ProgramError, + rent::Rent, + sysvar::Sysvar, + }, +}; /// Dispatch to the right instruction in the oracle. pub fn process_instruction( @@ -90,3 +108,41 @@ pub fn process_instruction( InitPriceFeedIndex => init_price_feed_index(program_id, accounts, instruction_data), } } + +fn reserve_new_price_feed_index<'a>( + funding_account: &AccountInfo<'a>, + permissions_account: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> Result { + if permissions_account.data_len() < PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX { + let new_size = PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX; + let rent = Rent::get()?; + let new_minimum_balance = rent.minimum_balance(new_size); + let lamports_diff = new_minimum_balance.saturating_sub(permissions_account.lamports()); + if lamports_diff > 0 { + invoke( + &system_instruction::transfer( + funding_account.key, + permissions_account.key, + lamports_diff, + ), + &[ + funding_account.clone(), + permissions_account.clone(), + system_program.clone(), + ], + )?; + } + + permissions_account.realloc(new_size, true)?; + let mut header = load_account_as_mut::(permissions_account)?; + header.size = try_convert(new_size)?; + } + let mut last_feed_index = PermissionAccount::load_last_feed_index_mut(permissions_account)?; + *last_feed_index += 1; + pyth_assert( + *last_feed_index < (1 << 28), + OracleError::MaxLastFeedIndexReached.into(), + )?; + Ok(*last_feed_index) +} diff --git a/program/rust/src/processor/add_price.rs b/program/rust/src/processor/add_price.rs index cd6da424..f85df157 100644 --- a/program/rust/src/processor/add_price.rs +++ b/program/rust/src/processor/add_price.rs @@ -1,4 +1,5 @@ use { + super::reserve_new_price_feed_index, crate::{ accounts::{ PriceAccount, @@ -18,6 +19,7 @@ use { check_exponent_range, check_permissioned_funding_account, check_valid_funding_account, + check_valid_writable_account, pyth_assert, }, OracleError, @@ -31,9 +33,11 @@ use { }; /// Add new price account to a product account -// account[0] funding account [signer writable] -// account[1] product account [signer writable] -// account[2] new price account [signer writable] +// account[0] funding account [signer writable] +// account[1] product account [writable] +// account[2] new price account [writable] +// account[3] permissions account [writable] +// account[4] system program account [] pub fn add_price( program_id: &Pubkey, accounts: &[AccountInfo], @@ -48,10 +52,11 @@ pub fn add_price( )?; - let (funding_account, product_account, price_account, permissions_account) = match accounts { - [x, y, z, p] => Ok((x, y, z, p)), - _ => Err(OracleError::InvalidNumberOfAccounts), - }?; + let (funding_account, product_account, price_account, permissions_account, system_program) = + match accounts { + [x, y, z, p, q] => Ok((x, y, z, p, q)), + _ => Err(OracleError::InvalidNumberOfAccounts), + }?; check_valid_funding_account(funding_account)?; check_permissioned_funding_account( @@ -68,16 +73,24 @@ pub fn add_price( permissions_account, &cmd_args.header, )?; + check_valid_writable_account(program_id, permissions_account)?; + pyth_assert( + solana_program::system_program::check_id(system_program.key), + OracleError::InvalidSystemAccount.into(), + )?; let mut product_data = load_checked::(product_account, cmd_args.header.version)?; + // TODO: where is it created??? let mut price_data = PriceAccount::initialize(price_account, cmd_args.header.version)?; price_data.exponent = cmd_args.exponent; price_data.price_type = cmd_args.price_type; price_data.product_account = *product_account.key; price_data.next_price_account = product_data.first_price_account; price_data.min_pub_ = PRICE_ACCOUNT_DEFAULT_MIN_PUB; + price_data.feed_index = + reserve_new_price_feed_index(funding_account, permissions_account, system_program)?; product_data.first_price_account = *price_account.key; Ok(()) diff --git a/program/rust/src/processor/init_price_feed_index.rs b/program/rust/src/processor/init_price_feed_index.rs index 11238f99..ea991bc2 100644 --- a/program/rust/src/processor/init_price_feed_index.rs +++ b/program/rust/src/processor/init_price_feed_index.rs @@ -1,9 +1,7 @@ use { + super::reserve_new_price_feed_index, crate::{ - accounts::{ - PermissionAccount, - PriceAccount, - }, + accounts::PriceAccount, deserialize::{ load, load_checked, @@ -12,6 +10,7 @@ use { utils::{ check_permissioned_funding_account, check_valid_funding_account, + check_valid_writable_account, pyth_assert, }, OracleError, @@ -19,21 +18,17 @@ use { solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, - program::invoke, program_error::ProgramError, pubkey::Pubkey, - rent::Rent, - system_instruction, - sysvar::Sysvar, }, std::mem::size_of, }; /// Init price feed index -// account[0] funding account [signer writable] -// account[1] price account [writable] -// account[2] permissions account [writable] -// account[3] system program account +// account[0] funding account [signer writable] +// account[1] price account [writable] +// account[2] permissions account [writable] +// account[3] system program account [] pub fn init_price_feed_index( program_id: &Pubkey, accounts: &[AccountInfo], @@ -59,42 +54,19 @@ pub fn init_price_feed_index( permissions_account, cmd, )?; + check_valid_writable_account(program_id, permissions_account)?; pyth_assert( solana_program::system_program::check_id(system_program.key), OracleError::InvalidSystemAccount.into(), )?; - if permissions_account.data_len() < PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX { - let new_size = PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX; - let rent = Rent::get()?; - let new_minimum_balance = rent.minimum_balance(new_size); - let lamports_diff = new_minimum_balance.saturating_sub(permissions_account.lamports()); - if lamports_diff > 0 { - invoke( - &system_instruction::transfer( - funding_account.key, - permissions_account.key, - lamports_diff, - ), - &[ - funding_account.clone(), - permissions_account.clone(), - system_program.clone(), - ], - )?; - } - - permissions_account.realloc(new_size, true)?; - } - let mut last_feed_index = PermissionAccount::load_last_feed_index_mut(permissions_account)?; - *last_feed_index += 1; + let mut price_account_data = load_checked::(price_account, cmd.version)?; pyth_assert( - *last_feed_index < (1 << 28), - OracleError::MaxLastFeedIndexReached.into(), + price_account_data.feed_index == 0, + OracleError::FeedIndexAlreadyInitialized.into(), )?; - - let mut price_account_data = load_checked::(price_account, cmd.version)?; - price_account_data.feed_index = *last_feed_index; + price_account_data.feed_index = + reserve_new_price_feed_index(funding_account, permissions_account, system_program)?; Ok(()) } diff --git a/program/rust/src/tests/pyth_simulator.rs b/program/rust/src/tests/pyth_simulator.rs index 87640503..cb01bdf1 100644 --- a/program/rust/src/tests/pyth_simulator.rs +++ b/program/rust/src/tests/pyth_simulator.rs @@ -338,6 +338,7 @@ impl PythSimulator { AccountMeta::new(product_keypair.pubkey(), true), AccountMeta::new(price_keypair.pubkey(), true), AccountMeta::new(self.get_permissions_pubkey(), false), + AccountMeta::new(system_program::id(), false), ], ); diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index b282f1a5..2f96297b 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -62,6 +62,9 @@ fn test_add_price() { let mut permissions_setup = AccountSetup::new_permission(&program_id); let permissions_account = permissions_setup.as_account_info(); + let mut system_program = AccountSetup::new_system_program(); + let system_program_account = system_program.as_account_info(); + { let mut permissions_account_data = PermissionAccount::initialize(&permissions_account, PC_VERSION).unwrap(); @@ -82,17 +85,18 @@ fn test_add_price() { ) .is_ok()); - assert!(process_instruction( + process_instruction( &program_id, &[ funding_account.clone(), product_account.clone(), price_account.clone(), permissions_account.clone(), + system_program_account.clone(), ], - instruction_data_add_price + instruction_data_add_price, ) - .is_ok()); + .unwrap(); { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); @@ -105,17 +109,18 @@ fn test_add_price() { assert!(product_data.first_price_account == *price_account.key); } - assert!(process_instruction( + process_instruction( &program_id, &[ funding_account.clone(), product_account.clone(), price_account_2.clone(), permissions_account.clone(), + system_program_account.clone(), ], - instruction_data_add_price + instruction_data_add_price, ) - .is_ok()); + .unwrap(); { let price_data_2 = load_checked::(&price_account_2, PC_VERSION).unwrap(); @@ -137,6 +142,7 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), + system_program_account.clone(), permissions_account.clone(), ], instruction_data_add_price @@ -153,6 +159,7 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), + system_program_account.clone(), ], instruction_data_add_price ), @@ -177,6 +184,7 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), + system_program_account.clone(), ], instruction_data_add_price ), @@ -202,6 +210,7 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), + system_program_account.clone(), ], instruction_data_add_price ), diff --git a/program/rust/src/tests/test_permission_migration.rs b/program/rust/src/tests/test_permission_migration.rs index abffcbe6..2690bb31 100644 --- a/program/rust/src/tests/test_permission_migration.rs +++ b/program/rust/src/tests/test_permission_migration.rs @@ -67,6 +67,10 @@ fn test_permission_migration() { let mut price_account = price_setup.as_account_info(); PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); + let mut system_program = AccountSetup::new_system_program(); + let system_program_account = system_program.as_account_info(); + + product_account.is_signer = false; mapping_account.is_signer = false; price_account.is_signer = false; @@ -150,7 +154,8 @@ fn test_permission_migration() { attacker_account.clone(), product_account.clone(), price_account.clone(), - permissions_account.clone() + permissions_account.clone(), + system_program_account.clone(), ], bytes_of::(&AddPriceArgs { header: AddPrice.into(), diff --git a/program/rust/src/tests/test_upd_permissions.rs b/program/rust/src/tests/test_upd_permissions.rs index df40d240..7bf9463c 100644 --- a/program/rust/src/tests/test_upd_permissions.rs +++ b/program/rust/src/tests/test_upd_permissions.rs @@ -1,9 +1,6 @@ use { crate::{ - accounts::{ - PermissionAccount, - PythAccount, - }, + accounts::PermissionAccount, deserialize::load, error::OracleError, instruction::{ @@ -69,7 +66,7 @@ async fn test_upd_permissions() { assert_eq!( permission_account.data.len(), - PermissionAccount::MINIMUM_SIZE + PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX ); assert_eq!( Rent::default().minimum_balance(permission_account.data.len()), diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 853fdcd6..2987723f 100644 --- a/program/rust/src/tests/test_utils.rs +++ b/program/rust/src/tests/test_utils.rs @@ -87,8 +87,8 @@ impl AccountSetup { pub fn new_permission(owner: &Pubkey) -> Self { let (key, _bump) = Pubkey::find_program_address(&[PERMISSIONS_SEED.as_bytes()], owner); let owner = *owner; - let balance = Rent::minimum_balance(&Rent::default(), PermissionAccount::MINIMUM_SIZE); - let size = PermissionAccount::MINIMUM_SIZE; + let balance = Rent::minimum_balance(&Rent::default(), PermissionAccount::NEW_ACCOUNT_SPACE); + let size = PermissionAccount::NEW_ACCOUNT_SPACE; let data = [0; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; AccountSetup { key, @@ -114,6 +114,20 @@ impl AccountSetup { } } + pub fn new_system_program() -> Self { + let key = system_program::id(); + let owner = system_program::id(); //? + let balance = Rent::minimum_balance(&Rent::default(), 0); + let data = [0u8; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; + AccountSetup { + key, + owner, + balance, + size: 0, + data, + } + } + pub fn as_account_info(&mut self) -> AccountInfo { AccountInfo::new( &self.key, From f466deb73c4a15ea13cb271c3a900c5a85810c87 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Tue, 6 Aug 2024 11:47:18 +0100 Subject: [PATCH 6/9] refactor: remove transfer to save contract size --- program/rust/src/processor.rs | 27 ++++--------------- program/rust/src/processor/add_price.rs | 18 ++++--------- program/rust/src/processor/add_product.rs | 1 - program/rust/src/processor/del_product.rs | 1 - program/rust/src/processor/init_price.rs | 1 - .../src/processor/init_price_feed_index.rs | 12 +++------ program/rust/src/processor/set_min_pub.rs | 1 - program/rust/src/processor/upd_permissions.rs | 1 - program/rust/src/processor/upd_product.rs | 1 - program/rust/src/tests/mod.rs | 1 - program/rust/src/tests/pyth_simulator.rs | 1 - program/rust/src/tests/test_add_price.rs | 9 ------- program/rust/src/tests/test_add_product.rs | 2 -- program/rust/src/tests/test_del_price.rs | 1 - program/rust/src/tests/test_del_product.rs | 2 -- program/rust/src/tests/test_ema.rs | 3 --- .../rust/src/tests/test_full_publisher_set.rs | 1 - program/rust/src/tests/test_message.rs | 1 - .../src/tests/test_permission_migration.rs | 5 ---- program/rust/src/tests/test_upd_aggregate.rs | 1 - .../rust/src/tests/test_upd_permissions.rs | 1 - .../tests/test_upd_price_no_fail_on_error.rs | 4 --- program/rust/src/tests/test_upd_price_v2.rs | 1 - .../tests/test_upd_price_with_validator.rs | 4 --- program/rust/src/tests/test_utils.rs | 14 ---------- 25 files changed, 13 insertions(+), 101 deletions(-) diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index b1cf8cb3..31473f9f 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -17,9 +17,7 @@ use { }, solana_program::{ entrypoint::ProgramResult, - program::invoke, pubkey::Pubkey, - system_instruction, sysvar::slot_history::AccountInfo, }, }; @@ -109,30 +107,15 @@ pub fn process_instruction( } } -fn reserve_new_price_feed_index<'a>( - funding_account: &AccountInfo<'a>, - permissions_account: &AccountInfo<'a>, - system_program: &AccountInfo<'a>, -) -> Result { +fn reserve_new_price_feed_index(permissions_account: &AccountInfo) -> Result { if permissions_account.data_len() < PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX { let new_size = PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX; let rent = Rent::get()?; let new_minimum_balance = rent.minimum_balance(new_size); - let lamports_diff = new_minimum_balance.saturating_sub(permissions_account.lamports()); - if lamports_diff > 0 { - invoke( - &system_instruction::transfer( - funding_account.key, - permissions_account.key, - lamports_diff, - ), - &[ - funding_account.clone(), - permissions_account.clone(), - system_program.clone(), - ], - )?; - } + pyth_assert( + permissions_account.lamports() >= new_minimum_balance, + ProgramError::AccountNotRentExempt, + )?; permissions_account.realloc(new_size, true)?; let mut header = load_account_as_mut::(permissions_account)?; diff --git a/program/rust/src/processor/add_price.rs b/program/rust/src/processor/add_price.rs index f85df157..b06bffae 100644 --- a/program/rust/src/processor/add_price.rs +++ b/program/rust/src/processor/add_price.rs @@ -37,7 +37,6 @@ use { // account[1] product account [writable] // account[2] new price account [writable] // account[3] permissions account [writable] -// account[4] system program account [] pub fn add_price( program_id: &Pubkey, accounts: &[AccountInfo], @@ -51,12 +50,10 @@ pub fn add_price( ProgramError::InvalidArgument, )?; - - let (funding_account, product_account, price_account, permissions_account, system_program) = - match accounts { - [x, y, z, p, q] => Ok((x, y, z, p, q)), - _ => Err(OracleError::InvalidNumberOfAccounts), - }?; + let (funding_account, product_account, price_account, permissions_account) = match accounts { + [x, y, z, p] => Ok((x, y, z, p)), + _ => Err(OracleError::InvalidNumberOfAccounts), + }?; check_valid_funding_account(funding_account)?; check_permissioned_funding_account( @@ -74,10 +71,6 @@ pub fn add_price( &cmd_args.header, )?; check_valid_writable_account(program_id, permissions_account)?; - pyth_assert( - solana_program::system_program::check_id(system_program.key), - OracleError::InvalidSystemAccount.into(), - )?; let mut product_data = load_checked::(product_account, cmd_args.header.version)?; @@ -89,8 +82,7 @@ pub fn add_price( price_data.product_account = *product_account.key; price_data.next_price_account = product_data.first_price_account; price_data.min_pub_ = PRICE_ACCOUNT_DEFAULT_MIN_PUB; - price_data.feed_index = - reserve_new_price_feed_index(funding_account, permissions_account, system_program)?; + price_data.feed_index = reserve_new_price_feed_index(permissions_account)?; product_data.first_price_account = *price_account.key; Ok(()) diff --git a/program/rust/src/processor/add_product.rs b/program/rust/src/processor/add_product.rs index e953489f..cb94a117 100644 --- a/program/rust/src/processor/add_product.rs +++ b/program/rust/src/processor/add_product.rs @@ -65,7 +65,6 @@ pub fn add_product( hdr, )?; - let mut mapping_data = load_checked::(tail_mapping_account, hdr.version)?; // The mapping account must have free space to add the product account pyth_assert( diff --git a/program/rust/src/processor/del_product.rs b/program/rust/src/processor/del_product.rs index c563207c..2862789c 100644 --- a/program/rust/src/processor/del_product.rs +++ b/program/rust/src/processor/del_product.rs @@ -68,7 +68,6 @@ pub fn del_product( cmd_args, )?; - { let mut mapping_data = load_checked::(mapping_account, cmd_args.version)?; let product_data = load_checked::(product_account, cmd_args.version)?; diff --git a/program/rust/src/processor/init_price.rs b/program/rust/src/processor/init_price.rs index b9f14656..161a53f2 100644 --- a/program/rust/src/processor/init_price.rs +++ b/program/rust/src/processor/init_price.rs @@ -55,7 +55,6 @@ pub fn init_price( &cmd_args.header, )?; - let mut price_data = load_checked::(price_account, cmd_args.header.version)?; pyth_assert( price_data.price_type == cmd_args.price_type, diff --git a/program/rust/src/processor/init_price_feed_index.rs b/program/rust/src/processor/init_price_feed_index.rs index ea991bc2..72efc81f 100644 --- a/program/rust/src/processor/init_price_feed_index.rs +++ b/program/rust/src/processor/init_price_feed_index.rs @@ -28,7 +28,6 @@ use { // account[0] funding account [signer writable] // account[1] price account [writable] // account[2] permissions account [writable] -// account[3] system program account [] pub fn init_price_feed_index( program_id: &Pubkey, accounts: &[AccountInfo], @@ -41,8 +40,8 @@ pub fn init_price_feed_index( ProgramError::InvalidArgument, )?; - let (funding_account, price_account, permissions_account, system_program) = match accounts { - [x, y, p, z] => Ok((x, y, p, z)), + let (funding_account, price_account, permissions_account) = match accounts { + [x, y, p] => Ok((x, y, p)), _ => Err(OracleError::InvalidNumberOfAccounts), }?; @@ -55,18 +54,13 @@ pub fn init_price_feed_index( cmd, )?; check_valid_writable_account(program_id, permissions_account)?; - pyth_assert( - solana_program::system_program::check_id(system_program.key), - OracleError::InvalidSystemAccount.into(), - )?; let mut price_account_data = load_checked::(price_account, cmd.version)?; pyth_assert( price_account_data.feed_index == 0, OracleError::FeedIndexAlreadyInitialized.into(), )?; - price_account_data.feed_index = - reserve_new_price_feed_index(funding_account, permissions_account, system_program)?; + price_account_data.feed_index = reserve_new_price_feed_index(permissions_account)?; Ok(()) } diff --git a/program/rust/src/processor/set_min_pub.rs b/program/rust/src/processor/set_min_pub.rs index 55f1571d..f1bf31d2 100644 --- a/program/rust/src/processor/set_min_pub.rs +++ b/program/rust/src/processor/set_min_pub.rs @@ -51,7 +51,6 @@ pub fn set_min_pub( &cmd.header, )?; - let mut price_account_data = load_checked::(price_account, cmd.header.version)?; price_account_data.min_pub_ = cmd.minimum_publishers; diff --git a/program/rust/src/processor/upd_permissions.rs b/program/rust/src/processor/upd_permissions.rs index f4c2a43d..01dd1ced 100644 --- a/program/rust/src/processor/upd_permissions.rs +++ b/program/rust/src/processor/upd_permissions.rs @@ -61,7 +61,6 @@ pub fn upd_permissions( OracleError::InvalidSystemAccount.into(), )?; - // Create PermissionAccount if it doesn't exist PermissionAccount::initialize_pda( permissions_account, diff --git a/program/rust/src/processor/upd_product.rs b/program/rust/src/processor/upd_product.rs index 4b44608a..8e20cd98 100644 --- a/program/rust/src/processor/upd_product.rs +++ b/program/rust/src/processor/upd_product.rs @@ -47,7 +47,6 @@ pub fn upd_product( hdr, )?; - { // Validate that product_account contains the appropriate account header let mut _product_data = load_checked::(product_account, hdr.version)?; diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index 33178ccd..d5791ee8 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -28,6 +28,5 @@ mod test_upd_price_with_validator; mod test_upd_product; mod test_utils; - mod test_twap; mod test_upd_price_v2; diff --git a/program/rust/src/tests/pyth_simulator.rs b/program/rust/src/tests/pyth_simulator.rs index cb01bdf1..87640503 100644 --- a/program/rust/src/tests/pyth_simulator.rs +++ b/program/rust/src/tests/pyth_simulator.rs @@ -338,7 +338,6 @@ impl PythSimulator { AccountMeta::new(product_keypair.pubkey(), true), AccountMeta::new(price_keypair.pubkey(), true), AccountMeta::new(self.get_permissions_pubkey(), false), - AccountMeta::new(system_program::id(), false), ], ); diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index 2f96297b..28dd429d 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -62,9 +62,6 @@ fn test_add_price() { let mut permissions_setup = AccountSetup::new_permission(&program_id); let permissions_account = permissions_setup.as_account_info(); - let mut system_program = AccountSetup::new_system_program(); - let system_program_account = system_program.as_account_info(); - { let mut permissions_account_data = PermissionAccount::initialize(&permissions_account, PC_VERSION).unwrap(); @@ -92,7 +89,6 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), - system_program_account.clone(), ], instruction_data_add_price, ) @@ -116,7 +112,6 @@ fn test_add_price() { product_account.clone(), price_account_2.clone(), permissions_account.clone(), - system_program_account.clone(), ], instruction_data_add_price, ) @@ -142,7 +137,6 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), - system_program_account.clone(), permissions_account.clone(), ], instruction_data_add_price @@ -159,7 +153,6 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), - system_program_account.clone(), ], instruction_data_add_price ), @@ -184,7 +177,6 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), - system_program_account.clone(), ], instruction_data_add_price ), @@ -210,7 +202,6 @@ fn test_add_price() { product_account.clone(), price_account.clone(), permissions_account.clone(), - system_program_account.clone(), ], instruction_data_add_price ), diff --git a/program/rust/src/tests/test_add_product.rs b/program/rust/src/tests/test_add_product.rs index d4404a8d..801eca94 100644 --- a/program/rust/src/tests/test_add_product.rs +++ b/program/rust/src/tests/test_add_product.rs @@ -38,7 +38,6 @@ use { std::mem::size_of, }; - #[test] fn test_add_product() { let mut instruction_data = [0u8; PC_PROD_ACC_SIZE as usize]; @@ -201,7 +200,6 @@ fn test_add_product() { assert_eq!(mapping_data.number_of_products, PC_MAP_TABLE_SIZE); } - // Create an add_product instruction that sets the product metadata to strings pub fn populate_instruction(instruction_data: &mut [u8], strings: &[&str]) -> usize { { diff --git a/program/rust/src/tests/test_del_price.rs b/program/rust/src/tests/test_del_price.rs index 27c3f7fe..acf48fa7 100644 --- a/program/rust/src/tests/test_del_price.rs +++ b/program/rust/src/tests/test_del_price.rs @@ -36,7 +36,6 @@ async fn test_del_price() { .unwrap(); assert!(product1_data.first_price_account == Pubkey::default()); - // price2_1 is the 2nd item in the linked list since price2_2 got added after t. assert!(sim.del_price(&product2, &price2_1).await.is_err()); // Can delete the accounts in the opposite order though diff --git a/program/rust/src/tests/test_del_product.rs b/program/rust/src/tests/test_del_product.rs index 02e315ef..864d85b3 100644 --- a/program/rust/src/tests/test_del_product.rs +++ b/program/rust/src/tests/test_del_product.rs @@ -11,7 +11,6 @@ use { }, }; - #[tokio::test] async fn test_del_product() { let mut sim = PythSimulator::new().await; @@ -56,7 +55,6 @@ async fn test_del_product() { )); assert!(sim.get_account(product5.pubkey()).await.is_some()); - assert!(sim.del_product(&mapping_keypair, &product4).await.is_ok()); let mapping_data = sim .get_account_data_as::(mapping_keypair.pubkey()) diff --git a/program/rust/src/tests/test_ema.rs b/program/rust/src/tests/test_ema.rs index 1a1cae10..73b15f2c 100644 --- a/program/rust/src/tests/test_ema.rs +++ b/program/rust/src/tests/test_ema.rs @@ -18,7 +18,6 @@ use { test_generator::test_resources, }; - #[test_resources("program/rust/test_data/ema/*.csv")] fn test_ema(input_path_raw: &str) { let (inputs, expected_outputs) = read_test_data(input_path_raw); @@ -110,7 +109,6 @@ fn run_ema_test(inputs: &[InputRecord], expected_outputs: &[OutputRecord]) { } } - // TODO: put these functions somewhere more accessible pub fn upd_aggregate( price_account: &mut PriceAccount, @@ -130,7 +128,6 @@ 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, diff --git a/program/rust/src/tests/test_full_publisher_set.rs b/program/rust/src/tests/test_full_publisher_set.rs index bc4cfd13..fd856b62 100644 --- a/program/rust/src/tests/test_full_publisher_set.rs +++ b/program/rust/src/tests/test_full_publisher_set.rs @@ -36,7 +36,6 @@ 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) diff --git a/program/rust/src/tests/test_message.rs b/program/rust/src/tests/test_message.rs index 427b106a..a68f8be0 100644 --- a/program/rust/src/tests/test_message.rs +++ b/program/rust/src/tests/test_message.rs @@ -44,7 +44,6 @@ fn test_twap_message_roundtrip(input: TwapMessage) -> bool { } } - fn prop_publisher_caps_message_roundtrip(input: PublisherStakeCapsMessage) -> bool { let reconstructed = from_slice::(&input.clone().to_bytes()).unwrap(); diff --git a/program/rust/src/tests/test_permission_migration.rs b/program/rust/src/tests/test_permission_migration.rs index 2690bb31..f324c1d4 100644 --- a/program/rust/src/tests/test_permission_migration.rs +++ b/program/rust/src/tests/test_permission_migration.rs @@ -67,10 +67,6 @@ fn test_permission_migration() { let mut price_account = price_setup.as_account_info(); PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); - let mut system_program = AccountSetup::new_system_program(); - let system_program_account = system_program.as_account_info(); - - product_account.is_signer = false; mapping_account.is_signer = false; price_account.is_signer = false; @@ -155,7 +151,6 @@ fn test_permission_migration() { product_account.clone(), price_account.clone(), permissions_account.clone(), - system_program_account.clone(), ], bytes_of::(&AddPriceArgs { header: AddPrice.into(), diff --git a/program/rust/src/tests/test_upd_aggregate.rs b/program/rust/src/tests/test_upd_aggregate.rs index c9e962cf..f273aaa3 100644 --- a/program/rust/src/tests/test_upd_aggregate.rs +++ b/program/rust/src/tests/test_upd_aggregate.rs @@ -67,7 +67,6 @@ fn test_upd_aggregate() { corp_act_status_: 0, }; - let mut instruction_data = [0u8; size_of::()]; populate_instruction(&mut instruction_data, 42, 2, 1); diff --git a/program/rust/src/tests/test_upd_permissions.rs b/program/rust/src/tests/test_upd_permissions.rs index 7bf9463c..edd6416c 100644 --- a/program/rust/src/tests/test_upd_permissions.rs +++ b/program/rust/src/tests/test_upd_permissions.rs @@ -88,7 +88,6 @@ async fn test_upd_permissions() { master_authority = Pubkey::new_unique(); security_authority = Pubkey::new_unique(); - // Should fail because payer is not the authority assert_eq!( sim.upd_permissions( 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 6e764e36..703959b3 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,7 +52,6 @@ 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); @@ -69,7 +68,6 @@ 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 @@ -85,7 +83,6 @@ 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); @@ -169,7 +166,6 @@ fn test_upd_price_no_fail_on_error_no_fail_on_error() { } } - // 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 index 63785819..ec008cd5 100644 --- a/program/rust/src/tests/test_upd_price_v2.rs +++ b/program/rust/src/tests/test_upd_price_v2.rs @@ -511,7 +511,6 @@ fn test_upd_works_with_unordered_publisher_set() -> Result<(), Box(&price_account, PC_VERSION).unwrap(); // The result will be the following only if all the diff --git a/program/rust/src/tests/test_upd_price_with_validator.rs b/program/rust/src/tests/test_upd_price_with_validator.rs index 0c6cb204..07ade617 100644 --- a/program/rust/src/tests/test_upd_price_with_validator.rs +++ b/program/rust/src/tests/test_upd_price_with_validator.rs @@ -86,7 +86,6 @@ fn test_upd_price_with_validator() { ) .is_ok()); - { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); assert_eq!(price_data.comp_[0].latest_.price_, 42); @@ -372,7 +371,6 @@ fn test_upd_price_with_validator() { .unwrap(); update_clock_slot(&mut clock_account, 7); - assert!(process_instruction( &program_id, &[ @@ -407,7 +405,6 @@ fn test_upd_price_with_validator() { .unwrap(); update_clock_slot(&mut clock_account, 8); - assert!(process_instruction( &program_id, &[ @@ -442,7 +439,6 @@ fn test_upd_price_with_validator() { .unwrap(); update_clock_slot(&mut clock_account, 9); - assert!(process_instruction( &program_id, &[ diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 2987723f..28d8f44c 100644 --- a/program/rust/src/tests/test_utils.rs +++ b/program/rust/src/tests/test_utils.rs @@ -114,20 +114,6 @@ impl AccountSetup { } } - pub fn new_system_program() -> Self { - let key = system_program::id(); - let owner = system_program::id(); //? - let balance = Rent::minimum_balance(&Rent::default(), 0); - let data = [0u8; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; - AccountSetup { - key, - owner, - balance, - size: 0, - data, - } - } - pub fn as_account_info(&mut self) -> AccountInfo { AccountInfo::new( &self.key, From 500b6273d6629f5612f36243821336cc2a8ea128 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Thu, 8 Aug 2024 13:45:32 +0100 Subject: [PATCH 7/9] test: add test for price feed indexes --- program/rust/src/tests/test_add_price.rs | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index 28dd429d..689d852b 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -102,6 +102,7 @@ fn test_add_price() { assert_eq!(price_data.min_pub_, PRICE_ACCOUNT_DEFAULT_MIN_PUB); assert!(price_data.product_account == *product_account.key); assert!(price_data.next_price_account == Pubkey::default()); + assert_eq!(price_data.feed_index, 1); assert!(product_data.first_price_account == *price_account.key); } @@ -125,9 +126,62 @@ fn test_add_price() { assert_eq!(price_data_2.min_pub_, PRICE_ACCOUNT_DEFAULT_MIN_PUB); assert!(price_data_2.product_account == *product_account.key); assert!(price_data_2.next_price_account == *price_account.key); + assert_eq!(price_data_2.feed_index, 2); assert!(product_data.first_price_account == *price_account_2.key); } + // Emulate pre-existing price accounts without a feed index. + { + let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + price_data.feed_index = 0; + let mut price_data_2 = load_checked::(&price_account_2, PC_VERSION).unwrap(); + price_data_2.feed_index = 0; + } + let hdr_init_price_feed_index = CommandHeader::from(OracleCommand::InitPriceFeedIndex); + let instruction_data_init_price_feed_index = bytes_of(&hdr_init_price_feed_index); + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + permissions_account.clone(), + ], + instruction_data_init_price_feed_index, + ) + .unwrap(); + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.feed_index, 3); + } + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account_2.clone(), + permissions_account.clone(), + ], + instruction_data_init_price_feed_index, + ) + .unwrap(); + { + let price_data_2 = load_checked::(&price_account_2, PC_VERSION).unwrap(); + assert_eq!(price_data_2.feed_index, 4); + } + + // Feed index is already set. + assert_eq!( + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account_2.clone(), + permissions_account.clone(), + ], + instruction_data_init_price_feed_index, + ), + Err(OracleError::FeedIndexAlreadyInitialized.into()) + ); + // Wrong number of accounts assert_eq!( process_instruction( From 2f3c39c7646dcdd5eb830901528634a7e372fe36 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Fri, 9 Aug 2024 12:39:15 +0100 Subject: [PATCH 8/9] refactor: address feedback --- program/rust/src/accounts.rs | 1 + program/rust/src/accounts/permission.rs | 9 +++------ program/rust/src/accounts/price.rs | 6 +++++- program/rust/src/processor.rs | 8 +++++--- program/rust/src/processor/add_price.rs | 2 +- program/rust/src/processor/add_product.rs | 1 + program/rust/src/processor/del_product.rs | 1 + program/rust/src/processor/init_price.rs | 1 + program/rust/src/processor/set_min_pub.rs | 1 + program/rust/src/processor/upd_permissions.rs | 1 + program/rust/src/processor/upd_product.rs | 1 + program/rust/src/tests/mod.rs | 1 + program/rust/src/tests/test_add_product.rs | 2 ++ program/rust/src/tests/test_del_price.rs | 1 + program/rust/src/tests/test_del_product.rs | 2 ++ program/rust/src/tests/test_ema.rs | 3 +++ program/rust/src/tests/test_full_publisher_set.rs | 1 + program/rust/src/tests/test_message.rs | 1 + program/rust/src/tests/test_upd_aggregate.rs | 1 + program/rust/src/tests/test_upd_permissions.rs | 8 ++++++-- .../rust/src/tests/test_upd_price_no_fail_on_error.rs | 4 ++++ program/rust/src/tests/test_upd_price_v2.rs | 1 + program/rust/src/tests/test_upd_price_with_validator.rs | 4 ++++ program/rust/src/validator.rs | 1 + 24 files changed, 49 insertions(+), 13 deletions(-) diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 609efd79..8b1afaff 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -59,6 +59,7 @@ pub use { PriceEma, PriceInfo, PythOracleSerialize, + MAX_FEED_INDEX, }, product::{ update_product_metadata, diff --git a/program/rust/src/accounts/permission.rs b/program/rust/src/accounts/permission.rs index 46c4b209..876026fa 100644 --- a/program/rust/src/accounts/permission.rs +++ b/program/rust/src/accounts/permission.rs @@ -47,9 +47,6 @@ pub struct PermissionAccount { } impl PermissionAccount { - pub const MIN_SIZE_WITH_LAST_FEED_INDEX: usize = - size_of::() + size_of::(); - pub fn is_authorized(&self, key: &Pubkey, command: OracleCommand) -> bool { #[allow(clippy::match_like_matches_macro)] match (*key, command) { @@ -66,7 +63,7 @@ impl PermissionAccount { ) -> Result, ProgramError> { let start = size_of::(); let end = start + size_of::(); - assert_eq!(Self::MIN_SIZE_WITH_LAST_FEED_INDEX, end); + assert_eq!(Self::NEW_ACCOUNT_SPACE, end); if account.data_len() < end { return Err(ProgramError::AccountDataTooSmall); } @@ -78,6 +75,6 @@ impl PermissionAccount { impl PythAccount for PermissionAccount { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS; - const INITIAL_SIZE: u32 = Self::MIN_SIZE_WITH_LAST_FEED_INDEX as u32; - const NEW_ACCOUNT_SPACE: usize = Self::MIN_SIZE_WITH_LAST_FEED_INDEX; + const NEW_ACCOUNT_SPACE: usize = size_of::() + size_of::(); + const INITIAL_SIZE: u32 = Self::NEW_ACCOUNT_SPACE as u32; } diff --git a/program/rust/src/accounts/price.rs b/program/rust/src/accounts/price.rs index 309a9889..fc207539 100644 --- a/program/rust/src/accounts/price.rs +++ b/program/rust/src/accounts/price.rs @@ -70,7 +70,7 @@ mod price_pythnet { /// Various flags pub flags: PriceAccountFlags, /// Globally unique price feed index used for publishing. - /// Limited to 28 bites. + /// Limited to 28 bites so that it can be packed together with trading status in a single u32. pub feed_index: u32, /// Corresponding product account pub product_account: Pubkey, @@ -95,6 +95,10 @@ mod price_pythnet { pub price_cumulative: PriceCumulative, } + // Feed index is limited to 28 bites so that it can be packed + // together with trading status in a single u32. + pub const MAX_FEED_INDEX: u32 = (1 << 28) - 1; + bitflags! { #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index 31473f9f..8246e0e8 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -3,6 +3,8 @@ use { accounts::{ AccountHeader, PermissionAccount, + PythAccount, + MAX_FEED_INDEX, }, deserialize::load_account_as_mut, error::OracleError, @@ -108,8 +110,8 @@ pub fn process_instruction( } fn reserve_new_price_feed_index(permissions_account: &AccountInfo) -> Result { - if permissions_account.data_len() < PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX { - let new_size = PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX; + if permissions_account.data_len() < PermissionAccount::NEW_ACCOUNT_SPACE { + let new_size = PermissionAccount::NEW_ACCOUNT_SPACE; let rent = Rent::get()?; let new_minimum_balance = rent.minimum_balance(new_size); pyth_assert( @@ -124,7 +126,7 @@ fn reserve_new_price_feed_index(permissions_account: &AccountInfo) -> Result Ok((x, y, z, p)), _ => Err(OracleError::InvalidNumberOfAccounts), @@ -75,7 +76,6 @@ pub fn add_price( let mut product_data = load_checked::(product_account, cmd_args.header.version)?; - // TODO: where is it created??? let mut price_data = PriceAccount::initialize(price_account, cmd_args.header.version)?; price_data.exponent = cmd_args.exponent; price_data.price_type = cmd_args.price_type; diff --git a/program/rust/src/processor/add_product.rs b/program/rust/src/processor/add_product.rs index cb94a117..e953489f 100644 --- a/program/rust/src/processor/add_product.rs +++ b/program/rust/src/processor/add_product.rs @@ -65,6 +65,7 @@ pub fn add_product( hdr, )?; + let mut mapping_data = load_checked::(tail_mapping_account, hdr.version)?; // The mapping account must have free space to add the product account pyth_assert( diff --git a/program/rust/src/processor/del_product.rs b/program/rust/src/processor/del_product.rs index 2862789c..c563207c 100644 --- a/program/rust/src/processor/del_product.rs +++ b/program/rust/src/processor/del_product.rs @@ -68,6 +68,7 @@ pub fn del_product( cmd_args, )?; + { let mut mapping_data = load_checked::(mapping_account, cmd_args.version)?; let product_data = load_checked::(product_account, cmd_args.version)?; diff --git a/program/rust/src/processor/init_price.rs b/program/rust/src/processor/init_price.rs index 161a53f2..b9f14656 100644 --- a/program/rust/src/processor/init_price.rs +++ b/program/rust/src/processor/init_price.rs @@ -55,6 +55,7 @@ pub fn init_price( &cmd_args.header, )?; + let mut price_data = load_checked::(price_account, cmd_args.header.version)?; pyth_assert( price_data.price_type == cmd_args.price_type, diff --git a/program/rust/src/processor/set_min_pub.rs b/program/rust/src/processor/set_min_pub.rs index f1bf31d2..55f1571d 100644 --- a/program/rust/src/processor/set_min_pub.rs +++ b/program/rust/src/processor/set_min_pub.rs @@ -51,6 +51,7 @@ pub fn set_min_pub( &cmd.header, )?; + let mut price_account_data = load_checked::(price_account, cmd.header.version)?; price_account_data.min_pub_ = cmd.minimum_publishers; diff --git a/program/rust/src/processor/upd_permissions.rs b/program/rust/src/processor/upd_permissions.rs index 01dd1ced..f4c2a43d 100644 --- a/program/rust/src/processor/upd_permissions.rs +++ b/program/rust/src/processor/upd_permissions.rs @@ -61,6 +61,7 @@ pub fn upd_permissions( OracleError::InvalidSystemAccount.into(), )?; + // Create PermissionAccount if it doesn't exist PermissionAccount::initialize_pda( permissions_account, diff --git a/program/rust/src/processor/upd_product.rs b/program/rust/src/processor/upd_product.rs index 8e20cd98..4b44608a 100644 --- a/program/rust/src/processor/upd_product.rs +++ b/program/rust/src/processor/upd_product.rs @@ -47,6 +47,7 @@ pub fn upd_product( hdr, )?; + { // Validate that product_account contains the appropriate account header let mut _product_data = load_checked::(product_account, hdr.version)?; diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index d5791ee8..33178ccd 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -28,5 +28,6 @@ mod test_upd_price_with_validator; mod test_upd_product; mod test_utils; + mod test_twap; mod test_upd_price_v2; diff --git a/program/rust/src/tests/test_add_product.rs b/program/rust/src/tests/test_add_product.rs index 801eca94..d4404a8d 100644 --- a/program/rust/src/tests/test_add_product.rs +++ b/program/rust/src/tests/test_add_product.rs @@ -38,6 +38,7 @@ use { std::mem::size_of, }; + #[test] fn test_add_product() { let mut instruction_data = [0u8; PC_PROD_ACC_SIZE as usize]; @@ -200,6 +201,7 @@ fn test_add_product() { assert_eq!(mapping_data.number_of_products, PC_MAP_TABLE_SIZE); } + // Create an add_product instruction that sets the product metadata to strings pub fn populate_instruction(instruction_data: &mut [u8], strings: &[&str]) -> usize { { diff --git a/program/rust/src/tests/test_del_price.rs b/program/rust/src/tests/test_del_price.rs index acf48fa7..27c3f7fe 100644 --- a/program/rust/src/tests/test_del_price.rs +++ b/program/rust/src/tests/test_del_price.rs @@ -36,6 +36,7 @@ async fn test_del_price() { .unwrap(); assert!(product1_data.first_price_account == Pubkey::default()); + // price2_1 is the 2nd item in the linked list since price2_2 got added after t. assert!(sim.del_price(&product2, &price2_1).await.is_err()); // Can delete the accounts in the opposite order though diff --git a/program/rust/src/tests/test_del_product.rs b/program/rust/src/tests/test_del_product.rs index 864d85b3..02e315ef 100644 --- a/program/rust/src/tests/test_del_product.rs +++ b/program/rust/src/tests/test_del_product.rs @@ -11,6 +11,7 @@ use { }, }; + #[tokio::test] async fn test_del_product() { let mut sim = PythSimulator::new().await; @@ -55,6 +56,7 @@ async fn test_del_product() { )); assert!(sim.get_account(product5.pubkey()).await.is_some()); + assert!(sim.del_product(&mapping_keypair, &product4).await.is_ok()); let mapping_data = sim .get_account_data_as::(mapping_keypair.pubkey()) diff --git a/program/rust/src/tests/test_ema.rs b/program/rust/src/tests/test_ema.rs index 73b15f2c..1a1cae10 100644 --- a/program/rust/src/tests/test_ema.rs +++ b/program/rust/src/tests/test_ema.rs @@ -18,6 +18,7 @@ use { test_generator::test_resources, }; + #[test_resources("program/rust/test_data/ema/*.csv")] fn test_ema(input_path_raw: &str) { let (inputs, expected_outputs) = read_test_data(input_path_raw); @@ -109,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, @@ -128,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, diff --git a/program/rust/src/tests/test_full_publisher_set.rs b/program/rust/src/tests/test_full_publisher_set.rs index fd856b62..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) diff --git a/program/rust/src/tests/test_message.rs b/program/rust/src/tests/test_message.rs index a68f8be0..427b106a 100644 --- a/program/rust/src/tests/test_message.rs +++ b/program/rust/src/tests/test_message.rs @@ -44,6 +44,7 @@ fn test_twap_message_roundtrip(input: TwapMessage) -> bool { } } + fn prop_publisher_caps_message_roundtrip(input: PublisherStakeCapsMessage) -> bool { let reconstructed = from_slice::(&input.clone().to_bytes()).unwrap(); diff --git a/program/rust/src/tests/test_upd_aggregate.rs b/program/rust/src/tests/test_upd_aggregate.rs index f273aaa3..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); diff --git a/program/rust/src/tests/test_upd_permissions.rs b/program/rust/src/tests/test_upd_permissions.rs index edd6416c..d260c791 100644 --- a/program/rust/src/tests/test_upd_permissions.rs +++ b/program/rust/src/tests/test_upd_permissions.rs @@ -1,6 +1,9 @@ use { crate::{ - accounts::PermissionAccount, + accounts::{ + PermissionAccount, + PythAccount, + }, deserialize::load, error::OracleError, instruction::{ @@ -66,7 +69,7 @@ async fn test_upd_permissions() { assert_eq!( permission_account.data.len(), - PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX + PermissionAccount::NEW_ACCOUNT_SPACE ); assert_eq!( Rent::default().minimum_balance(permission_account.data.len()), @@ -88,6 +91,7 @@ async fn test_upd_permissions() { master_authority = Pubkey::new_unique(); security_authority = Pubkey::new_unique(); + // Should fail because payer is not the authority assert_eq!( sim.upd_permissions( 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 703959b3..6e764e36 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); @@ -166,6 +169,7 @@ fn test_upd_price_no_fail_on_error_no_fail_on_error() { } } + // 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 index ec008cd5..63785819 100644 --- a/program/rust/src/tests/test_upd_price_v2.rs +++ b/program/rust/src/tests/test_upd_price_v2.rs @@ -511,6 +511,7 @@ fn test_upd_works_with_unordered_publisher_set() -> Result<(), Box(&price_account, PC_VERSION).unwrap(); // The result will be the following only if all the diff --git a/program/rust/src/tests/test_upd_price_with_validator.rs b/program/rust/src/tests/test_upd_price_with_validator.rs index 07ade617..343583a9 100644 --- a/program/rust/src/tests/test_upd_price_with_validator.rs +++ b/program/rust/src/tests/test_upd_price_with_validator.rs @@ -86,6 +86,7 @@ fn test_upd_price_with_validator() { ) .is_ok()); + { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); assert_eq!(price_data.comp_[0].latest_.price_, 42); @@ -336,6 +337,7 @@ fn test_upd_price_with_validator() { assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); } + assert!(process_instruction( &program_id, &[ @@ -405,6 +407,7 @@ fn test_upd_price_with_validator() { .unwrap(); update_clock_slot(&mut clock_account, 8); + assert!(process_instruction( &program_id, &[ @@ -439,6 +442,7 @@ fn test_upd_price_with_validator() { .unwrap(); update_clock_slot(&mut clock_account, 9); + assert!(process_instruction( &program_id, &[ diff --git a/program/rust/src/validator.rs b/program/rust/src/validator.rs index bc78fe46..5f1e930d 100644 --- a/program/rust/src/validator.rs +++ b/program/rust/src/validator.rs @@ -45,6 +45,7 @@ fn check_price_account_header(price_account_info: &[u8]) -> Result<(), ProgramEr && account_header.account_type == PriceAccount::ACCOUNT_TYPE, OracleError::InvalidAccountHeader.into(), )?; + Ok(()) } From 9342f63bcdbf68eb718aa89e7cac60c3bc6d8b83 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Tue, 27 Aug 2024 13:30:08 +0100 Subject: [PATCH 9/9] chore: fix some outdated account list docs --- program/rust/src/instruction.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/program/rust/src/instruction.rs b/program/rust/src/instruction.rs index 33d60d0b..c61cbda9 100644 --- a/program/rust/src/instruction.rs +++ b/program/rust/src/instruction.rs @@ -39,9 +39,10 @@ pub enum OracleCommand { // account[1] product account [signer writable] UpdProduct = 3, /// Add new price account to a product account - // account[0] funding account [signer writable] - // account[1] product account [signer writable] - // account[2] new price account [signer writable] + // account[0] funding account [signer writable] + // account[1] product account [writable] + // account[2] new price account [writable] + // account[3] permissions account [writable] AddPrice = 4, /// Add publisher to symbol account // account[0] funding account [signer writable] @@ -107,7 +108,6 @@ pub enum OracleCommand { // account[0] funding account [signer writable] // account[1] price account [writable] // account[2] permissions account [writable] - // account[3] system program account [] InitPriceFeedIndex = 19, }