2
2
crate :: {
3
3
accounts:: {
4
4
PriceAccount ,
5
+ PriceComponent ,
5
6
PriceInfo ,
6
7
PythOracleSerialize ,
7
8
UPD_PRICE_WRITE_SEED ,
31
32
} ,
32
33
program:: invoke_signed,
33
34
program_error:: ProgramError ,
35
+ program_memory:: sol_memcmp,
34
36
pubkey:: Pubkey ,
35
37
sysvar:: Sysvar ,
36
38
} ,
@@ -127,7 +129,7 @@ pub fn upd_price(
127
129
// Check clock
128
130
let clock = Clock :: from_account_info ( clock_account) ?;
129
131
130
- let mut publisher_index: usize = 0 ;
132
+ let publisher_index: usize ;
131
133
let latest_aggregate_price: PriceInfo ;
132
134
133
135
// The price_data borrow happens in a scope because it must be
@@ -137,17 +139,15 @@ pub fn upd_price(
137
139
// Verify that symbol account is initialized
138
140
let price_data = load_checked :: < PriceAccount > ( price_account, cmd_args. header . version ) ?;
139
141
140
- // Verify that publisher is authorized
141
- while publisher_index < try_convert :: < u32 , usize > ( price_data. num_ ) ? {
142
- if price_data. comp_ [ publisher_index] . pub_ == * funding_account. key {
143
- break ;
142
+ publisher_index = match find_publisher_index (
143
+ & price_data. comp_ [ ..try_convert :: < u32 , usize > ( price_data. num_ ) ?] ,
144
+ funding_account. key ,
145
+ ) {
146
+ Some ( index) => index,
147
+ None => {
148
+ return Err ( OracleError :: PermissionViolation . into ( ) ) ;
144
149
}
145
- publisher_index += 1 ;
146
- }
147
- pyth_assert (
148
- publisher_index < try_convert :: < u32 , usize > ( price_data. num_ ) ?,
149
- OracleError :: PermissionViolation . into ( ) ,
150
- ) ?;
150
+ } ;
151
151
152
152
latest_aggregate_price = price_data. agg_ ;
153
153
let latest_publisher_price = price_data. comp_ [ publisher_index] . latest_ ;
@@ -281,6 +281,62 @@ pub fn upd_price(
281
281
Ok ( ( ) )
282
282
}
283
283
284
+ /// Find the index of the publisher in the list of components.
285
+ ///
286
+ /// This method first tries to binary search for the publisher's key in the list of components
287
+ /// to get the result faster if the list is sorted. If the list is not sorted, it falls back to
288
+ /// a linear search.
289
+ #[ inline( always) ]
290
+ fn find_publisher_index ( comps : & [ PriceComponent ] , key : & Pubkey ) -> Option < usize > {
291
+ // Verify that publisher is authorized by initially binary searching
292
+ // for the publisher's component in the price account. The binary
293
+ // search might not work if the publisher list is not sorted; therefore
294
+ // we fall back to a linear search.
295
+ let mut binary_search_result = None ;
296
+
297
+ {
298
+ // Binary search to find the publisher key. Rust std binary search is not used because
299
+ // they guarantee valid outcome only if the array is sorted whereas we want to rely on
300
+ // a Equal match if it is a result on an unsorted array. Currently the rust
301
+ // implementation behaves the same but we do not want to rely on api internals.
302
+ let mut left = 0 ;
303
+ let mut right = comps. len ( ) ;
304
+ while left < right {
305
+ let mid = left + ( right - left) / 2 ;
306
+ match sol_memcmp ( comps[ mid] . pub_ . as_ref ( ) , key. as_ref ( ) , 32 ) {
307
+ i if i < 0 => {
308
+ left = mid + 1 ;
309
+ }
310
+ i if i > 0 => {
311
+ right = mid;
312
+ }
313
+ _ => {
314
+ binary_search_result = Some ( mid) ;
315
+ break ;
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ match binary_search_result {
322
+ Some ( index) => Some ( index) ,
323
+ None => {
324
+ let mut index = 0 ;
325
+ while index < comps. len ( ) {
326
+ if sol_memcmp ( comps[ index] . pub_ . as_ref ( ) , key. as_ref ( ) , 32 ) == 0 {
327
+ break ;
328
+ }
329
+ index += 1 ;
330
+ }
331
+ if index == comps. len ( ) {
332
+ None
333
+ } else {
334
+ Some ( index)
335
+ }
336
+ }
337
+ }
338
+ }
339
+
284
340
#[ allow( dead_code) ]
285
341
// Wrapper struct for the accounts required to add data to the accumulator program.
286
342
struct MessageBufferAccounts < ' a , ' b : ' a > {
@@ -289,3 +345,58 @@ struct MessageBufferAccounts<'a, 'b: 'a> {
289
345
oracle_auth_pda : & ' a AccountInfo < ' b > ,
290
346
message_buffer_data : & ' a AccountInfo < ' b > ,
291
347
}
348
+
349
+ #[ cfg( test) ]
350
+ mod test {
351
+ use {
352
+ super :: * ,
353
+ crate :: accounts:: PriceComponent ,
354
+ solana_program:: pubkey:: Pubkey ,
355
+ } ;
356
+
357
+ fn dummy_price_component ( pub_ : Pubkey ) -> PriceComponent {
358
+ let dummy_price_info = PriceInfo {
359
+ price_ : 0 ,
360
+ conf_ : 0 ,
361
+ status_ : 0 ,
362
+ pub_slot_ : 0 ,
363
+ corp_act_status_ : 0 ,
364
+ } ;
365
+
366
+ PriceComponent {
367
+ latest_ : dummy_price_info,
368
+ agg_ : dummy_price_info,
369
+ pub_,
370
+ }
371
+ }
372
+
373
+ /// Test the find_publisher_index method works with an unordered list of components.
374
+ #[ test]
375
+ pub fn test_find_publisher_index_unordered_comp ( ) {
376
+ let comps = ( 0 ..64 )
377
+ . map ( |_| dummy_price_component ( Pubkey :: new_unique ( ) ) )
378
+ . collect :: < Vec < _ > > ( ) ;
379
+
380
+ comps. iter ( ) . enumerate ( ) . for_each ( |( idx, comp) | {
381
+ assert_eq ! ( find_publisher_index( & comps, & comp. pub_) , Some ( idx) ) ;
382
+ } ) ;
383
+
384
+ assert_eq ! ( find_publisher_index( & comps, & Pubkey :: new_unique( ) ) , None ) ;
385
+ }
386
+
387
+ /// Test the find_publisher_index method works with a sorted list of components.
388
+ #[ test]
389
+ pub fn test_find_publisher_index_ordered_comp ( ) {
390
+ let mut comps = ( 0 ..64 )
391
+ . map ( |_| dummy_price_component ( Pubkey :: new_unique ( ) ) )
392
+ . collect :: < Vec < _ > > ( ) ;
393
+
394
+ comps. sort_by_key ( |comp| comp. pub_ ) ;
395
+
396
+ comps. iter ( ) . enumerate ( ) . for_each ( |( idx, comp) | {
397
+ assert_eq ! ( find_publisher_index( & comps, & comp. pub_) , Some ( idx) ) ;
398
+ } ) ;
399
+
400
+ assert_eq ! ( find_publisher_index( & comps, & Pubkey :: new_unique( ) ) , None ) ;
401
+ }
402
+ }
0 commit comments