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.rs b/program/rust/src/accounts.rs index 8bc83ce5..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, @@ -105,6 +106,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 +145,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 +153,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 990faeb4..876026fa 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 @@ -50,9 +57,24 @@ 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::NEW_ACCOUNT_SPACE, 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; - const INITIAL_SIZE: u32 = size_of::() as u32; + 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 a569d054..fc207539 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 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, /// Next price account in the list @@ -94,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/error.rs b/program/rust/src/error.rs index 22fb27e5..b4eb642b 100644 --- a/program/rust/src/error.rs +++ b/program/rust/src/error.rs @@ -52,6 +52,10 @@ pub enum OracleError { PermissionViolation = 619, #[error("NeedsSuccesfulAggregation")] NeedsSuccesfulAggregation = 620, + #[error("MaxLastFeedIndexReached")] + MaxLastFeedIndexReached = 621, + #[error("FeedIndexAlreadyInitialized")] + FeedIndexAlreadyInitialized = 622, } impl From for ProgramError { diff --git a/program/rust/src/instruction.rs b/program/rust/src/instruction.rs index c3d8a3de..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] @@ -103,6 +104,11 @@ 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] + InitPriceFeedIndex = 19, } #[repr(C)] diff --git a/program/rust/src/lib.rs b/program/rust/src/lib.rs index eb1881ef..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")] @@ -45,8 +45,12 @@ pub use accounts::{ PythAccount, PythOracleSerialize, }; +#[cfg(feature = "library")] +pub use { + processor::find_publisher_index, + utils::get_status_for_conf_price_ratio, +}; use { - crate::error::OracleError, processor::process_instruction, solana_program::entrypoint, }; diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index b4104fc1..8246e0e8 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -1,10 +1,21 @@ use { crate::{ + accounts::{ + AccountHeader, + PermissionAccount, + PythAccount, + MAX_FEED_INDEX, + }, + deserialize::load_account_as_mut, error::OracleError, instruction::{ load_command_header_checked, OracleCommand, }, + utils::{ + pyth_assert, + try_convert, + }, }, solana_program::{ entrypoint::ProgramResult, @@ -21,6 +32,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; @@ -47,11 +59,20 @@ pub use { upd_price::{ c_upd_aggregate, c_upd_twap, + find_publisher_index, upd_price, upd_price_no_fail_on_error, }, 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( @@ -84,5 +105,29 @@ 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), + } +} + +fn reserve_new_price_feed_index(permissions_account: &AccountInfo) -> Result { + 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( + permissions_account.lamports() >= new_minimum_balance, + ProgramError::AccountNotRentExempt, + )?; + + 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 <= MAX_FEED_INDEX, + 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..44354544 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,10 @@ 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] pub fn add_price( program_id: &Pubkey, accounts: &[AccountInfo], @@ -68,6 +71,7 @@ pub fn add_price( permissions_account, &cmd_args.header, )?; + check_valid_writable_account(program_id, permissions_account)?; let mut product_data = load_checked::(product_account, cmd_args.header.version)?; @@ -78,6 +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(permissions_account)?; 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 new file mode 100644 index 00000000..72efc81f --- /dev/null +++ b/program/rust/src/processor/init_price_feed_index.rs @@ -0,0 +1,66 @@ +use { + super::reserve_new_price_feed_index, + crate::{ + accounts::PriceAccount, + deserialize::{ + load, + load_checked, + }, + instruction::CommandHeader, + utils::{ + check_permissioned_funding_account, + check_valid_funding_account, + check_valid_writable_account, + pyth_assert, + }, + OracleError, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + program_error::ProgramError, + pubkey::Pubkey, + }, + std::mem::size_of, +}; + +/// Init price feed index +// account[0] funding account [signer writable] +// account[1] price account [writable] +// account[2] permissions account [writable] +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) = match accounts { + [x, y, p] => Ok((x, y, p)), + _ => Err(OracleError::InvalidNumberOfAccounts), + }?; + + check_valid_funding_account(funding_account)?; + check_permissioned_funding_account( + program_id, + price_account, + funding_account, + permissions_account, + cmd, + )?; + check_valid_writable_account(program_id, permissions_account)?; + + 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(permissions_account)?; + + Ok(()) +} diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index b282f1a5..689d852b 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -82,7 +82,7 @@ fn test_add_price() { ) .is_ok()); - assert!(process_instruction( + process_instruction( &program_id, &[ funding_account.clone(), @@ -90,9 +90,9 @@ fn test_add_price() { price_account.clone(), permissions_account.clone(), ], - instruction_data_add_price + instruction_data_add_price, ) - .is_ok()); + .unwrap(); { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); @@ -102,10 +102,11 @@ 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); } - assert!(process_instruction( + process_instruction( &program_id, &[ funding_account.clone(), @@ -113,9 +114,9 @@ fn test_add_price() { price_account_2.clone(), permissions_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(); @@ -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( diff --git a/program/rust/src/tests/test_permission_migration.rs b/program/rust/src/tests/test_permission_migration.rs index abffcbe6..f324c1d4 100644 --- a/program/rust/src/tests/test_permission_migration.rs +++ b/program/rust/src/tests/test_permission_migration.rs @@ -150,7 +150,7 @@ fn test_permission_migration() { attacker_account.clone(), product_account.clone(), price_account.clone(), - permissions_account.clone() + permissions_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..d260c791 100644 --- a/program/rust/src/tests/test_upd_permissions.rs +++ b/program/rust/src/tests/test_upd_permissions.rs @@ -69,7 +69,7 @@ async fn test_upd_permissions() { assert_eq!( permission_account.data.len(), - PermissionAccount::MINIMUM_SIZE + PermissionAccount::NEW_ACCOUNT_SPACE ); assert_eq!( Rent::default().minimum_balance(permission_account.data.len()), 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..343583a9 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. @@ -311,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, &[ @@ -337,11 +364,15 @@ 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); - assert!(process_instruction( &program_id, &[ @@ -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); diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 853fdcd6..28d8f44c 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, diff --git a/program/rust/src/validator.rs b/program/rust/src/validator.rs index 17a3aa10..5f1e930d 100644 --- a/program/rust/src/validator.rs +++ b/program/rust/src/validator.rs @@ -49,31 +49,6 @@ fn check_price_account_header(price_account_info: &[u8]) -> Result<(), ProgramEr Ok(()) } -// Attempts to validate and access the contents of an account as a PriceAccount. -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 @@ -123,9 +98,23 @@ 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 + .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). @@ -143,13 +132,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