Skip to content

Commit 3a7a991

Browse files
authored
[pyth_oracle] Allow add_product to create product metadata (#332)
* Refactor complete * Format * Add product updates metadata * Include testt * Checkpoint * Restore mod * Comment * Fix comment
1 parent 07d1bdf commit 3a7a991

File tree

7 files changed

+185
-122
lines changed

7 files changed

+185
-122
lines changed

program/rust/src/accounts.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ mod permission;
3636
mod price;
3737
mod product;
3838

39+
#[cfg(test)]
40+
pub use product::{
41+
account_has_key_values,
42+
create_pc_str_t,
43+
};
3944
pub use {
4045
mapping::MappingAccount,
4146
permission::{
@@ -48,7 +53,11 @@ pub use {
4853
PriceEma,
4954
PriceInfo,
5055
},
51-
product::ProductAccount,
56+
product::{
57+
read_pc_str_t,
58+
update_product_metadata,
59+
ProductAccount,
60+
},
5261
};
5362

5463
#[repr(C)]

program/rust/src/accounts/product.rs

+128-4
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,29 @@ use {
33
AccountHeader,
44
PythAccount,
55
},
6-
crate::c_oracle_header::{
7-
PC_ACCTYPE_PRODUCT,
8-
PC_PROD_ACC_SIZE,
6+
crate::{
7+
c_oracle_header::{
8+
PC_ACCTYPE_PRODUCT,
9+
PC_PROD_ACC_SIZE,
10+
},
11+
deserialize::load_checked,
12+
instruction::CommandHeader,
13+
utils::{
14+
pyth_assert,
15+
try_convert,
16+
},
917
},
1018
bytemuck::{
1119
Pod,
1220
Zeroable,
1321
},
14-
solana_program::pubkey::Pubkey,
22+
solana_program::{
23+
account_info::AccountInfo,
24+
entrypoint::ProgramResult,
25+
program_error::ProgramError,
26+
program_memory::sol_memcpy,
27+
pubkey::Pubkey,
28+
},
1529
std::mem::size_of,
1630
};
1731

@@ -27,3 +41,113 @@ impl PythAccount for ProductAccount {
2741
const INITIAL_SIZE: u32 = size_of::<ProductAccount>() as u32;
2842
const MINIMUM_SIZE: usize = PC_PROD_ACC_SIZE as usize;
2943
}
44+
45+
/// Updates the metadata in a product account.
46+
/// The product metadata is located after the header. It is a key-value storage
47+
/// where keys are strings and values are strings
48+
/// that is represented as a byte array with the following schema :
49+
/// `[len(key1), ...key1, len(val1), ...val1, len(key2), ...key2, len(val2), ...val2, ...]`
50+
pub fn update_product_metadata(
51+
instruction_data: &[u8],
52+
product_account: &AccountInfo,
53+
version: u32,
54+
) -> ProgramResult {
55+
pyth_assert(
56+
instruction_data.len() >= size_of::<CommandHeader>(),
57+
ProgramError::InvalidInstructionData,
58+
)?;
59+
60+
let new_data_len = instruction_data.len() - size_of::<CommandHeader>();
61+
let max_data_len = try_convert::<_, usize>(PC_PROD_ACC_SIZE)? - size_of::<ProductAccount>();
62+
pyth_assert(new_data_len <= max_data_len, ProgramError::InvalidArgument)?;
63+
64+
let new_data = &instruction_data[size_of::<CommandHeader>()..instruction_data.len()];
65+
let mut idx = 0;
66+
// new_data must be a list of key-value pairs, both of which are instances of pc_str_t.
67+
// Try reading the key-value pairs to validate that new_data is properly formatted.
68+
while idx < new_data.len() {
69+
let key = read_pc_str_t(&new_data[idx..])?;
70+
idx += key.len();
71+
let value = read_pc_str_t(&new_data[idx..])?;
72+
idx += value.len();
73+
}
74+
75+
// This assertion shouldn't ever fail, but be defensive.
76+
pyth_assert(idx == new_data.len(), ProgramError::InvalidArgument)?;
77+
78+
{
79+
let mut data = product_account.try_borrow_mut_data()?;
80+
// Note that this memcpy doesn't necessarily overwrite all existing data in the account.
81+
// This case is handled by updating the .size_ field below.
82+
sol_memcpy(
83+
&mut data[size_of::<ProductAccount>()..],
84+
new_data,
85+
new_data.len(),
86+
);
87+
}
88+
89+
let mut product_data = load_checked::<ProductAccount>(product_account, version)?;
90+
product_data.header.size = try_convert(size_of::<ProductAccount>() + new_data.len())?;
91+
Ok(())
92+
}
93+
94+
/// Read a `pc_str_t` from the beginning of `source`. Returns a slice of `source` containing
95+
/// the bytes of the `pc_str_t`.
96+
pub fn read_pc_str_t(source: &[u8]) -> Result<&[u8], ProgramError> {
97+
if source.is_empty() {
98+
Err(ProgramError::InvalidArgument)
99+
} else {
100+
let tag_len: usize = try_convert(source[0])?;
101+
if tag_len + 1 > source.len() {
102+
Err(ProgramError::InvalidArgument)
103+
} else {
104+
Ok(&source[..(1 + tag_len)])
105+
}
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
pub fn create_pc_str_t(s: &str) -> Vec<u8> {
111+
let mut v = vec![s.len() as u8];
112+
v.extend_from_slice(s.as_bytes());
113+
v
114+
}
115+
116+
// Check that the key-value list in product_account equals the strings in expected
117+
// Returns an Err if the account data is incorrectly formatted and the comparison cannot be
118+
// performed.
119+
#[cfg(test)]
120+
pub fn account_has_key_values(
121+
product_account: &AccountInfo,
122+
expected: &[&str],
123+
) -> Result<bool, ProgramError> {
124+
let account_size: usize = try_convert(
125+
load_checked::<ProductAccount>(product_account, crate::c_oracle_header::PC_VERSION)?
126+
.header
127+
.size,
128+
)?;
129+
let mut all_account_data = product_account.try_borrow_mut_data()?;
130+
let kv_data = &mut all_account_data[size_of::<ProductAccount>()..account_size];
131+
let mut kv_idx = 0;
132+
let mut expected_idx = 0;
133+
134+
while kv_idx < kv_data.len() {
135+
let key = read_pc_str_t(&kv_data[kv_idx..])?;
136+
if key[0] != try_convert::<_, u8>(key.len())? - 1 {
137+
return Ok(false);
138+
}
139+
140+
if &key[1..] != expected[expected_idx].as_bytes() {
141+
return Ok(false);
142+
}
143+
144+
kv_idx += key.len();
145+
expected_idx += 1;
146+
}
147+
148+
if expected_idx != expected.len() {
149+
return Ok(false);
150+
}
151+
152+
Ok(true)
153+
}

program/rust/src/processor/add_product.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use {
22
crate::{
33
accounts::{
4+
update_product_metadata,
45
MappingAccount,
56
ProductAccount,
67
PythAccount,
@@ -83,5 +84,7 @@ pub fn add_product(
8384
)? + mapping_data.number_of_products
8485
* try_convert::<_, u32>(size_of::<Pubkey>())?;
8586

87+
update_product_metadata(instruction_data, new_product_account, hdr.version)?;
88+
8689
Ok(())
8790
}
+5-43
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use {
22
crate::{
3-
accounts::ProductAccount,
4-
c_oracle_header::PC_PROD_ACC_SIZE,
3+
accounts::{
4+
update_product_metadata,
5+
ProductAccount,
6+
},
57
deserialize::{
68
load,
79
load_checked,
@@ -10,20 +12,14 @@ use {
1012
utils::{
1113
check_valid_funding_account,
1214
check_valid_signable_account_or_permissioned_funding_account,
13-
pyth_assert,
14-
read_pc_str_t,
15-
try_convert,
1615
},
1716
OracleError,
1817
},
1918
solana_program::{
2019
account_info::AccountInfo,
2120
entrypoint::ProgramResult,
22-
program_error::ProgramError,
23-
program_memory::sol_memcpy,
2421
pubkey::Pubkey,
2522
},
26-
std::mem::size_of,
2723
};
2824

2925
/// Update the metadata associated with a product, overwriting any existing metadata.
@@ -58,41 +54,7 @@ pub fn upd_product(
5854
let mut _product_data = load_checked::<ProductAccount>(product_account, hdr.version)?;
5955
}
6056

61-
pyth_assert(
62-
instruction_data.len() >= size_of::<CommandHeader>(),
63-
ProgramError::InvalidInstructionData,
64-
)?;
65-
let new_data_len = instruction_data.len() - size_of::<CommandHeader>();
66-
let max_data_len = try_convert::<_, usize>(PC_PROD_ACC_SIZE)? - size_of::<ProductAccount>();
67-
pyth_assert(new_data_len <= max_data_len, ProgramError::InvalidArgument)?;
68-
69-
let new_data = &instruction_data[size_of::<CommandHeader>()..instruction_data.len()];
70-
let mut idx = 0;
71-
// new_data must be a list of key-value pairs, both of which are instances of pc_str_t.
72-
// Try reading the key-value pairs to validate that new_data is properly formatted.
73-
while idx < new_data.len() {
74-
let key = read_pc_str_t(&new_data[idx..])?;
75-
idx += key.len();
76-
let value = read_pc_str_t(&new_data[idx..])?;
77-
idx += value.len();
78-
}
79-
80-
// This assertion shouldn't ever fail, but be defensive.
81-
pyth_assert(idx == new_data.len(), ProgramError::InvalidArgument)?;
82-
83-
{
84-
let mut data = product_account.try_borrow_mut_data()?;
85-
// Note that this memcpy doesn't necessarily overwrite all existing data in the account.
86-
// This case is handled by updating the .size_ field below.
87-
sol_memcpy(
88-
&mut data[size_of::<ProductAccount>()..],
89-
new_data,
90-
new_data.len(),
91-
);
92-
}
93-
94-
let mut product_data = load_checked::<ProductAccount>(product_account, hdr.version)?;
95-
product_data.header.size = try_convert(size_of::<ProductAccount>() + new_data.len())?;
57+
update_product_metadata(instruction_data, product_account, hdr.version)?;
9658

9759
Ok(())
9860
}

0 commit comments

Comments
 (0)