From 0f0a12368899f93fcfbe176b0320ce7de2f83df4 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 14:55:08 +0000 Subject: [PATCH 01/17] Simplistic trait --- program/rust/src/bindings.h | 4 + program/rust/src/c_oracle_header.rs | 23 ++- program/rust/src/rust_oracle.rs | 107 +++++--------- program/rust/src/tests/test_add_mapping.rs | 17 ++- program/rust/src/tests/test_add_product.rs | 14 +- program/rust/src/tests/test_upd_product.rs | 163 +++++++++++++++++++++ 6 files changed, 239 insertions(+), 89 deletions(-) create mode 100644 program/rust/src/tests/test_upd_product.rs diff --git a/program/rust/src/bindings.h b/program/rust/src/bindings.h index ab8ce0656..2706b4648 100644 --- a/program/rust/src/bindings.h +++ b/program/rust/src/bindings.h @@ -11,4 +11,8 @@ typedef unsigned int uint32_t; typedef signed long int int64_t; typedef unsigned long int uint64_t; +#include #include "../../c/src/oracle/oracle.h" + +const size_t PC_PRICE_T_COMP_OFFSET = offsetof(struct pc_price, comp_); +const size_t PC_MAP_TABLE_T_PROD_OFFSET = offsetof(struct pc_map_table, prod_); diff --git a/program/rust/src/c_oracle_header.rs b/program/rust/src/c_oracle_header.rs index d9802bb2a..37f55e487 100644 --- a/program/rust/src/c_oracle_header.rs +++ b/program/rust/src/c_oracle_header.rs @@ -7,11 +7,32 @@ use bytemuck::{ Pod, Zeroable, }; - +use std::mem::size_of; //bindings.rs is generated by build.rs to include //things defined in bindings.h include!("../bindings.rs"); +pub trait PythStruct: Pod { + const ACCOUNT_TYPE: u32; + const INITIAL_SIZE: u32; +} + +impl PythStruct for pc_map_table_t { + const ACCOUNT_TYPE: u32 = PC_ACCTYPE_MAPPING; + const INITIAL_SIZE: u32 = PC_MAP_TABLE_T_PROD_OFFSET as u32; +} + +impl PythStruct for pc_prod_t { + const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PRODUCT; + const INITIAL_SIZE: u32 = size_of::() as u32; +} + +impl PythStruct for pc_price_t { + const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PRICE; + const INITIAL_SIZE: u32 = PC_PRICE_T_COMP_OFFSET as u32; +} + + #[cfg(target_endian = "little")] unsafe impl Zeroable for pc_acc { } diff --git a/program/rust/src/rust_oracle.rs b/program/rust/src/rust_oracle.rs index e91870d96..fac5c4aee 100644 --- a/program/rust/src/rust_oracle.rs +++ b/program/rust/src/rust_oracle.rs @@ -28,9 +28,7 @@ use crate::c_oracle_header::{ pc_price_t, pc_prod_t, pc_pub_key_t, - PC_ACCTYPE_MAPPING, - PC_ACCTYPE_PRICE, - PC_ACCTYPE_PRODUCT, + PythStruct, PC_MAGIC, PC_MAP_TABLE_SIZE, PC_MAX_NUM_DECIMALS, @@ -90,7 +88,7 @@ pub fn init_mapping( // Initialize by setting to zero again (just in case) and populating the account header let hdr = load::(instruction_data)?; - initialize_mapping_account(fresh_mapping_account, hdr.ver_)?; + initialize_checked::(fresh_mapping_account, hdr.ver_)?; Ok(SUCCESS) } @@ -111,14 +109,14 @@ pub fn add_mapping( check_valid_fresh_account(next_mapping)?; let hdr = load::(instruction_data)?; - let mut cur_mapping = load_mapping_account_mut(cur_mapping, hdr.ver_)?; + let mut cur_mapping = load_checked::(cur_mapping, hdr.ver_)?; pyth_assert( cur_mapping.num_ == PC_MAP_TABLE_SIZE && unsafe { cur_mapping.next_.k8_.iter().all(|x| *x == 0) }, ProgramError::InvalidArgument, )?; - initialize_mapping_account(next_mapping, hdr.ver_)?; + initialize_checked::(next_mapping, hdr.ver_)?; pubkey_assign(&mut cur_mapping.next_, &next_mapping.key.to_bytes()); Ok(SUCCESS) @@ -152,15 +150,9 @@ pub fn add_price( check_valid_signable_account(program_id, price_account, size_of::())?; check_valid_fresh_account(price_account)?; - let mut product_data = load_product_account_mut(product_account, cmd_args.ver_)?; + let mut product_data = load_checked::(product_account, cmd_args.ver_)?; - clear_account(price_account)?; - - let mut price_data = load_account_as_mut::(price_account)?; - price_data.magic_ = PC_MAGIC; - price_data.ver_ = cmd_args.ver_; - price_data.type_ = PC_ACCTYPE_PRICE; - price_data.size_ = (size_of::() - size_of_val(&price_data.comp_)) as u32; + let mut price_data = initialize_checked::(price_account, cmd_args.ver_)?; price_data.expo_ = cmd_args.expo_; price_data.ptype_ = cmd_args.ptype_; pubkey_assign(&mut price_data.prod_, &product_account.key.to_bytes()); @@ -190,14 +182,14 @@ pub fn add_product( check_valid_fresh_account(new_product_account)?; let hdr = load::(instruction_data)?; - let mut mapping_data = load_mapping_account_mut(tail_mapping_account, hdr.ver_)?; + let mut mapping_data = load_checked::(tail_mapping_account, hdr.ver_)?; // The mapping account must have free space to add the product account pyth_assert( mapping_data.num_ < PC_MAP_TABLE_SIZE, ProgramError::InvalidArgument, )?; - initialize_product_account(new_product_account, hdr.ver_)?; + initialize_checked::(new_product_account, hdr.ver_)?; let current_index: usize = try_convert(mapping_data.num_)?; unsafe { @@ -267,73 +259,40 @@ pub fn clear_account(account: &AccountInfo) -> Result<(), ProgramError> { Ok(()) } - -/// Mutably borrow the data in `account` as a mapping account, validating that the account -/// is properly formatted. Any mutations to the returned value will be reflected in the -/// account data. Use this to read already-initialized accounts. -pub fn load_mapping_account_mut<'a>( +pub fn load_checked<'a, T: PythStruct>( account: &'a AccountInfo, - expected_version: u32, -) -> Result, ProgramError> { - let mapping_data = load_account_as_mut::(account)?; - - pyth_assert( - mapping_data.magic_ == PC_MAGIC - && mapping_data.ver_ == expected_version - && mapping_data.type_ == PC_ACCTYPE_MAPPING, - ProgramError::InvalidArgument, - )?; + version: u32, +) -> Result, ProgramError> { + { + let account_header = load_account_as::(account)?; + pyth_assert( + account_header.magic_ == PC_MAGIC + && account_header.ver_ == version + && account_header.type_ == T::ACCOUNT_TYPE, + ProgramError::InvalidArgument, + )?; + } - Ok(mapping_data) + load_account_as_mut::(account) } -/// Initialize account as a new mapping account. This function will zero out any existing data in -/// the account. -pub fn initialize_mapping_account(account: &AccountInfo, version: u32) -> Result<(), ProgramError> { - clear_account(account)?; - - let mut mapping_account = load_account_as_mut::(account)?; - mapping_account.magic_ = PC_MAGIC; - mapping_account.ver_ = version; - mapping_account.type_ = PC_ACCTYPE_MAPPING; - mapping_account.size_ = - try_convert(size_of::() - size_of_val(&mapping_account.prod_))?; - - Ok(()) -} - -/// Initialize account as a new product account. This function will zero out any existing data in -/// the account. -pub fn initialize_product_account(account: &AccountInfo, version: u32) -> Result<(), ProgramError> { +pub fn initialize_checked<'a, T: PythStruct>( + account: &'a AccountInfo, + version: u32, +) -> Result, ProgramError> { clear_account(account)?; - let mut prod_account = load_account_as_mut::(account)?; - prod_account.magic_ = PC_MAGIC; - prod_account.ver_ = version; - prod_account.type_ = PC_ACCTYPE_PRODUCT; - prod_account.size_ = try_convert(size_of::())?; + { + let mut account_header = load_account_as_mut::(account)?; + account_header.magic_ = PC_MAGIC; + account_header.ver_ = version; + account_header.type_ = T::ACCOUNT_TYPE; + account_header.size_ = T::INITIAL_SIZE; + } - Ok(()) + load_account_as_mut::(account) } -/// Mutably borrow the data in `account` as a product account, validating that the account -/// is properly formatted. Any mutations to the returned value will be reflected in the -/// account data. Use this to read already-initialized accounts. -pub fn load_product_account_mut<'a>( - account: &'a AccountInfo, - expected_version: u32, -) -> Result, ProgramError> { - let product_data = load_account_as_mut::(account)?; - - pyth_assert( - product_data.magic_ == PC_MAGIC - && product_data.ver_ == expected_version - && product_data.type_ == PC_ACCTYPE_PRODUCT, - ProgramError::InvalidArgument, - )?; - - Ok(product_data) -} // Assign pubkey bytes from source to target, fails if source is not 32 bytes pub fn pubkey_assign(target: &mut pc_pub_key_t, source: &[u8]) { diff --git a/program/rust/src/tests/test_add_mapping.rs b/program/rust/src/tests/test_add_mapping.rs index 7422f5b50..b78aea8ac 100644 --- a/program/rust/src/tests/test_add_mapping.rs +++ b/program/rust/src/tests/test_add_mapping.rs @@ -10,8 +10,8 @@ use crate::deserialize::load_account_as_mut; use crate::rust_oracle::{ add_mapping, clear_account, - initialize_mapping_account, - load_mapping_account_mut, + initialize_checked, + load_checked, pubkey_assign, }; use bytemuck::bytes_of; @@ -65,10 +65,11 @@ fn test_add_mapping() { Epoch::default(), ); - initialize_mapping_account(&cur_mapping, PC_VERSION).unwrap(); + initialize_checked::(&cur_mapping, PC_VERSION).unwrap(); { - let mut cur_mapping_data = load_mapping_account_mut(&cur_mapping, PC_VERSION).unwrap(); + let mut cur_mapping_data = + load_checked::(&cur_mapping, PC_VERSION).unwrap(); cur_mapping_data.num_ = PC_MAP_TABLE_SIZE; } @@ -99,8 +100,9 @@ fn test_add_mapping() { .is_ok()); { - let next_mapping_data = load_mapping_account_mut(&next_mapping, PC_VERSION).unwrap(); - let mut cur_mapping_data = load_mapping_account_mut(&cur_mapping, PC_VERSION).unwrap(); + let next_mapping_data = load_checked::(&next_mapping, PC_VERSION).unwrap(); + let mut cur_mapping_data = + load_checked::(&cur_mapping, PC_VERSION).unwrap(); assert!(unsafe { cur_mapping_data @@ -131,7 +133,8 @@ fn test_add_mapping() { ); { - let mut cur_mapping_data = load_mapping_account_mut(&cur_mapping, PC_VERSION).unwrap(); + let mut cur_mapping_data = + load_checked::(&cur_mapping, PC_VERSION).unwrap(); assert!(unsafe { cur_mapping_data.next_.k8_.iter().all(|x| *x == 0) }); cur_mapping_data.num_ = PC_MAP_TABLE_SIZE; cur_mapping_data.magic_ = 0; diff --git a/program/rust/src/tests/test_add_product.rs b/program/rust/src/tests/test_add_product.rs index 7645cbfac..db266d411 100644 --- a/program/rust/src/tests/test_add_product.rs +++ b/program/rust/src/tests/test_add_product.rs @@ -28,8 +28,8 @@ use crate::deserialize::load_account_as; use crate::rust_oracle::{ add_product, clear_account, - initialize_mapping_account, - load_mapping_account_mut, + initialize_checked, + load_checked, }; #[test] @@ -116,7 +116,7 @@ fn test_add_product() { { let product_data = load_account_as::(&product_account).unwrap(); - let mapping_data = load_mapping_account_mut(&mapping_account, PC_VERSION).unwrap(); + let mapping_data = load_checked::(&mapping_account, PC_VERSION).unwrap(); assert_eq!(product_data.magic_, PC_MAGIC); assert_eq!(product_data.ver_, PC_VERSION); @@ -140,7 +140,7 @@ fn test_add_product() { ) .is_ok()); { - let mapping_data = load_mapping_account_mut(&mapping_account, PC_VERSION).unwrap(); + let mapping_data = load_checked::(&mapping_account, PC_VERSION).unwrap(); assert_eq!(mapping_data.num_, 2); assert!(pubkey_equal( &mapping_data.prod_[1], @@ -175,7 +175,7 @@ fn test_add_product() { // test fill up of mapping table clear_account(&mapping_account).unwrap(); - initialize_mapping_account(&mapping_account, PC_VERSION).unwrap(); + initialize_checked::(&mapping_account, PC_VERSION).unwrap(); for i in 0..PC_MAP_TABLE_SIZE { clear_account(&product_account).unwrap(); @@ -190,7 +190,7 @@ fn test_add_product() { instruction_data ) .is_ok()); - let mapping_data = load_mapping_account_mut(&mapping_account, PC_VERSION).unwrap(); + let mapping_data = load_checked::(&mapping_account, PC_VERSION).unwrap(); assert_eq!(mapping_data.num_, i + 1); } @@ -207,7 +207,7 @@ fn test_add_product() { ) .is_err()); - let mapping_data = load_mapping_account_mut(&mapping_account, PC_VERSION).unwrap(); + let mapping_data = load_checked::(&mapping_account, PC_VERSION).unwrap(); assert_eq!(mapping_data.num_, PC_MAP_TABLE_SIZE); } diff --git a/program/rust/src/tests/test_upd_product.rs b/program/rust/src/tests/test_upd_product.rs new file mode 100644 index 000000000..e70751d94 --- /dev/null +++ b/program/rust/src/tests/test_upd_product.rs @@ -0,0 +1,163 @@ +use std::mem::size_of; + +use solana_program::account_info::AccountInfo; +use solana_program::clock::Epoch; +use solana_program::native_token::LAMPORTS_PER_SOL; +use solana_program::program_error::ProgramError; +use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; +use solana_program::system_program; + +use crate::c_oracle_header::{ + cmd_hdr_t, + cmd_upd_product_t, + command_t_e_cmd_upd_product, + pc_prod_t, + PC_PROD_ACC_SIZE, + PC_VERSION, +}; +use crate::deserialize::load_mut; +use crate::rust_oracle::{ + initialize_checked, + load_product_account_mut, + read_pc_str_t, + try_convert, + upd_product, +}; + +#[test] +fn test_upd_product() { + let mut instruction_data = [0u8; PC_PROD_ACC_SIZE as usize]; + + let program_id = Pubkey::new_unique(); + let funding_key = Pubkey::new_unique(); + let product_key = Pubkey::new_unique(); + + let system_program = system_program::id(); + let mut funding_balance = LAMPORTS_PER_SOL.clone(); + let funding_account = AccountInfo::new( + &funding_key, + true, + true, + &mut funding_balance, + &mut [], + &system_program, + false, + Epoch::default(), + ); + + let mut product_balance = Rent::minimum_balance(&Rent::default(), PC_PROD_ACC_SIZE as usize); + let mut prod_raw_data = [0u8; PC_PROD_ACC_SIZE as usize]; + let product_account = AccountInfo::new( + &product_key, + true, + true, + &mut product_balance, + &mut prod_raw_data, + &program_id, + false, + Epoch::default(), + ); + + initialize_checked::(&product_account, PC_VERSION).unwrap(); + + let kvs = ["foo", "barz"]; + let size = populate_instruction(&mut instruction_data, &kvs); + assert!(upd_product( + &program_id, + &[funding_account.clone(), product_account.clone()], + &instruction_data[..size] + ) + .is_ok()); + assert!(account_has_key_values(&product_account, &kvs).unwrap()); + + let kvs = []; + let size = populate_instruction(&mut instruction_data, &kvs); + assert!(upd_product( + &program_id, + &[funding_account.clone(), product_account.clone()], + &instruction_data[..size] + ) + .is_ok()); + assert!(account_has_key_values(&product_account, &kvs).unwrap()); + + // bad size on the string + instruction_data[0] = 7; + assert!(upd_product( + &program_id, + &[funding_account.clone(), product_account.clone()], + &instruction_data[..size] + ) + .is_err()); + assert!(account_has_key_values(&product_account, &kvs).unwrap()); + + // uneven number of keys and values + let bad_kvs = ["foo", "bar", "baz"]; + let size = populate_instruction(&mut instruction_data, &bad_kvs); + assert!(upd_product( + &program_id, + &[funding_account.clone(), product_account.clone()], + &instruction_data[..size] + ) + .is_err()); + assert!(account_has_key_values(&product_account, &kvs).unwrap()); +} + +// Create an upd_product instruction that sets the product metadata to strings +fn populate_instruction(instruction_data: &mut [u8], strings: &[&str]) -> usize { + { + let mut hdr = load_mut::(instruction_data).unwrap(); + hdr.ver_ = PC_VERSION; + hdr.cmd_ = command_t_e_cmd_upd_product as i32 + } + + let mut idx = size_of::(); + for s in strings.iter() { + let pc_str = create_pc_str_t(s); + instruction_data[idx..(idx + pc_str.len())].copy_from_slice(pc_str.as_slice()); + idx += pc_str.len() + } + + idx +} + +fn create_pc_str_t(s: &str) -> Vec { + let mut v = vec![s.len() as u8]; + v.extend_from_slice(s.as_bytes()); + v +} + +// Check that the key-value list in product_account equals the strings in expected +// Returns an Err if the account data is incorrectly formatted and the comparison cannot be +// performed. +fn account_has_key_values( + product_account: &AccountInfo, + expected: &[&str], +) -> Result { + let account_size: usize = + try_convert(load_checked::(product_account, PC_VERSION)?.size_)?; + let mut all_account_data = product_account.try_borrow_mut_data()?; + let kv_data = &mut all_account_data[size_of::()..account_size]; + let mut kv_idx = 0; + let mut expected_idx = 0; + + while kv_idx < kv_data.len() { + let key = read_pc_str_t(&kv_data[kv_idx..])?; + if key[0] != try_convert::<_, u8>(key.len())? - 1 { + return Ok(false); + } + + if &key[1..] != expected[expected_idx].as_bytes() { + return Ok(false); + } + + kv_idx += key.len(); + expected_idx += 1; + } + + if expected_idx != expected.len() { + return Ok(false); + } + + Ok(true) +} From dfdc62b95d9e4e641f9deefae454fa51aaee9def Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 14:56:11 +0000 Subject: [PATCH 02/17] Remove irrelevant file --- program/rust/src/tests/test_upd_product.rs | 163 --------------------- 1 file changed, 163 deletions(-) delete mode 100644 program/rust/src/tests/test_upd_product.rs diff --git a/program/rust/src/tests/test_upd_product.rs b/program/rust/src/tests/test_upd_product.rs deleted file mode 100644 index e70751d94..000000000 --- a/program/rust/src/tests/test_upd_product.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::mem::size_of; - -use solana_program::account_info::AccountInfo; -use solana_program::clock::Epoch; -use solana_program::native_token::LAMPORTS_PER_SOL; -use solana_program::program_error::ProgramError; -use solana_program::pubkey::Pubkey; -use solana_program::rent::Rent; -use solana_program::system_program; - -use crate::c_oracle_header::{ - cmd_hdr_t, - cmd_upd_product_t, - command_t_e_cmd_upd_product, - pc_prod_t, - PC_PROD_ACC_SIZE, - PC_VERSION, -}; -use crate::deserialize::load_mut; -use crate::rust_oracle::{ - initialize_checked, - load_product_account_mut, - read_pc_str_t, - try_convert, - upd_product, -}; - -#[test] -fn test_upd_product() { - let mut instruction_data = [0u8; PC_PROD_ACC_SIZE as usize]; - - let program_id = Pubkey::new_unique(); - let funding_key = Pubkey::new_unique(); - let product_key = Pubkey::new_unique(); - - let system_program = system_program::id(); - let mut funding_balance = LAMPORTS_PER_SOL.clone(); - let funding_account = AccountInfo::new( - &funding_key, - true, - true, - &mut funding_balance, - &mut [], - &system_program, - false, - Epoch::default(), - ); - - let mut product_balance = Rent::minimum_balance(&Rent::default(), PC_PROD_ACC_SIZE as usize); - let mut prod_raw_data = [0u8; PC_PROD_ACC_SIZE as usize]; - let product_account = AccountInfo::new( - &product_key, - true, - true, - &mut product_balance, - &mut prod_raw_data, - &program_id, - false, - Epoch::default(), - ); - - initialize_checked::(&product_account, PC_VERSION).unwrap(); - - let kvs = ["foo", "barz"]; - let size = populate_instruction(&mut instruction_data, &kvs); - assert!(upd_product( - &program_id, - &[funding_account.clone(), product_account.clone()], - &instruction_data[..size] - ) - .is_ok()); - assert!(account_has_key_values(&product_account, &kvs).unwrap()); - - let kvs = []; - let size = populate_instruction(&mut instruction_data, &kvs); - assert!(upd_product( - &program_id, - &[funding_account.clone(), product_account.clone()], - &instruction_data[..size] - ) - .is_ok()); - assert!(account_has_key_values(&product_account, &kvs).unwrap()); - - // bad size on the string - instruction_data[0] = 7; - assert!(upd_product( - &program_id, - &[funding_account.clone(), product_account.clone()], - &instruction_data[..size] - ) - .is_err()); - assert!(account_has_key_values(&product_account, &kvs).unwrap()); - - // uneven number of keys and values - let bad_kvs = ["foo", "bar", "baz"]; - let size = populate_instruction(&mut instruction_data, &bad_kvs); - assert!(upd_product( - &program_id, - &[funding_account.clone(), product_account.clone()], - &instruction_data[..size] - ) - .is_err()); - assert!(account_has_key_values(&product_account, &kvs).unwrap()); -} - -// Create an upd_product instruction that sets the product metadata to strings -fn populate_instruction(instruction_data: &mut [u8], strings: &[&str]) -> usize { - { - let mut hdr = load_mut::(instruction_data).unwrap(); - hdr.ver_ = PC_VERSION; - hdr.cmd_ = command_t_e_cmd_upd_product as i32 - } - - let mut idx = size_of::(); - for s in strings.iter() { - let pc_str = create_pc_str_t(s); - instruction_data[idx..(idx + pc_str.len())].copy_from_slice(pc_str.as_slice()); - idx += pc_str.len() - } - - idx -} - -fn create_pc_str_t(s: &str) -> Vec { - let mut v = vec![s.len() as u8]; - v.extend_from_slice(s.as_bytes()); - v -} - -// Check that the key-value list in product_account equals the strings in expected -// Returns an Err if the account data is incorrectly formatted and the comparison cannot be -// performed. -fn account_has_key_values( - product_account: &AccountInfo, - expected: &[&str], -) -> Result { - let account_size: usize = - try_convert(load_checked::(product_account, PC_VERSION)?.size_)?; - let mut all_account_data = product_account.try_borrow_mut_data()?; - let kv_data = &mut all_account_data[size_of::()..account_size]; - let mut kv_idx = 0; - let mut expected_idx = 0; - - while kv_idx < kv_data.len() { - let key = read_pc_str_t(&kv_data[kv_idx..])?; - if key[0] != try_convert::<_, u8>(key.len())? - 1 { - return Ok(false); - } - - if &key[1..] != expected[expected_idx].as_bytes() { - return Ok(false); - } - - kv_idx += key.len(); - expected_idx += 1; - } - - if expected_idx != expected.len() { - return Ok(false); - } - - Ok(true) -} From 2a07e8c9dfdd46252f012ad9fa086e5c4871687b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 17:29:23 +0000 Subject: [PATCH 03/17] Rename --- program/rust/src/c_oracle_header.rs | 11 +++++++---- program/rust/src/rust_oracle.rs | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/program/rust/src/c_oracle_header.rs b/program/rust/src/c_oracle_header.rs index 492b5de0c..0e2bdc0ba 100644 --- a/program/rust/src/c_oracle_header.rs +++ b/program/rust/src/c_oracle_header.rs @@ -12,22 +12,25 @@ use std::mem::size_of; //things defined in bindings.h include!("../bindings.rs"); -pub trait PythStruct: Pod { +/// The PythAccount trait's purpose is to attach constants to the 3 types of accounts that Pyth has +/// (mapping, price, product). This allows less duplicated code, because now we can create generic +/// functions to perform common checks on the accounts and to load and initialize the accounts. +pub trait PythAccount: Pod { const ACCOUNT_TYPE: u32; const INITIAL_SIZE: u32; } -impl PythStruct for pc_map_table_t { +impl PythAccount for pc_map_table_t { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_MAPPING; const INITIAL_SIZE: u32 = PC_MAP_TABLE_T_PROD_OFFSET as u32; } -impl PythStruct for pc_prod_t { +impl PythAccount for pc_prod_t { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PRODUCT; const INITIAL_SIZE: u32 = size_of::() as u32; } -impl PythStruct for pc_price_t { +impl PythAccount for pc_price_t { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PRICE; const INITIAL_SIZE: u32 = PC_PRICE_T_COMP_OFFSET as u32; } diff --git a/program/rust/src/rust_oracle.rs b/program/rust/src/rust_oracle.rs index 55989ccf4..22f503c52 100644 --- a/program/rust/src/rust_oracle.rs +++ b/program/rust/src/rust_oracle.rs @@ -32,7 +32,7 @@ use crate::c_oracle_header::{ pc_price_t, pc_prod_t, pc_pub_key_t, - PythStruct, + PythAccount, PC_COMP_SIZE, PC_MAGIC, PC_MAP_TABLE_SIZE, @@ -315,7 +315,7 @@ pub fn clear_account(account: &AccountInfo) -> Result<(), ProgramError> { Ok(()) } -pub fn load_checked<'a, T: PythStruct>( +pub fn load_checked<'a, T: PythAccount>( account: &'a AccountInfo, version: u32, ) -> Result, ProgramError> { @@ -332,7 +332,7 @@ pub fn load_checked<'a, T: PythStruct>( load_account_as_mut::(account) } -pub fn initialize_checked<'a, T: PythStruct>( +pub fn initialize_checked<'a, T: PythAccount>( account: &'a AccountInfo, version: u32, ) -> Result, ProgramError> { From 4ba95e06f110f5d819b698e81216fd8df6ac102b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 17:57:52 +0000 Subject: [PATCH 04/17] Delete --- program/rust/src/tests/test_utils.rs | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 program/rust/src/tests/test_utils.rs diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs deleted file mode 100644 index 8b7ffdbf8..000000000 --- a/program/rust/src/tests/test_utils.rs +++ /dev/null @@ -1,24 +0,0 @@ -use solana_program::account_info::AccountInfo; -use solana_program::system_program; -use solana_program::native_token::LAMPORTS_PER_SOL; -use solana_program::pubkey::Pubkey; -use solana_program::clock::Epoch; - -fn setup_funding_account<'a>(& mut funding_balance : u64) -> AccountInfo<'a> { - let mut funding_balance = LAMPORTS_PER_SOL.clone(); - static system_program : Pubkey = system_program::ID; - static funding_key : Pubkey = Pubkey::new_unique(); - - let funding_account = AccountInfo::new( - &funding_key, - true, - true, - funding_balance, - &mut [], - &system_program, - false, - Epoch::default(), - ); - - return funding_account -} From effb52985174fa061a2cbd0d3e70d5215dbf64a6 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 19:26:06 +0000 Subject: [PATCH 05/17] Add functions to initialize tests --- program/rust/src/c_oracle_header.rs | 6 ++ program/rust/src/rust_oracle.rs | 22 ++++-- program/rust/src/tests/mod.rs | 1 + program/rust/src/tests/test_add_mapping.rs | 61 +++-------------- program/rust/src/tests/test_add_product.rs | 75 +++------------------ program/rust/src/tests/test_init_mapping.rs | 38 ++--------- program/rust/src/tests/test_utils.rs | 62 +++++++++++++++++ 7 files changed, 109 insertions(+), 156 deletions(-) create mode 100644 program/rust/src/tests/test_utils.rs diff --git a/program/rust/src/c_oracle_header.rs b/program/rust/src/c_oracle_header.rs index 0e2bdc0ba..71919f94d 100644 --- a/program/rust/src/c_oracle_header.rs +++ b/program/rust/src/c_oracle_header.rs @@ -18,6 +18,9 @@ include!("../bindings.rs"); pub trait PythAccount: Pod { const ACCOUNT_TYPE: u32; const INITIAL_SIZE: u32; + fn minimum_size() -> usize { + size_of::() + } } impl PythAccount for pc_map_table_t { @@ -28,6 +31,9 @@ impl PythAccount for pc_map_table_t { impl PythAccount for pc_prod_t { const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PRODUCT; const INITIAL_SIZE: u32 = size_of::() as u32; + fn minimum_size() -> usize { + PC_PROD_ACC_SIZE as usize + } } impl PythAccount for pc_price_t { diff --git a/program/rust/src/rust_oracle.rs b/program/rust/src/rust_oracle.rs index ddee73360..5765c7eac 100644 --- a/program/rust/src/rust_oracle.rs +++ b/program/rust/src/rust_oracle.rs @@ -1,10 +1,3 @@ -use std::borrow::BorrowMut; -use std::cell::RefMut; -use std::mem::{ - size_of, - size_of_val, -}; - use crate::deserialize::{ load, load_account_as, @@ -14,6 +7,13 @@ use bytemuck::{ bytes_of, bytes_of_mut, }; +use solana_program::msg; +use std::borrow::BorrowMut; +use std::cell::RefMut; +use std::mem::{ + size_of, + size_of_val, +}; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::SUCCESS; @@ -223,20 +223,28 @@ pub fn add_product( accounts: &[AccountInfo], instruction_data: &[u8], ) -> OracleResult { + msg!("one"); let [funding_account, tail_mapping_account, new_product_account] = match accounts { [x, y, z] => Ok([x, y, z]), _ => Err(ProgramError::InvalidArgument), }?; + msg!("two"); + check_valid_funding_account(funding_account)?; + msg!("three"); check_valid_signable_account( program_id, tail_mapping_account, size_of::(), )?; + msg!("{:?}", new_product_account.data_len()); check_valid_signable_account(program_id, new_product_account, PC_PROD_ACC_SIZE as usize)?; + msg!("three"); check_valid_fresh_account(new_product_account)?; + msg!("three"); + let hdr = load::(instruction_data)?; let mut mapping_data = load_checked::(tail_mapping_account, hdr.ver_)?; // The mapping account must have free space to add the product account diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index fba2a74a9..545bb9679 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -1,3 +1,4 @@ mod test_add_mapping; mod test_add_product; mod test_init_mapping; +mod test_utils; diff --git a/program/rust/src/tests/test_add_mapping.rs b/program/rust/src/tests/test_add_mapping.rs index eb9fea709..c24e4bfb1 100644 --- a/program/rust/src/tests/test_add_mapping.rs +++ b/program/rust/src/tests/test_add_mapping.rs @@ -16,15 +16,10 @@ use crate::rust_oracle::{ pubkey_equal, pubkey_is_zero, }; +use crate::tests::test_utils::AccountSetup; use bytemuck::bytes_of; -use solana_program::account_info::AccountInfo; -use solana_program::clock::Epoch; -use solana_program::native_token::LAMPORTS_PER_SOL; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::rent::Rent; -use solana_program::system_program; -use std::mem::size_of; #[test] fn test_add_mapping() { @@ -35,61 +30,23 @@ fn test_add_mapping() { let instruction_data = bytes_of::(&hdr); let program_id = Pubkey::new_unique(); - let funding_key = Pubkey::new_unique(); - let cur_mapping_key = Pubkey::new_unique(); - let next_mapping_key = Pubkey::new_unique(); - let system_program = system_program::id(); - let mut funding_balance = LAMPORTS_PER_SOL.clone(); - let funding_account = AccountInfo::new( - &funding_key, - true, - true, - &mut funding_balance, - &mut [], - &system_program, - false, - Epoch::default(), - ); - - let mut cur_mapping_balance = - Rent::minimum_balance(&Rent::default(), size_of::()); - let mut cur_mapping_raw_data = [0u8; size_of::()]; - - let cur_mapping = AccountInfo::new( - &cur_mapping_key, - true, - true, - &mut cur_mapping_balance, - &mut cur_mapping_raw_data, - &program_id, - false, - Epoch::default(), - ); + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.to_account_info(); + let mut curr_mapping_setup = AccountSetup::new::(&program_id); + let cur_mapping = curr_mapping_setup.to_account_info(); initialize_checked::(&cur_mapping, PC_VERSION).unwrap(); + let mut next_mapping_setup = AccountSetup::new::(&program_id); + let next_mapping = next_mapping_setup.to_account_info(); + { let mut cur_mapping_data = load_checked::(&cur_mapping, PC_VERSION).unwrap(); cur_mapping_data.num_ = PC_MAP_TABLE_SIZE; } - let mut next_mapping_balance = - Rent::minimum_balance(&Rent::default(), size_of::()); - let mut next_mapping_raw_data = [0u8; size_of::()]; - - let next_mapping = AccountInfo::new( - &next_mapping_key, - true, - true, - &mut next_mapping_balance, - &mut next_mapping_raw_data, - &program_id, - false, - Epoch::default(), - ); - assert!(add_mapping( &program_id, &[ @@ -108,7 +65,7 @@ fn test_add_mapping() { assert!(pubkey_equal( &cur_mapping_data.next_, - &next_mapping_key.to_bytes() + &next_mapping.key.to_bytes() )); assert!(pubkey_is_zero(&next_mapping_data.next_)); pubkey_assign(&mut cur_mapping_data.next_, &Pubkey::default().to_bytes()); diff --git a/program/rust/src/tests/test_add_product.rs b/program/rust/src/tests/test_add_product.rs index 15c27a6b9..07ac7e0de 100644 --- a/program/rust/src/tests/test_add_product.rs +++ b/program/rust/src/tests/test_add_product.rs @@ -1,22 +1,17 @@ use std::mem::size_of; -use bytemuck::{ - bytes_of, - Zeroable, -}; +use crate::tests::test_utils::AccountSetup; +use bytemuck::bytes_of; use solana_program::account_info::AccountInfo; use solana_program::clock::Epoch; -use solana_program::native_token::LAMPORTS_PER_SOL; use solana_program::pubkey::Pubkey; use solana_program::rent::Rent; -use solana_program::system_program; use crate::c_oracle_header::{ cmd_hdr_t, command_t_e_cmd_add_product, pc_map_table_t, pc_prod_t, - PC_ACCTYPE_MAPPING, PC_ACCTYPE_PRODUCT, PC_MAGIC, PC_MAP_TABLE_SIZE, @@ -41,67 +36,19 @@ fn test_add_product() { let instruction_data = bytes_of::(&hdr); let program_id = Pubkey::new_unique(); - let funding_key = Pubkey::new_unique(); - let mkey = Pubkey::new_unique(); - let product_key_1 = Pubkey::new_unique(); - let product_key_2 = Pubkey::new_unique(); - - let system_program = system_program::id(); - let mut funding_balance = LAMPORTS_PER_SOL.clone(); - let funding_account = AccountInfo::new( - &funding_key, - true, - true, - &mut funding_balance, - &mut [], - &system_program, - false, - Epoch::default(), - ); - let mut mapping_balance = Rent::minimum_balance(&Rent::default(), size_of::()); - let mut mapping_data: pc_map_table_t = pc_map_table_t::zeroed(); - mapping_data.magic_ = PC_MAGIC; - mapping_data.ver_ = PC_VERSION; - mapping_data.type_ = PC_ACCTYPE_MAPPING; - let mut mapping_bytes = bytemuck::bytes_of_mut(&mut mapping_data); + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.to_account_info(); - let mapping_account = AccountInfo::new( - &mkey, - true, - true, - &mut mapping_balance, - &mut mapping_bytes, - &program_id, - false, - Epoch::default(), - ); + let mut mapping_setup = AccountSetup::new::(&program_id); + let mapping_account = mapping_setup.to_account_info(); + initialize_checked::(&mapping_account, PC_VERSION).unwrap(); - let mut product_balance = Rent::minimum_balance(&Rent::default(), PC_PROD_ACC_SIZE as usize); - let mut prod_raw_data = [0u8; PC_PROD_ACC_SIZE as usize]; - let product_account = AccountInfo::new( - &product_key_1, - true, - true, - &mut product_balance, - &mut prod_raw_data, - &program_id, - false, - Epoch::default(), - ); + let mut product_setup = AccountSetup::new::(&program_id); + let product_account = product_setup.to_account_info(); - let mut product_balance_2 = Rent::minimum_balance(&Rent::default(), PC_PROD_ACC_SIZE as usize); - let mut prod_raw_data_2 = [0u8; PC_PROD_ACC_SIZE as usize]; - let product_account_2 = AccountInfo::new( - &product_key_2, - true, - true, - &mut product_balance_2, - &mut prod_raw_data_2, - &program_id, - false, - Epoch::default(), - ); + let mut product_setup_2 = AccountSetup::new::(&program_id); + let product_account_2 = product_setup_2.to_account_info(); assert!(add_product( &program_id, diff --git a/program/rust/src/tests/test_init_mapping.rs b/program/rust/src/tests/test_init_mapping.rs index 6e0e40be5..66fffe0d2 100644 --- a/program/rust/src/tests/test_init_mapping.rs +++ b/program/rust/src/tests/test_init_mapping.rs @@ -11,16 +11,11 @@ use crate::rust_oracle::{ clear_account, init_mapping, }; +use crate::tests::test_utils::AccountSetup; use bytemuck::bytes_of; -use solana_program::account_info::AccountInfo; -use solana_program::clock::Epoch; -use solana_program::native_token::LAMPORTS_PER_SOL; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::rent::Rent; -use solana_program::system_program; use std::cell::RefCell; -use std::mem::size_of; use std::rc::Rc; #[test] @@ -33,35 +28,12 @@ fn test_init_mapping() { let program_id = Pubkey::new_unique(); let program_id_2 = Pubkey::new_unique(); - let funding_key = Pubkey::new_unique(); - let mapping_key = Pubkey::new_unique(); - let system_program = system_program::id(); - - let mut funding_balance = LAMPORTS_PER_SOL.clone(); - let mut funding_account = AccountInfo::new( - &funding_key, - true, - true, - &mut funding_balance, - &mut [], - &system_program, - false, - Epoch::default(), - ); - let mut mapping_balance = Rent::minimum_balance(&Rent::default(), size_of::()); - let mut mapping_raw_data = [0u8; size_of::()]; + let mut funding_setup = AccountSetup::new_funding(); + let mut funding_account = funding_setup.to_account_info(); - let mut mapping_account = AccountInfo::new( - &mapping_key, - true, - true, - &mut mapping_balance, - &mut mapping_raw_data, - &program_id, - false, - Epoch::default(), - ); + let mut mapping_setup = AccountSetup::new::(&program_id); + let mut mapping_account = mapping_setup.to_account_info(); assert!(init_mapping( &program_id, diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs new file mode 100644 index 000000000..4abbdc453 --- /dev/null +++ b/program/rust/src/tests/test_utils.rs @@ -0,0 +1,62 @@ +use crate::c_oracle_header::PythAccount; +use solana_program::account_info::AccountInfo; +use solana_program::clock::Epoch; +use solana_program::native_token::LAMPORTS_PER_SOL; +use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; +use solana_program::system_program; + +const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 20536; + +pub struct AccountSetup { + key: Pubkey, + owner: Pubkey, + balance: u64, + size: usize, + data: [u8; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES], +} + +impl AccountSetup { + pub fn new(owner: &Pubkey) -> Self { + let key = Pubkey::new_unique(); + let owner = owner.clone(); + let balance = Rent::minimum_balance(&Rent::default(), T::minimum_size()); + let size = T::minimum_size(); + let data = [0; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; + return AccountSetup { + key, + owner, + balance, + size, + data, + }; + } + + pub fn new_funding() -> Self { + let key = Pubkey::new_unique(); + let owner = system_program::id(); + let balance = LAMPORTS_PER_SOL; + let size = 0; + let data = [0; UPPER_BOUND_OF_ALL_ACCOUNT_SIZES]; + return AccountSetup { + key, + owner, + balance, + size, + data, + }; + } + + pub fn to_account_info(&mut self) -> AccountInfo { + return AccountInfo::new( + &self.key, + true, + true, + &mut self.balance, + &mut self.data[..self.size], + &self.owner, + false, + Epoch::default(), + ); + } +} From 52242b32c72e57cb5a4f3f6fcccf5115c8c79f2f Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 19:28:56 +0000 Subject: [PATCH 06/17] Restore rust oracle --- program/rust/src/rust_oracle.rs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/program/rust/src/rust_oracle.rs b/program/rust/src/rust_oracle.rs index 5765c7eac..ddee73360 100644 --- a/program/rust/src/rust_oracle.rs +++ b/program/rust/src/rust_oracle.rs @@ -1,3 +1,10 @@ +use std::borrow::BorrowMut; +use std::cell::RefMut; +use std::mem::{ + size_of, + size_of_val, +}; + use crate::deserialize::{ load, load_account_as, @@ -7,13 +14,6 @@ use bytemuck::{ bytes_of, bytes_of_mut, }; -use solana_program::msg; -use std::borrow::BorrowMut; -use std::cell::RefMut; -use std::mem::{ - size_of, - size_of_val, -}; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::SUCCESS; @@ -223,28 +223,20 @@ pub fn add_product( accounts: &[AccountInfo], instruction_data: &[u8], ) -> OracleResult { - msg!("one"); let [funding_account, tail_mapping_account, new_product_account] = match accounts { [x, y, z] => Ok([x, y, z]), _ => Err(ProgramError::InvalidArgument), }?; - msg!("two"); - check_valid_funding_account(funding_account)?; - msg!("three"); check_valid_signable_account( program_id, tail_mapping_account, size_of::(), )?; - msg!("{:?}", new_product_account.data_len()); check_valid_signable_account(program_id, new_product_account, PC_PROD_ACC_SIZE as usize)?; - msg!("three"); check_valid_fresh_account(new_product_account)?; - msg!("three"); - let hdr = load::(instruction_data)?; let mut mapping_data = load_checked::(tail_mapping_account, hdr.ver_)?; // The mapping account must have free space to add the product account From cde41385845ef6949decc0daee6f35ca162bfe3b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 19:30:25 +0000 Subject: [PATCH 07/17] Add comment --- program/rust/src/tests/test_utils.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 4abbdc453..43fe15f0a 100644 --- a/program/rust/src/tests/test_utils.rs +++ b/program/rust/src/tests/test_utils.rs @@ -8,6 +8,8 @@ use solana_program::system_program; const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 20536; +/// The goal of this struct is to easily instantiate zeroed solana accounts +/// for the Pyth program to use in tests. pub struct AccountSetup { key: Pubkey, owner: Pubkey, From e2ae4eff96be1d3d76969a3324f61d56dbf7e535 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 20:36:36 +0000 Subject: [PATCH 08/17] Stage --- program/rust/src/c_oracle_header.rs | 10 ++ program/rust/src/lib.rs | 1 - program/rust/src/tests/mod.rs | 1 + program/rust/src/tests/test_add_publisher.rs | 148 +++++++++++++++++++ 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 program/rust/src/tests/test_add_publisher.rs diff --git a/program/rust/src/c_oracle_header.rs b/program/rust/src/c_oracle_header.rs index 71919f94d..13c271e43 100644 --- a/program/rust/src/c_oracle_header.rs +++ b/program/rust/src/c_oracle_header.rs @@ -7,6 +7,7 @@ use bytemuck::{ Pod, Zeroable, }; +use solana_program::pubkey::Pubkey; use std::mem::size_of; //bindings.rs is generated by build.rs to include //things defined in bindings.h @@ -41,6 +42,15 @@ impl PythAccount for pc_price_t { const INITIAL_SIZE: u32 = PC_PRICE_T_COMP_OFFSET as u32; } +impl pc_pub_key_t { + pub fn new_unique() -> pc_pub_key_t { + let solana_unique = Pubkey::new_unique(); + pc_pub_key_t { + k1_: solana_unique.to_bytes(), + } + } +} + #[cfg(target_endian = "little")] unsafe impl Zeroable for pc_acc { diff --git a/program/rust/src/lib.rs b/program/rust/src/lib.rs index bcf864e2d..0860796cb 100644 --- a/program/rust/src/lib.rs +++ b/program/rust/src/lib.rs @@ -1,4 +1,3 @@ -#![deny(warnings)] // Allow non upper case globals from C #![allow(non_upper_case_globals)] // Allow using the solana_program::entrypoint::deserialize function diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index f2cf5b4d3..c1844e5f0 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -1,5 +1,6 @@ mod test_add_mapping; mod test_add_product; +mod test_add_publisher; mod test_init_mapping; mod test_upd_product; mod test_utils; diff --git a/program/rust/src/tests/test_add_publisher.rs b/program/rust/src/tests/test_add_publisher.rs new file mode 100644 index 000000000..ed2531662 --- /dev/null +++ b/program/rust/src/tests/test_add_publisher.rs @@ -0,0 +1,148 @@ +use std::mem::size_of; + +use crate::tests::test_utils::AccountSetup; +use bytemuck::bytes_of; +use solana_program::account_info::AccountInfo; +use solana_program::clock::Epoch; +use solana_program::msg; +use solana_program::program_error::ProgramError; +use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; +use std::mem::swap; + +use crate::c_oracle_header::{ + cmd_add_publisher, + cmd_hdr_t, + command_t_e_cmd_add_product, + pc_map_table_t, + pc_price_t, + pc_prod_t, + pc_pub_key_t, + PythAccount, + PC_ACCTYPE_PRODUCT, + PC_COMP_SIZE, + PC_MAGIC, + PC_MAP_TABLE_SIZE, + PC_PROD_ACC_SIZE, + PC_VERSION, +}; +use crate::deserialize::load_account_as; +use crate::rust_oracle::{ + add_product, + add_publisher, + clear_account, + initialize_checked, + load_checked, + pubkey_equal, +}; + +#[test] +fn test_add_publisher() { + let program_id = Pubkey::new_unique(); + let publisher = pc_pub_key_t::new_unique(); + + let mut cmd = cmd_add_publisher { + ver_: PC_VERSION, + cmd_: command_t_e_cmd_add_product as i32, + pub_: publisher, + }; + let mut instruction_data = bytes_of::(&cmd); + + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.to_account_info(); + + let mut price_setup = AccountSetup::new::(&program_id); + let price_account = price_setup.to_account_info(); + initialize_checked::(&price_account, PC_VERSION).unwrap(); + + + **price_account.try_borrow_mut_lamports().unwrap() = 100; + + // Expect the instruction to fail, because the price account isn't rent exempt + assert_eq!( + add_publisher( + &program_id, + &[funding_account.clone(), price_account.clone(),], + instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + // Now give the price account enough lamports to be rent exempt + **price_account.try_borrow_mut_lamports().unwrap() = + Rent::minimum_balance(&Rent::default(), pc_price_t::minimum_size()); + + + assert!(add_publisher( + &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.num_, 1); + assert!(pubkey_equal( + &price_data.comp_[0].pub_, + bytes_of(&publisher) + )); + } + + // Can't add twice + assert_eq!( + add_publisher( + &program_id, + &[funding_account.clone(), price_account.clone(),], + instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + clear_account(&price_account).unwrap(); + + // Bad price account + assert_eq!( + add_publisher( + &program_id, + &[funding_account.clone(), price_account.clone(),], + instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + initialize_checked::(&price_account, PC_VERSION).unwrap(); + + //Fill up price node + for i in 0..PC_COMP_SIZE { + cmd.pub_ = pc_pub_key_t::new_unique(); + instruction_data = bytes_of::(&cmd); + assert!(add_publisher( + &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.num_, i + 1); + assert!(pubkey_equal( + &price_data.comp_[i as usize].pub_, + bytes_of(&cmd.pub_) + )); + } + } + + cmd.pub_ = pc_pub_key_t::new_unique(); + instruction_data = bytes_of::(&cmd); + assert_eq!( + add_publisher( + &program_id, + &[funding_account.clone(), price_account.clone(),], + instruction_data + ), + Err(ProgramError::InvalidArgument) + ); +} From b2cf3d3dceb167cd704707972c32a127dbb48339 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 20:38:19 +0000 Subject: [PATCH 09/17] Fix warnings --- program/rust/src/lib.rs | 1 + program/rust/src/tests/test_add_publisher.rs | 17 ++--------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/program/rust/src/lib.rs b/program/rust/src/lib.rs index 0860796cb..bcf864e2d 100644 --- a/program/rust/src/lib.rs +++ b/program/rust/src/lib.rs @@ -1,3 +1,4 @@ +#![deny(warnings)] // Allow non upper case globals from C #![allow(non_upper_case_globals)] // Allow using the solana_program::entrypoint::deserialize function diff --git a/program/rust/src/tests/test_add_publisher.rs b/program/rust/src/tests/test_add_publisher.rs index ed2531662..0758847f8 100644 --- a/program/rust/src/tests/test_add_publisher.rs +++ b/program/rust/src/tests/test_add_publisher.rs @@ -1,34 +1,21 @@ -use std::mem::size_of; - use crate::tests::test_utils::AccountSetup; use bytemuck::bytes_of; -use solana_program::account_info::AccountInfo; -use solana_program::clock::Epoch; -use solana_program::msg; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use solana_program::rent::Rent; -use std::mem::swap; + use crate::c_oracle_header::{ cmd_add_publisher, - cmd_hdr_t, command_t_e_cmd_add_product, - pc_map_table_t, pc_price_t, - pc_prod_t, pc_pub_key_t, PythAccount, - PC_ACCTYPE_PRODUCT, PC_COMP_SIZE, - PC_MAGIC, - PC_MAP_TABLE_SIZE, - PC_PROD_ACC_SIZE, PC_VERSION, }; -use crate::deserialize::load_account_as; + use crate::rust_oracle::{ - add_product, add_publisher, clear_account, initialize_checked, From 28b6b27060a1b1d83c57f4196cb0393499c46ce2 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 20:45:12 +0000 Subject: [PATCH 10/17] Prune C code --- program/c/src/oracle/oracle.c | 100 +---------------------------- program/c/src/oracle/test_oracle.c | 82 ----------------------- 2 files changed, 2 insertions(+), 180 deletions(-) diff --git a/program/c/src/oracle/oracle.c b/program/c/src/oracle/oracle.c index 620c4b1f1..01dc27405 100644 --- a/program/c/src/oracle/oracle.c +++ b/program/c/src/oracle/oracle.c @@ -59,55 +59,6 @@ static bool valid_writable_account( SolParameters *prm, is_rent_exempt( *ka->lamports, ka->data_len ); } -static uint64_t add_price( SolParameters *prm, SolAccountInfo *ka ) -{ - // Validate command parameters - cmd_add_price_t *cptr = (cmd_add_price_t*)prm->data; - if ( prm->data_len != sizeof( cmd_add_price_t ) || - cptr->expo_ > PC_MAX_NUM_DECIMALS || - cptr->expo_ < -PC_MAX_NUM_DECIMALS || - cptr->ptype_ == PC_PTYPE_UNKNOWN ) { - return ERROR_INVALID_ARGUMENT; - } - - // Account (1) is the product account that we're going to add to - // Account (2) is the new price account - // Verify that these are signed, writable accounts with correct ownership - // and size - if ( prm->ka_num != 3 || - !valid_funding_account( &ka[0] ) || - !valid_signable_account( prm, &ka[1], PC_PROD_ACC_SIZE ) || - !valid_signable_account( prm, &ka[2], sizeof( pc_price_t ) ) ) { - return ERROR_INVALID_ARGUMENT; - } - - // Verify that the product account is valid - // and that the new price account is uninitialized - pc_prod_t *pptr = (pc_prod_t*)ka[1].data; - pc_price_t *sptr = (pc_price_t*)ka[2].data; - if ( pptr->magic_ != PC_MAGIC || - pptr->ver_ != cptr->ver_ || - pptr->type_ != PC_ACCTYPE_PRODUCT || - sptr->magic_ != 0 ) { - return ERROR_INVALID_ARGUMENT; - } - - // Initialize symbol account - sol_memset( sptr, 0, sizeof( pc_price_t ) ); - sptr->magic_ = PC_MAGIC; - sptr->ver_ = cptr->ver_; - sptr->type_ = PC_ACCTYPE_PRICE; - sptr->size_ = sizeof( pc_price_t ) - sizeof( sptr->comp_ ); - sptr->expo_ = cptr->expo_; - sptr->ptype_ = cptr->ptype_; - pc_pub_key_assign( &sptr->prod_, (pc_pub_key_t*)ka[1].key ); - - // bind price account to product account - pc_pub_key_assign( &sptr->next_, &pptr->px_acc_ ); - pc_pub_key_assign( &pptr->px_acc_, (pc_pub_key_t*)ka[2].key ); - return SUCCESS; -} - static uint64_t init_price( SolParameters *prm, SolAccountInfo *ka ) { // Validate command parameters @@ -186,53 +137,6 @@ static uint64_t set_min_pub( SolParameters *prm, SolAccountInfo *ka ) return SUCCESS; } -static uint64_t add_publisher( SolParameters *prm, SolAccountInfo *ka ) -{ - // Validate command parameters - cmd_add_publisher_t *cptr = (cmd_add_publisher_t*)prm->data; - if ( prm->data_len != sizeof( cmd_add_publisher_t ) || - pc_pub_key_is_zero( &cptr->pub_ ) ) { - return ERROR_INVALID_ARGUMENT; - } - - // Account (1) is the price account - // Verify that this is signed, writable 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 symbol account is initialized and corresponds to the - // same symbol and price-type in the instruction parameters - pc_price_t *sptr = (pc_price_t*)ka[1].data; - if ( sptr->magic_ != PC_MAGIC || - sptr->ver_ != cptr->ver_ || - sptr->type_ != PC_ACCTYPE_PRICE ) { - return ERROR_INVALID_ARGUMENT; - } - - // try to add publisher - for(uint32_t i=0; i != sptr->num_; ++i ) { - pc_price_comp_t *iptr = &sptr->comp_[i]; - if ( pc_pub_key_equal( &iptr->pub_, &cptr->pub_ ) ) { - return ERROR_INVALID_ARGUMENT; - } - } - if ( sptr->num_ >= PC_COMP_SIZE ) { - return ERROR_INVALID_ARGUMENT; - } - pc_price_comp_t *iptr = &sptr->comp_[sptr->num_++]; - sol_memset( iptr, 0, sizeof( pc_price_comp_t ) ); - pc_pub_key_assign( &iptr->pub_, &cptr->pub_ ); - - // update size of account - sptr->size_ = sizeof( pc_price_t ) - sizeof( sptr->comp_ ) + - sptr->num_ * sizeof( pc_price_comp_t ); - return SUCCESS; -} - // remove publisher from price node static uint64_t del_publisher( SolParameters *prm, SolAccountInfo *ka ) { @@ -383,8 +287,8 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka ) case e_cmd_add_mapping: return ERROR_INVALID_ARGUMENT; case e_cmd_add_product: return ERROR_INVALID_ARGUMENT; case e_cmd_upd_product: return ERROR_INVALID_ARGUMENT; - case e_cmd_add_price: return add_price( prm, ka ); - case e_cmd_add_publisher: return add_publisher( prm, 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_test: return ERROR_INVALID_ARGUMENT; diff --git a/program/c/src/oracle/test_oracle.c b/program/c/src/oracle/test_oracle.c index 316345eef..74e44e5a2 100644 --- a/program/c/src/oracle/test_oracle.c +++ b/program/c/src/oracle/test_oracle.c @@ -7,88 +7,6 @@ uint64_t MAPPING_ACCOUNT_LAMPORTS = 143821440; uint64_t PRODUCT_ACCOUNT_LAMPORTS = 4454400; uint64_t PRICE_ACCOUNT_LAMPORTS = 23942400; -Test( oracle, add_publisher ) { - // start with perfect inputs - cmd_add_publisher_t idata = { - .ver_ = PC_VERSION, - .cmd_ = e_cmd_add_publisher, - .pub_ = { .k8_ = { 3UL, 4UL, 5UL, 6UL } } - }; - SolPubkey p_id = {.x = { 0xff, }}; - SolPubkey pkey = {.x = { 1, }}; - SolPubkey skey = {.x = { 3, }}; - uint64_t pqty = 100; - pc_price_t sptr[1]; - sol_memset( sptr, 0, sizeof( pc_price_t ) ); - sptr->magic_ = PC_MAGIC; - sptr->ver_ = PC_VERSION; - sptr->type_ = PC_ACCTYPE_PRICE; - sptr->ptype_ = PC_PTYPE_PRICE; - SolAccountInfo acc[] = {{ - .key = &pkey, - .lamports = &pqty, - .data_len = 0, - .data = NULL, - .owner = NULL, - .rent_epoch = 0, - .is_signer = true, - .is_writable = true, - .executable = false - },{ - .key = &skey, - .lamports = &pqty, - .data_len = sizeof( pc_price_t ), - .data = (uint8_t*)sptr, - .owner = &p_id, - .rent_epoch = 0, - .is_signer = true, - .is_writable = true, - .executable = false - } }; - SolParameters prm = { - .ka = acc, - .ka_num = 2, - .data = (const uint8_t*)&idata, - .data_len = sizeof( idata ), - .program_id = &p_id - }; - - // Expect the instruction to fail, because the price account isn't rent exempt - cr_assert( ERROR_INVALID_ARGUMENT == dispatch( &prm, acc ) ); - - // Now give the price account enough lamports to be rent exempt - acc[1].lamports = &PRICE_ACCOUNT_LAMPORTS; - - cr_assert( SUCCESS == dispatch( &prm, acc ) ); - cr_assert( sptr->num_ == 1 ); - cr_assert( pc_pub_key_equal( &idata.pub_, &sptr->comp_[0].pub_ ) ); - // cant add twice - cr_assert( ERROR_INVALID_ARGUMENT == dispatch( &prm, acc ) ); - - // invalid params - sol_memset( sptr, 0, sizeof( pc_price_t ) ); - sptr->magic_ = PC_MAGIC; - sptr->ver_ = PC_VERSION; - sptr->type_ = PC_ACCTYPE_PRICE; - // bad price account - sptr->magic_ = 0; - cr_assert( ERROR_INVALID_ARGUMENT == dispatch( &prm, acc ) ); - sptr->magic_ = PC_MAGIC; - - // fill up price node - for( unsigned i = 0;; ++i ) { - idata.pub_.k8_[0] = 10 + i; - uint64_t rc = dispatch( &prm, acc ); - if ( rc != SUCCESS ) { - cr_assert( i == ( unsigned )(PC_COMP_SIZE) ); - break; - } - cr_assert( sptr->num_ == i + 1 ); - cr_assert( pc_pub_key_equal( &idata.pub_, &sptr->comp_[i].pub_ ) ); - cr_assert( rc == SUCCESS ); - } -} - Test(oracle, pc_size ) { cr_assert( sizeof( pc_pub_key_t ) == 32 ); cr_assert( sizeof( pc_map_table_t ) == From a955fe30fa4e60cd4e2d3c0e082c2b7f7c8d0385 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 21:17:42 +0000 Subject: [PATCH 11/17] Add price --- program/rust/src/tests/mod.rs | 1 + program/rust/src/tests/test_add_price.rs | 228 +++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 program/rust/src/tests/test_add_price.rs diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index c1844e5f0..c4e1e3af8 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -1,4 +1,5 @@ mod test_add_mapping; +mod test_add_price; mod test_add_product; mod test_add_publisher; mod test_init_mapping; diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs new file mode 100644 index 000000000..28a919f34 --- /dev/null +++ b/program/rust/src/tests/test_add_price.rs @@ -0,0 +1,228 @@ +use std::mem::size_of; + +use crate::tests::test_utils::AccountSetup; +use bytemuck::bytes_of; +use solana_program::account_info::AccountInfo; +use solana_program::clock::Epoch; +use solana_program::program_error::ProgramError; +use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; + +use crate::c_oracle_header::{ + cmd_add_price, + cmd_hdr_t, + command_t_e_cmd_add_price, + command_t_e_cmd_add_product, + pc_map_table_t, + pc_price_t, + pc_prod_t, + PC_ACCTYPE_PRODUCT, + PC_MAGIC, + PC_MAP_TABLE_SIZE, + PC_PROD_ACC_SIZE, + PC_VERSION, +}; +use crate::deserialize::load_account_as; +use crate::rust_oracle::{ + add_price, + add_product, + clear_account, + initialize_checked, + load_checked, + pubkey_equal, + pubkey_is_zero, +}; + +#[test] +fn test_add_price() { + let hdr_add_product = cmd_hdr_t { + ver_: PC_VERSION, + cmd_: command_t_e_cmd_add_product as i32, + }; + + let mut hdr_add_price = cmd_add_price { + ver_: PC_VERSION, + cmd_: command_t_e_cmd_add_price as i32, + expo_: 1, + ptype_: 69, + }; + let instruction_data_add_product = bytes_of::(&hdr_add_product); + let mut instruction_data_add_price = bytes_of::(&hdr_add_price); + + let program_id = Pubkey::new_unique(); + + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.to_account_info(); + + let mut mapping_setup = AccountSetup::new::(&program_id); + let mapping_account = mapping_setup.to_account_info(); + initialize_checked::(&mapping_account, PC_VERSION).unwrap(); + + let mut product_setup = AccountSetup::new::(&program_id); + let product_account = product_setup.to_account_info(); + + let mut price_setup = AccountSetup::new::(&program_id); + let mut price_account = price_setup.to_account_info(); + + let mut price_setup_2 = AccountSetup::new::(&program_id); + let price_account_2 = price_setup_2.to_account_info(); + + assert!(add_product( + &program_id, + &[ + funding_account.clone(), + mapping_account.clone(), + product_account.clone() + ], + instruction_data_add_product + ) + .is_ok()); + + assert!(add_price( + &program_id, + &[ + funding_account.clone(), + product_account.clone(), + price_account.clone() + ], + instruction_data_add_price + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + let product_data = load_checked::(&product_account, PC_VERSION).unwrap(); + assert_eq!(price_data.expo_, 1); + assert_eq!(price_data.ptype_, 69); + assert!(pubkey_equal( + &price_data.prod_, + &product_account.key.to_bytes() + )); + assert!(pubkey_is_zero(&price_data.next_)); + assert!(pubkey_equal( + &product_data.px_acc_, + &price_account.key.to_bytes() + )); + } + + assert!(add_price( + &program_id, + &[ + funding_account.clone(), + product_account.clone(), + price_account_2.clone() + ], + instruction_data_add_price + ) + .is_ok()); + + { + let price_data_2 = load_checked::(&price_account_2, PC_VERSION).unwrap(); + let product_data = load_checked::(&product_account, PC_VERSION).unwrap(); + assert_eq!(price_data_2.expo_, 1); + assert_eq!(price_data_2.ptype_, 69); + assert!(pubkey_equal( + &price_data_2.prod_, + &product_account.key.to_bytes() + )); + assert!(pubkey_equal( + &price_data_2.next_, + &price_account.key.to_bytes() + )); + assert!(pubkey_equal( + &product_data.px_acc_, + &price_account_2.key.to_bytes() + )); + } + + // Wrong number of accounts + assert_eq!( + add_price( + &program_id, + &[funding_account.clone(), product_account.clone()], + instruction_data_add_price + ), + Err(ProgramError::InvalidArgument) + ); + + // Price account is already initialized + assert_eq!( + add_price( + &program_id, + &[ + funding_account.clone(), + product_account.clone(), + price_account.clone() + ], + instruction_data_add_price + ), + Err(ProgramError::InvalidArgument) + ); + + clear_account(&price_account).unwrap(); + + // Wrong ptype + hdr_add_price = cmd_add_price { + ver_: PC_VERSION, + cmd_: command_t_e_cmd_add_price as i32, + expo_: 6, + ptype_: 0, + }; + instruction_data_add_price = bytes_of::(&hdr_add_price); + + + assert_eq!( + add_price( + &program_id, + &[ + funding_account.clone(), + product_account.clone(), + price_account.clone() + ], + instruction_data_add_price + ), + Err(ProgramError::InvalidArgument) + ); + + + //Price not signing + hdr_add_price = cmd_add_price { + ver_: PC_VERSION, + cmd_: command_t_e_cmd_add_price as i32, + expo_: 6, + ptype_: 1, + }; + instruction_data_add_price = bytes_of::(&hdr_add_price); + price_account.is_signer = false; + + assert_eq!( + add_price( + &program_id, + &[ + funding_account.clone(), + product_account.clone(), + price_account.clone() + ], + instruction_data_add_price + ), + Err(ProgramError::InvalidArgument) + ); + + // Fresh product account + price_account.is_signer = true; + clear_account(&product_account).unwrap(); + + + assert_eq!( + add_price( + &program_id, + &[ + funding_account.clone(), + product_account.clone(), + price_account.clone() + ], + instruction_data_add_price + ), + Err(ProgramError::InvalidArgument) + ); +} From 2c7aae6bfa5d97934840c3cfe4bd16de296aef95 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 21:20:10 +0000 Subject: [PATCH 12/17] Cleanup imports --- program/rust/src/tests/test_add_price.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/program/rust/src/tests/test_add_price.rs b/program/rust/src/tests/test_add_price.rs index 28a919f34..7134b0c91 100644 --- a/program/rust/src/tests/test_add_price.rs +++ b/program/rust/src/tests/test_add_price.rs @@ -1,12 +1,7 @@ -use std::mem::size_of; - use crate::tests::test_utils::AccountSetup; use bytemuck::bytes_of; -use solana_program::account_info::AccountInfo; -use solana_program::clock::Epoch; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::rent::Rent; use crate::c_oracle_header::{ cmd_add_price, @@ -16,13 +11,8 @@ use crate::c_oracle_header::{ pc_map_table_t, pc_price_t, pc_prod_t, - PC_ACCTYPE_PRODUCT, - PC_MAGIC, - PC_MAP_TABLE_SIZE, - PC_PROD_ACC_SIZE, PC_VERSION, }; -use crate::deserialize::load_account_as; use crate::rust_oracle::{ add_price, add_product, From 8996c50af58d63d3000db83e7911d52acd899111 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 21:29:24 +0000 Subject: [PATCH 13/17] Upd product uses the new syntax --- program/rust/src/tests/test_upd_product.rs | 37 ++++------------------ program/rust/src/tests/test_utils.rs | 8 ++++- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/program/rust/src/tests/test_upd_product.rs b/program/rust/src/tests/test_upd_product.rs index a547d8c2f..7f2ba72eb 100644 --- a/program/rust/src/tests/test_upd_product.rs +++ b/program/rust/src/tests/test_upd_product.rs @@ -1,12 +1,9 @@ use std::mem::size_of; +use crate::tests::test_utils::AccountSetup; use solana_program::account_info::AccountInfo; -use solana_program::clock::Epoch; -use solana_program::native_token::LAMPORTS_PER_SOL; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::rent::Rent; -use solana_program::system_program; use crate::c_oracle_header::{ cmd_hdr_t, @@ -30,34 +27,12 @@ fn test_upd_product() { let mut instruction_data = [0u8; PC_PROD_ACC_SIZE as usize]; let program_id = Pubkey::new_unique(); - let funding_key = Pubkey::new_unique(); - let product_key = Pubkey::new_unique(); - - let system_program = system_program::id(); - let mut funding_balance = LAMPORTS_PER_SOL.clone(); - let funding_account = AccountInfo::new( - &funding_key, - true, - true, - &mut funding_balance, - &mut [], - &system_program, - false, - Epoch::default(), - ); - let mut product_balance = Rent::minimum_balance(&Rent::default(), PC_PROD_ACC_SIZE as usize); - let mut prod_raw_data = [0u8; PC_PROD_ACC_SIZE as usize]; - let product_account = AccountInfo::new( - &product_key, - true, - true, - &mut product_balance, - &mut prod_raw_data, - &program_id, - false, - Epoch::default(), - ); + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.to_account_info(); + + let mut product_setup = AccountSetup::new::(&program_id); + let product_account = product_setup.to_account_info(); initialize_checked::(&product_account, PC_VERSION).unwrap(); diff --git a/program/rust/src/tests/test_utils.rs b/program/rust/src/tests/test_utils.rs index 43fe15f0a..65dba0261 100644 --- a/program/rust/src/tests/test_utils.rs +++ b/program/rust/src/tests/test_utils.rs @@ -8,8 +8,14 @@ use solana_program::system_program; const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 20536; -/// The goal of this struct is to easily instantiate zeroed solana accounts +/// The goal of this struct is to easily instantiate fresh solana accounts /// for the Pyth program to use in tests. +/// The reason why we can't just create an `AccountInfo` object +/// is that we need to give ownership of `key`, `owner` and `data` to the outside scope +/// otherwise AccountInfo will become a dangling pointer. +/// After instantiating the setup `AccountSetup` with `new` (that line will transfer the fields to +/// the outer scope), `to_account_info` gives the user an `AccountInfo` pointing to the fields of +/// the AccountSetup. pub struct AccountSetup { key: Pubkey, owner: Pubkey, From 7c1f41f7e1aca2f3101807b29b1c09512d96a692 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 21:38:31 +0000 Subject: [PATCH 14/17] Formatted --- program/rust/src/c_oracle_header.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/program/rust/src/c_oracle_header.rs b/program/rust/src/c_oracle_header.rs index 71919f94d..2e3708e00 100644 --- a/program/rust/src/c_oracle_header.rs +++ b/program/rust/src/c_oracle_header.rs @@ -16,8 +16,18 @@ include!("../bindings.rs"); /// (mapping, price, product). This allows less duplicated code, because now we can create generic /// functions to perform common checks on the accounts and to load and initialize the accounts. pub trait PythAccount: Pod { + /// `ACCOUNT_TYPE` is just the account discriminator, it is different for mapping, product and + /// price const ACCOUNT_TYPE: u32; + /// `INITIAL_SIZE` is the value that the field `size_` will take when the account is first + /// initialized this one is slightly tricky because for mapping (resp. price) `size_` won't + /// include the unpopulated entries of `prod_` (resp. `comp_`). At the beginning there are 0 + /// products (resp. 0 components) therefore `INITIAL_SIZE` will be equal to the offset of + /// `prod_` (resp. `comp_`) Similarly the product account `INITIAL_SIZE` won't include any + /// key values. const INITIAL_SIZE: u32; + /// `minimum_size()` is the minimum size that the solana account holding the struct needs to + /// have. `INITIAL_SIZE` <= `minimum_size()` fn minimum_size() -> usize { size_of::() } From 518cf16b291ac1fd6a09c8615496ff015b907a5d Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 9 Aug 2022 22:37:33 +0000 Subject: [PATCH 15/17] Add size check to previous tests --- program/rust/src/tests/test_add_product.rs | 7 +++++++ program/rust/src/tests/test_add_publisher.rs | 11 ++++++++++- program/rust/src/tests/test_upd_product.rs | 10 ++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/program/rust/src/tests/test_add_product.rs b/program/rust/src/tests/test_add_product.rs index 5f198766c..2aa629691 100644 --- a/program/rust/src/tests/test_add_product.rs +++ b/program/rust/src/tests/test_add_product.rs @@ -13,6 +13,7 @@ use crate::c_oracle_header::{ command_t_e_cmd_add_product, pc_map_table_t, pc_prod_t, + PythAccount, PC_ACCTYPE_PRODUCT, PC_MAGIC, PC_MAP_TABLE_SIZE, @@ -71,6 +72,7 @@ fn test_add_product() { assert_eq!(product_data.type_, PC_ACCTYPE_PRODUCT); assert_eq!(product_data.size_, size_of::() as u32); assert_eq!(mapping_data.num_, 1); + assert_eq!(mapping_data.size_, (pc_map_table_t::INITIAL_SIZE + 32)); assert!(pubkey_equal( &mapping_data.prod_[0], &product_account.key.to_bytes() @@ -90,6 +92,7 @@ fn test_add_product() { { let mapping_data = load_checked::(&mapping_account, PC_VERSION).unwrap(); assert_eq!(mapping_data.num_, 2); + assert_eq!(mapping_data.size_, (pc_map_table_t::INITIAL_SIZE + 2 * 32)); assert!(pubkey_equal( &mapping_data.prod_[1], &product_account_2.key.to_bytes() @@ -141,6 +144,10 @@ fn test_add_product() { ) .is_ok()); let mapping_data = load_checked::(&mapping_account, PC_VERSION).unwrap(); + assert_eq!( + mapping_data.size_, + pc_map_table_t::INITIAL_SIZE + (i + 1) * 32 + ); assert_eq!(mapping_data.num_, i + 1); } diff --git a/program/rust/src/tests/test_add_publisher.rs b/program/rust/src/tests/test_add_publisher.rs index 0758847f8..9d0591f34 100644 --- a/program/rust/src/tests/test_add_publisher.rs +++ b/program/rust/src/tests/test_add_publisher.rs @@ -4,16 +4,17 @@ use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use solana_program::rent::Rent; - use crate::c_oracle_header::{ cmd_add_publisher, command_t_e_cmd_add_product, + pc_price_comp_t, pc_price_t, pc_pub_key_t, PythAccount, PC_COMP_SIZE, PC_VERSION, }; +use std::mem::size_of; use crate::rust_oracle::{ add_publisher, @@ -70,6 +71,10 @@ fn test_add_publisher() { { let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); assert_eq!(price_data.num_, 1); + assert_eq!( + price_data.size_, + pc_price_t::INITIAL_SIZE + (size_of::() as u32) + ); assert!(pubkey_equal( &price_data.comp_[0].pub_, bytes_of(&publisher) @@ -119,6 +124,10 @@ fn test_add_publisher() { &price_data.comp_[i as usize].pub_, bytes_of(&cmd.pub_) )); + assert_eq!( + price_data.size_, + pc_price_t::INITIAL_SIZE + (size_of::() as u32) * (i + 1) + ); } } diff --git a/program/rust/src/tests/test_upd_product.rs b/program/rust/src/tests/test_upd_product.rs index 7f2ba72eb..1fda8445f 100644 --- a/program/rust/src/tests/test_upd_product.rs +++ b/program/rust/src/tests/test_upd_product.rs @@ -10,6 +10,7 @@ use crate::c_oracle_header::{ cmd_upd_product_t, command_t_e_cmd_upd_product, pc_prod_t, + PythAccount, PC_PROD_ACC_SIZE, PC_VERSION, }; @@ -46,6 +47,11 @@ fn test_upd_product() { .is_ok()); assert!(account_has_key_values(&product_account, &kvs).unwrap_or(false)); + { + let product_data = load_checked::(&product_account, PC_VERSION).unwrap(); + assert_eq!(product_data.size_, pc_prod_t::INITIAL_SIZE + 9); + } + // bad size on the 1st string in the key-value pair list instruction_data[size_of::()] = 2; assert_eq!( @@ -67,6 +73,10 @@ fn test_upd_product() { ) .is_ok()); assert!(account_has_key_values(&product_account, &kvs).unwrap_or(false)); + { + let product_data = load_checked::(&product_account, PC_VERSION).unwrap(); + assert_eq!(product_data.size_, pc_prod_t::INITIAL_SIZE); + } // uneven number of keys and values let bad_kvs = ["foo", "bar", "baz"]; From cdb9a2bc4ce09021ae973f6048d1edd1cfc3407b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 10 Aug 2022 15:33:11 +0000 Subject: [PATCH 16/17] Update tests for min pub --- program/rust/src/tests/test_set_min_pub.rs | 36 ++++------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/program/rust/src/tests/test_set_min_pub.rs b/program/rust/src/tests/test_set_min_pub.rs index 0ce65bbe9..f5fdce2c8 100644 --- a/program/rust/src/tests/test_set_min_pub.rs +++ b/program/rust/src/tests/test_set_min_pub.rs @@ -1,11 +1,8 @@ use std::mem::size_of; use solana_program::account_info::AccountInfo; -use solana_program::clock::Epoch; -use solana_program::native_token::LAMPORTS_PER_SOL; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::rent::Rent; use solana_program::system_program; use crate::c_oracle_header::{ @@ -20,42 +17,21 @@ use crate::rust_oracle::{ load_checked, set_min_pub, }; +use crate::tests::test_utils::AccountSetup; #[test] fn test_set_min_pub() { let mut instruction_data = [0u8; size_of::()]; let program_id = Pubkey::new_unique(); - let funding_key = Pubkey::new_unique(); - let price_key = Pubkey::new_unique(); - let system_program = system_program::id(); - let mut funding_balance = LAMPORTS_PER_SOL.clone(); - let funding_account = AccountInfo::new( - &funding_key, - true, - true, - &mut funding_balance, - &mut [], - &system_program, - false, - Epoch::default(), - ); - - let mut price_balance = Rent::minimum_balance(&Rent::default(), size_of::()); - let mut price_raw_data = [0u8; size_of::()]; - let price_account = AccountInfo::new( - &price_key, - true, - true, - &mut price_balance, - &mut price_raw_data, - &program_id, - false, - Epoch::default(), - ); + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.to_account_info(); + let mut price_setup = AccountSetup::new::(&program_id); + let price_account = price_setup.to_account_info(); initialize_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(get_min_pub(&price_account), Ok(0)); populate_instruction(&mut instruction_data, 10); From 3c05a7a23856c331c791e7cde5893b156e4fdc76 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 10 Aug 2022 15:34:12 +0000 Subject: [PATCH 17/17] Remove import --- program/rust/src/tests/test_set_min_pub.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/program/rust/src/tests/test_set_min_pub.rs b/program/rust/src/tests/test_set_min_pub.rs index f5fdce2c8..319743bb9 100644 --- a/program/rust/src/tests/test_set_min_pub.rs +++ b/program/rust/src/tests/test_set_min_pub.rs @@ -3,7 +3,6 @@ use std::mem::size_of; use solana_program::account_info::AccountInfo; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::system_program; use crate::c_oracle_header::{ cmd_set_min_pub_t,