@@ -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- } ;
138use bytemuck:: {
149 bytes_of,
1510 bytes_of_mut,
1611} ;
17-
1812use solana_program:: account_info:: AccountInfo ;
1913use solana_program:: entrypoint:: SUCCESS ;
2014use 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+ } ;
2219use solana_program:: pubkey:: Pubkey ;
2320use solana_program:: rent:: Rent ;
2421
2522use 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+ } ;
4346use crate :: error:: OracleResult ;
44- use crate :: OracleError ;
45-
4647use crate :: utils:: pyth_assert;
48+ use crate :: OracleError ;
4749
4850use 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+
263325fn 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+ }
0 commit comments