3
3
AccountHeader ,
4
4
PythAccount ,
5
5
} ,
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
+ } ,
9
17
} ,
10
18
bytemuck:: {
11
19
Pod ,
12
20
Zeroable ,
13
21
} ,
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
+ } ,
15
29
std:: mem:: size_of,
16
30
} ;
17
31
@@ -27,3 +41,113 @@ impl PythAccount for ProductAccount {
27
41
const INITIAL_SIZE : u32 = size_of :: < ProductAccount > ( ) as u32 ;
28
42
const MINIMUM_SIZE : usize = PC_PROD_ACC_SIZE as usize ;
29
43
}
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