22 crate :: {
33 accounts:: {
44 PriceAccount ,
5+ PriceComponent ,
56 PriceInfo ,
67 PythOracleSerialize ,
78 UPD_PRICE_WRITE_SEED ,
@@ -127,7 +128,7 @@ pub fn upd_price(
127128 // Check clock
128129 let clock = Clock :: from_account_info ( clock_account) ?;
129130
130- let mut publisher_index: usize = 0 ;
131+ let publisher_index: usize ;
131132 let latest_aggregate_price: PriceInfo ;
132133
133134 // The price_data borrow happens in a scope because it must be
@@ -137,17 +138,15 @@ pub fn upd_price(
137138 // Verify that symbol account is initialized
138139 let price_data = load_checked :: < PriceAccount > ( price_account, cmd_args. header . version ) ?;
139140
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 ( ) ) ;
144148 }
145- publisher_index += 1 ;
146- }
147- pyth_assert (
148- publisher_index < try_convert :: < u32 , usize > ( price_data. num_ ) ?,
149- OracleError :: PermissionViolation . into ( ) ,
150- ) ?;
149+ } ;
151150
152151 latest_aggregate_price = price_data. agg_ ;
153152 let latest_publisher_price = price_data. comp_ [ publisher_index] . latest_ ;
@@ -281,6 +280,62 @@ pub fn upd_price(
281280 Ok ( ( ) )
282281}
283282
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+
284339#[ allow( dead_code) ]
285340// Wrapper struct for the accounts required to add data to the accumulator program.
286341struct MessageBufferAccounts < ' a , ' b : ' a > {
@@ -289,3 +344,58 @@ struct MessageBufferAccounts<'a, 'b: 'a> {
289344 oracle_auth_pda : & ' a AccountInfo < ' b > ,
290345 message_buffer_data : & ' a AccountInfo < ' b > ,
291346}
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