diff --git a/program/c/src/oracle/oracle.c b/program/c/src/oracle/oracle.c index 9f30791ec..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 ) @@ -59,54 +48,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; -} - static uint64_t upd_price( SolParameters *prm, SolAccountInfo *ka ) { // Validate command parameters @@ -212,7 +153,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 ERROR_INVALID_ARGUMENT; - 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 62557073f..aa58d09ff 100644 --- a/program/rust/src/c_oracle_header.rs +++ b/program/rust/src/c_oracle_header.rs @@ -93,7 +93,6 @@ unsafe impl Pod for cmd_hdr { } #[cfg(target_endian = "little")] - unsafe impl Zeroable for pc_price_info { } @@ -117,6 +116,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 { } @@ -124,6 +124,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 c54568753..be052b9ef 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -12,6 +12,7 @@ use crate::c_oracle_header::{ command_t_e_cmd_agg_price, command_t_e_cmd_del_publisher, 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, @@ -31,6 +32,7 @@ use crate::rust_oracle::{ add_publisher, del_publisher, init_mapping, + init_price, set_min_pub, upd_product, update_price, @@ -66,6 +68,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_del_publisher => del_publisher(program_id, accounts, instruction_data), diff --git a/program/rust/src/rust_oracle.rs b/program/rust/src/rust_oracle.rs index 9f6adeff4..68986d08f 100644 --- a/program/rust/src/rust_oracle.rs +++ b/program/rust/src/rust_oracle.rs @@ -24,11 +24,14 @@ use crate::c_oracle_header::{ cmd_add_publisher_t, cmd_del_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, @@ -141,12 +144,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]), @@ -170,6 +172,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] @@ -445,6 +510,14 @@ fn check_valid_fresh_account(account: &AccountInfo) -> Result<(), ProgramError> ) } +// 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 499ac7530..e3cdc2e0e 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -4,6 +4,7 @@ mod test_add_product; mod test_add_publisher; mod test_del_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..50ddcec3b --- /dev/null +++ b/program/rust/src/tests/test_init_price.rs @@ -0,0 +1,167 @@ +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_pub_key_t, + PC_MAX_NUM_DECIMALS, + PC_VERSION, +}; +use crate::rust_oracle::{ + init_price, + initialize_checked, + load_checked, + pubkey_assign, + pubkey_equal, +}; +use crate::tests::test_utils::AccountSetup; +use crate::OracleError; + +#[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 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(); + + 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.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; + 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.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); + 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(OracleError::InvalidSignableAccount.into()) + ); + + 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, + 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) + ); +}