22 crate :: {
33 accounts:: {
44 PriceAccount ,
5+ PriceComponent ,
56 PriceInfo ,
67 PythOracleSerialize ,
78 UPD_PRICE_WRITE_SEED ,
3132 } ,
3233 program:: invoke_signed,
3334 program_error:: ProgramError ,
35+ program_memory:: sol_memcmp,
3436 pubkey:: Pubkey ,
3537 sysvar:: Sysvar ,
3638 } ,
@@ -127,7 +129,7 @@ pub fn upd_price(
127129 // Check clock
128130 let clock = Clock :: from_account_info ( clock_account) ?;
129131
130- let mut publisher_index: usize = 0 ;
132+ let publisher_index: usize ;
131133 let latest_aggregate_price: PriceInfo ;
132134
133135 // The price_data borrow happens in a scope because it must be
@@ -137,17 +139,15 @@ pub fn upd_price(
137139 // Verify that symbol account is initialized
138140 let price_data = load_checked :: < PriceAccount > ( price_account, cmd_args. header . version ) ?;
139141
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 ( ) ) ;
144149 }
145- publisher_index += 1 ;
146- }
147- pyth_assert (
148- publisher_index < try_convert :: < u32 , usize > ( price_data. num_ ) ?,
149- OracleError :: PermissionViolation . into ( ) ,
150- ) ?;
150+ } ;
151151
152152 latest_aggregate_price = price_data. agg_ ;
153153 let latest_publisher_price = price_data. comp_ [ publisher_index] . latest_ ;
@@ -281,6 +281,62 @@ pub fn upd_price(
281281 Ok ( ( ) )
282282}
283283
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+
284340#[ allow( dead_code) ]
285341// Wrapper struct for the accounts required to add data to the accumulator program.
286342struct MessageBufferAccounts < ' a , ' b : ' a > {
@@ -289,3 +345,58 @@ struct MessageBufferAccounts<'a, 'b: 'a> {
289345 oracle_auth_pda : & ' a AccountInfo < ' b > ,
290346 message_buffer_data : & ' a AccountInfo < ' b > ,
291347}
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