2424 account_info:: AccountInfo ,
2525 entrypoint:: ProgramResult ,
2626 program_error:: ProgramError ,
27- program_memory:: sol_memset,
27+ program_memory:: {
28+ sol_memcmp,
29+ sol_memset,
30+ } ,
2831 pubkey:: Pubkey ,
2932 } ,
3033 std:: mem:: size_of,
@@ -41,8 +44,7 @@ pub fn add_publisher(
4144 let cmd_args = load :: < AddPublisherArgs > ( instruction_data) ?;
4245
4346 pyth_assert (
44- instruction_data. len ( ) == size_of :: < AddPublisherArgs > ( )
45- && cmd_args. publisher != Pubkey :: default ( ) ,
47+ instruction_data. len ( ) == size_of :: < AddPublisherArgs > ( ) ,
4648 ProgramError :: InvalidArgument ,
4749 ) ?;
4850
@@ -63,6 +65,14 @@ pub fn add_publisher(
6365
6466 let mut price_data = load_checked :: < PriceAccount > ( price_account, cmd_args. header . version ) ?;
6567
68+ // Use the call with the default pubkey (000..) as a trigger to sort the publishers as a
69+ // migration step from unsorted list to sorted list.
70+ if cmd_args. publisher == Pubkey :: default ( ) {
71+ let num_comps = try_convert :: < u32 , usize > ( price_data. num_ ) ?;
72+ sort_price_comps ( & mut price_data. comp_ , num_comps) ?;
73+ return Ok ( ( ) ) ;
74+ }
75+
6676 if price_data. num_ >= PC_NUM_COMP {
6777 return Err ( ProgramError :: InvalidArgument ) ;
6878 }
@@ -73,14 +83,149 @@ pub fn add_publisher(
7383 }
7484 }
7585
76- let current_index: usize = try_convert ( price_data. num_ ) ?;
86+ let mut current_index: usize = try_convert ( price_data. num_ ) ?;
7787 sol_memset (
7888 bytes_of_mut ( & mut price_data. comp_ [ current_index] ) ,
7989 0 ,
8090 size_of :: < PriceComponent > ( ) ,
8191 ) ;
8292 price_data. comp_ [ current_index] . pub_ = cmd_args. publisher ;
93+
94+ // Shift the element back to keep the publishers components sorted.
95+ while current_index > 0
96+ && price_data. comp_ [ current_index] . pub_ < price_data. comp_ [ current_index - 1 ] . pub_
97+ {
98+ price_data. comp_ . swap ( current_index, current_index - 1 ) ;
99+ current_index -= 1 ;
100+ }
101+
83102 price_data. num_ += 1 ;
84103 price_data. header . size = try_convert :: < _ , u32 > ( PriceAccount :: INITIAL_SIZE ) ?;
85104 Ok ( ( ) )
86105}
106+
107+ /// A copy of rust slice/sort.rs heapsort implementation which is small and fast. We couldn't use
108+ /// the sort directly because it was only accessible behind a unstable feature flag at the time of
109+ /// writing this code.
110+ #[ inline( always) ]
111+ fn heapsort ( v : & mut [ ( Pubkey , usize ) ] ) {
112+ // This binary heap respects the invariant `parent >= child`.
113+ let sift_down = |v : & mut [ ( Pubkey , usize ) ] , mut node : usize | {
114+ loop {
115+ // Children of `node`.
116+ let mut child = 2 * node + 1 ;
117+ if child >= v. len ( ) {
118+ break ;
119+ }
120+
121+ // Choose the greater child.
122+ if child + 1 < v. len ( )
123+ && sol_memcmp ( v[ child] . 0 . as_ref ( ) , v[ child + 1 ] . 0 . as_ref ( ) , 32 ) < 0
124+ {
125+ child += 1 ;
126+ }
127+
128+ // Stop if the invariant holds at `node`.
129+ if sol_memcmp ( v[ node] . 0 . as_ref ( ) , v[ child] . 0 . as_ref ( ) , 32 ) >= 0 {
130+ break ;
131+ }
132+
133+ // Swap `node` with the greater child, move one step down, and continue sifting.
134+ v. swap ( node, child) ;
135+ node = child;
136+ }
137+ } ;
138+
139+ // Build the heap in linear time.
140+ for i in ( 0 ..v. len ( ) / 2 ) . rev ( ) {
141+ sift_down ( v, i) ;
142+ }
143+
144+ // Pop maximal elements from the heap.
145+ for i in ( 1 ..v. len ( ) ) . rev ( ) {
146+ v. swap ( 0 , i) ;
147+ sift_down ( & mut v[ ..i] , 0 ) ;
148+ }
149+ }
150+
151+ /// Sort the publishers price component list in place by performing minimal swaps.
152+ /// This code is inspired by the sort_by_cached_key implementation in the Rust stdlib.
153+ /// The rust stdlib implementation is not used because it uses a fast sort variant that has
154+ /// a large code size.
155+ ///
156+ /// num_publishers is the number of publishers in the list that should be sorted. It is explicitly
157+ /// passed to avoid callers mistake of passing the full slice which may contain uninitialized values.
158+ #[ inline( always) ]
159+ fn sort_price_comps ( comps : & mut [ PriceComponent ] , num_comps : usize ) -> Result < ( ) , ProgramError > {
160+ let comps = comps
161+ . get_mut ( ..num_comps)
162+ . ok_or ( ProgramError :: InvalidArgument ) ?;
163+
164+ let mut keys = comps
165+ . iter ( )
166+ . enumerate ( )
167+ . map ( |( i, x) | ( x. pub_ , i) )
168+ . collect :: < Vec < _ > > ( ) ;
169+
170+ heapsort ( & mut keys) ;
171+
172+ for i in 0 ..num_comps {
173+ // We know that the publisher with key[i].0 should be at index i in the sorted array and
174+ // want to swap them in-place in O(n). Normally, the publisher at key[i].0 should be at
175+ // key[i].1 but if it is swapped, we need to find the correct index by following the chain
176+ // of swaps.
177+ let mut index = keys[ i] . 1 ;
178+
179+ while index < i {
180+ index = keys[ index] . 1 ;
181+ }
182+ // Setting the final index here is important to make the code linear as we won't
183+ // loop over from i to index again when we reach i again.
184+ keys[ i] . 1 = index;
185+ comps. swap ( i, index) ;
186+ }
187+
188+ Ok ( ( ) )
189+ }
190+
191+ #[ cfg( test) ]
192+ mod test {
193+ use {
194+ super :: * ,
195+ quickcheck_macros:: quickcheck,
196+ } ;
197+
198+ #[ quickcheck]
199+ pub fn test_sort_price_comps ( mut comps : Vec < PriceComponent > ) {
200+ let mut rust_std_sorted_comps = comps. clone ( ) ;
201+ rust_std_sorted_comps. sort_by_key ( |x| x. pub_ ) ;
202+
203+ let num_comps = comps. len ( ) ;
204+ assert_eq ! (
205+ sort_price_comps( & mut comps, num_comps + 1 ) ,
206+ Err ( ProgramError :: InvalidArgument )
207+ ) ;
208+
209+ assert_eq ! ( sort_price_comps( & mut comps, num_comps) , Ok ( ( ) ) ) ;
210+ assert_eq ! ( comps, rust_std_sorted_comps) ;
211+ }
212+
213+ #[ quickcheck]
214+ pub fn test_sort_price_comps_smaller_slice (
215+ mut comps : Vec < PriceComponent > ,
216+ mut num_comps : usize ,
217+ ) {
218+ num_comps = if comps. is_empty ( ) {
219+ 0
220+ } else {
221+ num_comps % comps. len ( )
222+ } ;
223+
224+ let mut rust_std_sorted_comps = comps. get ( ..num_comps) . unwrap ( ) . to_vec ( ) ;
225+ rust_std_sorted_comps. sort_by_key ( |x| x. pub_ ) ;
226+
227+
228+ assert_eq ! ( sort_price_comps( & mut comps, num_comps) , Ok ( ( ) ) ) ;
229+ assert_eq ! ( comps. get( ..num_comps) . unwrap( ) , rust_std_sorted_comps) ;
230+ }
231+ }
0 commit comments