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..43a32b9c 100644 --- a/program/rust/src/instruction.rs +++ b/program/rust/src/instruction.rs @@ -109,6 +109,9 @@ pub enum OracleCommand { // account[1] price account [writable] // account[2] permissions account [writable] InitPriceFeedIndex = 19, + // account[0] funding account [signer writable] + // account[1] mapping account [writable] + ResizeMapping = 20, } #[repr(C)] diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index 8246e0e8..3188ae87 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -33,6 +33,7 @@ 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; @@ -53,6 +54,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, @@ -106,6 +108,7 @@ pub fn process_instruction( 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), + ResizeMapping => resize_mapping(program_id, accounts, instruction_data), } } diff --git a/program/rust/src/processor/resize_mapping.rs b/program/rust/src/processor/resize_mapping.rs new file mode 100644 index 00000000..914d1b0c --- /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[1] 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..b8f59f80 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(), true)], + ); + + self.process_ixs( + &[instruction], + &vec![mapping_keypair], + ©_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_del_product.rs b/program/rust/src/tests/test_del_product.rs index 02e315ef..9acad9c4 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,