Skip to content

Commit 4faa1f7

Browse files
jayantkJayant Krishnamurthyguibescos
authored
Migrate upd_product instruction (#228)
* initial implementation * with a test * with a test * test case * Fix some feedback * fix tests * merge again Co-authored-by: Jayant Krishnamurthy <jkrishnamurthy@jumptrading.com> Co-authored-by: Guillermo Bescos <gbescos@stanford.edu>
1 parent 13ba56f commit 4faa1f7

File tree

6 files changed

+285
-82
lines changed

6 files changed

+285
-82
lines changed

program/c/src/oracle/oracle.c

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -59,56 +59,6 @@ static bool valid_writable_account( SolParameters *prm,
5959
is_rent_exempt( *ka->lamports, ka->data_len );
6060
}
6161

62-
#define PC_ADD_STR \
63-
tag = (pc_str_t*)src;\
64-
tag_len = 1 + tag->len_;\
65-
if ( &src[tag_len] > end ) return ERROR_INVALID_ARGUMENT;\
66-
sol_memcpy( tgt, tag, tag_len );\
67-
tgt += tag_len;\
68-
src += tag_len;\
69-
70-
static uint64_t upd_product( SolParameters *prm, SolAccountInfo *ka )
71-
{
72-
// Account (1) is the existing product account
73-
// Verify that these are signed, writable accounts with correct ownership
74-
// and size
75-
if ( prm->ka_num != 2 ||
76-
!valid_funding_account( &ka[0] ) ||
77-
!valid_signable_account( prm, &ka[1], PC_PROD_ACC_SIZE ) ) {
78-
return ERROR_INVALID_ARGUMENT;
79-
}
80-
81-
// verify that product account is valid
82-
cmd_hdr_t *hdr = (cmd_hdr_t*)prm->data;
83-
pc_prod_t *pptr = (pc_prod_t*)ka[1].data;
84-
if ( pptr->magic_ != PC_MAGIC ||
85-
pptr->ver_ != hdr->ver_ ||
86-
pptr->type_ != PC_ACCTYPE_PRODUCT ) {
87-
return ERROR_INVALID_ARGUMENT;
88-
}
89-
90-
// unpack and verify attribute set and ssign to product account
91-
if ( prm->data_len < sizeof( cmd_upd_product_t ) ||
92-
prm->data_len > PC_PROD_ACC_SIZE +
93-
sizeof( cmd_upd_product_t ) - sizeof( pc_prod_t ) ) {
94-
return ERROR_INVALID_ARGUMENT;
95-
}
96-
pptr->size_ = ( uint32_t )( sizeof( pc_prod_t ) + prm->data_len -
97-
sizeof( cmd_upd_product_t ) );
98-
uint8_t *tgt = (uint8_t*)pptr + sizeof( pc_prod_t );
99-
const uint8_t *src = prm->data + sizeof( cmd_upd_product_t );
100-
const uint8_t *end = prm->data + prm->data_len;
101-
const pc_str_t *tag;
102-
int tag_len;
103-
while( src != end ) {
104-
// check key string
105-
PC_ADD_STR
106-
// check value string
107-
PC_ADD_STR
108-
}
109-
return SUCCESS;
110-
}
111-
11262
static uint64_t add_price( SolParameters *prm, SolAccountInfo *ka )
11363
{
11464
// Validate command parameters
@@ -432,7 +382,7 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka )
432382
case e_cmd_init_mapping: return ERROR_INVALID_ARGUMENT;
433383
case e_cmd_add_mapping: return ERROR_INVALID_ARGUMENT;
434384
case e_cmd_add_product: return ERROR_INVALID_ARGUMENT;
435-
case e_cmd_upd_product: return upd_product( prm, ka );
385+
case e_cmd_upd_product: return ERROR_INVALID_ARGUMENT;
436386
case e_cmd_add_price: return add_price( prm, ka );
437387
case e_cmd_add_publisher: return add_publisher( prm, ka );
438388
case e_cmd_del_publisher: return del_publisher( prm, ka );

program/rust/src/processor.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ use crate::c_oracle_header::{
1616
command_t_e_cmd_upd_account_version,
1717
command_t_e_cmd_upd_price,
1818
command_t_e_cmd_upd_price_no_fail_on_error,
19+
command_t_e_cmd_upd_product,
1920
PC_VERSION,
2021
};
22+
use crate::deserialize::load;
2123
use crate::error::{
2224
OracleError,
2325
OracleResult,
@@ -28,11 +30,11 @@ use crate::rust_oracle::{
2830
add_product,
2931
add_publisher,
3032
init_mapping,
33+
upd_product,
3134
update_price,
3235
update_version,
3336
};
3437

35-
use crate::deserialize::load;
3638
///dispatch to the right instruction in the oracle
3739
pub fn process_instruction(
3840
program_id: &Pubkey,
@@ -69,6 +71,7 @@ pub fn process_instruction(
6971
command_t_e_cmd_add_mapping => add_mapping(program_id, accounts, instruction_data),
7072
command_t_e_cmd_add_publisher => add_publisher(program_id, accounts, instruction_data),
7173
command_t_e_cmd_add_product => add_product(program_id, accounts, instruction_data),
74+
command_t_e_cmd_upd_product => upd_product(program_id, accounts, instruction_data),
7275
_ => c_entrypoint_wrapper(input),
7376
}
7477
}

program/rust/src/rust_oracle.rs

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,25 @@ use std::mem::{
55
size_of_val,
66
};
77

8-
use crate::deserialize::{
9-
load,
10-
load_account_as,
11-
load_account_as_mut,
12-
};
138
use bytemuck::{
149
bytes_of,
1510
bytes_of_mut,
1611
};
17-
1812
use solana_program::account_info::AccountInfo;
1913
use solana_program::entrypoint::SUCCESS;
2014
use solana_program::program_error::ProgramError;
21-
use solana_program::program_memory::sol_memset;
15+
use solana_program::program_memory::{
16+
sol_memcpy,
17+
sol_memset,
18+
};
2219
use solana_program::pubkey::Pubkey;
2320
use solana_program::rent::Rent;
2421

2522
use crate::c_oracle_header::{
2623
cmd_add_price_t,
2724
cmd_add_publisher_t,
2825
cmd_hdr_t,
26+
cmd_upd_product_t,
2927
pc_acc,
3028
pc_map_table_t,
3129
pc_price_comp,
@@ -40,10 +38,14 @@ use crate::c_oracle_header::{
4038
PC_PROD_ACC_SIZE,
4139
PC_PTYPE_UNKNOWN,
4240
};
41+
use crate::deserialize::{
42+
load,
43+
load_account_as,
44+
load_account_as_mut,
45+
};
4346
use crate::error::OracleResult;
44-
use crate::OracleError;
45-
4647
use crate::utils::pyth_assert;
48+
use crate::OracleError;
4749

4850
use super::c_entrypoint_wrapper;
4951

@@ -260,6 +262,66 @@ pub fn add_product(
260262
Ok(SUCCESS)
261263
}
262264

265+
/// Update the metadata associated with a product, overwriting any existing metadata.
266+
/// The metadata is provided as a list of key-value pairs at the end of the `instruction_data`.
267+
pub fn upd_product(
268+
program_id: &Pubkey,
269+
accounts: &[AccountInfo],
270+
instruction_data: &[u8],
271+
) -> OracleResult {
272+
let [funding_account, product_account] = match accounts {
273+
[x, y] => Ok([x, y]),
274+
_ => Err(ProgramError::InvalidArgument),
275+
}?;
276+
277+
check_valid_funding_account(funding_account)?;
278+
check_valid_signable_account(program_id, product_account, try_convert(PC_PROD_ACC_SIZE)?)?;
279+
280+
let hdr = load::<cmd_hdr_t>(instruction_data)?;
281+
{
282+
// Validate that product_account contains the appropriate account header
283+
let mut _product_data = load_checked::<pc_prod_t>(product_account, hdr.ver_)?;
284+
}
285+
286+
pyth_assert(
287+
instruction_data.len() >= size_of::<cmd_upd_product_t>(),
288+
ProgramError::InvalidInstructionData,
289+
)?;
290+
let new_data_len = instruction_data.len() - size_of::<cmd_upd_product_t>();
291+
let max_data_len = try_convert::<_, usize>(PC_PROD_ACC_SIZE)? - size_of::<pc_prod_t>();
292+
pyth_assert(new_data_len <= max_data_len, ProgramError::InvalidArgument)?;
293+
294+
let new_data = &instruction_data[size_of::<cmd_upd_product_t>()..instruction_data.len()];
295+
let mut idx = 0;
296+
// new_data must be a list of key-value pairs, both of which are instances of pc_str_t.
297+
// Try reading the key-value pairs to validate that new_data is properly formatted.
298+
while idx < new_data.len() {
299+
let key = read_pc_str_t(&new_data[idx..])?;
300+
idx += key.len();
301+
let value = read_pc_str_t(&new_data[idx..])?;
302+
idx += value.len();
303+
}
304+
305+
// This assertion shouldn't ever fail, but be defensive.
306+
pyth_assert(idx == new_data.len(), ProgramError::InvalidArgument)?;
307+
308+
{
309+
let mut data = product_account.try_borrow_mut_data()?;
310+
// Note that this memcpy doesn't necessarily overwrite all existing data in the account.
311+
// This case is handled by updating the .size_ field below.
312+
sol_memcpy(
313+
&mut data[size_of::<pc_prod_t>()..],
314+
new_data,
315+
new_data.len(),
316+
);
317+
}
318+
319+
let mut product_data = load_checked::<pc_prod_t>(product_account, hdr.ver_)?;
320+
product_data.size_ = try_convert(size_of::<pc_prod_t>() + new_data.len())?;
321+
322+
Ok(SUCCESS)
323+
}
324+
263325
fn valid_funding_account(account: &AccountInfo) -> bool {
264326
account.is_signer && account.is_writable
265327
}
@@ -362,7 +424,22 @@ pub fn pubkey_equal(target: &pc_pub_key_t, source: &[u8]) -> bool {
362424
}
363425

364426
/// Convert `x: T` into a `U`, returning the appropriate `OracleError` if the conversion fails.
365-
fn try_convert<T, U: TryFrom<T>>(x: T) -> Result<U, OracleError> {
427+
pub fn try_convert<T, U: TryFrom<T>>(x: T) -> Result<U, OracleError> {
366428
// Note: the error here assumes we're only applying this function to integers right now.
367429
U::try_from(x).map_err(|_| OracleError::IntegerCastingError)
368430
}
431+
432+
/// Read a `pc_str_t` from the beginning of `source`. Returns a slice of `source` containing
433+
/// the bytes of the `pc_str_t`.
434+
pub fn read_pc_str_t(source: &[u8]) -> Result<&[u8], ProgramError> {
435+
if source.is_empty() {
436+
Err(ProgramError::InvalidArgument)
437+
} else {
438+
let tag_len: usize = try_convert(source[0])?;
439+
if tag_len + 1 > source.len() {
440+
Err(ProgramError::InvalidArgument)
441+
} else {
442+
Ok(&source[..(1 + tag_len)])
443+
}
444+
}
445+
}

program/rust/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mod test_add_mapping;
22
mod test_add_product;
33
mod test_init_mapping;
4+
mod test_upd_product;

program/rust/src/tests/test_add_product.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bytemuck::{
77
use solana_program::account_info::AccountInfo;
88
use solana_program::clock::Epoch;
99
use solana_program::native_token::LAMPORTS_PER_SOL;
10+
use solana_program::program_error::ProgramError;
1011
use solana_program::pubkey::Pubkey;
1112
use solana_program::rent::Rent;
1213
use solana_program::system_program;
@@ -162,16 +163,18 @@ fn test_add_product() {
162163
false,
163164
Epoch::default(),
164165
);
165-
assert!(add_product(
166-
&program_id,
167-
&[
168-
funding_account.clone(),
169-
mapping_account.clone(),
170-
product_account_3.clone()
171-
],
172-
instruction_data
173-
)
174-
.is_err());
166+
assert_eq!(
167+
add_product(
168+
&program_id,
169+
&[
170+
funding_account.clone(),
171+
mapping_account.clone(),
172+
product_account_3.clone()
173+
],
174+
instruction_data
175+
),
176+
Err(ProgramError::InvalidArgument)
177+
);
175178

176179
// test fill up of mapping table
177180
clear_account(&mapping_account).unwrap();
@@ -196,16 +199,18 @@ fn test_add_product() {
196199

197200
clear_account(&product_account).unwrap();
198201

199-
assert!(add_product(
200-
&program_id,
201-
&[
202-
funding_account.clone(),
203-
mapping_account.clone(),
204-
product_account.clone()
205-
],
206-
instruction_data
207-
)
208-
.is_err());
202+
assert_eq!(
203+
add_product(
204+
&program_id,
205+
&[
206+
funding_account.clone(),
207+
mapping_account.clone(),
208+
product_account.clone()
209+
],
210+
instruction_data
211+
),
212+
Err(ProgramError::InvalidArgument)
213+
);
209214

210215
let mapping_data = load_checked::<pc_map_table_t>(&mapping_account, PC_VERSION).unwrap();
211216
assert_eq!(mapping_data.num_, PC_MAP_TABLE_SIZE);

0 commit comments

Comments
 (0)