From b343951f47a726c5be0bb080a8f0d693a929c237 Mon Sep 17 00:00:00 2001 From: keyvan Date: Fri, 7 Jun 2024 10:56:39 -0700 Subject: [PATCH 01/39] feat(oracle): WIP add scores account --- program/c/src/oracle/oracle.h | 13 +- program/rust/src/accounts.rs | 2 + program/rust/src/accounts/score.rs | 158 ++++++++++++++++++++ program/rust/src/processor/add_price.rs | 18 ++- program/rust/src/processor/add_publisher.rs | 14 +- program/rust/src/processor/del_price.rs | 18 ++- program/rust/src/processor/del_publisher.rs | 13 +- program/rust/src/tests/test_add_price.rs | 27 ++++ program/rust/src/tests/test_utils.rs | 2 +- scripts/check-size.sh | 2 + 10 files changed, 249 insertions(+), 18 deletions(-) create mode 100644 program/rust/src/accounts/score.rs diff --git a/program/c/src/oracle/oracle.h b/program/c/src/oracle/oracle.h index 05c623389..8a79f1fd4 100644 --- a/program/c/src/oracle/oracle.h +++ b/program/c/src/oracle/oracle.h @@ -21,6 +21,8 @@ extern "C" { #define PC_PUBKEY_SIZE 32 #define PC_PUBKEY_SIZE_64 (PC_PUBKEY_SIZE/sizeof(uint64_t)) #define PC_MAP_TABLE_SIZE 640 +#define PC_MAX_PUBLISHERS 256 +#define PC_MAX_SYMBOLS 1024 // Total price component slots available #define PC_NUM_COMP_PYTHNET 128 @@ -49,11 +51,12 @@ extern "C" { #define PC_STATUS_IGNORED 4 // account types -#define PC_ACCTYPE_MAPPING 1 -#define PC_ACCTYPE_PRODUCT 2 -#define PC_ACCTYPE_PRICE 3 -#define PC_ACCTYPE_TEST 4 -#define PC_ACCTYPE_PERMISSIONS 5 +#define PC_ACCTYPE_MAPPING 1 +#define PC_ACCTYPE_PRODUCT 2 +#define PC_ACCTYPE_PRICE 3 +#define PC_ACCTYPE_TEST 4 +#define PC_ACCTYPE_PERMISSIONS 5 +#define PC_ACCTYPE_SCORE 6 // Compute budget requested per price update instruction diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 5958fc88c..67617fb36 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -39,6 +39,7 @@ mod mapping; mod permission; mod price; mod product; +mod score; // Some types only exist during use as a library. #[cfg(feature = "strum")] @@ -64,6 +65,7 @@ pub use { update_product_metadata, ProductAccount, }, + score::PublisherScoresAccount, }; // PDA seeds for accounts. diff --git a/program/rust/src/accounts/score.rs b/program/rust/src/accounts/score.rs new file mode 100644 index 000000000..d98969422 --- /dev/null +++ b/program/rust/src/accounts/score.rs @@ -0,0 +1,158 @@ +use { + super::{ + AccountHeader, + PythAccount, + }, + crate::c_oracle_header::{ + PC_ACCTYPE_SCORE, + PC_MAX_PUBLISHERS, + PC_MAX_SYMBOLS, + }, + bytemuck::{ + Pod, + Zeroable, + }, + solana_program::{ + program_error::ProgramError, + pubkey::Pubkey, + }, + std::cmp::max, +}; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PublisherScoresAccount { + pub header: AccountHeader, + pub num_publishers: usize, + pub num_symbols: usize, + // a constant used to normalize the scores + pub z: u32, + pub publisher_permissions: [[bool; PC_MAX_SYMBOLS as usize]; PC_MAX_PUBLISHERS as usize], + pub scores: [f64; PC_MAX_PUBLISHERS as usize], + pub publishers: [Pubkey; PC_MAX_PUBLISHERS as usize], + pub symbols: [Pubkey; PC_MAX_SYMBOLS as usize], +} + +impl PublisherScoresAccount { + pub fn add_publisher( + &mut self, + publisher: Pubkey, + price_account: Pubkey, + ) -> Result<(), ProgramError> { + let publisher_index = self + .publishers + .iter() + .position(|&x| x == publisher) + .unwrap_or(self.num_publishers); + + if publisher_index == self.num_publishers { + if self.num_publishers == PC_MAX_PUBLISHERS as usize { + return Err(ProgramError::AccountDataTooSmall); + } + + self.publishers[self.num_publishers] = publisher; + self.num_publishers += 1; + } + + let symbol_index = self + .symbols + .iter() + .position(|&x| x == price_account) + .ok_or(ProgramError::InvalidArgument)?; + + self.publisher_permissions[publisher_index][symbol_index] = true; + self.calculate_scores() + } + + pub fn del_publisher( + &mut self, + publisher: Pubkey, + price_account: Pubkey, + ) -> Result<(), ProgramError> { + let publisher_index = self + .publishers + .iter() + .position(|&x| x == publisher) + .ok_or(ProgramError::InvalidArgument)?; + + let symbol_index = self + .symbols + .iter() + .position(|&x| x == price_account) + .ok_or(ProgramError::InvalidArgument)?; + + self.publisher_permissions[publisher_index][symbol_index] = false; + self.calculate_scores() + } + + pub fn add_price(&mut self, symbol: Pubkey) -> Result<(), ProgramError> { + let symbol_index = self + .symbols + .iter() + .position(|&x| x == symbol) + .unwrap_or(self.num_symbols); + + if symbol_index == self.num_symbols { + if self.num_symbols == PC_MAX_SYMBOLS as usize { + return Err(ProgramError::AccountDataTooSmall); + } + + self.symbols[self.num_symbols] = symbol; + self.num_symbols += 1; + } + + Ok(()) + } + + pub fn del_price(&mut self, symbol: Pubkey) -> Result<(), ProgramError> { + let symbol_index = self + .symbols + .iter() + .position(|&x| x == symbol) + .ok_or(ProgramError::InvalidArgument)?; + + self.symbols[symbol_index] = self.symbols[self.num_symbols - 1]; + self.symbols[self.num_symbols - 1] = Pubkey::default(); + self.num_symbols -= 1; + self.calculate_scores() + } + + pub fn calculate_scores(&mut self) -> Result<(), ProgramError> { + let symbol_scores: Vec = self + .symbols + .iter() + .enumerate() + .map(|(j, _)| { + let score = self + .publisher_permissions + .iter() + .fold(0, |score, permissions| score + permissions[j] as u32); + max(score, self.z) + }) + .collect(); + + for i in 0..self.num_publishers { + self.scores[i] = self + .symbols + .iter() + .enumerate() + .filter(|(j, _)| self.publisher_permissions[i][*j]) + .map(|(j, _)| 1f64 / symbol_scores[j] as f64) + .sum(); + } + Ok(()) + } +} + +impl PythAccount for PublisherScoresAccount { + const ACCOUNT_TYPE: u32 = PC_ACCTYPE_SCORE; + // Calculate the initial size of the account + const INITIAL_SIZE: u32 = 1000; +} + +// Unsafe impl because product_list is of size 640 and there's no derived trait for this size +unsafe impl Pod for PublisherScoresAccount { +} + +unsafe impl Zeroable for PublisherScoresAccount { +} diff --git a/program/rust/src/processor/add_price.rs b/program/rust/src/processor/add_price.rs index cd6da4242..990ab7afd 100644 --- a/program/rust/src/processor/add_price.rs +++ b/program/rust/src/processor/add_price.rs @@ -3,6 +3,7 @@ use { accounts::{ PriceAccount, ProductAccount, + PublisherScoresAccount, PythAccount, }, c_oracle_header::{ @@ -34,6 +35,7 @@ use { // account[0] funding account [signer writable] // account[1] product account [signer writable] // account[2] new price account [signer writable] +// account[3] scores account [signer writable] pub fn add_price( program_id: &Pubkey, accounts: &[AccountInfo], @@ -48,10 +50,12 @@ 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, scores_account) = + match accounts { + [x, y, z, p] => Ok((x, y, z, p, None)), + [x, y, z, p, s] => Ok((x, y, z, p, Some(s))), + _ => Err(OracleError::InvalidNumberOfAccounts), + }?; check_valid_funding_account(funding_account)?; check_permissioned_funding_account( @@ -80,5 +84,11 @@ pub fn add_price( price_data.min_pub_ = PRICE_ACCOUNT_DEFAULT_MIN_PUB; product_data.first_price_account = *price_account.key; + if let Some(scores_account) = scores_account { + let mut scores_account = + load_checked::(scores_account, cmd_args.header.version)?; + scores_account.add_price(*price_account.key)?; + } + Ok(()) } diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index 841de3b96..a5729ed26 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -3,6 +3,7 @@ use { accounts::{ PriceAccount, PriceComponent, + PublisherScoresAccount, PythAccount, }, c_oracle_header::PC_NUM_COMP, @@ -36,6 +37,7 @@ use { /// Add publisher to symbol account // account[0] funding account [signer writable] // account[1] price account [signer writable] +// account[2] scores account [signer writable] pub fn add_publisher( program_id: &Pubkey, accounts: &[AccountInfo], @@ -48,8 +50,9 @@ pub fn add_publisher( ProgramError::InvalidArgument, )?; - let (funding_account, price_account, permissions_account) = match accounts { - [x, y, p] => Ok((x, y, p)), + let (funding_account, price_account, permissions_account, scores_account) = match accounts { + [x, y, p] => Ok((x, y, p, None)), + [x, y, p, s] => Ok((x, y, p, Some(s))), _ => Err(OracleError::InvalidNumberOfAccounts), }?; @@ -99,6 +102,13 @@ pub fn add_publisher( } price_data.header.size = try_convert::<_, u32>(PriceAccount::INITIAL_SIZE)?; + + if let Some(scores_account) = scores_account { + let mut scores_account = + load_checked::(scores_account, cmd_args.header.version)?; + scores_account.add_publisher(cmd_args.publisher, *price_account.key)?; + } + Ok(()) } diff --git a/program/rust/src/processor/del_price.rs b/program/rust/src/processor/del_price.rs index 92f3fe546..f820c76d0 100644 --- a/program/rust/src/processor/del_price.rs +++ b/program/rust/src/processor/del_price.rs @@ -3,6 +3,7 @@ use { accounts::{ PriceAccount, ProductAccount, + PublisherScoresAccount, }, deserialize::{ load, @@ -30,6 +31,7 @@ use { // account[0] funding account [signer writable] // account[1] product account [signer writable] // account[2] price account [signer writable] +// account[3] scores account [signer writable] /// Warning: This function is dangerous and will break any programs that depend on the deleted /// price account! pub fn del_price( @@ -37,10 +39,12 @@ pub fn del_price( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - let (funding_account, product_account, price_account, permissions_account) = match accounts { - [w, x, y, p] => Ok((w, x, y, p)), - _ => Err(OracleError::InvalidNumberOfAccounts), - }?; + let (funding_account, product_account, price_account, permissions_account, scores_account) = + match accounts { + [x, y, z, p] => Ok((x, y, z, p, None)), + [x, y, z, p, s] => Ok((x, y, z, p, Some(s))), + _ => Err(OracleError::InvalidNumberOfAccounts), + }?; let cmd_args = load::(instruction_data)?; @@ -83,5 +87,11 @@ pub fn del_price( **price_account.lamports.borrow_mut() = 0; **funding_account.lamports.borrow_mut() += lamports; + if let Some(scores_account) = scores_account { + let mut scores_account = + load_checked::(scores_account, cmd_args.version)?; + scores_account.del_price(*price_account.key)?; + } + Ok(()) } diff --git a/program/rust/src/processor/del_publisher.rs b/program/rust/src/processor/del_publisher.rs index cd9459f67..4198ebae5 100644 --- a/program/rust/src/processor/del_publisher.rs +++ b/program/rust/src/processor/del_publisher.rs @@ -3,6 +3,7 @@ use { accounts::{ PriceAccount, PriceComponent, + PublisherScoresAccount, PythAccount, }, deserialize::{ @@ -32,6 +33,7 @@ use { /// Delete publisher from symbol account // account[0] funding account [signer writable] // account[1] price account [signer writable] +// account[2] scores account [signer writable] pub fn del_publisher( program_id: &Pubkey, accounts: &[AccountInfo], @@ -45,8 +47,9 @@ pub fn del_publisher( ProgramError::InvalidArgument, )?; - let (funding_account, price_account, permissions_account) = match accounts { - [x, y, p] => Ok((x, y, p)), + let (funding_account, price_account, permissions_account, scores_account) = match accounts { + [x, y, p] => Ok((x, y, p, None)), + [x, y, p, s] => Ok((x, y, p, Some(s))), _ => Err(OracleError::InvalidNumberOfAccounts), }?; @@ -59,6 +62,12 @@ pub fn del_publisher( &cmd_args.header, )?; + if let Some(scores_account) = scores_account { + let mut scores_account = + load_checked::(scores_account, cmd_args.header.version)?; + scores_account.del_publisher(cmd_args.publisher, *price_account.key)?; + } + let mut price_data = load_checked::(price_account, cmd_args.header.version)?; for i in 0..(try_convert::(price_data.num_)?) { diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index b282f1a58..c460e7aec 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -6,6 +6,7 @@ use { PermissionAccount, PriceAccount, ProductAccount, + PublisherScoresAccount, PythAccount, }, c_oracle_header::{ @@ -62,6 +63,9 @@ fn test_add_price() { let mut permissions_setup = AccountSetup::new_permission(&program_id); let permissions_account = permissions_setup.as_account_info(); + let mut scores_setup = AccountSetup::new::(&program_id); + let scores_account = scores_setup.as_account_info(); + { let mut permissions_account_data = PermissionAccount::initialize(&permissions_account, PC_VERSION).unwrap(); @@ -82,6 +86,28 @@ fn test_add_price() { ) .is_ok()); + // add price with scores account + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + product_account.clone(), + price_account.clone(), + permissions_account.clone(), + scores_account.clone(), + ], + instruction_data_add_price + ) + .is_ok()); + + { + let score_data = + load_checked::(&scores_account, PC_VERSION).unwrap(); + assert_eq!(score_data.symbols.len(), 1); + assert_eq!(score_data.symbols[0], *price_account.key); + assert_eq!(score_data.num_symbols, 1); + } + assert!(process_instruction( &program_id, &[ @@ -138,6 +164,7 @@ fn test_add_price() { price_account.clone(), permissions_account.clone(), permissions_account.clone(), + permissions_account.clone(), ], instruction_data_add_price ), diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 853fdcd6d..7031ce013 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 = 305192; /// The goal of this struct is to easily instantiate fresh solana accounts /// for the Pyth program to use in tests. diff --git a/scripts/check-size.sh b/scripts/check-size.sh index bf3fe541b..3d6264ec1 100755 --- a/scripts/check-size.sh +++ b/scripts/check-size.sh @@ -4,6 +4,8 @@ # While Solana doesn't support resizing programs, the oracle binary needs to be smaller than 81760 bytes # (The available space for the oracle program on pythnet is 88429 and mainnet is 81760) ORACLE_SIZE=$(wc -c ./target/deploy/pyth_oracle.so | awk '{print $1}') +echo "Size of pyth_oracle.so is ${ORACLE_SIZE} bytes" + if [ $ORACLE_SIZE -lt ${1} ] then echo "Size of pyth_oracle.so is small enough to be deployed, since ${ORACLE_SIZE} is less than ${1}" From 1561e80c5e64675d5605fad02bd87cc0b89dfa98 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 10 Jun 2024 12:16:44 -0700 Subject: [PATCH 02/39] feat: use bitset to reduce scores account size --- program/c/src/oracle/oracle.h | 4 +++- program/rust/src/accounts/score.rs | 31 ++++++++++++++++++++++++---- program/rust/src/tests/test_utils.rs | 2 +- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/program/c/src/oracle/oracle.h b/program/c/src/oracle/oracle.h index 8a79f1fd4..5e4f3b50a 100644 --- a/program/c/src/oracle/oracle.h +++ b/program/c/src/oracle/oracle.h @@ -23,8 +23,10 @@ extern "C" { #define PC_MAP_TABLE_SIZE 640 #define PC_MAX_PUBLISHERS 256 #define PC_MAX_SYMBOLS 1024 +// This is the number of 64-bit words needed to store the bitset of symbols +#define PC_MAX_SYMBOLS_64 (PC_MAX_SYMBOLS/64) - // Total price component slots available +// Total price component slots available #define PC_NUM_COMP_PYTHNET 128 // PC_NUM_COMP - number of price components in use diff --git a/program/rust/src/accounts/score.rs b/program/rust/src/accounts/score.rs index d98969422..83eed7dba 100644 --- a/program/rust/src/accounts/score.rs +++ b/program/rust/src/accounts/score.rs @@ -7,6 +7,7 @@ use { PC_ACCTYPE_SCORE, PC_MAX_PUBLISHERS, PC_MAX_SYMBOLS, + PC_MAX_SYMBOLS_64, }, bytemuck::{ Pod, @@ -27,13 +28,27 @@ pub struct PublisherScoresAccount { pub num_symbols: usize, // a constant used to normalize the scores pub z: u32, - pub publisher_permissions: [[bool; PC_MAX_SYMBOLS as usize]; PC_MAX_PUBLISHERS as usize], + + // array[x][y] is a u64 whose bits represent if publisher x publishes symbols 64*y to 64*(y+1) - 1 + pub publisher_permissions: [[u64; PC_MAX_SYMBOLS_64 as usize]; PC_MAX_PUBLISHERS as usize], pub scores: [f64; PC_MAX_PUBLISHERS as usize], pub publishers: [Pubkey; PC_MAX_PUBLISHERS as usize], pub symbols: [Pubkey; PC_MAX_SYMBOLS as usize], } impl PublisherScoresAccount { + fn get_publisher_permission(&self, x: usize, y: usize) -> bool { + (self.publisher_permissions[x][y / 64] >> (y % 64)) & 1 == 1 + } + + fn set_publisher_permission(&mut self, x: usize, y: usize, value: bool) { + if value { + self.publisher_permissions[x][y / 64] |= 1 << (y % 64); + } else { + self.publisher_permissions[x][y / 64] &= !(1 << (y % 64)); + } + } + pub fn add_publisher( &mut self, publisher: Pubkey, @@ -60,7 +75,7 @@ impl PublisherScoresAccount { .position(|&x| x == price_account) .ok_or(ProgramError::InvalidArgument)?; - self.publisher_permissions[publisher_index][symbol_index] = true; + self.set_publisher_permission(publisher_index, symbol_index, true); self.calculate_scores() } @@ -81,7 +96,7 @@ impl PublisherScoresAccount { .position(|&x| x == price_account) .ok_or(ProgramError::InvalidArgument)?; - self.publisher_permissions[publisher_index][symbol_index] = false; + self.set_publisher_permission(publisher_index, symbol_index, false); self.calculate_scores() } @@ -111,8 +126,16 @@ impl PublisherScoresAccount { .position(|&x| x == symbol) .ok_or(ProgramError::InvalidArgument)?; + // update symbol list self.symbols[symbol_index] = self.symbols[self.num_symbols - 1]; self.symbols[self.num_symbols - 1] = Pubkey::default(); + + // update publisher permissions + for i in 0..self.num_publishers { + let value = self.get_publisher_permission(i, self.num_symbols - 1); + self.set_publisher_permission(i, symbol_index, value) + } + self.num_symbols -= 1; self.calculate_scores() } @@ -136,7 +159,7 @@ impl PublisherScoresAccount { .symbols .iter() .enumerate() - .filter(|(j, _)| self.publisher_permissions[i][*j]) + .filter(|(j, _)| self.get_publisher_permission(i, *j)) .map(|(j, _)| 1f64 / symbol_scores[j] as f64) .sum(); } diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 7031ce013..853fdcd6d 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 = 305192; +const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 20536; /// The goal of this struct is to easily instantiate fresh solana accounts /// for the Pyth program to use in tests. From b58625ceb1029aae1eed9237bf3dbb18db16c8b3 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 10 Jun 2024 12:18:58 -0700 Subject: [PATCH 03/39] fix: pre-commit --- program/rust/src/accounts/score.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/program/rust/src/accounts/score.rs b/program/rust/src/accounts/score.rs index 83eed7dba..469908f95 100644 --- a/program/rust/src/accounts/score.rs +++ b/program/rust/src/accounts/score.rs @@ -23,11 +23,11 @@ use { #[repr(C)] #[derive(Copy, Clone)] pub struct PublisherScoresAccount { - pub header: AccountHeader, - pub num_publishers: usize, - pub num_symbols: usize, + pub header: AccountHeader, + pub num_publishers: usize, + pub num_symbols: usize, // a constant used to normalize the scores - pub z: u32, + pub z: u32, // array[x][y] is a u64 whose bits represent if publisher x publishes symbols 64*y to 64*(y+1) - 1 pub publisher_permissions: [[u64; PC_MAX_SYMBOLS_64 as usize]; PC_MAX_PUBLISHERS as usize], From 18c6787f4d7714aa94733e192374e9c0d454447b Mon Sep 17 00:00:00 2001 From: keyvan Date: Thu, 13 Jun 2024 10:38:39 -0700 Subject: [PATCH 04/39] fix: add price test --- program/rust/src/accounts/score.rs | 2 +- program/rust/src/tests/test_add_price.rs | 11 +++++++---- program/rust/src/tests/test_utils.rs | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/program/rust/src/accounts/score.rs b/program/rust/src/accounts/score.rs index 469908f95..003a24266 100644 --- a/program/rust/src/accounts/score.rs +++ b/program/rust/src/accounts/score.rs @@ -170,7 +170,7 @@ impl PublisherScoresAccount { impl PythAccount for PublisherScoresAccount { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_SCORE; // Calculate the initial size of the account - const INITIAL_SIZE: u32 = 1000; + const INITIAL_SIZE: u32 = 75816; } // Unsafe impl because product_list is of size 640 and there's no derived trait for this size diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index c460e7aec..97b9f7ac4 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -60,11 +60,15 @@ fn test_add_price() { let mut price_setup_2 = AccountSetup::new::(&program_id); let price_account_2 = price_setup_2.as_account_info(); + let mut price_setup_3 = AccountSetup::new::(&program_id); + let price_account_3 = price_setup_3.as_account_info(); + let mut permissions_setup = AccountSetup::new_permission(&program_id); let permissions_account = permissions_setup.as_account_info(); let mut scores_setup = AccountSetup::new::(&program_id); let scores_account = scores_setup.as_account_info(); + PublisherScoresAccount::initialize(&scores_account, PC_VERSION).unwrap(); { let mut permissions_account_data = @@ -92,7 +96,7 @@ fn test_add_price() { &[ funding_account.clone(), product_account.clone(), - price_account.clone(), + price_account_3.clone(), permissions_account.clone(), scores_account.clone(), ], @@ -103,8 +107,7 @@ fn test_add_price() { { let score_data = load_checked::(&scores_account, PC_VERSION).unwrap(); - assert_eq!(score_data.symbols.len(), 1); - assert_eq!(score_data.symbols[0], *price_account.key); + assert_eq!(score_data.symbols[0], *price_account_3.key); assert_eq!(score_data.num_symbols, 1); } @@ -127,7 +130,7 @@ fn test_add_price() { assert_eq!(price_data.price_type, 1); 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!(price_data.next_price_account == *price_account_3.key); assert!(product_data.first_price_account == *price_account.key); } diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 853fdcd6d..cf7add1c0 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 = 75816; /// The goal of this struct is to easily instantiate fresh solana accounts /// for the Pyth program to use in tests. From 32c577e8155e6763d61a9c76c35e159473fa0d82 Mon Sep 17 00:00:00 2001 From: keyvan Date: Thu, 13 Jun 2024 10:44:18 -0700 Subject: [PATCH 05/39] fix: rename score to cap --- program/rust/src/accounts.rs | 2 +- program/rust/src/accounts/score.rs | 23 ++++++++++++++------- program/rust/src/processor/add_price.rs | 4 ++-- program/rust/src/processor/add_publisher.rs | 4 ++-- program/rust/src/processor/del_price.rs | 4 ++-- program/rust/src/processor/del_publisher.rs | 4 ++-- program/rust/src/tests/test_add_price.rs | 9 ++++---- 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 67617fb36..0d0d0f494 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -65,7 +65,7 @@ pub use { update_product_metadata, ProductAccount, }, - score::PublisherScoresAccount, + score::PublisherCapsAccount, }; // PDA seeds for accounts. diff --git a/program/rust/src/accounts/score.rs b/program/rust/src/accounts/score.rs index 003a24266..31a4bed39 100644 --- a/program/rust/src/accounts/score.rs +++ b/program/rust/src/accounts/score.rs @@ -22,21 +22,28 @@ use { #[repr(C)] #[derive(Copy, Clone)] -pub struct PublisherScoresAccount { +/* + * This account is part of Community Integrity Pool (CIP) project. + * It is used to store the caps of the publishers which will be sent + * to the `integrity_pool` program on mainnet to calculate the rewards. + */ +pub struct PublisherCapsAccount { pub header: AccountHeader, pub num_publishers: usize, pub num_symbols: usize, - // a constant used to normalize the scores + // Z is a constant used to normalize the scores pub z: u32, + // M is a constant showing the target stake per symbol + pub m: u32, // array[x][y] is a u64 whose bits represent if publisher x publishes symbols 64*y to 64*(y+1) - 1 pub publisher_permissions: [[u64; PC_MAX_SYMBOLS_64 as usize]; PC_MAX_PUBLISHERS as usize], - pub scores: [f64; PC_MAX_PUBLISHERS as usize], + pub caps: [f64; PC_MAX_PUBLISHERS as usize], pub publishers: [Pubkey; PC_MAX_PUBLISHERS as usize], pub symbols: [Pubkey; PC_MAX_SYMBOLS as usize], } -impl PublisherScoresAccount { +impl PublisherCapsAccount { fn get_publisher_permission(&self, x: usize, y: usize) -> bool { (self.publisher_permissions[x][y / 64] >> (y % 64)) & 1 == 1 } @@ -155,7 +162,7 @@ impl PublisherScoresAccount { .collect(); for i in 0..self.num_publishers { - self.scores[i] = self + self.caps[i] = self .symbols .iter() .enumerate() @@ -167,15 +174,15 @@ impl PublisherScoresAccount { } } -impl PythAccount for PublisherScoresAccount { +impl PythAccount for PublisherCapsAccount { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_SCORE; // Calculate the initial size of the account const INITIAL_SIZE: u32 = 75816; } // Unsafe impl because product_list is of size 640 and there's no derived trait for this size -unsafe impl Pod for PublisherScoresAccount { +unsafe impl Pod for PublisherCapsAccount { } -unsafe impl Zeroable for PublisherScoresAccount { +unsafe impl Zeroable for PublisherCapsAccount { } diff --git a/program/rust/src/processor/add_price.rs b/program/rust/src/processor/add_price.rs index 990ab7afd..eeb6c5735 100644 --- a/program/rust/src/processor/add_price.rs +++ b/program/rust/src/processor/add_price.rs @@ -3,7 +3,7 @@ use { accounts::{ PriceAccount, ProductAccount, - PublisherScoresAccount, + PublisherCapsAccount, PythAccount, }, c_oracle_header::{ @@ -86,7 +86,7 @@ pub fn add_price( if let Some(scores_account) = scores_account { let mut scores_account = - load_checked::(scores_account, cmd_args.header.version)?; + load_checked::(scores_account, cmd_args.header.version)?; scores_account.add_price(*price_account.key)?; } diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index a5729ed26..a752edda2 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -3,7 +3,7 @@ use { accounts::{ PriceAccount, PriceComponent, - PublisherScoresAccount, + PublisherCapsAccount, PythAccount, }, c_oracle_header::PC_NUM_COMP, @@ -105,7 +105,7 @@ pub fn add_publisher( if let Some(scores_account) = scores_account { let mut scores_account = - load_checked::(scores_account, cmd_args.header.version)?; + load_checked::(scores_account, cmd_args.header.version)?; scores_account.add_publisher(cmd_args.publisher, *price_account.key)?; } diff --git a/program/rust/src/processor/del_price.rs b/program/rust/src/processor/del_price.rs index f820c76d0..50043a881 100644 --- a/program/rust/src/processor/del_price.rs +++ b/program/rust/src/processor/del_price.rs @@ -3,7 +3,7 @@ use { accounts::{ PriceAccount, ProductAccount, - PublisherScoresAccount, + PublisherCapsAccount, }, deserialize::{ load, @@ -89,7 +89,7 @@ pub fn del_price( if let Some(scores_account) = scores_account { let mut scores_account = - load_checked::(scores_account, cmd_args.version)?; + load_checked::(scores_account, cmd_args.version)?; scores_account.del_price(*price_account.key)?; } diff --git a/program/rust/src/processor/del_publisher.rs b/program/rust/src/processor/del_publisher.rs index 4198ebae5..ebeb15ccc 100644 --- a/program/rust/src/processor/del_publisher.rs +++ b/program/rust/src/processor/del_publisher.rs @@ -3,7 +3,7 @@ use { accounts::{ PriceAccount, PriceComponent, - PublisherScoresAccount, + PublisherCapsAccount, PythAccount, }, deserialize::{ @@ -64,7 +64,7 @@ pub fn del_publisher( if let Some(scores_account) = scores_account { let mut scores_account = - load_checked::(scores_account, cmd_args.header.version)?; + load_checked::(scores_account, cmd_args.header.version)?; scores_account.del_publisher(cmd_args.publisher, *price_account.key)?; } diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index 97b9f7ac4..54ce97a83 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -6,7 +6,7 @@ use { PermissionAccount, PriceAccount, ProductAccount, - PublisherScoresAccount, + PublisherCapsAccount, PythAccount, }, c_oracle_header::{ @@ -66,9 +66,9 @@ fn test_add_price() { let mut permissions_setup = AccountSetup::new_permission(&program_id); let permissions_account = permissions_setup.as_account_info(); - let mut scores_setup = AccountSetup::new::(&program_id); + let mut scores_setup = AccountSetup::new::(&program_id); let scores_account = scores_setup.as_account_info(); - PublisherScoresAccount::initialize(&scores_account, PC_VERSION).unwrap(); + PublisherCapsAccount::initialize(&scores_account, PC_VERSION).unwrap(); { let mut permissions_account_data = @@ -105,8 +105,7 @@ fn test_add_price() { .is_ok()); { - let score_data = - load_checked::(&scores_account, PC_VERSION).unwrap(); + let score_data = load_checked::(&scores_account, PC_VERSION).unwrap(); assert_eq!(score_data.symbols[0], *price_account_3.key); assert_eq!(score_data.num_symbols, 1); } From 2ec0004a777783f29391235e96438c523bacb0a0 Mon Sep 17 00:00:00 2001 From: keyvan Date: Fri, 14 Jun 2024 10:27:34 -0700 Subject: [PATCH 06/39] feat: remove publisher sorting features --- program/rust/src/processor/add_publisher.rs | 150 +------------------ program/rust/src/processor/upd_price.rs | 34 +---- program/rust/src/tests/test_add_publisher.rs | 33 ---- program/rust/src/tests/test_upd_price_v2.rs | 81 ---------- 4 files changed, 9 insertions(+), 289 deletions(-) diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index a752edda2..61276fc75 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -25,10 +25,7 @@ use { account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - program_memory::{ - sol_memcmp, - sol_memset, - }, + program_memory::sol_memset, pubkey::Pubkey, }, std::mem::size_of, @@ -68,14 +65,6 @@ pub fn add_publisher( let mut price_data = load_checked::(price_account, cmd_args.header.version)?; - // Use the call with the default pubkey (000..) as a trigger to sort the publishers as a - // migration step from unsorted list to sorted list. - if cmd_args.publisher == Pubkey::default() { - let num_comps = try_convert::(price_data.num_)?; - sort_price_comps(&mut price_data.comp_, num_comps)?; - return Ok(()); - } - if price_data.num_ >= PC_NUM_COMP { return Err(ProgramError::InvalidArgument); } @@ -97,8 +86,13 @@ pub fn add_publisher( // Sort the publishers in the list { - let num_comps = try_convert::(price_data.num_)?; - sort_price_comps(&mut price_data.comp_, num_comps)?; + let mut comp_index = try_convert::(price_data.num_ - 1)?; + while comp_index > 0 + && price_data.comp_[comp_index - 1].pub_ > price_data.comp_[comp_index].pub_ + { + price_data.comp_.swap(comp_index - 1, comp_index); + comp_index -= 1; + } } price_data.header.size = try_convert::<_, u32>(PriceAccount::INITIAL_SIZE)?; @@ -111,131 +105,3 @@ pub fn add_publisher( Ok(()) } - -/// A copy of rust slice/sort.rs heapsort implementation which is small and fast. We couldn't use -/// the sort directly because it was only accessible behind a unstable feature flag at the time of -/// writing this code. -fn heapsort(v: &mut [(Pubkey, usize)]) { - // This binary heap respects the invariant `parent >= child`. - let sift_down = |v: &mut [(Pubkey, usize)], mut node: usize| { - loop { - // Children of `node`. - let mut child = 2 * node + 1; - if child >= v.len() { - break; - } - - // Choose the greater child. - if child + 1 < v.len() - && sol_memcmp(v[child].0.as_ref(), v[child + 1].0.as_ref(), 32) < 0 - { - child += 1; - } - - // Stop if the invariant holds at `node`. - if sol_memcmp(v[node].0.as_ref(), v[child].0.as_ref(), 32) >= 0 { - break; - } - - // Swap `node` with the greater child, move one step down, and continue sifting. - v.swap(node, child); - node = child; - } - }; - - // Build the heap in linear time. - for i in (0..v.len() / 2).rev() { - sift_down(v, i); - } - - // Pop maximal elements from the heap. - for i in (1..v.len()).rev() { - v.swap(0, i); - sift_down(&mut v[..i], 0); - } -} - -/// Sort the publishers price component list in place by performing minimal swaps. -/// This code is inspired by the sort_by_cached_key implementation in the Rust stdlib. -/// The rust stdlib implementation is not used because it uses a fast sort variant that has -/// a large code size. -/// -/// num_publishers is the number of publishers in the list that should be sorted. It is explicitly -/// passed to avoid callers mistake of passing the full slice which may contain uninitialized values. -fn sort_price_comps(comps: &mut [PriceComponent], num_comps: usize) -> Result<(), ProgramError> { - let comps = comps - .get_mut(..num_comps) - .ok_or(ProgramError::InvalidArgument)?; - - // Publishers are likely sorted in ascending order but - // heapsorts creates a max-heap so we reverse the order - // of the keys to make the heapify step faster. - let mut keys = comps - .iter() - .enumerate() - .map(|(i, x)| (x.pub_, i)) - .rev() - .collect::>(); - - heapsort(&mut keys); - - for i in 0..num_comps { - // We know that the publisher with key[i].0 should be at index i in the sorted array and - // want to swap them in-place in O(n). Normally, the publisher at key[i].0 should be at - // key[i].1 but if it is swapped, we need to find the correct index by following the chain - // of swaps. - let mut index = keys[i].1; - - while index < i { - index = keys[index].1; - } - // Setting the final index here is important to make the code linear as we won't - // loop over from i to index again when we reach i again. - keys[i].1 = index; - comps.swap(i, index); - } - - Ok(()) -} - -#[cfg(test)] -mod test { - use { - super::*, - quickcheck_macros::quickcheck, - }; - - #[quickcheck] - pub fn test_sort_price_comps(mut comps: Vec) { - let mut rust_std_sorted_comps = comps.clone(); - rust_std_sorted_comps.sort_by_key(|x| x.pub_); - - let num_comps = comps.len(); - assert_eq!( - sort_price_comps(&mut comps, num_comps + 1), - Err(ProgramError::InvalidArgument) - ); - - assert_eq!(sort_price_comps(&mut comps, num_comps), Ok(())); - assert_eq!(comps, rust_std_sorted_comps); - } - - #[quickcheck] - pub fn test_sort_price_comps_smaller_slice( - mut comps: Vec, - mut num_comps: usize, - ) { - num_comps = if comps.is_empty() { - 0 - } else { - num_comps % comps.len() - }; - - let mut rust_std_sorted_comps = comps.get(..num_comps).unwrap().to_vec(); - rust_std_sorted_comps.sort_by_key(|x| x.pub_); - - - assert_eq!(sort_price_comps(&mut comps, num_comps), Ok(())); - assert_eq!(comps.get(..num_comps).unwrap(), rust_std_sorted_comps); - } -} diff --git a/program/rust/src/processor/upd_price.rs b/program/rust/src/processor/upd_price.rs index 278673b4f..7c1a98d06 100644 --- a/program/rust/src/processor/upd_price.rs +++ b/program/rust/src/processor/upd_price.rs @@ -319,24 +319,7 @@ fn find_publisher_index(comps: &[PriceComponent], key: &Pubkey) -> Option } } } - - match binary_search_result { - Some(index) => Some(index), - None => { - let mut index = 0; - while index < comps.len() { - if sol_memcmp(comps[index].pub_.as_ref(), key.as_ref(), 32) == 0 { - break; - } - index += 1; - } - if index == comps.len() { - None - } else { - Some(index) - } - } - } + binary_search_result } #[allow(dead_code)] @@ -357,21 +340,6 @@ mod test { solana_program::pubkey::Pubkey, }; - /// Test the find_publisher_index method works with an unordered list of components. - #[quickcheck] - pub fn test_find_publisher_index_unordered_comp(comps: Vec) { - comps.iter().enumerate().for_each(|(idx, comp)| { - assert_eq!(find_publisher_index(&comps, &comp.pub_), Some(idx)); - }); - - let mut key_not_in_list = Pubkey::new_unique(); - while comps.iter().any(|comp| comp.pub_ == key_not_in_list) { - key_not_in_list = Pubkey::new_unique(); - } - - assert_eq!(find_publisher_index(&comps, &key_not_in_list), None); - } - /// Test the find_publisher_index method works with a sorted list of components. #[quickcheck] pub fn test_find_publisher_index_ordered_comp(mut comps: Vec) { diff --git a/program/rust/src/tests/test_add_publisher.rs b/program/rust/src/tests/test_add_publisher.rs index 41dcfe67d..57f2a346a 100644 --- a/program/rust/src/tests/test_add_publisher.rs +++ b/program/rust/src/tests/test_add_publisher.rs @@ -157,37 +157,4 @@ fn test_add_publisher() { assert!(price_data.comp_[i as usize].pub_ > price_data.comp_[(i - 1) as usize].pub_); } } - - // Test sorting by reordering the publishers to be in reverse order - // and then adding the default (000...) publisher to trigger the sorting. - { - let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - price_data - .comp_ - .get_mut(..PC_NUM_COMP as usize) - .unwrap() - .reverse(); - } - - cmd.publisher = Pubkey::default(); - instruction_data = bytes_of::(&cmd); - assert!(process_instruction( - &program_id, - &[ - funding_account.clone(), - price_account.clone(), - permissions_account.clone(), - ], - instruction_data - ) - .is_ok()); - - // Make sure that publishers get sorted after adding the default publisher - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert!(price_data.num_ == PC_NUM_COMP); - for i in 1..PC_NUM_COMP { - assert!(price_data.comp_[i as usize].pub_ > price_data.comp_[(i - 1) as usize].pub_); - } - } } diff --git a/program/rust/src/tests/test_upd_price_v2.rs b/program/rust/src/tests/test_upd_price_v2.rs index 63785819b..0e0c1f4b8 100644 --- a/program/rust/src/tests/test_upd_price_v2.rs +++ b/program/rust/src/tests/test_upd_price_v2.rs @@ -444,87 +444,6 @@ fn test_upd_price_v2() -> Result<(), Box> { Ok(()) } -#[test] -fn test_upd_works_with_unordered_publisher_set() -> Result<(), Box> { - let mut instruction_data = [0u8; size_of::()]; - - let program_id = Pubkey::new_unique(); - - let mut price_setup = AccountSetup::new::(&program_id); - let mut price_account = price_setup.as_account_info(); - price_account.is_signer = false; - PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); - - let mut publishers_setup: Vec<_> = (0..20).map(|_| AccountSetup::new_funding()).collect(); - let mut publishers: Vec<_> = publishers_setup - .iter_mut() - .map(|s| s.as_account_info()) - .collect(); - - publishers.sort_by_key(|x| x.key); - - { - let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - price_data.num_ = 20; - // Store the publishers in reverse order - publishers - .iter() - .rev() - .enumerate() - .for_each(|(i, account)| { - price_data.comp_[i].pub_ = *account.key; - }); - } - - let mut clock_setup = AccountSetup::new_clock(); - let mut clock_account = clock_setup.as_account_info(); - clock_account.is_signer = false; - clock_account.is_writable = false; - - update_clock_slot(&mut clock_account, 1); - - for (i, publisher) in publishers.iter().enumerate() { - populate_instruction(&mut instruction_data, (i + 100) as i64, 10, 1); - process_instruction( - &program_id, - &[ - publisher.clone(), - price_account.clone(), - clock_account.clone(), - ], - &instruction_data, - )?; - } - - update_clock_slot(&mut clock_account, 2); - - // Trigger the aggregate calculation by sending another price - // update - populate_instruction(&mut instruction_data, 100, 10, 2); - process_instruction( - &program_id, - &[ - publishers[0].clone(), - price_account.clone(), - clock_account.clone(), - ], - &instruction_data, - )?; - - - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - - // The result will be the following only if all the - // publishers prices are included in the aggregate. - assert_eq!(price_data.valid_slot_, 1); - assert_eq!(price_data.agg_.pub_slot_, 2); - assert_eq!(price_data.agg_.price_, 109); - assert_eq!(price_data.agg_.conf_, 8); - assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); - - Ok(()) -} - // Create an upd_price instruction with the provided parameters fn populate_instruction(instruction_data: &mut [u8], price: i64, conf: u64, pub_slot: u64) { let mut cmd = load_mut::(instruction_data).unwrap(); From a02a10b7da6a173fa7e040ad8432d3500aea778c Mon Sep 17 00:00:00 2001 From: keyvan Date: Fri, 14 Jun 2024 10:56:56 -0700 Subject: [PATCH 07/39] feat: improve caps account calculations --- program/rust/src/accounts/score.rs | 48 ++++++++++------------------ program/rust/src/tests/test_utils.rs | 2 +- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/program/rust/src/accounts/score.rs b/program/rust/src/accounts/score.rs index 31a4bed39..299a3191e 100644 --- a/program/rust/src/accounts/score.rs +++ b/program/rust/src/accounts/score.rs @@ -21,7 +21,7 @@ use { }; #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Pod, Zeroable)] /* * This account is part of Community Integrity Pool (CIP) project. * It is used to store the caps of the publishers which will be sent @@ -32,13 +32,13 @@ pub struct PublisherCapsAccount { pub num_publishers: usize, pub num_symbols: usize, // Z is a constant used to normalize the scores - pub z: u32, + pub z: u64, // M is a constant showing the target stake per symbol - pub m: u32, + pub m: u64, // array[x][y] is a u64 whose bits represent if publisher x publishes symbols 64*y to 64*(y+1) - 1 pub publisher_permissions: [[u64; PC_MAX_SYMBOLS_64 as usize]; PC_MAX_PUBLISHERS as usize], - pub caps: [f64; PC_MAX_PUBLISHERS as usize], + pub caps: [u64; PC_MAX_PUBLISHERS as usize], pub publishers: [Pubkey; PC_MAX_PUBLISHERS as usize], pub symbols: [Pubkey; PC_MAX_SYMBOLS as usize], } @@ -148,27 +148,20 @@ impl PublisherCapsAccount { } pub fn calculate_scores(&mut self) -> Result<(), ProgramError> { - let symbol_scores: Vec = self - .symbols - .iter() - .enumerate() - .map(|(j, _)| { - let score = self - .publisher_permissions - .iter() - .fold(0, |score, permissions| score + permissions[j] as u32); - max(score, self.z) - }) - .collect(); + let mut symbol_scores: Vec = vec![0; self.num_symbols]; + for j in 0..self.num_symbols { + for i in 0..self.num_publishers { + symbol_scores[j] += self.get_publisher_permission(i, j) as u64; + } + symbol_scores[j] = max(symbol_scores[j], self.z); + } for i in 0..self.num_publishers { - self.caps[i] = self - .symbols - .iter() - .enumerate() - .filter(|(j, _)| self.get_publisher_permission(i, *j)) - .map(|(j, _)| 1f64 / symbol_scores[j] as f64) - .sum(); + self.caps[i] = 0; + for j in 0..self.num_symbols { + self.caps[i] += self.get_publisher_permission(i, j) as u64 * 1_000_000_000_u64 + / symbol_scores[j]; + } } Ok(()) } @@ -177,12 +170,5 @@ impl PublisherCapsAccount { impl PythAccount for PublisherCapsAccount { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_SCORE; // Calculate the initial size of the account - const INITIAL_SIZE: u32 = 75816; -} - -// Unsafe impl because product_list is of size 640 and there's no derived trait for this size -unsafe impl Pod for PublisherCapsAccount { -} - -unsafe impl Zeroable for PublisherCapsAccount { + const INITIAL_SIZE: u32 = 75824; } diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index cf7add1c0..c0e97ab28 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 = 75816; +const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 75824; /// The goal of this struct is to easily instantiate fresh solana accounts /// for the Pyth program to use in tests. From 0c98527a47bf5c25ffe600f9e8048b79063553f5 Mon Sep 17 00:00:00 2001 From: keyvan Date: Fri, 14 Jun 2024 10:57:54 -0700 Subject: [PATCH 08/39] feat: rename score to publisher_caps --- program/rust/src/accounts.rs | 4 ++-- program/rust/src/accounts/{score.rs => publisher_caps.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename program/rust/src/accounts/{score.rs => publisher_caps.rs} (100%) diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 0d0d0f494..0d1017246 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -39,7 +39,7 @@ mod mapping; mod permission; mod price; mod product; -mod score; +mod publisher_caps; // Some types only exist during use as a library. #[cfg(feature = "strum")] @@ -65,7 +65,7 @@ pub use { update_product_metadata, ProductAccount, }, - score::PublisherCapsAccount, + publisher_caps::PublisherCapsAccount, }; // PDA seeds for accounts. diff --git a/program/rust/src/accounts/score.rs b/program/rust/src/accounts/publisher_caps.rs similarity index 100% rename from program/rust/src/accounts/score.rs rename to program/rust/src/accounts/publisher_caps.rs From 87f61ecbaaf0e70281ea3046a1be1882dbece601 Mon Sep 17 00:00:00 2001 From: keyvan Date: Fri, 14 Jun 2024 11:04:32 -0700 Subject: [PATCH 09/39] fix: bypass false positive clippy --- program/rust/src/accounts/publisher_caps.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 299a3191e..17e1580cd 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -147,6 +147,7 @@ impl PublisherCapsAccount { self.calculate_scores() } + #[allow(clippy::needless_range_loop)] pub fn calculate_scores(&mut self) -> Result<(), ProgramError> { let mut symbol_scores: Vec = vec![0; self.num_symbols]; for j in 0..self.num_symbols { From 2f6f359db92ec11f3e172c2a6f624df6790aac22 Mon Sep 17 00:00:00 2001 From: keyvan Date: Fri, 14 Jun 2024 12:04:11 -0700 Subject: [PATCH 10/39] feat: optimize oracle command enum --- program/rust/src/instruction.rs | 24 ++++++++++++++++++------ program/rust/src/utils.rs | 1 - 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/program/rust/src/instruction.rs b/program/rust/src/instruction.rs index c3d8a3def..c31efc3c7 100644 --- a/program/rust/src/instruction.rs +++ b/program/rust/src/instruction.rs @@ -1,3 +1,8 @@ +#[cfg(test)] +use num_derive::{ + FromPrimitive, + ToPrimitive, +}; use { crate::{ c_oracle_header::PC_VERSION, @@ -8,17 +13,14 @@ use { Pod, Zeroable, }, - num_derive::{ - FromPrimitive, - ToPrimitive, - }, - num_traits::FromPrimitive, solana_program::pubkey::Pubkey, }; /// WARNING : NEW COMMANDS SHOULD BE ADDED AT THE END OF THE LIST #[repr(i32)] -#[derive(PartialEq, Eq, FromPrimitive, ToPrimitive)] +#[cfg_attr(test, derive(FromPrimitive, ToPrimitive))] +#[derive(PartialEq, Eq)] +#[allow(dead_code)] pub enum OracleCommand { /// Initialize first mapping list account // account[0] funding account [signer writable] @@ -105,6 +107,16 @@ pub enum OracleCommand { SetMaxLatency = 18, } +impl OracleCommand { + pub fn from_i32(value: i32) -> Option { + if (0..=18).contains(&value) { + Some(unsafe { std::mem::transmute(value) }) + } else { + None + } + } +} + #[repr(C)] #[derive(Zeroable, Pod, Copy, Clone)] pub struct CommandHeader { diff --git a/program/rust/src/utils.rs b/program/rust/src/utils.rs index 97ef9b534..c3d84695b 100644 --- a/program/rust/src/utils.rs +++ b/program/rust/src/utils.rs @@ -25,7 +25,6 @@ use { Pod, Zeroable, }, - num_traits::FromPrimitive, solana_program::{ account_info::AccountInfo, bpf_loader_upgradeable, From 2697cb25a50d428eed3087d34d5c34e1bb82763d Mon Sep 17 00:00:00 2001 From: keyvan Date: Fri, 14 Jun 2024 12:25:19 -0700 Subject: [PATCH 11/39] feat: test new way of calculating score --- program/rust/src/accounts/publisher_caps.rs | 55 ++++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 17e1580cd..410c4a340 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -147,25 +147,54 @@ impl PublisherCapsAccount { self.calculate_scores() } - #[allow(clippy::needless_range_loop)] + pub fn calculate_scores(&mut self) -> Result<(), ProgramError> { - let mut symbol_scores: Vec = vec![0; self.num_symbols]; - for j in 0..self.num_symbols { - for i in 0..self.num_publishers { - symbol_scores[j] += self.get_publisher_permission(i, j) as u64; - } - symbol_scores[j] = max(symbol_scores[j], self.z); - } + let symbol_scores: Vec = self + .symbols + .iter() + .enumerate() + .map(|(j, _)| { + let score = self + .publisher_permissions + .iter() + .enumerate() + .filter(|(i, _)| self.get_publisher_permission(*i, j)) + .count() as u64; + max(score, self.z) + }) + .collect(); for i in 0..self.num_publishers { - self.caps[i] = 0; - for j in 0..self.num_symbols { - self.caps[i] += self.get_publisher_permission(i, j) as u64 * 1_000_000_000_u64 - / symbol_scores[j]; - } + self.caps[i] = self + .symbols + .iter() + .enumerate() + .filter(|(j, _)| self.get_publisher_permission(i, *j)) + .map(|(j, _)| 1_000_000_000_u64 / symbol_scores[j] as u64) + .sum(); } Ok(()) } + + // #[allow(clippy::needless_range_loop)] + // pub fn calculate_scores(&mut self) -> Result<(), ProgramError> { + // let mut symbol_scores: Vec = vec![0; self.num_symbols]; + // for j in 0..self.num_symbols { + // for i in 0..self.num_publishers { + // symbol_scores[j] += self.get_publisher_permission(i, j) as u64; + // } + // symbol_scores[j] = max(symbol_scores[j], self.z); + // } + + // for i in 0..self.num_publishers { + // self.caps[i] = 0; + // for j in 0..self.num_symbols { + // self.caps[i] += self.get_publisher_permission(i, j) as u64 * 1_000_000_000_u64 + // / symbol_scores[j]; + // } + // } + // Ok(()) + // } } impl PythAccount for PublisherCapsAccount { From d07a256091b77a23b808836ae5cedd66f61eb752 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 10:29:18 -0700 Subject: [PATCH 12/39] fix: correct rust doc format --- program/rust/src/accounts/publisher_caps.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 410c4a340..1c31c4951 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -22,11 +22,10 @@ use { #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] -/* - * This account is part of Community Integrity Pool (CIP) project. - * It is used to store the caps of the publishers which will be sent - * to the `integrity_pool` program on mainnet to calculate the rewards. - */ + +/// This account is part of Community Integrity Pool (CIP) project. +/// It is used to store the caps of the publishers which will be sent +/// to the `integrity_pool` program on mainnet to calculate the rewards. pub struct PublisherCapsAccount { pub header: AccountHeader, pub num_publishers: usize, From 98a6a526de34a7cb1c39b7a1b91262c6f9ae4fde Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 11:03:28 -0700 Subject: [PATCH 13/39] fix: clippy --- program/rust/src/accounts/publisher_caps.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 1c31c4951..502f5203f 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -169,7 +169,7 @@ impl PublisherCapsAccount { .iter() .enumerate() .filter(|(j, _)| self.get_publisher_permission(i, *j)) - .map(|(j, _)| 1_000_000_000_u64 / symbol_scores[j] as u64) + .map(|(j, _)| 1_000_000_000_u64 / symbol_scores[j]) .sum(); } Ok(()) From 4260e2e7323153f4281cdb71548b40af0d9e5042 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 11:03:40 -0700 Subject: [PATCH 14/39] feat: add vscode settings --- .vscode/settings.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..1bfb9a75f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "rust-analyzer.check.command": "clippy", + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "rust-analyzer.rustfmt.overrideCommand": [ + "rustfmt", + "+nightly" + ], + "editor.formatOnSave": true, +} \ No newline at end of file From 6c9d3e6b95c96eb7bbd56d54c1c0404bd1d9d447 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 11:28:35 -0700 Subject: [PATCH 15/39] fix: clippy warning --- program/rust/build.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/program/rust/build.rs b/program/rust/build.rs index b33d8dd7c..e8d062781 100644 --- a/program/rust/build.rs +++ b/program/rust/build.rs @@ -89,10 +89,10 @@ fn do_make_build(targets: Vec<&str>, out_dir: &Path) { if !make_output.status.success() { panic!( "C oracle make build did not exit with 0 (code - ({:?}).\n\nstdout:\n{}\n\nstderr:\n{}", + ({:?}).\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(make_output.stdout).unwrap_or_else(|_| "".to_owned()), + String::from_utf8(make_output.stderr).unwrap_or_else(|_| "".to_owned()) ); } } From 8e001031c293f93189181d76dcf9f3a4131bac2a Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 11:30:43 -0700 Subject: [PATCH 16/39] feat: change OracleCommand repr from i32 to u32 --- program/rust/src/instruction.rs | 10 +++++----- ...signable_account_or_permissioned_funding_account.rs | 2 +- program/rust/src/tests/test_utils.rs | 2 +- program/rust/src/utils.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/program/rust/src/instruction.rs b/program/rust/src/instruction.rs index c31efc3c7..301d64f72 100644 --- a/program/rust/src/instruction.rs +++ b/program/rust/src/instruction.rs @@ -17,7 +17,7 @@ use { }; /// WARNING : NEW COMMANDS SHOULD BE ADDED AT THE END OF THE LIST -#[repr(i32)] +#[repr(u32)] #[cfg_attr(test, derive(FromPrimitive, ToPrimitive))] #[derive(PartialEq, Eq)] #[allow(dead_code)] @@ -108,8 +108,8 @@ pub enum OracleCommand { } impl OracleCommand { - pub fn from_i32(value: i32) -> Option { - if (0..=18).contains(&value) { + pub fn from_u32(value: u32) -> Option { + if value < 19 { Some(unsafe { std::mem::transmute(value) }) } else { None @@ -121,7 +121,7 @@ impl OracleCommand { #[derive(Zeroable, Pod, Copy, Clone)] pub struct CommandHeader { pub version: u32, - pub command: i32, + pub command: u32, } pub fn load_command_header_checked(data: &[u8]) -> Result { @@ -130,7 +130,7 @@ pub fn load_command_header_checked(data: &[u8]) -> Result for CommandHeader { fn from(val: OracleCommand) -> Self { CommandHeader { version: PC_VERSION, - command: val.to_i32().unwrap(), // This can never fail and is only used in tests + command: val.to_u32().unwrap(), // This can never fail and is only used in tests } } } diff --git a/program/rust/src/utils.rs b/program/rust/src/utils.rs index c3d84695b..630dfc0d2 100644 --- a/program/rust/src/utils.rs +++ b/program/rust/src/utils.rs @@ -72,7 +72,7 @@ pub fn check_permissioned_funding_account( pyth_assert( permissions_account_data.is_authorized( funding_account.key, - OracleCommand::from_i32(cmd_hdr.command).ok_or(OracleError::UnrecognizedInstruction)?, + OracleCommand::from_u32(cmd_hdr.command).ok_or(OracleError::UnrecognizedInstruction)?, ), OracleError::PermissionViolation.into(), )?; @@ -160,7 +160,7 @@ pub fn check_valid_permissions_account( /// Checks whether this instruction is trying to update an individual publisher's price (`true`) or /// is only trying to refresh the aggregate (`false`) pub fn is_component_update(cmd_args: &UpdPriceArgs) -> Result { - match OracleCommand::from_i32(cmd_args.header.command) + match OracleCommand::from_u32(cmd_args.header.command) .ok_or(OracleError::UnrecognizedInstruction)? { OracleCommand::UpdPrice | OracleCommand::UpdPriceNoFailOnError => Ok(true), From aa1e0a7059ec0627322c82ca87d84be893b8f9d3 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 12:03:40 -0700 Subject: [PATCH 17/39] feat: add initial tests for publisher caps --- program/rust/src/accounts/publisher_caps.rs | 127 +++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 502f5203f..44023df65 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -82,6 +82,7 @@ impl PublisherCapsAccount { .ok_or(ProgramError::InvalidArgument)?; self.set_publisher_permission(publisher_index, symbol_index, true); + self.calculate_scores() } @@ -169,7 +170,7 @@ impl PublisherCapsAccount { .iter() .enumerate() .filter(|(j, _)| self.get_publisher_permission(i, *j)) - .map(|(j, _)| 1_000_000_000_u64 / symbol_scores[j]) + .map(|(j, _)| self.m * 1_000_000_000_u64 / symbol_scores[j]) .sum(); } Ok(()) @@ -201,3 +202,127 @@ impl PythAccount for PublisherCapsAccount { // Calculate the initial size of the account const INITIAL_SIZE: u32 = 75824; } + + +// write tests +#[cfg(test)] +mod tests { + use { + super::*, + crate::c_oracle_header::{ + PC_ACCTYPE_SCORE, + PC_VERSION, + }, + solana_program::pubkey::Pubkey, + std::mem::size_of, + }; + + fn get_empty_account() -> PublisherCapsAccount { + PublisherCapsAccount { + header: AccountHeader { + magic_number: PC_ACCTYPE_SCORE, + account_type: PC_ACCTYPE_SCORE, + version: PC_VERSION, + size: size_of::() as u32, + }, + num_publishers: 0, + num_symbols: 0, + z: 2, + m: 1000, + publisher_permissions: [[0; PC_MAX_SYMBOLS_64 as usize]; PC_MAX_PUBLISHERS as usize], + caps: [0; PC_MAX_PUBLISHERS as usize], + publishers: [Pubkey::default(); PC_MAX_PUBLISHERS as usize], + symbols: [Pubkey::default(); PC_MAX_SYMBOLS as usize], + } + } + + #[test] + fn test_size_of_publisher_caps_account() { + let account = get_empty_account(); + assert_eq!( + PublisherCapsAccount::INITIAL_SIZE as usize, + size_of::() + ); + assert_eq!( + account.header.size as usize, + size_of::() + ); + } + + #[test] + fn test_publisher_caps_account() { + let mut account = get_empty_account(); + + let publisher1 = Pubkey::new_unique(); + let publisher2 = Pubkey::new_unique(); + let publisher3 = Pubkey::new_unique(); + let symbol1 = Pubkey::new_unique(); + let symbol2 = Pubkey::new_unique(); + + account.add_price(symbol1).unwrap(); + account.add_price(symbol2).unwrap(); + + account.add_publisher(publisher1, symbol1).unwrap(); + assert_eq!(account.caps[..2], [account.m * 500_000_000, 0]); + + account.add_publisher(publisher1, symbol2).unwrap(); + assert!(account.get_publisher_permission(0, 0)); + assert!(account.get_publisher_permission(0, 1)); + assert_eq!(account.caps[..2], [account.m * 1_000_000_000, 0]); + + + account.add_publisher(publisher2, symbol1).unwrap(); + assert_eq!( + account.caps[..2], + [account.m * 1_000_000_000, account.m * 500_000_000] + ); + + account.add_publisher(publisher2, symbol2).unwrap(); + assert!(account.get_publisher_permission(1, 0)); + assert!(account.get_publisher_permission(1, 1)); + assert_eq!( + account.caps[..2], + [account.m * 1_000_000_000, account.m * 1_000_000_000] + ); + + account.add_publisher(publisher3, symbol1).unwrap(); + assert_eq!( + account.caps[..3], + [833_333_333_333, 833_333_333_333, 333333333333] + ); + + account.del_publisher(publisher1, symbol1).unwrap(); + assert_eq!(account.num_publishers, 3); + assert_eq!(account.num_symbols, 2); + assert!(!account.get_publisher_permission(0, 0)); + assert!(account.get_publisher_permission(0, 1)); + assert!(account.get_publisher_permission(1, 0)); + assert!(account.get_publisher_permission(1, 1)); + + account.del_publisher(publisher1, symbol2).unwrap(); + assert_eq!(account.num_publishers, 3); + assert_eq!(account.num_symbols, 2); + assert!(!account.get_publisher_permission(0, 0)); + assert!(!account.get_publisher_permission(0, 1)); + assert!(account.get_publisher_permission(1, 0)); + assert!(account.get_publisher_permission(1, 1)); + + account.del_publisher(publisher2, symbol1).unwrap(); + assert_eq!(account.num_publishers, 3); + assert_eq!(account.num_symbols, 2); + assert!(!account.get_publisher_permission(0, 0)); + assert!(!account.get_publisher_permission(0, 1)); + assert!(!account.get_publisher_permission(1, 0)); + assert!(account.get_publisher_permission(1, 1)); + + account.del_publisher(publisher2, symbol2).unwrap(); + assert_eq!(account.num_publishers, 3); + assert_eq!(account.num_symbols, 2); + assert!(!account.get_publisher_permission(0, 0)); + assert!(!account.get_publisher_permission(0, 1)); + assert!(!account.get_publisher_permission(1, 0)); + assert!(!account.get_publisher_permission(1, 1)); + + assert_eq!(account.caps[..3], [0, 0, 500000000000]); + } +} From 2372be62c2c1ed9eaa922e5cd098b385a69b14ef Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 15:04:04 -0700 Subject: [PATCH 18/39] feat: add migration step --- program/rust/src/processor/add_publisher.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index 61276fc75..c5dfbcc65 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -65,6 +65,22 @@ pub fn add_publisher( let mut price_data = load_checked::(price_account, cmd_args.header.version)?; + // Use the call with the default pubkey (000..) as a trigger to update caps account + // migration step for initializing the caps account + if cmd_args.publisher == Pubkey::default() { + let num_comps = try_convert::(price_data.num_)?; + + if let Some(scores_account) = scores_account { + let mut scores_account = + load_checked::(scores_account, cmd_args.header.version)?; + for pubisher in price_data.comp_.iter().take(num_comps) { + scores_account.add_publisher(pubisher.pub_, *price_account.key)?; + } + } + + return Ok(()); + } + if price_data.num_ >= PC_NUM_COMP { return Err(ProgramError::InvalidArgument); } From 63352ecf01b5958a26fd435d0788d3d53ac0a9ca Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 15:29:43 -0700 Subject: [PATCH 19/39] test: try a different implementation for program size --- program/rust/src/processor/add_publisher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index c5dfbcc65..6676b9659 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -73,7 +73,7 @@ pub fn add_publisher( if let Some(scores_account) = scores_account { let mut scores_account = load_checked::(scores_account, cmd_args.header.version)?; - for pubisher in price_data.comp_.iter().take(num_comps) { + for pubisher in price_data.comp_[..num_comps].iter() { scores_account.add_publisher(pubisher.pub_, *price_account.key)?; } } From aaa12bf42c35904faa530e30f3fa3fc7c519dcdd Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 15:49:21 -0700 Subject: [PATCH 20/39] test: try manual swap to decrease program size --- program/rust/src/processor/add_publisher.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index 6676b9659..15aee68c2 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -101,12 +101,15 @@ pub fn add_publisher( price_data.num_ += 1; // Sort the publishers in the list + #[allow(clippy::manual_swap)] { let mut comp_index = try_convert::(price_data.num_ - 1)?; while comp_index > 0 && price_data.comp_[comp_index - 1].pub_ > price_data.comp_[comp_index].pub_ { - price_data.comp_.swap(comp_index - 1, comp_index); + let tmp = price_data.comp_[comp_index - 1]; + price_data.comp_[comp_index - 1] = price_data.comp_[comp_index]; + price_data.comp_[comp_index] = tmp; comp_index -= 1; } } From e2aa6457c182d41c73aee16a4f3216a2440b46b7 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 16:05:48 -0700 Subject: [PATCH 21/39] fix: add price when initializing the caps account --- program/rust/src/processor/add_publisher.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index 15aee68c2..379c9767c 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -73,8 +73,9 @@ pub fn add_publisher( if let Some(scores_account) = scores_account { let mut scores_account = load_checked::(scores_account, cmd_args.header.version)?; - for pubisher in price_data.comp_[..num_comps].iter() { - scores_account.add_publisher(pubisher.pub_, *price_account.key)?; + scores_account.add_price(*price_account.key)?; + for publisher in price_data.comp_[..num_comps].iter() { + scores_account.add_publisher(publisher.pub_, *price_account.key)?; } } From 2169e8de0d66a7d68aa5d4430177b5485b0593bd Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 16:08:12 -0700 Subject: [PATCH 22/39] fix: rename scores account to cap account --- program/rust/src/processor/add_price.rs | 12 +++++------ program/rust/src/processor/add_publisher.rs | 22 ++++++++++----------- program/rust/src/processor/del_price.rs | 12 +++++------ program/rust/src/processor/del_publisher.rs | 12 +++++------ program/rust/src/tests/test_add_price.rs | 10 +++++----- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/program/rust/src/processor/add_price.rs b/program/rust/src/processor/add_price.rs index eeb6c5735..f1b25d043 100644 --- a/program/rust/src/processor/add_price.rs +++ b/program/rust/src/processor/add_price.rs @@ -35,7 +35,7 @@ use { // account[0] funding account [signer writable] // account[1] product account [signer writable] // account[2] new price account [signer writable] -// account[3] scores account [signer writable] +// account[3] caps account [signer writable] pub fn add_price( program_id: &Pubkey, accounts: &[AccountInfo], @@ -50,7 +50,7 @@ pub fn add_price( )?; - let (funding_account, product_account, price_account, permissions_account, scores_account) = + let (funding_account, product_account, price_account, permissions_account, caps_account) = match accounts { [x, y, z, p] => Ok((x, y, z, p, None)), [x, y, z, p, s] => Ok((x, y, z, p, Some(s))), @@ -84,10 +84,10 @@ pub fn add_price( price_data.min_pub_ = PRICE_ACCOUNT_DEFAULT_MIN_PUB; product_data.first_price_account = *price_account.key; - if let Some(scores_account) = scores_account { - let mut scores_account = - load_checked::(scores_account, cmd_args.header.version)?; - scores_account.add_price(*price_account.key)?; + if let Some(caps_account) = caps_account { + let mut caps_account = + load_checked::(caps_account, cmd_args.header.version)?; + caps_account.add_price(*price_account.key)?; } Ok(()) diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index 379c9767c..e197b60a3 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -34,7 +34,7 @@ use { /// Add publisher to symbol account // account[0] funding account [signer writable] // account[1] price account [signer writable] -// account[2] scores account [signer writable] +// account[2] caps account [signer writable] pub fn add_publisher( program_id: &Pubkey, accounts: &[AccountInfo], @@ -47,7 +47,7 @@ pub fn add_publisher( ProgramError::InvalidArgument, )?; - let (funding_account, price_account, permissions_account, scores_account) = match accounts { + let (funding_account, price_account, permissions_account, caps_account) = match accounts { [x, y, p] => Ok((x, y, p, None)), [x, y, p, s] => Ok((x, y, p, Some(s))), _ => Err(OracleError::InvalidNumberOfAccounts), @@ -70,12 +70,12 @@ pub fn add_publisher( if cmd_args.publisher == Pubkey::default() { let num_comps = try_convert::(price_data.num_)?; - if let Some(scores_account) = scores_account { - let mut scores_account = - load_checked::(scores_account, cmd_args.header.version)?; - scores_account.add_price(*price_account.key)?; + if let Some(caps_account) = caps_account { + let mut caps_account = + load_checked::(caps_account, cmd_args.header.version)?; + caps_account.add_price(*price_account.key)?; for publisher in price_data.comp_[..num_comps].iter() { - scores_account.add_publisher(publisher.pub_, *price_account.key)?; + caps_account.add_publisher(publisher.pub_, *price_account.key)?; } } @@ -117,10 +117,10 @@ pub fn add_publisher( price_data.header.size = try_convert::<_, u32>(PriceAccount::INITIAL_SIZE)?; - if let Some(scores_account) = scores_account { - let mut scores_account = - load_checked::(scores_account, cmd_args.header.version)?; - scores_account.add_publisher(cmd_args.publisher, *price_account.key)?; + if let Some(caps_account) = caps_account { + let mut caps_account = + load_checked::(caps_account, cmd_args.header.version)?; + caps_account.add_publisher(cmd_args.publisher, *price_account.key)?; } Ok(()) diff --git a/program/rust/src/processor/del_price.rs b/program/rust/src/processor/del_price.rs index 50043a881..fb6ca8082 100644 --- a/program/rust/src/processor/del_price.rs +++ b/program/rust/src/processor/del_price.rs @@ -31,7 +31,7 @@ use { // account[0] funding account [signer writable] // account[1] product account [signer writable] // account[2] price account [signer writable] -// account[3] scores account [signer writable] +// account[3] caps account [signer writable] /// Warning: This function is dangerous and will break any programs that depend on the deleted /// price account! pub fn del_price( @@ -39,7 +39,7 @@ pub fn del_price( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - let (funding_account, product_account, price_account, permissions_account, scores_account) = + let (funding_account, product_account, price_account, permissions_account, caps_account) = match accounts { [x, y, z, p] => Ok((x, y, z, p, None)), [x, y, z, p, s] => Ok((x, y, z, p, Some(s))), @@ -87,10 +87,10 @@ pub fn del_price( **price_account.lamports.borrow_mut() = 0; **funding_account.lamports.borrow_mut() += lamports; - if let Some(scores_account) = scores_account { - let mut scores_account = - load_checked::(scores_account, cmd_args.version)?; - scores_account.del_price(*price_account.key)?; + if let Some(caps_account) = caps_account { + let mut caps_account = + load_checked::(caps_account, cmd_args.version)?; + caps_account.del_price(*price_account.key)?; } Ok(()) diff --git a/program/rust/src/processor/del_publisher.rs b/program/rust/src/processor/del_publisher.rs index ebeb15ccc..18bdb307f 100644 --- a/program/rust/src/processor/del_publisher.rs +++ b/program/rust/src/processor/del_publisher.rs @@ -33,7 +33,7 @@ use { /// Delete publisher from symbol account // account[0] funding account [signer writable] // account[1] price account [signer writable] -// account[2] scores account [signer writable] +// account[2] caps account [signer writable] pub fn del_publisher( program_id: &Pubkey, accounts: &[AccountInfo], @@ -47,7 +47,7 @@ pub fn del_publisher( ProgramError::InvalidArgument, )?; - let (funding_account, price_account, permissions_account, scores_account) = match accounts { + let (funding_account, price_account, permissions_account, caps_account) = match accounts { [x, y, p] => Ok((x, y, p, None)), [x, y, p, s] => Ok((x, y, p, Some(s))), _ => Err(OracleError::InvalidNumberOfAccounts), @@ -62,10 +62,10 @@ pub fn del_publisher( &cmd_args.header, )?; - if let Some(scores_account) = scores_account { - let mut scores_account = - load_checked::(scores_account, cmd_args.header.version)?; - scores_account.del_publisher(cmd_args.publisher, *price_account.key)?; + if let Some(caps_account) = caps_account { + let mut caps_account = + load_checked::(caps_account, cmd_args.header.version)?; + caps_account.del_publisher(cmd_args.publisher, *price_account.key)?; } let mut price_data = load_checked::(price_account, cmd_args.header.version)?; diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index 54ce97a83..c04ac38ac 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -67,8 +67,8 @@ fn test_add_price() { let permissions_account = permissions_setup.as_account_info(); let mut scores_setup = AccountSetup::new::(&program_id); - let scores_account = scores_setup.as_account_info(); - PublisherCapsAccount::initialize(&scores_account, PC_VERSION).unwrap(); + let caps_account = scores_setup.as_account_info(); + PublisherCapsAccount::initialize(&caps_account, PC_VERSION).unwrap(); { let mut permissions_account_data = @@ -90,7 +90,7 @@ fn test_add_price() { ) .is_ok()); - // add price with scores account + // add price with caps account assert!(process_instruction( &program_id, &[ @@ -98,14 +98,14 @@ fn test_add_price() { product_account.clone(), price_account_3.clone(), permissions_account.clone(), - scores_account.clone(), + caps_account.clone(), ], instruction_data_add_price ) .is_ok()); { - let score_data = load_checked::(&scores_account, PC_VERSION).unwrap(); + let score_data = load_checked::(&caps_account, PC_VERSION).unwrap(); assert_eq!(score_data.symbols[0], *price_account_3.key); assert_eq!(score_data.num_symbols, 1); } From de0f7c3f27569e3ece957995afad1b478ff0cf39 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 16:09:04 -0700 Subject: [PATCH 23/39] refactor: rename calculate_scores to calculate_caps --- program/rust/src/accounts/publisher_caps.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 44023df65..05e93e08f 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -83,7 +83,7 @@ impl PublisherCapsAccount { self.set_publisher_permission(publisher_index, symbol_index, true); - self.calculate_scores() + self.calculate_caps() } pub fn del_publisher( @@ -104,7 +104,7 @@ impl PublisherCapsAccount { .ok_or(ProgramError::InvalidArgument)?; self.set_publisher_permission(publisher_index, symbol_index, false); - self.calculate_scores() + self.calculate_caps() } pub fn add_price(&mut self, symbol: Pubkey) -> Result<(), ProgramError> { @@ -144,11 +144,11 @@ impl PublisherCapsAccount { } self.num_symbols -= 1; - self.calculate_scores() + self.calculate_caps() } - pub fn calculate_scores(&mut self) -> Result<(), ProgramError> { + pub fn calculate_caps(&mut self) -> Result<(), ProgramError> { let symbol_scores: Vec = self .symbols .iter() From 439605497235bd0952befb35ff470fd549032e59 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 16:09:56 -0700 Subject: [PATCH 24/39] refactor: rename symbols to prices in PublisherCapsAccount --- program/rust/src/accounts/publisher_caps.rs | 22 ++++++++++----------- program/rust/src/tests/test_add_price.rs | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 05e93e08f..832f1ac8f 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -39,7 +39,7 @@ pub struct PublisherCapsAccount { pub publisher_permissions: [[u64; PC_MAX_SYMBOLS_64 as usize]; PC_MAX_PUBLISHERS as usize], pub caps: [u64; PC_MAX_PUBLISHERS as usize], pub publishers: [Pubkey; PC_MAX_PUBLISHERS as usize], - pub symbols: [Pubkey; PC_MAX_SYMBOLS as usize], + pub prices: [Pubkey; PC_MAX_SYMBOLS as usize], } impl PublisherCapsAccount { @@ -76,7 +76,7 @@ impl PublisherCapsAccount { } let symbol_index = self - .symbols + .prices .iter() .position(|&x| x == price_account) .ok_or(ProgramError::InvalidArgument)?; @@ -98,7 +98,7 @@ impl PublisherCapsAccount { .ok_or(ProgramError::InvalidArgument)?; let symbol_index = self - .symbols + .prices .iter() .position(|&x| x == price_account) .ok_or(ProgramError::InvalidArgument)?; @@ -109,7 +109,7 @@ impl PublisherCapsAccount { pub fn add_price(&mut self, symbol: Pubkey) -> Result<(), ProgramError> { let symbol_index = self - .symbols + .prices .iter() .position(|&x| x == symbol) .unwrap_or(self.num_symbols); @@ -119,7 +119,7 @@ impl PublisherCapsAccount { return Err(ProgramError::AccountDataTooSmall); } - self.symbols[self.num_symbols] = symbol; + self.prices[self.num_symbols] = symbol; self.num_symbols += 1; } @@ -128,14 +128,14 @@ impl PublisherCapsAccount { pub fn del_price(&mut self, symbol: Pubkey) -> Result<(), ProgramError> { let symbol_index = self - .symbols + .prices .iter() .position(|&x| x == symbol) .ok_or(ProgramError::InvalidArgument)?; // update symbol list - self.symbols[symbol_index] = self.symbols[self.num_symbols - 1]; - self.symbols[self.num_symbols - 1] = Pubkey::default(); + self.prices[symbol_index] = self.prices[self.num_symbols - 1]; + self.prices[self.num_symbols - 1] = Pubkey::default(); // update publisher permissions for i in 0..self.num_publishers { @@ -150,7 +150,7 @@ impl PublisherCapsAccount { pub fn calculate_caps(&mut self) -> Result<(), ProgramError> { let symbol_scores: Vec = self - .symbols + .prices .iter() .enumerate() .map(|(j, _)| { @@ -166,7 +166,7 @@ impl PublisherCapsAccount { for i in 0..self.num_publishers { self.caps[i] = self - .symbols + .prices .iter() .enumerate() .filter(|(j, _)| self.get_publisher_permission(i, *j)) @@ -232,7 +232,7 @@ mod tests { publisher_permissions: [[0; PC_MAX_SYMBOLS_64 as usize]; PC_MAX_PUBLISHERS as usize], caps: [0; PC_MAX_PUBLISHERS as usize], publishers: [Pubkey::default(); PC_MAX_PUBLISHERS as usize], - symbols: [Pubkey::default(); PC_MAX_SYMBOLS as usize], + prices: [Pubkey::default(); PC_MAX_SYMBOLS as usize], } } diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index c04ac38ac..f1851164b 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -106,7 +106,7 @@ fn test_add_price() { { let score_data = load_checked::(&caps_account, PC_VERSION).unwrap(); - assert_eq!(score_data.symbols[0], *price_account_3.key); + assert_eq!(score_data.prices[0], *price_account_3.key); assert_eq!(score_data.num_symbols, 1); } From 0563db618b37c8fe0688d44e3214f3e53794f1b0 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 16:12:08 -0700 Subject: [PATCH 25/39] refactor: update publisher permission index to use price instead of symbol --- program/rust/src/accounts/publisher_caps.rs | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 832f1ac8f..2b97c8a46 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -97,24 +97,24 @@ impl PublisherCapsAccount { .position(|&x| x == publisher) .ok_or(ProgramError::InvalidArgument)?; - let symbol_index = self + let price_index = self .prices .iter() .position(|&x| x == price_account) .ok_or(ProgramError::InvalidArgument)?; - self.set_publisher_permission(publisher_index, symbol_index, false); + self.set_publisher_permission(publisher_index, price_index, false); self.calculate_caps() } pub fn add_price(&mut self, symbol: Pubkey) -> Result<(), ProgramError> { - let symbol_index = self + let price_index = self .prices .iter() .position(|&x| x == symbol) .unwrap_or(self.num_symbols); - if symbol_index == self.num_symbols { + if price_index == self.num_symbols { if self.num_symbols == PC_MAX_SYMBOLS as usize { return Err(ProgramError::AccountDataTooSmall); } @@ -127,20 +127,20 @@ impl PublisherCapsAccount { } pub fn del_price(&mut self, symbol: Pubkey) -> Result<(), ProgramError> { - let symbol_index = self + let price_index = self .prices .iter() .position(|&x| x == symbol) .ok_or(ProgramError::InvalidArgument)?; // update symbol list - self.prices[symbol_index] = self.prices[self.num_symbols - 1]; + self.prices[price_index] = self.prices[self.num_symbols - 1]; self.prices[self.num_symbols - 1] = Pubkey::default(); // update publisher permissions for i in 0..self.num_publishers { let value = self.get_publisher_permission(i, self.num_symbols - 1); - self.set_publisher_permission(i, symbol_index, value) + self.set_publisher_permission(i, price_index, value) } self.num_symbols -= 1; @@ -149,18 +149,18 @@ impl PublisherCapsAccount { pub fn calculate_caps(&mut self) -> Result<(), ProgramError> { - let symbol_scores: Vec = self + let price_weights: Vec = self .prices .iter() .enumerate() .map(|(j, _)| { - let score = self + let weight = self .publisher_permissions .iter() .enumerate() .filter(|(i, _)| self.get_publisher_permission(*i, j)) .count() as u64; - max(score, self.z) + max(weight, self.z) }) .collect(); @@ -170,7 +170,7 @@ impl PublisherCapsAccount { .iter() .enumerate() .filter(|(j, _)| self.get_publisher_permission(i, *j)) - .map(|(j, _)| self.m * 1_000_000_000_u64 / symbol_scores[j]) + .map(|(j, _)| self.m * 1_000_000_000_u64 / price_weights[j]) .sum(); } Ok(()) From 6c321bf64e58e041b1ad4603d590147e9b98630a Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 16:13:24 -0700 Subject: [PATCH 26/39] refactor: update publisher permission index to use price instead of symbol --- program/rust/src/accounts/publisher_caps.rs | 23 +-------------------- program/rust/src/tests/test_add_price.rs | 4 ++-- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 2b97c8a46..42c720fc4 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -30,7 +30,7 @@ pub struct PublisherCapsAccount { pub header: AccountHeader, pub num_publishers: usize, pub num_symbols: usize, - // Z is a constant used to normalize the scores + // Z is a constant used to normalize the caps pub z: u64, // M is a constant showing the target stake per symbol pub m: u64, @@ -175,26 +175,6 @@ impl PublisherCapsAccount { } Ok(()) } - - // #[allow(clippy::needless_range_loop)] - // pub fn calculate_scores(&mut self) -> Result<(), ProgramError> { - // let mut symbol_scores: Vec = vec![0; self.num_symbols]; - // for j in 0..self.num_symbols { - // for i in 0..self.num_publishers { - // symbol_scores[j] += self.get_publisher_permission(i, j) as u64; - // } - // symbol_scores[j] = max(symbol_scores[j], self.z); - // } - - // for i in 0..self.num_publishers { - // self.caps[i] = 0; - // for j in 0..self.num_symbols { - // self.caps[i] += self.get_publisher_permission(i, j) as u64 * 1_000_000_000_u64 - // / symbol_scores[j]; - // } - // } - // Ok(()) - // } } impl PythAccount for PublisherCapsAccount { @@ -204,7 +184,6 @@ impl PythAccount for PublisherCapsAccount { } -// write tests #[cfg(test)] mod tests { use { diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index f1851164b..15d7cb82c 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -66,8 +66,8 @@ fn test_add_price() { let mut permissions_setup = AccountSetup::new_permission(&program_id); let permissions_account = permissions_setup.as_account_info(); - let mut scores_setup = AccountSetup::new::(&program_id); - let caps_account = scores_setup.as_account_info(); + let mut caps_setup = AccountSetup::new::(&program_id); + let caps_account = caps_setup.as_account_info(); PublisherCapsAccount::initialize(&caps_account, PC_VERSION).unwrap(); { From 9e90897d967781ceb660213b82c8f1e7315e9048 Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 17 Jun 2024 16:15:59 -0700 Subject: [PATCH 27/39] chore: Update num-traits dependency to version 0.2 --- program/rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program/rust/Cargo.toml b/program/rust/Cargo.toml index df7537556..feec931fa 100644 --- a/program/rust/Cargo.toml +++ b/program/rust/Cargo.toml @@ -13,7 +13,6 @@ solana-program = "=1.13.3" bytemuck = "1.11.0" thiserror = "1.0" num-derive = "0.3" -num-traits = "0.2" byteorder = "1.4.3" serde = { version = "1.0", features = ["derive"], optional = true } strum = { version = "0.24.1", features = ["derive"], optional = true } @@ -26,6 +25,7 @@ tokio = "1.14.1" hex = "0.3.1" quickcheck = "1" rand = "0.8.5" +num-traits = "0.2" quickcheck_macros = "1" bincode = "1.3.3" serde = { version = "1.0", features = ["derive"] } From 5c1f75024b2fd2d2116b209aaff7cafadf01580c Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 18 Jun 2024 16:23:17 -0700 Subject: [PATCH 28/39] feat: add initial quickcheck for caps account --- program/rust/src/accounts.rs | 1 + program/rust/src/accounts/publisher_caps.rs | 49 ++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 0d1017246..260a2cb05 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -79,6 +79,7 @@ pub const PERMISSIONS_SEED: &str = "permissions"; pub const UPD_PRICE_WRITE_SEED: &str = "upd_price_write"; #[repr(C)] +#[cfg_attr(test, derive(Debug))] #[derive(Copy, Clone, Zeroable, Pod)] pub struct AccountHeader { pub magic_number: u32, diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 42c720fc4..08b44d4cd 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -21,6 +21,7 @@ use { }; #[repr(C)] +#[cfg_attr(test, derive(Debug))] #[derive(Copy, Clone, Pod, Zeroable)] /// This account is part of Community Integrity Pool (CIP) project. @@ -190,16 +191,50 @@ mod tests { super::*, crate::c_oracle_header::{ PC_ACCTYPE_SCORE, + PC_MAGIC, PC_VERSION, }, + quickcheck::Arbitrary, + quickcheck_macros::quickcheck, solana_program::pubkey::Pubkey, std::mem::size_of, }; + fn arbitrary_pubkey(g: &mut quickcheck::Gen) -> Pubkey { + let mut key = [0u8; 32]; + key.iter_mut().for_each(|item| *item = u8::arbitrary(g)); + Pubkey::new_from_array(key) + } + + impl Arbitrary for PublisherCapsAccount { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let mut account = PublisherCapsAccount { + header: AccountHeader { + magic_number: PC_MAGIC, + account_type: PC_ACCTYPE_SCORE, + version: PC_VERSION, + size: size_of::() as u32, + }, + num_publishers: usize::arbitrary(g) % (PC_MAX_PUBLISHERS - 10) as usize + 10, + num_symbols: usize::arbitrary(g) % (PC_MAX_SYMBOLS - 10) as usize + 10, + z: u64::arbitrary(g) % 5 + 2, + m: u64::arbitrary(g) % 1000 + 1000, + publisher_permissions: [[u64::arbitrary(g); PC_MAX_SYMBOLS_64 as usize]; + PC_MAX_PUBLISHERS as usize], + caps: [0; PC_MAX_PUBLISHERS as usize], + publishers: [arbitrary_pubkey(g); PC_MAX_PUBLISHERS as usize], + prices: [arbitrary_pubkey(g); PC_MAX_SYMBOLS as usize], + }; + + account.calculate_caps().unwrap(); + account + } + } + fn get_empty_account() -> PublisherCapsAccount { PublisherCapsAccount { header: AccountHeader { - magic_number: PC_ACCTYPE_SCORE, + magic_number: PC_MAGIC, account_type: PC_ACCTYPE_SCORE, version: PC_VERSION, size: size_of::() as u32, @@ -304,4 +339,16 @@ mod tests { assert_eq!(account.caps[..3], [0, 0, 500000000000]); } + + #[allow(clippy::assertions_on_constants)] + #[quickcheck] + fn test_arbitrary_account(account: PublisherCapsAccount) { + assert_eq!(account.header.magic_number, PC_MAGIC); + assert_eq!(account.header.account_type, PC_ACCTYPE_SCORE); + assert_eq!(account.header.version, PC_VERSION); + assert_eq!( + account.header.size as usize, + size_of::() + ); + } } From 02f6257cb46dcd34238ff5d35c6d00d90a5cb4d4 Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 18 Jun 2024 16:44:32 -0700 Subject: [PATCH 29/39] refactor: update upd_price.rs to use send_message_to_message_buffer function --- program/rust/src/processor/upd_price.rs | 70 ++++------------------- program/rust/src/utils.rs | 76 ++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 61 deletions(-) diff --git a/program/rust/src/processor/upd_price.rs b/program/rust/src/processor/upd_price.rs index 7c1a98d06..44ddd065a 100644 --- a/program/rust/src/processor/upd_price.rs +++ b/program/rust/src/processor/upd_price.rs @@ -18,7 +18,9 @@ use { get_status_for_conf_price_ratio, is_component_update, pyth_assert, + send_message_to_message_buffer, try_convert, + MessageBufferAccounts, }, OracleError, }, @@ -26,11 +28,6 @@ use { account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, - instruction::{ - AccountMeta, - Instruction, - }, - program::invoke_signed, program_error::ProgramError, program_memory::sol_memcmp, pubkey::Pubkey, @@ -202,36 +199,6 @@ pub fn upd_price( { if let Some(accumulator_accounts) = maybe_accumulator_accounts { if price_data.message_sent_ == 0 { - // Check that the oracle PDA is correctly configured for the program we are calling. - let oracle_auth_seeds: &[&[u8]] = &[ - UPD_PRICE_WRITE_SEED.as_bytes(), - &accumulator_accounts.program_id.key.to_bytes(), - ]; - let (expected_oracle_auth_pda, bump) = - Pubkey::find_program_address(oracle_auth_seeds, program_id); - pyth_assert( - expected_oracle_auth_pda == *accumulator_accounts.oracle_auth_pda.key, - OracleError::InvalidPda.into(), - )?; - - let account_metas = vec![ - AccountMeta { - pubkey: *accumulator_accounts.whitelist.key, - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: *accumulator_accounts.oracle_auth_pda.key, - is_signer: true, - is_writable: false, - }, - AccountMeta { - pubkey: *accumulator_accounts.message_buffer_data.key, - is_signer: false, - is_writable: true, - }, - ]; - let message = vec![ price_data .as_price_feed_message(price_account.key) @@ -239,23 +206,15 @@ pub fn upd_price( price_data.as_twap_message(price_account.key).to_bytes(), ]; - // Append a TWAP message if available - - // anchor discriminator for "global:put_all" - let discriminator: [u8; 8] = [212, 225, 193, 91, 151, 238, 20, 93]; - let create_inputs_ix = Instruction::new_with_borsh( - *accumulator_accounts.program_id.key, - &(discriminator, price_account.key.to_bytes(), message), - account_metas, - ); - - let auth_seeds_with_bump: &[&[u8]] = &[ - UPD_PRICE_WRITE_SEED.as_bytes(), - &accumulator_accounts.program_id.key.to_bytes(), - &[bump], - ]; + send_message_to_message_buffer( + program_id, + price_account.key, + UPD_PRICE_WRITE_SEED, + accounts, + accumulator_accounts, + message, + )?; - invoke_signed(&create_inputs_ix, accounts, &[auth_seeds_with_bump])?; price_data.message_sent_ = 1; } } @@ -322,15 +281,6 @@ fn find_publisher_index(comps: &[PriceComponent], key: &Pubkey) -> Option binary_search_result } -#[allow(dead_code)] -// Wrapper struct for the accounts required to add data to the accumulator program. -struct MessageBufferAccounts<'a, 'b: 'a> { - program_id: &'a AccountInfo<'b>, - whitelist: &'a AccountInfo<'b>, - oracle_auth_pda: &'a AccountInfo<'b>, - message_buffer_data: &'a AccountInfo<'b>, -} - #[cfg(test)] mod test { use { diff --git a/program/rust/src/utils.rs b/program/rust/src/utils.rs index 630dfc0d2..86d1fb43c 100644 --- a/program/rust/src/utils.rs +++ b/program/rust/src/utils.rs @@ -28,7 +28,14 @@ use { solana_program::{ account_info::AccountInfo, bpf_loader_upgradeable, - program::invoke, + instruction::{ + AccountMeta, + Instruction, + }, + program::{ + invoke, + invoke_signed, + }, program_error::ProgramError, pubkey::Pubkey, system_instruction::transfer, @@ -258,3 +265,70 @@ pub fn send_lamports<'a>( )?; Ok(()) } + +// Wrapper struct for the accounts required to add data to the accumulator program. +pub struct MessageBufferAccounts<'a, 'b: 'a> { + pub program_id: &'a AccountInfo<'b>, + pub whitelist: &'a AccountInfo<'b>, + pub oracle_auth_pda: &'a AccountInfo<'b>, + pub message_buffer_data: &'a AccountInfo<'b>, +} + +pub fn send_message_to_message_buffer( + program_id: &Pubkey, + base_account_key: &Pubkey, + seed: &str, + accounts: &[AccountInfo], + accumulator_accounts: MessageBufferAccounts, + message: Vec>, +) -> Result<(), ProgramError> { + let account_metas = vec![ + AccountMeta { + pubkey: *accumulator_accounts.whitelist.key, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: *accumulator_accounts.oracle_auth_pda.key, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: *accumulator_accounts.message_buffer_data.key, + is_signer: false, + is_writable: true, + }, + ]; + + // Check that the oracle PDA is correctly configured for the program we are calling. + let oracle_auth_seeds: &[&[u8]] = &[ + seed.as_bytes(), + &accumulator_accounts.program_id.key.to_bytes(), + ]; + let (expected_oracle_auth_pda, bump) = + Pubkey::find_program_address(oracle_auth_seeds, program_id); + pyth_assert( + expected_oracle_auth_pda == *accumulator_accounts.oracle_auth_pda.key, + OracleError::InvalidPda.into(), + )?; + + // Append a TWAP message if available + + // anchor discriminator for "global:put_all" + let discriminator: [u8; 8] = [212, 225, 193, 91, 151, 238, 20, 93]; + let create_inputs_ix = Instruction::new_with_borsh( + *accumulator_accounts.program_id.key, + &(discriminator, base_account_key.to_bytes(), message), + account_metas, + ); + + let auth_seeds_with_bump: &[&[u8]] = &[ + seed.as_bytes(), + &accumulator_accounts.program_id.key.to_bytes(), + &[bump], + ]; + + invoke_signed(&create_inputs_ix, accounts, &[auth_seeds_with_bump])?; + + Ok(()) +} From 7800412817264af57cf6dca67189204c6eaff4f0 Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 18 Jun 2024 17:09:16 -0700 Subject: [PATCH 30/39] feat: send caps to message buffer for add publisher --- program/rust/src/accounts.rs | 2 + program/rust/src/accounts/publisher_caps.rs | 14 +++++++ program/rust/src/processor/add_publisher.rs | 42 ++++++++++++++++++--- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 260a2cb05..d41645ec2 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -78,6 +78,8 @@ pub const PERMISSIONS_SEED: &str = "permissions"; /// such that the caller can authenticate its origin. pub const UPD_PRICE_WRITE_SEED: &str = "upd_price_write"; +pub const CAPS_WRITE_SEED: &str = "caps_write"; + #[repr(C)] #[cfg_attr(test, derive(Debug))] #[derive(Copy, Clone, Zeroable, Pod)] diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 08b44d4cd..3a29d3c26 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -176,6 +176,20 @@ impl PublisherCapsAccount { } Ok(()) } + + pub fn to_message(self) -> Vec> { + self.publishers + .iter() + .enumerate() + .map(|(i, pubkey)| { + pubkey + .to_bytes() + .into_iter() + .chain(self.caps[i].to_le_bytes().into_iter()) + .collect() + }) + .collect() + } } impl PythAccount for PublisherCapsAccount { diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index e197b60a3..479b761c0 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -5,6 +5,7 @@ use { PriceComponent, PublisherCapsAccount, PythAccount, + CAPS_WRITE_SEED, }, c_oracle_header::PC_NUM_COMP, deserialize::{ @@ -16,7 +17,9 @@ use { check_permissioned_funding_account, check_valid_funding_account, pyth_assert, + send_message_to_message_buffer, try_convert, + MessageBufferAccounts, }, OracleError, }, @@ -47,9 +50,26 @@ pub fn add_publisher( ProgramError::InvalidArgument, )?; - let (funding_account, price_account, permissions_account, caps_account) = match accounts { - [x, y, p] => Ok((x, y, p, None)), - [x, y, p, s] => Ok((x, y, p, Some(s))), + let ( + funding_account, + price_account, + permissions_account, + caps_account, + maybe_accumulator_accounts, + ) = match accounts { + [x, y, p] => Ok((x, y, p, None, None)), + [x, y, p, s, a, b, c, d] => Ok(( + x, + y, + p, + Some(s), + Some(MessageBufferAccounts { + program_id: a, + whitelist: b, + oracle_auth_pda: c, + message_buffer_data: d, + }), + )), _ => Err(OracleError::InvalidNumberOfAccounts), }?; @@ -117,10 +137,22 @@ pub fn add_publisher( price_data.header.size = try_convert::<_, u32>(PriceAccount::INITIAL_SIZE)?; - if let Some(caps_account) = caps_account { + if let Some(caps_account_info) = caps_account { let mut caps_account = - load_checked::(caps_account, cmd_args.header.version)?; + load_checked::(caps_account_info, cmd_args.header.version)?; caps_account.add_publisher(cmd_args.publisher, *price_account.key)?; + + // Feature-gated accumulator-specific code, used only on pythnet/pythtest + if let Some(accumulator_accounts) = maybe_accumulator_accounts { + send_message_to_message_buffer( + program_id, + caps_account_info.key, + CAPS_WRITE_SEED, + accounts, + accumulator_accounts, + caps_account.to_message(), + )?; + } } Ok(()) From 9b52a40a1b1d4d2a588186acdc82dbf213c70d4e Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 18 Jun 2024 17:10:20 -0700 Subject: [PATCH 31/39] fix: comment migration code to test program size --- program/rust/src/processor/add_publisher.rs | 29 +++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index 479b761c0..d498e7574 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -85,22 +85,23 @@ pub fn add_publisher( let mut price_data = load_checked::(price_account, cmd_args.header.version)?; + // TODO: uncomment for migration // Use the call with the default pubkey (000..) as a trigger to update caps account // migration step for initializing the caps account - if cmd_args.publisher == Pubkey::default() { - let num_comps = try_convert::(price_data.num_)?; - - if let Some(caps_account) = caps_account { - let mut caps_account = - load_checked::(caps_account, cmd_args.header.version)?; - caps_account.add_price(*price_account.key)?; - for publisher in price_data.comp_[..num_comps].iter() { - caps_account.add_publisher(publisher.pub_, *price_account.key)?; - } - } - - return Ok(()); - } + // if cmd_args.publisher == Pubkey::default() { + // let num_comps = try_convert::(price_data.num_)?; + + // if let Some(caps_account) = caps_account { + // let mut caps_account = + // load_checked::(caps_account, cmd_args.header.version)?; + // caps_account.add_price(*price_account.key)?; + // for publisher in price_data.comp_[..num_comps].iter() { + // caps_account.add_publisher(publisher.pub_, *price_account.key)?; + // } + // } + + // return Ok(()); + // } if price_data.num_ >= PC_NUM_COMP { return Err(ProgramError::InvalidArgument); From a18ec0cbb33f4e9e20c458742b58114f3770f57b Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 18 Jun 2024 17:12:48 -0700 Subject: [PATCH 32/39] feat: remove init mapping to reduce program size --- program/rust/src/processor.rs | 4 +- program/rust/src/processor/init_mapping.rs | 50 ---------------------- 2 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 program/rust/src/processor/init_mapping.rs diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index b8902165f..621210171 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -19,7 +19,6 @@ mod add_publisher; mod del_price; mod del_product; mod del_publisher; -mod init_mapping; mod init_price; mod set_max_latency; mod set_min_pub; @@ -34,7 +33,6 @@ pub use { del_price::del_price, del_product::del_product, del_publisher::del_publisher, - init_mapping::init_mapping, init_price::init_price, set_max_latency::set_max_latency, set_min_pub::set_min_pub, @@ -57,7 +55,7 @@ pub fn process_instruction( use OracleCommand::*; match load_command_header_checked(instruction_data)? { - InitMapping => init_mapping(program_id, accounts, instruction_data), + InitMapping => Err(OracleError::UnrecognizedInstruction.into()), AddMapping => Err(OracleError::UnrecognizedInstruction.into()), AddProduct => add_product(program_id, accounts, instruction_data), UpdProduct => upd_product(program_id, accounts, instruction_data), diff --git a/program/rust/src/processor/init_mapping.rs b/program/rust/src/processor/init_mapping.rs deleted file mode 100644 index 9fa2c6547..000000000 --- a/program/rust/src/processor/init_mapping.rs +++ /dev/null @@ -1,50 +0,0 @@ -use { - crate::{ - accounts::{ - MappingAccount, - PythAccount, - }, - deserialize::load, - instruction::CommandHeader, - utils::{ - check_permissioned_funding_account, - check_valid_funding_account, - }, - OracleError, - }, - solana_program::{ - account_info::AccountInfo, - entrypoint::ProgramResult, - pubkey::Pubkey, - }, -}; - -/// Initialize first mapping list account -// account[0] funding account [signer writable] -// account[1] mapping account [signer writable] -pub fn init_mapping( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - let (funding_account, fresh_mapping_account, permissions_account) = match accounts { - [x, y, p] => Ok((x, y, p)), - _ => Err(OracleError::InvalidNumberOfAccounts), - }?; - - let hdr = load::(instruction_data)?; - - check_valid_funding_account(funding_account)?; - check_permissioned_funding_account( - program_id, - fresh_mapping_account, - funding_account, - permissions_account, - hdr, - )?; - - // Initialize by setting to zero again (just in case) and populating the account header - MappingAccount::initialize(fresh_mapping_account, hdr.version)?; - - Ok(()) -} From 59324023afecf0b7e2b41e726fee1fda528abde8 Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 18 Jun 2024 17:30:41 -0700 Subject: [PATCH 33/39] feat: keep init mapping for tests --- program/rust/src/processor.rs | 6 +++ program/rust/src/processor/init_mapping.rs | 52 ++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 program/rust/src/processor/init_mapping.rs diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index 621210171..b924ec820 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -19,6 +19,7 @@ mod add_publisher; mod del_price; mod del_product; mod del_publisher; +mod init_mapping; mod init_price; mod set_max_latency; mod set_min_pub; @@ -26,6 +27,8 @@ mod upd_permissions; mod upd_price; mod upd_product; +#[cfg(test)] +use init_mapping::init_mapping; pub use { add_price::add_price, add_product::add_product, @@ -55,6 +58,9 @@ pub fn process_instruction( use OracleCommand::*; match load_command_header_checked(instruction_data)? { + #[cfg(test)] + InitMapping => init_mapping(program_id, accounts, instruction_data), + #[cfg(not(test))] InitMapping => Err(OracleError::UnrecognizedInstruction.into()), AddMapping => Err(OracleError::UnrecognizedInstruction.into()), AddProduct => add_product(program_id, accounts, instruction_data), diff --git a/program/rust/src/processor/init_mapping.rs b/program/rust/src/processor/init_mapping.rs new file mode 100644 index 000000000..9197d1260 --- /dev/null +++ b/program/rust/src/processor/init_mapping.rs @@ -0,0 +1,52 @@ +#[cfg(test)] +use { + crate::{ + accounts::{ + MappingAccount, + PythAccount, + }, + deserialize::load, + instruction::CommandHeader, + utils::{ + check_permissioned_funding_account, + check_valid_funding_account, + }, + OracleError, + }, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + pubkey::Pubkey, + }, +}; + +/// Initialize first mapping list account +// account[0] funding account [signer writable] +// account[1] mapping account [signer writable] +#[cfg(test)] +pub fn init_mapping( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let (funding_account, fresh_mapping_account, permissions_account) = match accounts { + [x, y, p] => Ok((x, y, p)), + _ => Err(OracleError::InvalidNumberOfAccounts), + }?; + + let hdr = load::(instruction_data)?; + + check_valid_funding_account(funding_account)?; + check_permissioned_funding_account( + program_id, + fresh_mapping_account, + funding_account, + permissions_account, + hdr, + )?; + + // Initialize by setting to zero again (just in case) and populating the account header + MappingAccount::initialize(fresh_mapping_account, hdr.version)?; + + Ok(()) +} From 21c38c8582370e0bd1113535131a17c12fbca2b8 Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 18 Jun 2024 20:25:39 -0700 Subject: [PATCH 34/39] fix: self ref error --- program/rust/src/accounts/publisher_caps.rs | 2 +- program/rust/src/processor/add_publisher.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/program/rust/src/accounts/publisher_caps.rs b/program/rust/src/accounts/publisher_caps.rs index 3a29d3c26..14b54b599 100644 --- a/program/rust/src/accounts/publisher_caps.rs +++ b/program/rust/src/accounts/publisher_caps.rs @@ -177,7 +177,7 @@ impl PublisherCapsAccount { Ok(()) } - pub fn to_message(self) -> Vec> { + pub fn get_caps_message(&self) -> Vec> { self.publishers .iter() .enumerate() diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index d498e7574..44ce3b33d 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -151,7 +151,7 @@ pub fn add_publisher( CAPS_WRITE_SEED, accounts, accumulator_accounts, - caps_account.to_message(), + caps_account.get_caps_message(), )?; } } From 413abc38bd0822ebe8a965515e363a5de3b7497a Mon Sep 17 00:00:00 2001 From: keyvan Date: Wed, 19 Jun 2024 11:31:39 -0700 Subject: [PATCH 35/39] fix: revert mapping account changes --- program/rust/src/processor.rs | 6 +----- program/rust/src/processor/init_mapping.rs | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index b924ec820..b8902165f 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -27,8 +27,6 @@ mod upd_permissions; mod upd_price; mod upd_product; -#[cfg(test)] -use init_mapping::init_mapping; pub use { add_price::add_price, add_product::add_product, @@ -36,6 +34,7 @@ pub use { del_price::del_price, del_product::del_product, del_publisher::del_publisher, + init_mapping::init_mapping, init_price::init_price, set_max_latency::set_max_latency, set_min_pub::set_min_pub, @@ -58,10 +57,7 @@ pub fn process_instruction( use OracleCommand::*; match load_command_header_checked(instruction_data)? { - #[cfg(test)] InitMapping => init_mapping(program_id, accounts, instruction_data), - #[cfg(not(test))] - InitMapping => Err(OracleError::UnrecognizedInstruction.into()), AddMapping => Err(OracleError::UnrecognizedInstruction.into()), AddProduct => add_product(program_id, accounts, instruction_data), UpdProduct => upd_product(program_id, accounts, instruction_data), diff --git a/program/rust/src/processor/init_mapping.rs b/program/rust/src/processor/init_mapping.rs index 9197d1260..9fa2c6547 100644 --- a/program/rust/src/processor/init_mapping.rs +++ b/program/rust/src/processor/init_mapping.rs @@ -1,4 +1,3 @@ -#[cfg(test)] use { crate::{ accounts::{ @@ -23,7 +22,6 @@ use { /// Initialize first mapping list account // account[0] funding account [signer writable] // account[1] mapping account [signer writable] -#[cfg(test)] pub fn init_mapping( program_id: &Pubkey, accounts: &[AccountInfo], From 23a1da86f97dc736483855e48e16202738a557a7 Mon Sep 17 00:00:00 2001 From: keyvan Date: Wed, 19 Jun 2024 12:18:17 -0700 Subject: [PATCH 36/39] feat: add cfg test flag for init mapping --- program/rust/src/processor.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index b8902165f..4b5b4dade 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -19,6 +19,7 @@ mod add_publisher; mod del_price; mod del_product; mod del_publisher; +#[cfg(test)] mod init_mapping; mod init_price; mod set_max_latency; @@ -27,6 +28,8 @@ mod upd_permissions; mod upd_price; mod upd_product; +#[cfg(test)] +pub use init_mapping::init_mapping; pub use { add_price::add_price, add_product::add_product, @@ -34,7 +37,6 @@ pub use { del_price::del_price, del_product::del_product, del_publisher::del_publisher, - init_mapping::init_mapping, init_price::init_price, set_max_latency::set_max_latency, set_min_pub::set_min_pub, @@ -57,7 +59,16 @@ pub fn process_instruction( use OracleCommand::*; match load_command_header_checked(instruction_data)? { - InitMapping => init_mapping(program_id, accounts, instruction_data), + InitMapping => { + #[cfg(test)] + { + init_mapping(program_id, accounts, instruction_data) + } + #[cfg(not(test))] + { + Err(OracleError::UnrecognizedInstruction.into()) + } + } AddMapping => Err(OracleError::UnrecognizedInstruction.into()), AddProduct => add_product(program_id, accounts, instruction_data), UpdProduct => upd_product(program_id, accounts, instruction_data), From 1a11ca4901f7b70d3d9018591cc4813507cc889f Mon Sep 17 00:00:00 2001 From: keyvan Date: Wed, 19 Jun 2024 12:18:33 -0700 Subject: [PATCH 37/39] temporary remove pipeline test --- scripts/build-bpf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-bpf.sh b/scripts/build-bpf.sh index fd85e4fa0..a1c36496b 100755 --- a/scripts/build-bpf.sh +++ b/scripts/build-bpf.sh @@ -20,7 +20,7 @@ set -x cd "${PYTH_DIR}" # Re-run tests affected by features -cargo-test-bpf +# cargo-test-bpf cargo-build-bpf -- --locked -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort sha256sum ./target/**/*.so From 1fa4c6a90533d615454e2139eccfbf00b718f2b1 Mon Sep 17 00:00:00 2001 From: keyvan Date: Wed, 19 Jun 2024 12:37:44 -0700 Subject: [PATCH 38/39] remove upd_permissions to check size --- program/rust/src/accounts.rs | 27 +++++++++++++++++---------- program/rust/src/processor.rs | 20 ++++++++++++++++---- program/rust/src/utils.rs | 20 +++++++++++++------- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index d41645ec2..1e0a531e1 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -1,5 +1,20 @@ //! Account types and utilities for working with Pyth accounts. +#[cfg(test)] +use { + crate::utils::get_rent, + solana_program::program_memory::sol_memset, + std::borrow::BorrowMut, +}; +#[cfg(test)] +use { + crate::utils::try_convert, + solana_program::{ + program::invoke_signed, + pubkey::Pubkey, + system_instruction::create_account, + }, +}; use { crate::{ c_oracle_header::PC_MAGIC, @@ -7,9 +22,7 @@ use { error::OracleError, utils::{ check_valid_fresh_account, - get_rent, pyth_assert, - try_convert, }, }, bytemuck::{ @@ -18,21 +31,13 @@ use { }, solana_program::{ account_info::AccountInfo, - program::invoke_signed, program_error::ProgramError, - pubkey::Pubkey, - system_instruction::create_account, }, std::{ cell::RefMut, mem::size_of, }, }; -#[cfg(test)] -use { - solana_program::program_memory::sol_memset, - std::borrow::BorrowMut, -}; mod mapping; @@ -136,6 +141,7 @@ pub trait PythAccount: Pod { /// Creates PDA accounts only when needed, and initializes it as one of the Pyth accounts. /// This PDA initialization assumes that the account has 0 lamports. /// TO DO: Fix this once we can resize the program. + #[cfg(test)] fn initialize_pda<'a>( account: &AccountInfo<'a>, funding_account: &AccountInfo<'a>, @@ -163,6 +169,7 @@ pub trait PythAccount: Pod { } } +#[cfg(test)] fn create<'a>( from: &AccountInfo<'a>, to: &AccountInfo<'a>, diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index 4b5b4dade..e2fade02e 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -24,12 +24,11 @@ mod init_mapping; mod init_price; mod set_max_latency; mod set_min_pub; +#[cfg(test)] mod upd_permissions; mod upd_price; mod upd_product; -#[cfg(test)] -pub use init_mapping::init_mapping; pub use { add_price::add_price, add_product::add_product, @@ -40,7 +39,6 @@ pub use { init_price::init_price, set_max_latency::set_max_latency, set_min_pub::set_min_pub, - upd_permissions::upd_permissions, upd_price::{ c_upd_aggregate, c_upd_twap, @@ -49,6 +47,11 @@ pub use { }, upd_product::upd_product, }; +#[cfg(test)] +pub use { + init_mapping::init_mapping, + upd_permissions::upd_permissions, +}; /// Dispatch to the right instruction in the oracle. pub fn process_instruction( @@ -88,7 +91,16 @@ pub fn process_instruction( } DelPrice => del_price(program_id, accounts, instruction_data), DelProduct => del_product(program_id, accounts, instruction_data), - UpdPermissions => upd_permissions(program_id, accounts, instruction_data), + UpdPermissions => { + #[cfg(test)] + { + upd_permissions(program_id, accounts, instruction_data) + } + #[cfg(not(test))] + { + Err(OracleError::UnrecognizedInstruction.into()) + } + } SetMaxLatency => set_max_latency(program_id, accounts, instruction_data), } } diff --git a/program/rust/src/utils.rs b/program/rust/src/utils.rs index 86d1fb43c..a45c6a40e 100644 --- a/program/rust/src/utils.rs +++ b/program/rust/src/utils.rs @@ -27,7 +27,6 @@ use { }, solana_program::{ account_info::AccountInfo, - bpf_loader_upgradeable, instruction::{ AccountMeta, Instruction, @@ -39,7 +38,13 @@ use { program_error::ProgramError, pubkey::Pubkey, system_instruction::transfer, - sysvar::rent::Rent, + }, +}; +#[cfg(test)] +use { + solana_program::{ + bpf_loader_upgradeable, + rent::Rent, }, std::cell::Ref, }; @@ -210,6 +215,7 @@ struct ProgramdataAccount { /// Check that `programdata_account` is actually the buffer for `program_id`. /// Check that the authority in `programdata_account` matches `upgrade_authority_account`. +#[cfg(test)] pub fn check_is_upgrade_authority_for_program( upgrade_authority_account: &AccountInfo, programdata_account: &AccountInfo, @@ -240,11 +246,11 @@ pub fn check_is_upgrade_authority_for_program( Ok(()) } -#[cfg(not(test))] -pub fn get_rent() -> Result { - use solana_program::sysvar::Sysvar; - Rent::get() -} +// #[cfg(not(test))] +// pub fn get_rent() -> Result { +// use solana_program::sysvar::Sysvar; +// Rent::get() +// } #[cfg(test)] pub fn get_rent() -> Result { From dd453fbe4a1e458e63994e6417b477f951e81e13 Mon Sep 17 00:00:00 2001 From: keyvan Date: Wed, 19 Jun 2024 16:11:24 -0700 Subject: [PATCH 39/39] add feature test to be able to compile oracle for test --- program/rust/Cargo.toml | 4 +++- program/rust/src/accounts.rs | 24 +++++++++++------------- program/rust/src/instruction.rs | 4 ++-- program/rust/src/processor.rs | 14 +++++++------- program/rust/src/tests/test_utils.rs | 5 ++++- program/rust/src/utils.rs | 6 +++--- scripts/build-bpf.sh | 2 +- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/program/rust/Cargo.toml b/program/rust/Cargo.toml index feec931fa..b4b90bbce 100644 --- a/program/rust/Cargo.toml +++ b/program/rust/Cargo.toml @@ -17,6 +17,8 @@ byteorder = "1.4.3" serde = { version = "1.0", features = ["derive"], optional = true } strum = { version = "0.24.1", features = ["derive"], optional = true } pythnet-sdk = { git = "https://github.com/pyth-network/pyth-crosschain", rev="60144002053a93f424be70decd8a8ccb8d618d81"} +num-traits = "0.2" + [dev-dependencies] solana-program-test = "=1.13.3" @@ -25,7 +27,6 @@ tokio = "1.14.1" hex = "0.3.1" quickcheck = "1" rand = "0.8.5" -num-traits = "0.2" quickcheck_macros = "1" bincode = "1.3.3" serde = { version = "1.0", features = ["derive"] } @@ -38,6 +39,7 @@ csv = "1.1" check = [] # Skips make build in build.rs, use with cargo-clippy and cargo-check debug = [] library = [] +test = [] [lib] crate-type = ["cdylib", "lib"] diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 1e0a531e1..9639123ca 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -1,19 +1,12 @@ //! Account types and utilities for working with Pyth accounts. -#[cfg(test)] +#[cfg(any(feature = "test", test))] use { crate::utils::get_rent, - solana_program::program_memory::sol_memset, - std::borrow::BorrowMut, -}; -#[cfg(test)] -use { crate::utils::try_convert, - solana_program::{ - program::invoke_signed, - pubkey::Pubkey, - system_instruction::create_account, - }, + solana_program::program::invoke_signed, + solana_program::pubkey::Pubkey, + solana_program::system_instruction::create_account, }; use { crate::{ @@ -38,6 +31,11 @@ use { mem::size_of, }, }; +#[cfg(test)] +use { + solana_program::program_memory::sol_memset, + std::borrow::BorrowMut, +}; mod mapping; @@ -141,7 +139,7 @@ pub trait PythAccount: Pod { /// Creates PDA accounts only when needed, and initializes it as one of the Pyth accounts. /// This PDA initialization assumes that the account has 0 lamports. /// TO DO: Fix this once we can resize the program. - #[cfg(test)] + #[cfg(any(feature = "test", test))] fn initialize_pda<'a>( account: &AccountInfo<'a>, funding_account: &AccountInfo<'a>, @@ -169,7 +167,7 @@ pub trait PythAccount: Pod { } } -#[cfg(test)] +#[cfg(any(feature = "test", test))] fn create<'a>( from: &AccountInfo<'a>, to: &AccountInfo<'a>, diff --git a/program/rust/src/instruction.rs b/program/rust/src/instruction.rs index 301d64f72..cdedd5b7b 100644 --- a/program/rust/src/instruction.rs +++ b/program/rust/src/instruction.rs @@ -1,4 +1,4 @@ -#[cfg(test)] +#[cfg(any(feature = "test", test))] use num_derive::{ FromPrimitive, ToPrimitive, @@ -18,7 +18,7 @@ use { /// WARNING : NEW COMMANDS SHOULD BE ADDED AT THE END OF THE LIST #[repr(u32)] -#[cfg_attr(test, derive(FromPrimitive, ToPrimitive))] +#[cfg_attr(any(feature = "test", test), derive(FromPrimitive, ToPrimitive))] #[derive(PartialEq, Eq)] #[allow(dead_code)] pub enum OracleCommand { diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index e2fade02e..9a9868d84 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -19,12 +19,12 @@ mod add_publisher; mod del_price; mod del_product; mod del_publisher; -#[cfg(test)] +#[cfg(any(feature = "test", test))] mod init_mapping; mod init_price; mod set_max_latency; mod set_min_pub; -#[cfg(test)] +#[cfg(any(feature = "test", test))] mod upd_permissions; mod upd_price; mod upd_product; @@ -47,7 +47,7 @@ pub use { }, upd_product::upd_product, }; -#[cfg(test)] +#[cfg(any(feature = "test", test))] pub use { init_mapping::init_mapping, upd_permissions::upd_permissions, @@ -63,11 +63,11 @@ pub fn process_instruction( match load_command_header_checked(instruction_data)? { InitMapping => { - #[cfg(test)] + #[cfg(any(feature = "test", test))] { init_mapping(program_id, accounts, instruction_data) } - #[cfg(not(test))] + #[cfg(not(any(feature = "test", test)))] { Err(OracleError::UnrecognizedInstruction.into()) } @@ -92,11 +92,11 @@ pub fn process_instruction( DelPrice => del_price(program_id, accounts, instruction_data), DelProduct => del_product(program_id, accounts, instruction_data), UpdPermissions => { - #[cfg(test)] + #[cfg(any(feature = "test", test))] { upd_permissions(program_id, accounts, instruction_data) } - #[cfg(not(test))] + #[cfg(not(any(feature = "test", test)))] { Err(OracleError::UnrecognizedInstruction.into()) } diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 6dfb7b8b4..6fcdd13f7 100644 --- a/program/rust/src/tests/test_utils.rs +++ b/program/rust/src/tests/test_utils.rs @@ -1,3 +1,5 @@ +#[cfg(test)] +use num_traits::ToPrimitive; use { crate::{ accounts::{ @@ -12,7 +14,6 @@ use { OracleCommand, }, }, - num_traits::ToPrimitive, solana_program::{ account_info::AccountInfo, clock::{ @@ -34,6 +35,7 @@ use { solana_sdk::transaction::TransactionError, }; + const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 75824; /// The goal of this struct is to easily instantiate fresh solana accounts @@ -134,6 +136,7 @@ pub fn update_clock_slot(clock_account: &mut AccountInfo, slot: u64) { clock_data.to_account_info(clock_account); } +#[cfg(test)] impl From for CommandHeader { fn from(val: OracleCommand) -> Self { CommandHeader { diff --git a/program/rust/src/utils.rs b/program/rust/src/utils.rs index a45c6a40e..2ed0a8b4d 100644 --- a/program/rust/src/utils.rs +++ b/program/rust/src/utils.rs @@ -40,7 +40,7 @@ use { system_instruction::transfer, }, }; -#[cfg(test)] +#[cfg(any(feature = "test", test))] use { solana_program::{ bpf_loader_upgradeable, @@ -215,7 +215,7 @@ struct ProgramdataAccount { /// Check that `programdata_account` is actually the buffer for `program_id`. /// Check that the authority in `programdata_account` matches `upgrade_authority_account`. -#[cfg(test)] +#[cfg(any(feature = "test", test))] pub fn check_is_upgrade_authority_for_program( upgrade_authority_account: &AccountInfo, programdata_account: &AccountInfo, @@ -252,7 +252,7 @@ pub fn check_is_upgrade_authority_for_program( // Rent::get() // } -#[cfg(test)] +#[cfg(any(feature = "test", test))] pub fn get_rent() -> Result { Ok(Rent::default()) } diff --git a/scripts/build-bpf.sh b/scripts/build-bpf.sh index a1c36496b..64b95458e 100755 --- a/scripts/build-bpf.sh +++ b/scripts/build-bpf.sh @@ -20,7 +20,7 @@ set -x cd "${PYTH_DIR}" # Re-run tests affected by features -# cargo-test-bpf +cargo-test-bpf --features test cargo-build-bpf -- --locked -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort sha256sum ./target/**/*.so