24
24
account_info:: AccountInfo ,
25
25
entrypoint:: ProgramResult ,
26
26
program_error:: ProgramError ,
27
- program_memory:: sol_memset,
27
+ program_memory:: {
28
+ sol_memcmp,
29
+ sol_memset,
30
+ } ,
28
31
pubkey:: Pubkey ,
29
32
} ,
30
33
std:: mem:: size_of,
@@ -41,8 +44,7 @@ pub fn add_publisher(
41
44
let cmd_args = load :: < AddPublisherArgs > ( instruction_data) ?;
42
45
43
46
pyth_assert (
44
- instruction_data. len ( ) == size_of :: < AddPublisherArgs > ( )
45
- && cmd_args. publisher != Pubkey :: default ( ) ,
47
+ instruction_data. len ( ) == size_of :: < AddPublisherArgs > ( ) ,
46
48
ProgramError :: InvalidArgument ,
47
49
) ?;
48
50
@@ -63,6 +65,14 @@ pub fn add_publisher(
63
65
64
66
let mut price_data = load_checked :: < PriceAccount > ( price_account, cmd_args. header . version ) ?;
65
67
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
+
66
76
if price_data. num_ >= PC_NUM_COMP {
67
77
return Err ( ProgramError :: InvalidArgument ) ;
68
78
}
@@ -73,14 +83,149 @@ pub fn add_publisher(
73
83
}
74
84
}
75
85
76
- let current_index: usize = try_convert ( price_data. num_ ) ?;
86
+ let mut current_index: usize = try_convert ( price_data. num_ ) ?;
77
87
sol_memset (
78
88
bytes_of_mut ( & mut price_data. comp_ [ current_index] ) ,
79
89
0 ,
80
90
size_of :: < PriceComponent > ( ) ,
81
91
) ;
82
92
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
+
83
102
price_data. num_ += 1 ;
84
103
price_data. header . size = try_convert :: < _ , u32 > ( PriceAccount :: INITIAL_SIZE ) ?;
85
104
Ok ( ( ) )
86
105
}
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