From c97841b806f8702e9c882003164e51453a4ea5b2 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Wed, 10 Aug 2022 11:03:48 -0700 Subject: [PATCH 1/6] init price instruction --- program/c/src/oracle/oracle.c | 50 +------- program/rust/src/c_oracle_header.rs | 10 +- program/rust/src/processor.rs | 3 + program/rust/src/rust_oracle.rs | 85 ++++++++++++- program/rust/src/tests/mod.rs | 1 + program/rust/src/tests/test_init_price.rs | 140 ++++++++++++++++++++++ 6 files changed, 233 insertions(+), 56 deletions(-) create mode 100644 program/rust/src/tests/test_init_price.rs diff --git a/program/c/src/oracle/oracle.c b/program/c/src/oracle/oracle.c index cdc341f91..1dfe2e8c8 100644 --- a/program/c/src/oracle/oracle.c +++ b/program/c/src/oracle/oracle.c @@ -59,54 +59,6 @@ static bool valid_writable_account( SolParameters *prm, is_rent_exempt( *ka->lamports, ka->data_len ); } -static uint64_t init_price( SolParameters *prm, SolAccountInfo *ka ) -{ - // Validate command parameters - cmd_init_price_t *cptr = (cmd_init_price_t*)prm->data; - if ( prm->data_len != sizeof( cmd_init_price_t ) || - cptr->expo_ > PC_MAX_NUM_DECIMALS || - cptr->expo_ < -PC_MAX_NUM_DECIMALS ) { - return ERROR_INVALID_ARGUMENT; - } - - // Account (1) is the price account to (re)initialize - // Verify that these are signed, writable accounts with correct ownership - // and size - if ( prm->ka_num != 2 || - !valid_funding_account( &ka[0] ) || - !valid_signable_account( prm, &ka[1], sizeof( pc_price_t ) )) { - return ERROR_INVALID_ARGUMENT; - } - - // Verify that the price account is initialized - pc_price_t *sptr = (pc_price_t*)ka[1].data; - if ( sptr->magic_ != PC_MAGIC || - sptr->ver_ != cptr->ver_ || - sptr->type_ != PC_ACCTYPE_PRICE || - sptr->ptype_ != cptr->ptype_ ) { - return ERROR_INVALID_ARGUMENT; - } - - // (re)initialize price exponent and clear-down all quotes - sptr->expo_ = cptr->expo_; - sptr->last_slot_ = 0UL; - sptr->valid_slot_ = 0UL; - sptr->agg_.pub_slot_ = 0UL; - sptr->prev_slot_ = 0UL; - sptr->prev_price_ = 0L; - sptr->prev_conf_ = 0L; - sptr->prev_timestamp_ = 0L; - sol_memset( &sptr->twap_, 0, sizeof( pc_ema_t ) ); - sol_memset( &sptr->twac_, 0, sizeof( pc_ema_t ) ); - sol_memset( &sptr->agg_, 0, sizeof( pc_price_info_t ) ); - for(unsigned i=0; i != sptr->num_; ++i ) { - pc_price_comp_t *iptr = &sptr->comp_[i]; - sol_memset( &iptr->agg_, 0, sizeof( pc_price_info_t ) ); - sol_memset( &iptr->latest_, 0, sizeof( pc_price_info_t ) ); - } - return SUCCESS; -} - // remove publisher from price node static uint64_t del_publisher( SolParameters *prm, SolAccountInfo *ka ) { @@ -260,7 +212,7 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka ) case e_cmd_add_price: return ERROR_INVALID_ARGUMENT; case e_cmd_add_publisher: return ERROR_INVALID_ARGUMENT; case e_cmd_del_publisher: return del_publisher( prm, ka ); - case e_cmd_init_price: return init_price( prm, ka ); + case e_cmd_init_price: return ERROR_INVALID_ARGUMENT; case e_cmd_init_test: return ERROR_INVALID_ARGUMENT; case e_cmd_upd_test: return ERROR_INVALID_ARGUMENT; case e_cmd_set_min_pub: return ERROR_INVALID_ARGUMENT; diff --git a/program/rust/src/c_oracle_header.rs b/program/rust/src/c_oracle_header.rs index f74e2c7d2..8d7c96c16 100644 --- a/program/rust/src/c_oracle_header.rs +++ b/program/rust/src/c_oracle_header.rs @@ -103,7 +103,6 @@ unsafe impl Pod for cmd_hdr { } #[cfg(target_endian = "little")] - unsafe impl Zeroable for pc_price_info { } @@ -127,6 +126,7 @@ unsafe impl Zeroable for pc_ema { unsafe impl Pod for pc_ema { } +#[cfg(target_endian = "little")] unsafe impl Zeroable for cmd_add_price_t { } @@ -134,6 +134,14 @@ unsafe impl Zeroable for cmd_add_price_t { unsafe impl Pod for cmd_add_price_t { } +#[cfg(target_endian = "little")] +unsafe impl Zeroable for cmd_init_price_t { +} + +#[cfg(target_endian = "little")] +unsafe impl Pod for cmd_init_price_t { +} + #[cfg(target_endian = "little")] unsafe impl Zeroable for cmd_add_publisher_t { } diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index 661c43e0d..49fcdfec0 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -11,6 +11,7 @@ use crate::c_oracle_header::{ command_t_e_cmd_add_publisher, command_t_e_cmd_agg_price, command_t_e_cmd_init_mapping, + command_t_e_cmd_init_price, command_t_e_cmd_set_min_pub, command_t_e_cmd_upd_account_version, command_t_e_cmd_upd_price, @@ -29,6 +30,7 @@ use crate::rust_oracle::{ add_product, add_publisher, init_mapping, + init_price, set_min_pub, upd_product, update_price, @@ -64,6 +66,7 @@ pub fn process_instruction( } command_t_e_cmd_add_price => add_price(program_id, accounts, instruction_data), command_t_e_cmd_init_mapping => init_mapping(program_id, accounts, instruction_data), + command_t_e_cmd_init_price => init_price(program_id, accounts, instruction_data), command_t_e_cmd_add_mapping => add_mapping(program_id, accounts, instruction_data), command_t_e_cmd_add_publisher => add_publisher(program_id, accounts, instruction_data), command_t_e_cmd_add_product => add_product(program_id, accounts, instruction_data), diff --git a/program/rust/src/rust_oracle.rs b/program/rust/src/rust_oracle.rs index 77ef3fae4..f6b3d7a09 100644 --- a/program/rust/src/rust_oracle.rs +++ b/program/rust/src/rust_oracle.rs @@ -23,11 +23,14 @@ use crate::c_oracle_header::{ cmd_add_price_t, cmd_add_publisher_t, cmd_hdr_t, + cmd_init_price_t, cmd_set_min_pub_t, cmd_upd_product_t, pc_acc, + pc_ema_t, pc_map_table_t, pc_price_comp, + pc_price_info_t, pc_price_t, pc_prod_t, pc_pub_key_t, @@ -140,12 +143,11 @@ pub fn add_price( ) -> OracleResult { let cmd_args = load::(instruction_data)?; - if cmd_args.expo_ > PC_MAX_NUM_DECIMALS as i32 - || cmd_args.expo_ < -(PC_MAX_NUM_DECIMALS as i32) - || cmd_args.ptype_ == PC_PTYPE_UNKNOWN - { - return Err(ProgramError::InvalidArgument); - } + check_exponent_range(cmd_args.expo_)?; + pyth_assert( + cmd_args.ptype_ != PC_PTYPE_UNKNOWN, + ProgramError::InvalidArgument, + )?; let [funding_account, product_account, price_account] = match accounts { [x, y, z] => Ok([x, y, z]), @@ -169,6 +171,69 @@ pub fn add_price( Ok(SUCCESS) } +pub fn init_price( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> OracleResult { + let cmd_args = load::(instruction_data)?; + + check_exponent_range(cmd_args.expo_)?; + + let [funding_account, price_account] = match accounts { + [x, y] => Ok([x, y]), + _ => Err(ProgramError::InvalidArgument), + }?; + + check_valid_funding_account(funding_account)?; + check_valid_signable_account(program_id, price_account, size_of::())?; + + let mut price_data = load_checked::(price_account, cmd_args.ver_)?; + pyth_assert( + price_data.ptype_ == cmd_args.ptype_, + ProgramError::InvalidArgument, + )?; + + price_data.expo_ = cmd_args.expo_; + + price_data.last_slot_ = 0; + price_data.valid_slot_ = 0; + price_data.agg_.pub_slot_ = 0; + price_data.prev_slot_ = 0; + price_data.prev_price_ = 0; + price_data.prev_conf_ = 0; + price_data.prev_timestamp_ = 0; + sol_memset( + bytes_of_mut(&mut price_data.twap_), + 0, + size_of::(), + ); + sol_memset( + bytes_of_mut(&mut price_data.twac_), + 0, + size_of::(), + ); + sol_memset( + bytes_of_mut(&mut price_data.agg_), + 0, + size_of::(), + ); + for i in 0..(price_data.comp_.len() as usize) { + sol_memset( + bytes_of_mut(&mut price_data.comp_[i].agg_), + 0, + size_of::(), + ); + sol_memset( + bytes_of_mut(&mut price_data.comp_[i].latest_), + 0, + size_of::(), + ); + } + + Ok(SUCCESS) +} + /// add a publisher to a price account /// accounts[0] funding account [signer writable] /// accounts[1] price account to add the publisher to [signer writable] @@ -393,6 +458,14 @@ fn check_valid_fresh_account(account: &AccountInfo) -> Result<(), ProgramError> pyth_assert(valid_fresh_account(account), ProgramError::InvalidArgument) } +// Check that an exponent is within the range of permitted exponents for price accounts. +fn check_exponent_range(expo: i32) -> Result<(), ProgramError> { + pyth_assert( + expo >= -(PC_MAX_NUM_DECIMALS as i32) && expo <= PC_MAX_NUM_DECIMALS as i32, + ProgramError::InvalidArgument, + ) +} + /// Sets the data of account to all-zero pub fn clear_account(account: &AccountInfo) -> Result<(), ProgramError> { let mut data = account diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index 42b869cde..47c75666e 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -3,6 +3,7 @@ mod test_add_price; mod test_add_product; mod test_add_publisher; mod test_init_mapping; +mod test_init_price; mod test_set_min_pub; mod test_upd_product; mod test_utils; diff --git a/program/rust/src/tests/test_init_price.rs b/program/rust/src/tests/test_init_price.rs new file mode 100644 index 000000000..4162016ee --- /dev/null +++ b/program/rust/src/tests/test_init_price.rs @@ -0,0 +1,140 @@ +use bytemuck::bytes_of; +use solana_program::program_error::ProgramError; +use solana_program::pubkey::Pubkey; + +use crate::c_oracle_header::{ + cmd_init_price_t, + command_t_e_cmd_init_price, + pc_price_t, + PC_MAX_NUM_DECIMALS, + PC_VERSION, +}; +use crate::rust_oracle::{ + init_price, + initialize_checked, + load_checked, +}; +use crate::tests::test_utils::AccountSetup; + +#[test] +fn test_init_price() { + let ptype = 3; + + let cmd: cmd_init_price_t = cmd_init_price_t { + ver_: PC_VERSION, + cmd_: command_t_e_cmd_init_price as i32, + expo_: -2, + ptype_: ptype, + }; + + let instruction_data = bytes_of::(&cmd); + + let program_id = Pubkey::new_unique(); + + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.to_account_info(); + + let mut price_setup = AccountSetup::new::(&program_id); + let mut price_account = price_setup.to_account_info(); + + // Price account must be initialized + assert_eq!( + init_price( + &program_id, + &[funding_account.clone(), price_account.clone()], + instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + initialize_checked::(&price_account, PC_VERSION).unwrap(); + { + let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + price_data.ptype_ = ptype; + price_data.expo_ = 0; + + price_data.last_slot_ = 100; + price_data.valid_slot_ = 100; + price_data.prev_slot_ = 100; + price_data.prev_price_ = 100; + price_data.prev_conf_ = 100; + price_data.prev_timestamp_ = 100; + + price_data.agg_.price_ = 100; + price_data.agg_.conf_ = 100; + price_data.agg_.pub_slot_ = 100; + + price_data.twap_.denom_ = 100; + price_data.twac_.denom_ = 100; + + price_data.comp_[0].agg_.price_ = 100; + price_data.comp_[0].latest_.price_ = 100; + price_data.comp_[3].agg_.price_ = 100; + price_data.comp_[3].latest_.conf_ = 100; + let num_components = price_data.comp_.len(); + price_data.comp_[num_components - 1].agg_.price_ = 100; + price_data.comp_[num_components - 1].latest_.price_ = 100; + } + + assert!(init_price( + &program_id, + &[funding_account.clone(), price_account.clone()], + instruction_data + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + + assert_eq!(price_data.expo_, -2); + + assert_eq!(price_data.last_slot_, 0); + assert_eq!(price_data.valid_slot_, 0); + assert_eq!(price_data.prev_slot_, 0); + assert_eq!(price_data.prev_price_, 0); + assert_eq!(price_data.prev_conf_, 0); + assert_eq!(price_data.prev_timestamp_, 0); + + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.conf_, 0); + assert_eq!(price_data.agg_.pub_slot_, 0); + + assert_eq!(price_data.twap_.denom_, 0); + assert_eq!(price_data.twac_.denom_, 0); + + assert_eq!(price_data.comp_[0].agg_.price_, 0); + assert_eq!(price_data.comp_[0].latest_.price_, 0); + assert_eq!(price_data.comp_[3].agg_.price_, 0); + assert_eq!(price_data.comp_[3].latest_.conf_, 0); + let num_components = price_data.comp_.len(); + assert_eq!(price_data.comp_[num_components - 1].agg_.price_, 0); + assert_eq!(price_data.comp_[num_components - 1].latest_.price_, 0); + } + + price_account.is_signer = false; + assert_eq!( + init_price( + &program_id, + &[funding_account.clone(), price_account.clone()], + instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + price_account.is_signer = false; + let cmd: cmd_init_price_t = cmd_init_price_t { + ver_: PC_VERSION, + cmd_: command_t_e_cmd_init_price as i32, + expo_: -(PC_MAX_NUM_DECIMALS as i32) - 1, + ptype_: ptype, + }; + let instruction_data = bytes_of::(&cmd); + assert_eq!( + init_price( + &program_id, + &[funding_account.clone(), price_account.clone()], + instruction_data + ), + Err(ProgramError::InvalidArgument) + ); +} From 6822e69bb25bf069e808169e7cee9398792a5930 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Thu, 11 Aug 2022 09:28:27 -0700 Subject: [PATCH 2/6] better test --- program/rust/src/tests/test_init_price.rs | 28 ++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/program/rust/src/tests/test_init_price.rs b/program/rust/src/tests/test_init_price.rs index 4162016ee..9034419f1 100644 --- a/program/rust/src/tests/test_init_price.rs +++ b/program/rust/src/tests/test_init_price.rs @@ -6,6 +6,7 @@ use crate::c_oracle_header::{ cmd_init_price_t, command_t_e_cmd_init_price, pc_price_t, + pc_pub_key_t, PC_MAX_NUM_DECIMALS, PC_VERSION, }; @@ -13,6 +14,8 @@ use crate::rust_oracle::{ init_price, initialize_checked, load_checked, + pubkey_assign, + pubkey_equal, }; use crate::tests::test_utils::AccountSetup; @@ -30,6 +33,10 @@ fn test_init_price() { let instruction_data = bytes_of::(&cmd); let program_id = Pubkey::new_unique(); + let publisher = pc_pub_key_t::new_unique(); + let publisher2 = pc_pub_key_t::new_unique(); + let product = pc_pub_key_t::new_unique(); + let next_price = pc_pub_key_t::new_unique(); let mut funding_setup = AccountSetup::new_funding(); let funding_account = funding_setup.to_account_info(); @@ -52,6 +59,12 @@ fn test_init_price() { let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); price_data.ptype_ = ptype; price_data.expo_ = 0; + price_data.min_pub_ = 7; + price_data.num_ = 4; + pubkey_assign(&mut price_data.comp_[0].pub_, bytes_of(&publisher)); + pubkey_assign(&mut price_data.comp_[3].pub_, bytes_of(&publisher2)); + pubkey_assign(&mut price_data.prod_, bytes_of(&product)); + pubkey_assign(&mut price_data.next_, bytes_of(&next_price)); price_data.last_slot_ = 100; price_data.valid_slot_ = 100; @@ -87,6 +100,19 @@ fn test_init_price() { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); assert_eq!(price_data.expo_, -2); + assert_eq!(price_data.ptype_, ptype); + assert_eq!(price_data.min_pub_, 7); + assert_eq!(price_data.num_, 4); + assert!(pubkey_equal( + &price_data.comp_[0].pub_, + bytes_of(&publisher) + )); + assert!(pubkey_equal( + &price_data.comp_[3].pub_, + bytes_of(&publisher2) + )); + assert!(pubkey_equal(&price_data.prod_, bytes_of(&product))); + assert!(pubkey_equal(&price_data.next_, bytes_of(&next_price))); assert_eq!(price_data.last_slot_, 0); assert_eq!(price_data.valid_slot_, 0); @@ -121,7 +147,7 @@ fn test_init_price() { Err(ProgramError::InvalidArgument) ); - price_account.is_signer = false; + price_account.is_signer = true; let cmd: cmd_init_price_t = cmd_init_price_t { ver_: PC_VERSION, cmd_: command_t_e_cmd_init_price as i32, From d4a3f36b79f3e7245a78d560a1e6d5e609498d7e Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Thu, 11 Aug 2022 10:03:19 -0700 Subject: [PATCH 3/6] merge better --- program/c/src/oracle/oracle.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/program/c/src/oracle/oracle.c b/program/c/src/oracle/oracle.c index f10b6b78b..f02eda28d 100644 --- a/program/c/src/oracle/oracle.c +++ b/program/c/src/oracle/oracle.c @@ -163,13 +163,9 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka ) case e_cmd_upd_product: return ERROR_INVALID_ARGUMENT; case e_cmd_add_price: return ERROR_INVALID_ARGUMENT; case e_cmd_add_publisher: return ERROR_INVALID_ARGUMENT; -<<<<<<< HEAD - case e_cmd_del_publisher: return del_publisher( prm, ka ); - case e_cmd_init_price: return ERROR_INVALID_ARGUMENT; -======= case e_cmd_del_publisher: return ERROR_INVALID_ARGUMENT; + case e_cmd_init_price: return ERROR_INVALID_ARGUMENT; case e_cmd_init_price: return init_price( prm, ka ); ->>>>>>> main case e_cmd_init_test: return ERROR_INVALID_ARGUMENT; case e_cmd_upd_test: return ERROR_INVALID_ARGUMENT; case e_cmd_set_min_pub: return ERROR_INVALID_ARGUMENT; From a8a088d039fe35c891c60e174c408583d63903b2 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Thu, 11 Aug 2022 10:03:36 -0700 Subject: [PATCH 4/6] merge better better --- program/c/src/oracle/oracle.c | 1 - 1 file changed, 1 deletion(-) diff --git a/program/c/src/oracle/oracle.c b/program/c/src/oracle/oracle.c index f02eda28d..9209ee4da 100644 --- a/program/c/src/oracle/oracle.c +++ b/program/c/src/oracle/oracle.c @@ -165,7 +165,6 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka ) case e_cmd_add_publisher: return ERROR_INVALID_ARGUMENT; case e_cmd_del_publisher: return ERROR_INVALID_ARGUMENT; case e_cmd_init_price: return ERROR_INVALID_ARGUMENT; - case e_cmd_init_price: return init_price( prm, ka ); case e_cmd_init_test: return ERROR_INVALID_ARGUMENT; case e_cmd_upd_test: return ERROR_INVALID_ARGUMENT; case e_cmd_set_min_pub: return ERROR_INVALID_ARGUMENT; From dbe62abb83f539757638cbdc3cc6ed2fb129c984 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Thu, 11 Aug 2022 10:22:39 -0700 Subject: [PATCH 5/6] unused function --- program/c/src/oracle/oracle.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/program/c/src/oracle/oracle.c b/program/c/src/oracle/oracle.c index 9209ee4da..1f0a9d705 100644 --- a/program/c/src/oracle/oracle.c +++ b/program/c/src/oracle/oracle.c @@ -38,17 +38,6 @@ static bool valid_funding_account( SolAccountInfo *ka ) ka->is_writable; } -static bool valid_signable_account( SolParameters *prm, - SolAccountInfo *ka, - uint64_t min_dlen ) -{ - return ka->is_signer && - ka->is_writable && - SolPubkey_same( ka->owner, prm->program_id ) && - ka->data_len >= min_dlen && - is_rent_exempt( *ka->lamports, ka->data_len ); -} - static bool valid_writable_account( SolParameters *prm, SolAccountInfo *ka, uint64_t min_dlen ) From 70ee93919ed2005075746f6d13bd41b266d72fc7 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Thu, 11 Aug 2022 10:33:56 -0700 Subject: [PATCH 6/6] doh --- program/rust/src/tests/test_init_price.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/program/rust/src/tests/test_init_price.rs b/program/rust/src/tests/test_init_price.rs index 9034419f1..50ddcec3b 100644 --- a/program/rust/src/tests/test_init_price.rs +++ b/program/rust/src/tests/test_init_price.rs @@ -18,6 +18,7 @@ use crate::rust_oracle::{ pubkey_equal, }; use crate::tests::test_utils::AccountSetup; +use crate::OracleError; #[test] fn test_init_price() { @@ -144,7 +145,7 @@ fn test_init_price() { &[funding_account.clone(), price_account.clone()], instruction_data ), - Err(ProgramError::InvalidArgument) + Err(OracleError::InvalidSignableAccount.into()) ); price_account.is_signer = true;