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+ }
0 commit comments