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