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