diff --git a/program/c/src/oracle/oracle.h b/program/c/src/oracle/oracle.h index 05c62338..8fb92c59 100644 --- a/program/c/src/oracle/oracle.h +++ b/program/c/src/oracle/oracle.h @@ -20,7 +20,7 @@ extern "C" { // various size constants #define PC_PUBKEY_SIZE 32 #define PC_PUBKEY_SIZE_64 (PC_PUBKEY_SIZE/sizeof(uint64_t)) -#define PC_MAP_TABLE_SIZE 640 +#define PC_MAP_TABLE_SIZE 5000 // Total price component slots available #define PC_NUM_COMP_PYTHNET 128 @@ -117,7 +117,7 @@ typedef struct pc_map_table pc_pub_key_t prod_[PC_MAP_TABLE_SIZE]; // product accounts } pc_map_table_t; -static_assert( sizeof( pc_map_table_t ) == 20536, "" ); +static_assert( sizeof( pc_map_table_t ) == 160056, "" ); // variable length string typedef struct pc_str diff --git a/program/rust/src/error.rs b/program/rust/src/error.rs index b4eb642b..73b52dee 100644 --- a/program/rust/src/error.rs +++ b/program/rust/src/error.rs @@ -56,6 +56,8 @@ pub enum OracleError { MaxLastFeedIndexReached = 621, #[error("FeedIndexAlreadyInitialized")] FeedIndexAlreadyInitialized = 622, + #[error("NoNeedToResize")] + NoNeedToResize = 623, } impl From for ProgramError { diff --git a/program/rust/src/instruction.rs b/program/rust/src/instruction.rs index c61cbda9..58ff3425 100644 --- a/program/rust/src/instruction.rs +++ b/program/rust/src/instruction.rs @@ -109,6 +109,8 @@ pub enum OracleCommand { // account[1] price account [writable] // account[2] permissions account [writable] InitPriceFeedIndex = 19, + // account[0] mapping account [writable] + ResizeMapping = 20, } #[repr(C)] diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index 8246e0e8..80ee1740 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -32,7 +32,7 @@ mod del_product; mod del_publisher; mod init_mapping; mod init_price; -mod init_price_feed_index; +mod resize_mapping; mod set_max_latency; mod set_min_pub; mod upd_permissions; @@ -44,6 +44,11 @@ pub use add_publisher::{ DISABLE_ACCUMULATOR_V2, ENABLE_ACCUMULATOR_V2, }; +use solana_program::{ + program_error::ProgramError, + rent::Rent, + sysvar::Sysvar, +}; pub use { add_price::add_price, add_product::add_product, @@ -53,6 +58,7 @@ pub use { del_publisher::del_publisher, init_mapping::init_mapping, init_price::init_price, + resize_mapping::resize_mapping, set_max_latency::set_max_latency, set_min_pub::set_min_pub, upd_permissions::upd_permissions, @@ -65,14 +71,7 @@ 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( @@ -105,7 +104,13 @@ 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), + InitPriceFeedIndex => { + solana_program::msg!( + "Oracle init price feed index instruction has been removed. Bailing out!" + ); + Err(OracleError::UnrecognizedInstruction.into()) + } + ResizeMapping => resize_mapping(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 deleted file mode 100644 index 72efc81f..00000000 --- a/program/rust/src/processor/init_price_feed_index.rs +++ /dev/null @@ -1,66 +0,0 @@ -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/processor/resize_mapping.rs b/program/rust/src/processor/resize_mapping.rs new file mode 100644 index 00000000..3ca8b106 --- /dev/null +++ b/program/rust/src/processor/resize_mapping.rs @@ -0,0 +1,71 @@ +use { + crate::{ + accounts::{ + AccountHeader, + MappingAccount, + PythAccount, + }, + c_oracle_header::PC_MAGIC, + deserialize::{ + load, + load_account_as, + }, + instruction::CommandHeader, + utils::{ + check_valid_writable_account, + pyth_assert, + }, + OracleError, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::{ + ProgramResult, + MAX_PERMITTED_DATA_INCREASE, + }, + pubkey::Pubkey, + }, + std::{ + cmp::min, + mem::size_of, + }, +}; + +/// Resize mapping account. +// account[0] mapping account [writable] +pub fn resize_mapping( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let mapping_account = match accounts { + [x] => Ok(x), + _ => Err(OracleError::InvalidNumberOfAccounts), + }?; + + let hdr = load::(instruction_data)?; + + check_valid_writable_account(program_id, mapping_account)?; + + { + let account_header = load_account_as::(mapping_account)?; + pyth_assert( + account_header.magic_number == PC_MAGIC + && account_header.version == hdr.version + && account_header.account_type == MappingAccount::ACCOUNT_TYPE, + OracleError::InvalidAccountHeader.into(), + )?; + } + + pyth_assert( + mapping_account.data_len() < size_of::(), + OracleError::NoNeedToResize.into(), + )?; + let new_size = min( + size_of::(), + mapping_account.data_len() + MAX_PERMITTED_DATA_INCREASE, + ); + mapping_account.realloc(new_size, true)?; + + Ok(()) +} diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index 33178ccd..789d7843 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -17,6 +17,7 @@ mod test_message; mod test_permission_migration; mod test_publish; mod test_publish_batch; +mod test_resize_mapping; mod test_set_max_latency; mod test_set_min_pub; mod test_sizes; diff --git a/program/rust/src/tests/pyth_simulator.rs b/program/rust/src/tests/pyth_simulator.rs index 87640503..f6704e8d 100644 --- a/program/rust/src/tests/pyth_simulator.rs +++ b/program/rust/src/tests/pyth_simulator.rs @@ -478,6 +478,31 @@ impl PythSimulator { .map(|_| permissions_pubkey) } + pub async fn truncate_account(&mut self, key: Pubkey, size: usize) { + let mut account = self.get_account(key).await.unwrap(); + account.data.truncate(size); + self.context.set_account(&key, &account.into()); + } + + pub async fn resize_mapping( + &mut self, + mapping_keypair: &Keypair, + ) -> Result<(), BanksClientError> { + let cmd: CommandHeader = OracleCommand::ResizeMapping.into(); + let instruction = Instruction::new_with_bytes( + self.program_id, + bytes_of(&cmd), + vec![AccountMeta::new(mapping_keypair.pubkey(), false)], + ); + + self.process_ixs( + &[instruction], + &vec![], + ©_keypair(&self.genesis_keypair), + ) + .await + } + /// Get the account at `key`. Returns `None` if no such account exists. pub async fn get_account(&mut self, key: Pubkey) -> Option { self.context.banks_client.get_account(key).await.unwrap() diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index 689d852b..01a9cfd9 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -130,58 +130,6 @@ fn test_add_price() { 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_del_product.rs b/program/rust/src/tests/test_del_product.rs index 02e315ef..d33d1dc6 100644 --- a/program/rust/src/tests/test_del_product.rs +++ b/program/rust/src/tests/test_del_product.rs @@ -1,6 +1,7 @@ use { crate::{ accounts::MappingAccount, + deserialize::load, tests::pyth_simulator::PythSimulator, }, solana_program::pubkey::Pubkey, @@ -41,32 +42,31 @@ async fn test_del_product() { assert!(sim.get_account(product2.pubkey()).await.is_none()); - let mapping_data = sim - .get_account_data_as::(mapping_keypair.pubkey()) - .await - .unwrap(); - assert!(mapping_product_list_equals( - &mapping_data, - vec![ - product1.pubkey(), - product5.pubkey(), - product3.pubkey(), - product4.pubkey() - ] - )); + { + let mapping_account = sim.get_account(mapping_keypair.pubkey()).await.unwrap(); + let mapping_data = load::(&mapping_account.data).unwrap(); + assert!(mapping_product_list_equals( + mapping_data, + vec![ + product1.pubkey(), + product5.pubkey(), + product3.pubkey(), + product4.pubkey() + ] + )); + } 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()) - .await - .unwrap(); - - assert!(mapping_product_list_equals( - &mapping_data, - vec![product1.pubkey(), product5.pubkey(), product3.pubkey()] - )); + { + let mapping_account = sim.get_account(mapping_keypair.pubkey()).await.unwrap(); + let mapping_data = load::(&mapping_account.data).unwrap(); + assert!(mapping_product_list_equals( + mapping_data, + vec![product1.pubkey(), product5.pubkey(), product3.pubkey()] + )); + } } /// Returns true if the list of products in `mapping_data` contains the keys in `expected` (in the diff --git a/program/rust/src/tests/test_resize_mapping.rs b/program/rust/src/tests/test_resize_mapping.rs new file mode 100644 index 00000000..99672e41 --- /dev/null +++ b/program/rust/src/tests/test_resize_mapping.rs @@ -0,0 +1,54 @@ +use { + super::pyth_simulator::PythSimulator, + crate::{ + accounts::MappingAccount, + error::OracleError, + }, + solana_sdk::{ + instruction::InstructionError, + signer::Signer, + transaction::TransactionError, + }, + std::mem::size_of, +}; + + +#[tokio::test] +async fn test_resize_mapping() { + let mut sim = PythSimulator::new().await; + let mapping_keypair = sim.init_mapping().await.unwrap(); + assert_eq!( + sim.resize_mapping(&mapping_keypair) + .await + .unwrap_err() + .unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(OracleError::NoNeedToResize as u32) + ) + ); + + sim.truncate_account(mapping_keypair.pubkey(), 20536).await; // Old size. + for i in 0..14 { + println!("resize #{i}"); + sim.resize_mapping(&mapping_keypair).await.unwrap(); + } + assert_eq!( + sim.resize_mapping(&mapping_keypair) + .await + .unwrap_err() + .unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(OracleError::NoNeedToResize as u32) + ) + ); + assert_eq!( + sim.get_account(mapping_keypair.pubkey()) + .await + .unwrap() + .data + .len(), + size_of::() + ); +} diff --git a/program/rust/src/tests/test_sizes.rs b/program/rust/src/tests/test_sizes.rs index 7a1d900d..dbd725e2 100644 --- a/program/rust/src/tests/test_sizes.rs +++ b/program/rust/src/tests/test_sizes.rs @@ -86,7 +86,7 @@ fn test_sizes() { assert_eq!(size_of::(), 40); assert_eq!(size_of::(), 32); assert_eq!(size_of::(), 16); - assert_eq!(size_of::(), 20536); + assert_eq!(size_of::(), 160056); assert_eq!(size_of::(), 48); assert_eq!(size_of::(), 96); assert_eq!(size_of::(), 24); diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 28d8f44c..e39c8401 100644 --- a/program/rust/src/tests/test_utils.rs +++ b/program/rust/src/tests/test_utils.rs @@ -34,7 +34,7 @@ use { solana_sdk::transaction::TransactionError, }; -const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 20536; +const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 160056; /// The goal of this struct is to easily instantiate fresh solana accounts /// for the Pyth program to use in tests. @@ -50,7 +50,7 @@ pub struct AccountSetup { owner: Pubkey, balance: u64, size: usize, - data: [u8; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES], + data: Vec, } impl AccountSetup { @@ -59,7 +59,7 @@ impl AccountSetup { let owner = *owner; let balance = Rent::minimum_balance(&Rent::default(), T::MINIMUM_SIZE); let size = T::MINIMUM_SIZE; - let data = [0; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; + let data = vec![0; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; AccountSetup { key, owner, @@ -74,7 +74,7 @@ impl AccountSetup { let owner = system_program::id(); let balance = LAMPORTS_PER_SOL; let size = 0; - let data = [0; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; + let data = vec![0; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; AccountSetup { key, owner, @@ -89,7 +89,7 @@ impl AccountSetup { let owner = *owner; 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]; + let data = vec![0; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; AccountSetup { key, owner, @@ -104,7 +104,7 @@ impl AccountSetup { let owner = sysvar::id(); let balance = Rent::minimum_balance(&Rent::default(), clock::Clock::size_of()); let size = clock::Clock::size_of(); - let data = [0u8; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; + let data = vec![0u8; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; AccountSetup { key, owner,