1use {
9 crate::{block_cost_limits::*, transaction_cost::*},
10 solana_bincode::limited_deserialize,
11 solana_borsh::v1::try_from_slice_unchecked,
12 solana_builtins_default_costs::get_builtin_instruction_cost,
13 solana_compute_budget::compute_budget_limits::{
14 DEFAULT_HEAP_COST, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT,
15 },
16 solana_compute_budget_interface::ComputeBudgetInstruction,
17 solana_feature_set::{self as feature_set, FeatureSet},
18 solana_fee_structure::FeeStructure,
19 solana_pubkey::Pubkey,
20 solana_runtime_transaction::{
21 transaction_meta::StaticMeta, transaction_with_meta::TransactionWithMeta,
22 },
23 solana_sdk_ids::{compute_budget, system_program},
24 solana_svm_transaction::instruction::SVMInstruction,
25 solana_system_interface::{
26 instruction::SystemInstruction, MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
27 MAX_PERMITTED_DATA_LENGTH,
28 },
29 std::num::Saturating,
30};
31
32pub struct CostModel;
33
34#[derive(Debug, PartialEq)]
35enum SystemProgramAccountAllocation {
36 None,
37 Some(u64),
38 Failed,
39}
40
41impl CostModel {
42 pub fn calculate_cost<'a, Tx: TransactionWithMeta>(
43 transaction: &'a Tx,
44 feature_set: &FeatureSet,
45 ) -> TransactionCost<'a, Tx> {
46 if transaction.is_simple_vote_transaction() {
47 TransactionCost::SimpleVote { transaction }
48 } else {
49 let (programs_execution_cost, loaded_accounts_data_size_cost, data_bytes_cost) =
50 Self::get_transaction_cost(
51 transaction,
52 transaction.program_instructions_iter(),
53 feature_set,
54 );
55 Self::calculate_non_vote_transaction_cost(
56 transaction,
57 transaction.program_instructions_iter(),
58 transaction.num_write_locks(),
59 programs_execution_cost,
60 loaded_accounts_data_size_cost,
61 data_bytes_cost,
62 feature_set,
63 )
64 }
65 }
66
67 pub fn calculate_cost_for_executed_transaction<'a, Tx: TransactionWithMeta>(
70 transaction: &'a Tx,
71 actual_programs_execution_cost: u64,
72 actual_loaded_accounts_data_size_bytes: u32,
73 feature_set: &FeatureSet,
74 ) -> TransactionCost<'a, Tx> {
75 if transaction.is_simple_vote_transaction() {
76 TransactionCost::SimpleVote { transaction }
77 } else {
78 let loaded_accounts_data_size_cost = Self::calculate_loaded_accounts_data_size_cost(
79 actual_loaded_accounts_data_size_bytes,
80 feature_set,
81 );
82 let instructions_data_cost =
83 Self::get_instructions_data_cost(transaction.program_instructions_iter());
84
85 Self::calculate_non_vote_transaction_cost(
86 transaction,
87 transaction.program_instructions_iter(),
88 transaction.num_write_locks(),
89 actual_programs_execution_cost,
90 loaded_accounts_data_size_cost,
91 instructions_data_cost,
92 feature_set,
93 )
94 }
95 }
96
97 pub fn estimate_cost<'a, Tx: StaticMeta>(
102 transaction: &'a Tx,
103 instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)> + Clone,
104 num_write_locks: u64,
105 feature_set: &FeatureSet,
106 ) -> TransactionCost<'a, Tx> {
107 if transaction.is_simple_vote_transaction() {
108 return TransactionCost::SimpleVote { transaction };
109 }
110 let (programs_execution_cost, loaded_accounts_data_size_cost, data_bytes_cost) =
111 Self::get_transaction_cost(transaction, instructions.clone(), feature_set);
112 Self::calculate_non_vote_transaction_cost(
113 transaction,
114 instructions,
115 num_write_locks,
116 programs_execution_cost,
117 loaded_accounts_data_size_cost,
118 data_bytes_cost,
119 feature_set,
120 )
121 }
122
123 fn calculate_non_vote_transaction_cost<'a, Tx: StaticMeta>(
124 transaction: &'a Tx,
125 instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)> + Clone,
126 num_write_locks: u64,
127 programs_execution_cost: u64,
128 loaded_accounts_data_size_cost: u64,
129 data_bytes_cost: u64,
130 feature_set: &FeatureSet,
131 ) -> TransactionCost<'a, Tx> {
132 let signature_cost = Self::get_signature_cost(transaction, feature_set);
133 let write_lock_cost = Self::get_write_lock_cost(num_write_locks);
134
135 let allocated_accounts_data_size =
136 Self::calculate_allocated_accounts_data_size(instructions);
137
138 let usage_cost_details = UsageCostDetails {
139 transaction,
140 signature_cost,
141 write_lock_cost,
142 data_bytes_cost,
143 programs_execution_cost,
144 loaded_accounts_data_size_cost,
145 allocated_accounts_data_size,
146 };
147
148 TransactionCost::Transaction(usage_cost_details)
149 }
150
151 fn get_signature_cost(transaction: &impl StaticMeta, feature_set: &FeatureSet) -> u64 {
153 let signatures_count_detail = transaction.signature_details();
154
155 let ed25519_verify_cost =
156 if feature_set.is_active(&feature_set::ed25519_precompile_verify_strict::id()) {
157 ED25519_VERIFY_STRICT_COST
158 } else {
159 ED25519_VERIFY_COST
160 };
161
162 let secp256r1_verify_cost =
163 if feature_set.is_active(&feature_set::enable_secp256r1_precompile::id()) {
164 SECP256R1_VERIFY_COST
165 } else {
166 0
167 };
168
169 signatures_count_detail
170 .num_transaction_signatures()
171 .saturating_mul(SIGNATURE_COST)
172 .saturating_add(
173 signatures_count_detail
174 .num_secp256k1_instruction_signatures()
175 .saturating_mul(SECP256K1_VERIFY_COST),
176 )
177 .saturating_add(
178 signatures_count_detail
179 .num_ed25519_instruction_signatures()
180 .saturating_mul(ed25519_verify_cost),
181 )
182 .saturating_add(
183 signatures_count_detail
184 .num_secp256r1_instruction_signatures()
185 .saturating_mul(secp256r1_verify_cost),
186 )
187 }
188
189 fn get_write_lock_cost(num_write_locks: u64) -> u64 {
191 WRITE_LOCK_UNITS.saturating_mul(num_write_locks)
192 }
193
194 fn get_transaction_cost<'a>(
196 meta: &impl StaticMeta,
197 instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)>,
198 feature_set: &FeatureSet,
199 ) -> (u64, u64, u64) {
200 if feature_set.is_active(&feature_set::reserve_minimal_cus_for_builtin_instructions::id()) {
201 let data_bytes_cost = Self::get_instructions_data_cost(instructions);
202 let (programs_execution_cost, loaded_accounts_data_size_cost) =
203 Self::get_estimated_execution_cost(meta, feature_set);
204 (
205 programs_execution_cost,
206 loaded_accounts_data_size_cost,
207 data_bytes_cost,
208 )
209 } else {
210 Self::get_transaction_cost_without_minimal_builtin_cus(meta, instructions, feature_set)
211 }
212 }
213
214 fn get_transaction_cost_without_minimal_builtin_cus<'a>(
215 meta: &impl StaticMeta,
216 instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)>,
217 feature_set: &FeatureSet,
218 ) -> (u64, u64, u64) {
219 let mut programs_execution_costs = 0u64;
220 let mut loaded_accounts_data_size_cost = 0u64;
221 let mut data_bytes_len_total = 0u64;
222 let mut compute_unit_limit_is_set = false;
223 let mut has_user_space_instructions = false;
224
225 for (program_id, instruction) in instructions {
226 let ix_execution_cost =
227 if let Some(builtin_cost) = get_builtin_instruction_cost(program_id, feature_set) {
228 builtin_cost
229 } else {
230 has_user_space_instructions = true;
231 u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT)
232 };
233
234 programs_execution_costs = programs_execution_costs
235 .saturating_add(ix_execution_cost)
236 .min(u64::from(MAX_COMPUTE_UNIT_LIMIT));
237
238 data_bytes_len_total =
239 data_bytes_len_total.saturating_add(instruction.data.len() as u64);
240
241 if compute_budget::check_id(program_id) {
242 if let Ok(ComputeBudgetInstruction::SetComputeUnitLimit(_)) =
243 try_from_slice_unchecked(instruction.data)
244 {
245 compute_unit_limit_is_set = true;
246 }
247 }
248 }
249
250 match meta
254 .compute_budget_instruction_details()
255 .sanitize_and_convert_to_compute_budget_limits(feature_set)
256 {
257 Ok(compute_budget_limits) => {
258 if has_user_space_instructions && compute_unit_limit_is_set {
268 programs_execution_costs = u64::from(compute_budget_limits.compute_unit_limit);
269 }
270
271 loaded_accounts_data_size_cost = Self::calculate_loaded_accounts_data_size_cost(
272 compute_budget_limits.loaded_accounts_bytes.get(),
273 feature_set,
274 );
275 }
276 Err(_) => {
277 programs_execution_costs = 0;
278 }
279 }
280
281 (
282 programs_execution_costs,
283 loaded_accounts_data_size_cost,
284 data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST,
285 )
286 }
287
288 fn get_estimated_execution_cost(
290 transaction: &impl StaticMeta,
291 feature_set: &FeatureSet,
292 ) -> (u64, u64) {
293 let (programs_execution_costs, loaded_accounts_data_size_cost) = match transaction
296 .compute_budget_instruction_details()
297 .sanitize_and_convert_to_compute_budget_limits(feature_set)
298 {
299 Ok(compute_budget_limits) => (
300 u64::from(compute_budget_limits.compute_unit_limit),
301 Self::calculate_loaded_accounts_data_size_cost(
302 compute_budget_limits.loaded_accounts_bytes.get(),
303 feature_set,
304 ),
305 ),
306 Err(_) => (0, 0),
307 };
308
309 (programs_execution_costs, loaded_accounts_data_size_cost)
310 }
311
312 fn get_instructions_data_cost<'a>(
314 instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)>,
315 ) -> u64 {
316 let ix_data_bytes_len_total: u64 = instructions
317 .map(|(_, instruction)| instruction.data.len() as u64)
318 .sum();
319
320 ix_data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST
321 }
322
323 pub fn calculate_loaded_accounts_data_size_cost(
324 loaded_accounts_data_size: u32,
325 _feature_set: &FeatureSet,
326 ) -> u64 {
327 FeeStructure::calculate_memory_usage_cost(loaded_accounts_data_size, DEFAULT_HEAP_COST)
328 }
329
330 fn calculate_account_data_size_on_deserialized_system_instruction(
331 instruction: SystemInstruction,
332 ) -> SystemProgramAccountAllocation {
333 match instruction {
334 SystemInstruction::CreateAccount { space, .. }
335 | SystemInstruction::CreateAccountWithSeed { space, .. }
336 | SystemInstruction::Allocate { space }
337 | SystemInstruction::AllocateWithSeed { space, .. } => {
338 if space > MAX_PERMITTED_DATA_LENGTH {
339 SystemProgramAccountAllocation::Failed
340 } else {
341 SystemProgramAccountAllocation::Some(space)
342 }
343 }
344 _ => SystemProgramAccountAllocation::None,
345 }
346 }
347
348 fn calculate_account_data_size_on_instruction(
349 program_id: &Pubkey,
350 instruction: SVMInstruction,
351 ) -> SystemProgramAccountAllocation {
352 if program_id == &system_program::id() {
353 if let Ok(instruction) =
354 limited_deserialize(instruction.data, solana_packet::PACKET_DATA_SIZE as u64)
355 {
356 Self::calculate_account_data_size_on_deserialized_system_instruction(instruction)
357 } else {
358 SystemProgramAccountAllocation::Failed
359 }
360 } else {
361 SystemProgramAccountAllocation::None
362 }
363 }
364
365 fn calculate_allocated_accounts_data_size<'a>(
368 instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)>,
369 ) -> u64 {
370 let mut tx_attempted_allocation_size = Saturating(0u64);
371 for (program_id, instruction) in instructions {
372 match Self::calculate_account_data_size_on_instruction(program_id, instruction) {
373 SystemProgramAccountAllocation::Failed => {
374 return 0;
380 }
381 SystemProgramAccountAllocation::None => continue,
382 SystemProgramAccountAllocation::Some(ix_attempted_allocation_size) => {
383 tx_attempted_allocation_size += ix_attempted_allocation_size;
384 }
385 }
386 }
387
388 (MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION as u64)
397 .min(tx_attempted_allocation_size.0)
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use {
404 super::*,
405 itertools::Itertools,
406 solana_compute_budget::{
407 self,
408 compute_budget_limits::{
409 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
410 },
411 },
412 solana_compute_budget_interface::ComputeBudgetInstruction,
413 solana_fee_structure::ACCOUNT_DATA_COST_PAGE_SIZE,
414 solana_hash::Hash,
415 solana_instruction::Instruction,
416 solana_keypair::Keypair,
417 solana_message::{compiled_instruction::CompiledInstruction, Message},
418 solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
419 solana_sdk_ids::system_program,
420 solana_signer::Signer,
421 solana_svm_transaction::svm_message::SVMMessage,
422 solana_system_interface::instruction::{self as system_instruction},
423 solana_system_transaction as system_transaction,
424 solana_transaction::Transaction,
425 };
426
427 fn test_setup() -> (Keypair, Hash) {
428 solana_logger::setup();
429 (Keypair::new(), Hash::new_unique())
430 }
431
432 #[test]
433 fn test_calculate_allocated_accounts_data_size_no_allocation() {
434 let transaction = Transaction::new_unsigned(Message::new(
435 &[system_instruction::transfer(
436 &Pubkey::new_unique(),
437 &Pubkey::new_unique(),
438 1,
439 )],
440 Some(&Pubkey::new_unique()),
441 ));
442 let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);
443
444 assert_eq!(
445 CostModel::calculate_allocated_accounts_data_size(
446 sanitized_tx.program_instructions_iter()
447 ),
448 0
449 );
450 }
451
452 #[test]
453 fn test_calculate_allocated_accounts_data_size_multiple_allocations() {
454 let space1 = 100;
455 let space2 = 200;
456 let transaction = Transaction::new_unsigned(Message::new(
457 &[
458 system_instruction::create_account(
459 &Pubkey::new_unique(),
460 &Pubkey::new_unique(),
461 1,
462 space1,
463 &Pubkey::new_unique(),
464 ),
465 system_instruction::allocate(&Pubkey::new_unique(), space2),
466 ],
467 Some(&Pubkey::new_unique()),
468 ));
469 let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);
470
471 assert_eq!(
472 CostModel::calculate_allocated_accounts_data_size(
473 sanitized_tx.program_instructions_iter()
474 ),
475 space1 + space2
476 );
477 }
478
479 #[test]
480 fn test_calculate_allocated_accounts_data_size_max_limit() {
481 let spaces = [MAX_PERMITTED_DATA_LENGTH, MAX_PERMITTED_DATA_LENGTH, 100];
482 assert!(
483 spaces.iter().copied().sum::<u64>()
484 > MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION as u64
485 );
486 let transaction = Transaction::new_unsigned(Message::new(
487 &[
488 system_instruction::create_account(
489 &Pubkey::new_unique(),
490 &Pubkey::new_unique(),
491 1,
492 spaces[0],
493 &Pubkey::new_unique(),
494 ),
495 system_instruction::create_account(
496 &Pubkey::new_unique(),
497 &Pubkey::new_unique(),
498 1,
499 spaces[1],
500 &Pubkey::new_unique(),
501 ),
502 system_instruction::create_account(
503 &Pubkey::new_unique(),
504 &Pubkey::new_unique(),
505 1,
506 spaces[2],
507 &Pubkey::new_unique(),
508 ),
509 ],
510 Some(&Pubkey::new_unique()),
511 ));
512 let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);
513
514 assert_eq!(
515 CostModel::calculate_allocated_accounts_data_size(
516 sanitized_tx.program_instructions_iter()
517 ),
518 MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION as u64,
519 );
520 }
521
522 #[test]
523 fn test_calculate_allocated_accounts_data_size_overflow() {
524 let transaction = Transaction::new_unsigned(Message::new(
525 &[
526 system_instruction::create_account(
527 &Pubkey::new_unique(),
528 &Pubkey::new_unique(),
529 1,
530 100,
531 &Pubkey::new_unique(),
532 ),
533 system_instruction::allocate(&Pubkey::new_unique(), u64::MAX),
534 ],
535 Some(&Pubkey::new_unique()),
536 ));
537 let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);
538
539 assert_eq!(
540 0, CostModel::calculate_allocated_accounts_data_size(
542 sanitized_tx.program_instructions_iter()
543 ),
544 );
545 }
546
547 #[test]
548 fn test_calculate_allocated_accounts_data_size_invalid_ix() {
549 let transaction = Transaction::new_unsigned(Message::new(
550 &[
551 system_instruction::allocate(&Pubkey::new_unique(), 100),
552 Instruction::new_with_bincode(system_program::id(), &(), vec![]),
553 ],
554 Some(&Pubkey::new_unique()),
555 ));
556 let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);
557
558 assert_eq!(
559 0, CostModel::calculate_allocated_accounts_data_size(
561 sanitized_tx.program_instructions_iter()
562 ),
563 );
564 }
565
566 #[test]
567 fn test_cost_model_data_len_cost() {
568 let lamports = 0;
569 let owner = Pubkey::default();
570 let seed = String::default();
571 let space = 100;
572 let base = Pubkey::default();
573 for instruction in [
574 SystemInstruction::CreateAccount {
575 lamports,
576 space,
577 owner,
578 },
579 SystemInstruction::CreateAccountWithSeed {
580 base,
581 seed: seed.clone(),
582 lamports,
583 space,
584 owner,
585 },
586 SystemInstruction::Allocate { space },
587 SystemInstruction::AllocateWithSeed {
588 base,
589 seed,
590 space,
591 owner,
592 },
593 ] {
594 assert_eq!(
595 SystemProgramAccountAllocation::Some(space),
596 CostModel::calculate_account_data_size_on_deserialized_system_instruction(
597 instruction
598 )
599 );
600 }
601 assert_eq!(
602 SystemProgramAccountAllocation::None,
603 CostModel::calculate_account_data_size_on_deserialized_system_instruction(
604 SystemInstruction::TransferWithSeed {
605 lamports,
606 from_seed: String::default(),
607 from_owner: Pubkey::default(),
608 }
609 )
610 );
611 }
612
613 #[test]
614 fn test_cost_model_simple_transaction() {
615 let (mint_keypair, start_hash) = test_setup();
616
617 let keypair = Keypair::new();
618 let simple_transaction = RuntimeTransaction::from_transaction_for_tests(
619 system_transaction::transfer(&mint_keypair, &keypair.pubkey(), 2, start_hash),
620 );
621
622 for (feature_set, expected_execution_cost) in [
623 (
624 FeatureSet::default(),
625 solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
626 ),
627 (
628 FeatureSet::all_enabled(),
629 u64::from(MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT),
630 ),
631 ] {
632 let (program_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) =
633 CostModel::get_transaction_cost(
634 &simple_transaction,
635 simple_transaction.program_instructions_iter(),
636 &feature_set,
637 );
638
639 assert_eq!(expected_execution_cost, program_execution_cost);
640 }
641 }
642
643 #[test]
644 fn test_cost_model_token_transaction() {
645 let (mint_keypair, start_hash) = test_setup();
646
647 let instructions = vec![CompiledInstruction::new(3, &(), vec![1, 2, 0])];
648 let tx = Transaction::new_with_compiled_instructions(
649 &[&mint_keypair],
650 &[solana_pubkey::new_rand(), solana_pubkey::new_rand()],
651 start_hash,
652 vec![Pubkey::new_unique()],
653 instructions,
654 );
655 let token_transaction = RuntimeTransaction::from_transaction_for_tests(tx);
656
657 for (feature_set, expected_execution_cost) in [
658 (
659 FeatureSet::default(),
660 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
661 ),
662 (
663 FeatureSet::all_enabled(),
664 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
665 ),
666 ] {
667 let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) =
668 CostModel::get_transaction_cost(
669 &token_transaction,
670 token_transaction.program_instructions_iter(),
671 &feature_set,
672 );
673
674 assert_eq!(expected_execution_cost, program_execution_cost);
675 assert_eq!(0, data_bytes_cost);
676 }
677 }
678
679 #[test]
680 fn test_cost_model_demoted_write_lock() {
681 let (mint_keypair, start_hash) = test_setup();
682
683 let simple_transaction = RuntimeTransaction::from_transaction_for_tests(
686 system_transaction::transfer(&mint_keypair, &system_program::id(), 2, start_hash),
687 );
688
689 let tx_cost = CostModel::calculate_cost(&simple_transaction, &FeatureSet::default());
691 assert_eq!(2 * WRITE_LOCK_UNITS, tx_cost.write_lock_cost());
692 assert_eq!(1, tx_cost.writable_accounts().count());
693 }
694
695 #[test]
696 fn test_cost_model_compute_budget_transaction() {
697 let (mint_keypair, start_hash) = test_setup();
698 let expected_cu_limit = 12_345;
699
700 let instructions = vec![
701 CompiledInstruction::new(3, &(), vec![1, 2, 0]),
702 CompiledInstruction::new_from_raw_parts(
703 4,
704 ComputeBudgetInstruction::SetComputeUnitLimit(expected_cu_limit)
705 .pack()
706 .unwrap(),
707 vec![],
708 ),
709 ];
710 let tx = Transaction::new_with_compiled_instructions(
711 &[&mint_keypair],
712 &[solana_pubkey::new_rand(), solana_pubkey::new_rand()],
713 start_hash,
714 vec![Pubkey::new_unique(), compute_budget::id()],
715 instructions,
716 );
717 let token_transaction = RuntimeTransaction::from_transaction_for_tests(tx);
718
719 for (feature_set, expected_execution_cost) in [
721 (FeatureSet::default(), expected_cu_limit as u64),
722 (FeatureSet::all_enabled(), expected_cu_limit as u64),
723 ] {
724 let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) =
725 CostModel::get_transaction_cost(
726 &token_transaction,
727 token_transaction.program_instructions_iter(),
728 &feature_set,
729 );
730
731 assert_eq!(expected_execution_cost, program_execution_cost);
732 assert_eq!(1, data_bytes_cost);
733 }
734 }
735
736 #[test]
737 fn test_cost_model_with_failed_compute_budget_transaction() {
738 let (mint_keypair, start_hash) = test_setup();
739
740 let instructions = vec![
741 CompiledInstruction::new(3, &(), vec![1, 2, 0]),
742 CompiledInstruction::new_from_raw_parts(
743 4,
744 ComputeBudgetInstruction::SetComputeUnitLimit(12_345)
745 .pack()
746 .unwrap(),
747 vec![],
748 ),
749 CompiledInstruction::new_from_raw_parts(
751 4,
752 ComputeBudgetInstruction::SetLoadedAccountsDataSizeLimit(0)
753 .pack()
754 .unwrap(),
755 vec![],
756 ),
757 ];
758 let tx = Transaction::new_with_compiled_instructions(
759 &[&mint_keypair],
760 &[solana_pubkey::new_rand(), solana_pubkey::new_rand()],
761 start_hash,
762 vec![Pubkey::new_unique(), compute_budget::id()],
763 instructions,
764 );
765 let token_transaction = RuntimeTransaction::from_transaction_for_tests(tx);
766
767 for feature_set in [FeatureSet::default(), FeatureSet::all_enabled()] {
768 let (program_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) =
769 CostModel::get_transaction_cost(
770 &token_transaction,
771 token_transaction.program_instructions_iter(),
772 &feature_set,
773 );
774 assert_eq!(0, program_execution_cost);
775 }
776 }
777
778 #[test]
779 fn test_cost_model_transaction_many_transfer_instructions() {
780 let (mint_keypair, start_hash) = test_setup();
781
782 let key1 = solana_pubkey::new_rand();
783 let key2 = solana_pubkey::new_rand();
784 let instructions =
785 system_instruction::transfer_many(&mint_keypair.pubkey(), &[(key1, 1), (key2, 1)]);
786 let message = Message::new(&instructions, Some(&mint_keypair.pubkey()));
787 let tx = RuntimeTransaction::from_transaction_for_tests(Transaction::new(
788 &[&mint_keypair],
789 message,
790 start_hash,
791 ));
792
793 for (feature_set, expected_execution_cost) in [
795 (
796 FeatureSet::default(),
797 2 * solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
798 ),
799 (
800 FeatureSet::all_enabled(),
801 2 * u64::from(MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT),
802 ),
803 ] {
804 let (programs_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) =
805 CostModel::get_transaction_cost(&tx, tx.program_instructions_iter(), &feature_set);
806 assert_eq!(expected_execution_cost, programs_execution_cost);
807 assert_eq!(6, data_bytes_cost);
808 }
809 }
810
811 #[test]
812 fn test_cost_model_message_many_different_instructions() {
813 let (mint_keypair, start_hash) = test_setup();
814
815 let key1 = solana_pubkey::new_rand();
817 let key2 = solana_pubkey::new_rand();
818 let prog1 = solana_pubkey::new_rand();
819 let prog2 = solana_pubkey::new_rand();
820 let instructions = vec![
821 CompiledInstruction::new(3, &(), vec![0, 1]),
822 CompiledInstruction::new(4, &(), vec![0, 2]),
823 ];
824 let tx = RuntimeTransaction::from_transaction_for_tests(
825 Transaction::new_with_compiled_instructions(
826 &[&mint_keypair],
827 &[key1, key2],
828 start_hash,
829 vec![prog1, prog2],
830 instructions,
831 ),
832 );
833
834 for (feature_set, expected_cost) in [
835 (
836 FeatureSet::default(),
837 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 2,
838 ),
839 (
840 FeatureSet::all_enabled(),
841 DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 2,
842 ),
843 ] {
844 let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) =
845 CostModel::get_transaction_cost(&tx, tx.program_instructions_iter(), &feature_set);
846 assert_eq!(expected_cost, program_execution_cost);
847 assert_eq!(0, data_bytes_cost);
848 }
849 }
850
851 #[test]
852 fn test_cost_model_sort_message_accounts_by_type() {
853 let signer1 = Keypair::new();
855 let signer2 = Keypair::new();
856 let key1 = Pubkey::new_unique();
857 let key2 = Pubkey::new_unique();
858 let prog1 = Pubkey::new_unique();
859 let prog2 = Pubkey::new_unique();
860 let instructions = vec![
861 CompiledInstruction::new(4, &(), vec![0, 2]),
862 CompiledInstruction::new(5, &(), vec![1, 3]),
863 ];
864 let tx = RuntimeTransaction::from_transaction_for_tests(
865 Transaction::new_with_compiled_instructions(
866 &[&signer1, &signer2],
867 &[key1, key2],
868 Hash::new_unique(),
869 vec![prog1, prog2],
870 instructions,
871 ),
872 );
873
874 let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled());
875 let writable_accounts = tx_cost.writable_accounts().collect_vec();
876 assert_eq!(2 + 2, writable_accounts.len());
877 assert_eq!(signer1.pubkey(), *writable_accounts[0]);
878 assert_eq!(signer2.pubkey(), *writable_accounts[1]);
879 assert_eq!(key1, *writable_accounts[2]);
880 assert_eq!(key2, *writable_accounts[3]);
881 }
882
883 #[test]
884 fn test_cost_model_calculate_cost_all_default() {
885 let (mint_keypair, start_hash) = test_setup();
886 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
887 &mint_keypair,
888 &Keypair::new().pubkey(),
889 2,
890 start_hash,
891 ));
892
893 let expected_account_cost = WRITE_LOCK_UNITS * 2;
894 for (feature_set, expected_execution_cost) in [
895 (
896 FeatureSet::default(),
897 solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
898 ),
899 (
900 FeatureSet::all_enabled(),
901 u64::from(MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT),
902 ),
903 ] {
904 const DEFAULT_PAGE_COST: u64 = 8;
905 let expected_loaded_accounts_data_size_cost =
906 solana_compute_budget::compute_budget_limits::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES
907 .get() as u64
908 / ACCOUNT_DATA_COST_PAGE_SIZE
909 * DEFAULT_PAGE_COST;
910
911 let tx_cost = CostModel::calculate_cost(&tx, &feature_set);
912 assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
913 assert_eq!(expected_execution_cost, tx_cost.programs_execution_cost());
914 assert_eq!(2, tx_cost.writable_accounts().count());
915 assert_eq!(
916 expected_loaded_accounts_data_size_cost,
917 tx_cost.loaded_accounts_data_size_cost()
918 );
919 }
920 }
921
922 #[test]
923 fn test_cost_model_calculate_cost_with_limit() {
924 let (mint_keypair, start_hash) = test_setup();
925 let to_keypair = Keypair::new();
926 let data_limit = 32 * 1024u32;
927 let tx =
928 RuntimeTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
929 &[
930 system_instruction::transfer(&mint_keypair.pubkey(), &to_keypair.pubkey(), 2),
931 ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_limit),
932 ],
933 Some(&mint_keypair.pubkey()),
934 &[&mint_keypair],
935 start_hash,
936 ));
937
938 let expected_account_cost = WRITE_LOCK_UNITS * 2;
939 for (feature_set, expected_execution_cost) in [
940 (
941 FeatureSet::default(),
942 solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS
943 + solana_compute_budget_program::DEFAULT_COMPUTE_UNITS,
944 ),
945 (
946 FeatureSet::all_enabled(),
947 2 * u64::from(MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT),
948 ),
949 ] {
950 let expected_loaded_accounts_data_size_cost = (data_limit as u64) / (32 * 1024) * 8;
951
952 let tx_cost = CostModel::calculate_cost(&tx, &feature_set);
953 assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
954 assert_eq!(expected_execution_cost, tx_cost.programs_execution_cost());
955 assert_eq!(2, tx_cost.writable_accounts().count());
956 assert_eq!(
957 expected_loaded_accounts_data_size_cost,
958 tx_cost.loaded_accounts_data_size_cost()
959 );
960 }
961 }
962
963 #[test]
964 fn test_transaction_cost_with_mix_instruction_without_compute_budget() {
965 let (mint_keypair, start_hash) = test_setup();
966
967 let transaction =
968 RuntimeTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
969 &[
970 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
971 system_instruction::transfer(&mint_keypair.pubkey(), &Pubkey::new_unique(), 2),
972 ],
973 Some(&mint_keypair.pubkey()),
974 &[&mint_keypair],
975 start_hash,
976 ));
977 for (feature_set, expected_execution_cost) in [
979 (
980 FeatureSet::default(),
981 solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS
982 + DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
983 ),
984 (
985 FeatureSet::all_enabled(),
986 u64::from(MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT)
987 + u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
988 ),
989 ] {
990 let (programs_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) =
991 CostModel::get_transaction_cost(
992 &transaction,
993 transaction.program_instructions_iter(),
994 &feature_set,
995 );
996
997 assert_eq!(expected_execution_cost, programs_execution_cost);
998 }
999 }
1000
1001 #[test]
1002 fn test_transaction_cost_with_mix_instruction_with_cu_limit() {
1003 let (mint_keypair, start_hash) = test_setup();
1004 let cu_limit: u32 = 12_345;
1005
1006 let transaction =
1007 RuntimeTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
1008 &[
1009 system_instruction::transfer(&mint_keypair.pubkey(), &Pubkey::new_unique(), 2),
1010 ComputeBudgetInstruction::set_compute_unit_limit(cu_limit),
1011 ],
1012 Some(&mint_keypair.pubkey()),
1013 &[&mint_keypair],
1014 start_hash,
1015 ));
1016 for (feature_set, expected_execution_cost) in [
1017 (
1018 FeatureSet::default(),
1019 solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS
1020 + solana_compute_budget_program::DEFAULT_COMPUTE_UNITS,
1021 ),
1022 (FeatureSet::all_enabled(), cu_limit as u64),
1023 ] {
1024 let (programs_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) =
1025 CostModel::get_transaction_cost(
1026 &transaction,
1027 transaction.program_instructions_iter(),
1028 &feature_set,
1029 );
1030
1031 assert_eq!(expected_execution_cost, programs_execution_cost);
1032 }
1033 }
1034}