solana_compute_budget_instruction/
instructions_processor.rs

1use {
2    crate::compute_budget_instruction_details::*, solana_compute_budget::compute_budget_limits::*,
3    solana_feature_set::FeatureSet, solana_pubkey::Pubkey,
4    solana_svm_transaction::instruction::SVMInstruction,
5    solana_transaction_error::TransactionError,
6};
7
8/// Processing compute_budget could be part of tx sanitizing, failed to process
9/// these instructions will drop the transaction eventually without execution,
10/// may as well fail it early.
11/// If succeeded, the transaction's specific limits/requests (could be default)
12/// are retrieved and returned,
13pub fn process_compute_budget_instructions<'a>(
14    instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)> + Clone,
15    feature_set: &FeatureSet,
16) -> Result<ComputeBudgetLimits, TransactionError> {
17    ComputeBudgetInstructionDetails::try_from(instructions)?
18        .sanitize_and_convert_to_compute_budget_limits(feature_set)
19}
20
21#[cfg(test)]
22mod tests {
23    use {
24        super::*,
25        solana_compute_budget_interface::ComputeBudgetInstruction,
26        solana_hash::Hash,
27        solana_instruction::{error::InstructionError, Instruction},
28        solana_keypair::Keypair,
29        solana_message::Message,
30        solana_pubkey::Pubkey,
31        solana_signer::Signer,
32        solana_svm_transaction::svm_message::SVMMessage,
33        solana_system_interface::instruction::transfer,
34        solana_transaction::{sanitized::SanitizedTransaction, Transaction},
35        solana_transaction_error::TransactionError,
36        std::num::NonZeroU32,
37    };
38
39    macro_rules! test {
40        ( $instructions: expr, $expected_result: expr) => {
41            for feature_set in [FeatureSet::default(), FeatureSet::all_enabled()] {
42                test!($instructions, $expected_result, &feature_set);
43            }
44        };
45        ( $instructions: expr, $expected_result: expr, $feature_set: expr) => {
46            let payer_keypair = Keypair::new();
47            let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
48                &[&payer_keypair],
49                Message::new($instructions, Some(&payer_keypair.pubkey())),
50                Hash::default(),
51            ));
52
53            let result = process_compute_budget_instructions(
54                SVMMessage::program_instructions_iter(&tx),
55                $feature_set,
56            );
57            assert_eq!($expected_result, result);
58        };
59    }
60
61    #[test]
62    fn test_process_instructions() {
63        // Units
64        test!(
65            &[],
66            Ok(ComputeBudgetLimits {
67                compute_unit_limit: 0,
68                ..ComputeBudgetLimits::default()
69            })
70        );
71        test!(
72            &[
73                ComputeBudgetInstruction::set_compute_unit_limit(1),
74                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
75            ],
76            Ok(ComputeBudgetLimits {
77                compute_unit_limit: 1,
78                ..ComputeBudgetLimits::default()
79            })
80        );
81        test!(
82            &[
83                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT + 1),
84                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
85            ],
86            Ok(ComputeBudgetLimits {
87                compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
88                ..ComputeBudgetLimits::default()
89            })
90        );
91        test!(
92            &[
93                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
94                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
95            ],
96            Ok(ComputeBudgetLimits {
97                compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
98                ..ComputeBudgetLimits::default()
99            })
100        );
101        test!(
102            &[
103                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
104                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
105                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
106                ComputeBudgetInstruction::set_compute_unit_limit(1),
107            ],
108            Ok(ComputeBudgetLimits {
109                compute_unit_limit: 1,
110                ..ComputeBudgetLimits::default()
111            })
112        );
113        test!(
114            &[
115                ComputeBudgetInstruction::set_compute_unit_limit(1),
116                ComputeBudgetInstruction::set_compute_unit_price(42)
117            ],
118            Ok(ComputeBudgetLimits {
119                compute_unit_limit: 1,
120                compute_unit_price: 42,
121                ..ComputeBudgetLimits::default()
122            })
123        );
124
125        // HeapFrame
126        test!(
127            &[],
128            Ok(ComputeBudgetLimits {
129                compute_unit_limit: 0,
130                ..ComputeBudgetLimits::default()
131            })
132        );
133        test!(
134            &[
135                ComputeBudgetInstruction::request_heap_frame(40 * 1024),
136                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
137            ],
138            Ok(ComputeBudgetLimits {
139                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
140                updated_heap_bytes: 40 * 1024,
141                ..ComputeBudgetLimits::default()
142            }),
143            &FeatureSet::default()
144        );
145        test!(
146            &[
147                ComputeBudgetInstruction::request_heap_frame(40 * 1024),
148                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
149            ],
150            Ok(ComputeBudgetLimits {
151                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
152                    + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
153                updated_heap_bytes: 40 * 1024,
154                ..ComputeBudgetLimits::default()
155            }),
156            &FeatureSet::all_enabled()
157        );
158        test!(
159            &[
160                ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1),
161                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
162            ],
163            Err(TransactionError::InstructionError(
164                0,
165                InstructionError::InvalidInstructionData,
166            ))
167        );
168        test!(
169            &[
170                ComputeBudgetInstruction::request_heap_frame(31 * 1024),
171                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
172            ],
173            Err(TransactionError::InstructionError(
174                0,
175                InstructionError::InvalidInstructionData,
176            ))
177        );
178        test!(
179            &[
180                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1),
181                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
182            ],
183            Err(TransactionError::InstructionError(
184                0,
185                InstructionError::InvalidInstructionData,
186            ))
187        );
188        test!(
189            &[
190                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
191                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
192            ],
193            Ok(ComputeBudgetLimits {
194                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
195                updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
196                ..ComputeBudgetLimits::default()
197            }),
198            &FeatureSet::default()
199        );
200        test!(
201            &[
202                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
203                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
204            ],
205            Ok(ComputeBudgetLimits {
206                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
207                    + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
208                updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
209                ..ComputeBudgetLimits::default()
210            }),
211            &FeatureSet::all_enabled()
212        );
213        test!(
214            &[
215                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
216                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
217                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
218                ComputeBudgetInstruction::request_heap_frame(1),
219            ],
220            Err(TransactionError::InstructionError(
221                3,
222                InstructionError::InvalidInstructionData,
223            ))
224        );
225        test!(
226            &[
227                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
228                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
229                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
230                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
231                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
232                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
233                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
234                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
235            ],
236            Ok(ComputeBudgetLimits {
237                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 7,
238                ..ComputeBudgetLimits::default()
239            })
240        );
241
242        // Combined
243        test!(
244            &[
245                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
246                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
247                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
248                ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
249            ],
250            Ok(ComputeBudgetLimits {
251                compute_unit_price: u64::MAX,
252                compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
253                updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
254                ..ComputeBudgetLimits::default()
255            })
256        );
257        test!(
258            &[
259                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
260                ComputeBudgetInstruction::set_compute_unit_limit(1),
261                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
262                ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
263            ],
264            Ok(ComputeBudgetLimits {
265                compute_unit_price: u64::MAX,
266                compute_unit_limit: 1,
267                updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
268                ..ComputeBudgetLimits::default()
269            })
270        );
271
272        // Duplicates
273        test!(
274            &[
275                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
276                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
277                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT - 1),
278            ],
279            Err(TransactionError::DuplicateInstruction(2))
280        );
281
282        test!(
283            &[
284                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
285                ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES),
286                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
287            ],
288            Err(TransactionError::DuplicateInstruction(2))
289        );
290        test!(
291            &[
292                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
293                ComputeBudgetInstruction::set_compute_unit_price(0),
294                ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
295            ],
296            Err(TransactionError::DuplicateInstruction(2))
297        );
298    }
299
300    #[test]
301    fn test_process_loaded_accounts_data_size_limit_instruction() {
302        test!(
303            &[],
304            Ok(ComputeBudgetLimits {
305                compute_unit_limit: 0,
306                ..ComputeBudgetLimits::default()
307            })
308        );
309
310        // Assert when set_loaded_accounts_data_size_limit presents,
311        // budget is set with data_size
312        let data_size = 1;
313        let expected_result = Ok(ComputeBudgetLimits {
314            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
315            loaded_accounts_bytes: NonZeroU32::new(data_size).unwrap(),
316            ..ComputeBudgetLimits::default()
317        });
318        test!(
319            &[
320                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
321                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
322            ],
323            expected_result,
324            &FeatureSet::default()
325        );
326
327        let expected_result = Ok(ComputeBudgetLimits {
328            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
329                + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
330            loaded_accounts_bytes: NonZeroU32::new(data_size).unwrap(),
331            ..ComputeBudgetLimits::default()
332        });
333        test!(
334            &[
335                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
336                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
337            ],
338            expected_result,
339            &FeatureSet::all_enabled()
340        );
341
342        // Assert when set_loaded_accounts_data_size_limit presents, with greater than max value
343        // budget is set to max data size
344        let data_size = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get() + 1;
345        let expected_result = Ok(ComputeBudgetLimits {
346            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
347            loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
348            ..ComputeBudgetLimits::default()
349        });
350        test!(
351            &[
352                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
353                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
354            ],
355            expected_result,
356            &FeatureSet::default()
357        );
358
359        let expected_result = Ok(ComputeBudgetLimits {
360            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
361                + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
362            loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
363            ..ComputeBudgetLimits::default()
364        });
365        test!(
366            &[
367                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
368                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
369            ],
370            expected_result,
371            &FeatureSet::all_enabled()
372        );
373
374        // Assert when set_loaded_accounts_data_size_limit is not presented
375        // budget is set to default data size
376        let expected_result = Ok(ComputeBudgetLimits {
377            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
378            loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
379            ..ComputeBudgetLimits::default()
380        });
381
382        test!(
383            &[Instruction::new_with_bincode(
384                Pubkey::new_unique(),
385                &0_u8,
386                vec![]
387            ),],
388            expected_result
389        );
390
391        // Assert when set_loaded_accounts_data_size_limit presents more than once,
392        // return DuplicateInstruction
393        let data_size = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get();
394        let expected_result = Err(TransactionError::DuplicateInstruction(2));
395
396        test!(
397            &[
398                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
399                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
400                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
401            ],
402            expected_result
403        );
404    }
405
406    #[test]
407    fn test_process_mixed_instructions_without_compute_budget() {
408        let payer_keypair = Keypair::new();
409
410        let transaction =
411            SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
412                &[
413                    Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
414                    transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 2),
415                ],
416                Some(&payer_keypair.pubkey()),
417                &[&payer_keypair],
418                Hash::default(),
419            ));
420
421        for (feature_set, expected_result) in [
422            (
423                FeatureSet::default(),
424                Ok(ComputeBudgetLimits {
425                    compute_unit_limit: 2 * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
426                    ..ComputeBudgetLimits::default()
427                }),
428            ),
429            (
430                FeatureSet::all_enabled(),
431                Ok(ComputeBudgetLimits {
432                    compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
433                        + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
434                    ..ComputeBudgetLimits::default()
435                }),
436            ),
437        ] {
438            let result = process_compute_budget_instructions(
439                SVMMessage::program_instructions_iter(&transaction),
440                &feature_set,
441            );
442
443            // assert process_instructions will be successful with default,
444            // and the default compute_unit_limit is 2 times default: one for bpf ix, one for
445            // builtin ix.
446            assert_eq!(result, expected_result);
447        }
448    }
449}