1use {
2 super::Bank,
3 crate::{bank::CollectorFeeDetails, reward_info::RewardInfo},
4 agave_reserved_account_keys::ReservedAccountKeys,
5 log::debug,
6 solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
7 solana_pubkey::Pubkey,
8 solana_rent::Rent,
9 solana_reward_info::RewardType,
10 solana_runtime_transaction::{
11 transaction_meta::TransactionConfiguration, transaction_with_meta::TransactionWithMeta,
12 },
13 solana_sdk_ids::incinerator,
14 solana_svm::rent_calculator::check_static_account_rent_state_transition,
15 solana_system_interface::program as system_program,
16 std::{result::Result, sync::atomic::Ordering::Relaxed},
17 thiserror::Error,
18};
19
20#[derive(Error, Debug, PartialEq)]
21pub(super) enum DepositFeeError {
22 #[error("fee account became rent paying")]
23 InvalidRentPayingAccount,
24 #[error("lamport overflow")]
25 LamportOverflow,
26 #[error("invalid fee account owner")]
27 InvalidAccountOwner,
28 #[error("collector is a reserved account")]
29 ReservedCollector,
30}
31
32pub(super) enum ExternalCollectorType {
38 SystemAccount,
41 Incinerator,
43}
44
45#[derive(Default)]
46pub struct FeeDistribution {
47 deposit: u64,
48 burn: u64,
49}
50
51impl FeeDistribution {
52 pub fn get_deposit(&self) -> u64 {
53 self.deposit
54 }
55}
56
57impl Bank {
58 pub(super) fn distribute_transaction_fee_details(&self) {
70 let fee_details = self.collector_fee_details.read().unwrap();
71
72 let FeeDistribution { deposit, burn } =
73 self.calculate_reward_and_burn_fee_details(&fee_details);
74
75 let total_burn = self.deposit_or_burn_fee(deposit).saturating_add(burn);
76 self.capitalization.fetch_sub(total_burn, Relaxed);
77 }
78
79 pub fn calculate_reward_for_transaction(
80 &self,
81 transaction: &impl TransactionWithMeta,
82 transaction_configuration: &TransactionConfiguration,
83 ) -> u64 {
84 let fee_details = solana_fee::calculate_fee_details(
85 transaction,
86 self.fee_structure().lamports_per_signature,
87 transaction_configuration.priority_fee_lamports,
88 self.fee_features(),
89 );
90 let FeeDistribution {
91 deposit: reward,
92 burn: _,
93 } = self.calculate_reward_and_burn_fee_details(&CollectorFeeDetails::from(fee_details));
94 reward
95 }
96
97 pub fn calculate_reward_and_burn_fee_details(
98 &self,
99 fee_details: &CollectorFeeDetails,
100 ) -> FeeDistribution {
101 let burn = fee_details.transaction_fee * self.burn_percent() / 100;
102 let deposit = fee_details
103 .priority_fee
104 .saturating_add(fee_details.transaction_fee.saturating_sub(burn));
105 FeeDistribution { deposit, burn }
106 }
107
108 const fn burn_percent(&self) -> u64 {
109 static_assertions::const_assert!(solana_fee_calculator::DEFAULT_BURN_PERCENT <= 100);
113
114 solana_fee_calculator::DEFAULT_BURN_PERCENT as u64
115 }
116
117 fn deposit_or_burn_fee(&self, deposit: u64) -> u64 {
121 if deposit == 0 {
122 return 0;
123 }
124
125 let feature_snapshot = self.feature_set.snapshot();
131 let collector_id = if feature_snapshot.custom_commission_collector {
132 let vote_account = self
133 .epoch_stakes
134 .get(&self.epoch)
135 .and_then(|stakes| {
136 stakes
137 .stakes()
138 .vote_accounts()
139 .get(&self.leader.vote_address)
140 })
141 .expect("The vote account for the leader must exist");
142 vote_account
146 .vote_state_view()
147 .block_revenue_collector()
148 .unwrap_or(&self.leader.id)
149 } else {
150 &self.leader.id
151 };
152
153 match self.deposit_fees(collector_id, deposit) {
154 Ok(post_balance) => {
155 self.rewards.write().unwrap().push((
156 *collector_id,
157 RewardInfo {
158 reward_type: RewardType::Fee,
159 lamports: deposit as i64,
160 post_balance,
161 commission_bps: None,
162 },
163 ));
164 0
165 }
166 Err(err) => {
167 debug!(
168 "Burned {deposit} lamport tx fee instead of sending to {collector_id} due to \
169 {err}"
170 );
171 datapoint_warn!(
172 "bank-burned_fee",
173 ("slot", self.slot(), i64),
174 ("num_lamports", deposit, i64),
175 ("error", err.to_string(), String),
176 );
177 deposit
178 }
179 }
180 }
181
182 fn deposit_fees(&self, collector_id: &Pubkey, fees: u64) -> Result<u64, DepositFeeError> {
184 let mut account = self
185 .get_account_with_fixed_root_no_cache(collector_id)
186 .unwrap_or_default();
187
188 let feature_snapshot = self.feature_set.snapshot();
189 if feature_snapshot.custom_commission_collector {
190 let pre_lamports = account.lamports();
191 account
192 .checked_add_lamports(fees)
193 .map_err(|_| DepositFeeError::LamportOverflow)?;
194 if collector_id != &self.leader.vote_address {
195 Bank::collector_type_checked(
196 collector_id,
197 pre_lamports,
198 &account,
199 &self.reserved_account_keys,
200 &self.rent_collector().rent,
201 feature_snapshot.relax_post_exec_min_balance_check,
202 )?;
203 }
204 } else {
205 if !system_program::check_id(account.owner()) {
206 return Err(DepositFeeError::InvalidAccountOwner);
207 }
208
209 let pre_balance = account.lamports();
210 let distribution = account.checked_add_lamports(fees);
211 if distribution.is_err() {
212 return Err(DepositFeeError::LamportOverflow);
213 }
214
215 if check_static_account_rent_state_transition(
218 pre_balance,
219 account.lamports(),
220 account.data().len(),
221 &self.rent_collector().rent,
222 0, feature_snapshot.relax_post_exec_min_balance_check,
224 )
225 .is_err()
226 {
227 return Err(DepositFeeError::InvalidRentPayingAccount);
228 }
229 }
230
231 self.store_account(collector_id, &account);
232 Ok(account.lamports())
233 }
234
235 pub(super) fn collector_type_checked(
242 collector_id: &Pubkey,
243 pre_lamports: u64,
244 account: &AccountSharedData,
245 reserved_account_keys: &ReservedAccountKeys,
246 rent: &Rent,
247 relax_post_execution_balance_checks: bool,
248 ) -> Result<ExternalCollectorType, DepositFeeError> {
249 if !system_program::check_id(account.owner()) {
250 return Err(DepositFeeError::InvalidAccountOwner);
251 }
252
253 if reserved_account_keys.is_reserved(collector_id) {
254 return Err(DepositFeeError::ReservedCollector);
255 }
256
257 if *collector_id == incinerator::id() {
260 Ok(ExternalCollectorType::Incinerator)
261 } else {
262 if !rent.is_exempt(account.lamports(), account.data().len())
263 && (!relax_post_execution_balance_checks || pre_lamports == 0)
264 {
265 Err(DepositFeeError::InvalidRentPayingAccount)
266 } else {
267 Ok(ExternalCollectorType::SystemAccount)
268 }
269 }
270 }
271}
272
273#[cfg(test)]
274pub mod tests {
275 use {
276 super::*,
277 crate::genesis_utils::{create_genesis_config, create_genesis_config_with_leader},
278 agave_feature_set::FeatureSet,
279 solana_account::state_traits::StateMutWincode as _,
280 solana_pubkey as pubkey,
281 solana_rent::Rent,
282 solana_signer::Signer,
283 solana_vote_interface::state::{VoteStateV4, VoteStateVersions},
284 std::sync::{Arc, RwLock},
285 test_case::test_case,
286 };
287
288 #[test]
289 fn test_deposit_or_burn_zero_fee() {
290 let genesis = create_genesis_config(0);
291 let bank = Bank::new_for_tests(&genesis.genesis_config);
292 assert_eq!(bank.deposit_or_burn_fee(0), 0);
293 }
294
295 #[test_case(true; "custom_commission_collector")]
296 #[test_case(false; "no_custom_commission_collector")]
297 fn test_deposit_or_burn_fee(custom_commission_collector: bool) {
298 #[derive(PartialEq)]
299 enum Scenario {
300 Normal,
301 InvalidOwner,
302 RentPayingAccount,
303 NonDefault,
304 VoteAccount,
305 Incinerator,
306 }
307
308 struct TestCase {
309 scenario: Scenario,
310 }
311
312 impl TestCase {
313 fn new(scenario: Scenario) -> Self {
314 Self { scenario }
315 }
316 }
317
318 for test_case in [
319 TestCase::new(Scenario::Normal),
320 TestCase::new(Scenario::InvalidOwner),
321 TestCase::new(Scenario::RentPayingAccount),
322 TestCase::new(Scenario::NonDefault),
323 TestCase::new(Scenario::VoteAccount),
324 TestCase::new(Scenario::Incinerator),
325 ] {
326 if !custom_commission_collector {
327 match test_case.scenario {
329 Scenario::NonDefault | Scenario::VoteAccount | Scenario::Incinerator => {
330 continue;
331 }
332 Scenario::Normal | Scenario::InvalidOwner | Scenario::RentPayingAccount => {}
333 }
334 }
335 let initial_balance = 1000;
336 let mut genesis =
337 create_genesis_config_with_leader(0, &pubkey::new_rand(), initial_balance);
338 let rent = Rent::default();
339 let min_rent_exempt_balance = rent.minimum_balance(0);
340 genesis.genesis_config.rent = rent; let maybe_collector_id = if custom_commission_collector {
344 let mut maybe_collector_id = None;
345 for (address, account) in genesis.genesis_config.accounts.iter_mut() {
346 if account.owner == solana_sdk_ids::vote::id() {
347 let mut vote_state =
348 VoteStateV4::deserialize(account.data(), &Pubkey::default()).unwrap();
349 let collector_id = match test_case.scenario {
350 Scenario::Normal => vote_state.block_revenue_collector,
351 Scenario::InvalidOwner
352 | Scenario::RentPayingAccount
353 | Scenario::NonDefault => Pubkey::new_unique(),
354 Scenario::Incinerator => incinerator::id(),
355 Scenario::VoteAccount => *address,
356 };
357 vote_state.block_revenue_collector = collector_id;
358 maybe_collector_id = Some(collector_id);
359 let versioned = VoteStateVersions::V4(Box::new(vote_state));
360 account.set_state(&versioned).unwrap();
361 }
362 }
363 maybe_collector_id
364 } else {
365 None
366 };
367
368 let mut bank = Bank::new_for_tests(&genesis.genesis_config);
369 let mut feature_set = FeatureSet::all_enabled();
370 if !custom_commission_collector {
371 feature_set.deactivate(&agave_feature_set::custom_commission_collector::id());
372 }
373 bank.feature_set = Arc::new(feature_set);
374
375 let collector_id = maybe_collector_id.unwrap_or(*bank.leader_id());
376
377 let deposit = 100;
378 let mut burn = 100;
379
380 match test_case.scenario {
381 Scenario::RentPayingAccount => {
382 let account = AccountSharedData::new(1, 1_000, &Pubkey::new_unique());
384 bank.store_account(&collector_id, &account);
385 }
386 Scenario::InvalidOwner => {
387 let account =
389 AccountSharedData::new(min_rent_exempt_balance, 0, &Pubkey::new_unique());
390 bank.store_account(&collector_id, &account);
391 }
392 Scenario::VoteAccount => {
393 }
396 Scenario::Incinerator => {
397 }
399 Scenario::NonDefault | Scenario::Normal => {
400 let account =
401 AccountSharedData::new(min_rent_exempt_balance, 0, &system_program::id());
402 bank.store_account(&collector_id, &account);
403 }
404 }
405
406 let initial_burn = burn;
407 let initial_collector_balance = bank.get_balance(&collector_id);
408 burn += bank.deposit_or_burn_fee(deposit);
409 let new_collector_balance = bank.get_balance(&collector_id);
410
411 match test_case.scenario {
412 Scenario::InvalidOwner | Scenario::RentPayingAccount => {
413 assert_eq!(initial_collector_balance, new_collector_balance);
414 assert_eq!(initial_burn + deposit, burn);
415 let locked_rewards = bank.rewards.read().unwrap();
416 assert!(
417 locked_rewards.is_empty(),
418 "There should be no rewards distributed"
419 );
420 }
421 Scenario::NonDefault
422 | Scenario::Normal
423 | Scenario::VoteAccount
424 | Scenario::Incinerator => {
425 assert_eq!(initial_collector_balance + deposit, new_collector_balance);
426
427 assert_eq!(initial_burn, burn);
428
429 let locked_rewards = bank.rewards.read().unwrap();
430 assert_eq!(
431 locked_rewards.len(),
432 1,
433 "There should be one reward distributed"
434 );
435
436 let reward_info = &locked_rewards[0];
437 assert_eq!(
438 reward_info.1.lamports, deposit as i64,
439 "The reward amount should match the expected deposit"
440 );
441 assert_eq!(
442 reward_info.1.reward_type,
443 RewardType::Fee,
444 "The reward type should be Fee"
445 );
446 }
447 }
448 }
449 }
450
451 #[test]
452 fn test_deposit_fees() {
453 let initial_balance = 1_000_000_000;
454 let genesis = create_genesis_config(initial_balance);
455 let bank = Bank::new_for_tests(&genesis.genesis_config);
456 let pubkey = genesis.mint_keypair.pubkey();
457 let deposit_amount = 500;
458
459 assert_eq!(
460 bank.deposit_fees(&pubkey, deposit_amount),
461 Ok(initial_balance + deposit_amount),
462 "New balance should be the sum of the initial balance and deposit amount"
463 );
464 }
465
466 #[test]
467 fn test_deposit_fees_with_overflow() {
468 let initial_balance = u64::MAX;
469 let genesis = create_genesis_config(initial_balance);
470 let bank = Bank::new_for_tests(&genesis.genesis_config);
471 let pubkey = genesis.mint_keypair.pubkey();
472 let deposit_amount = 500;
473
474 assert_eq!(
475 bank.deposit_fees(&pubkey, deposit_amount),
476 Err(DepositFeeError::LamportOverflow),
477 "Expected an error due to lamport overflow"
478 );
479 }
480
481 #[test_case(true, Ok(()); "allowed")]
482 #[test_case(false, Err(DepositFeeError::InvalidAccountOwner); "prohibited")]
483 fn test_deposit_fees_to_vote_account(
484 custom_commission_collector: bool,
485 expected: Result<(), DepositFeeError>,
486 ) {
487 let initial_balance = 1000;
488 let genesis = create_genesis_config_with_leader(0, &pubkey::new_rand(), initial_balance);
489 let mut bank = Bank::new_for_tests(&genesis.genesis_config);
490 let mut feature_set = FeatureSet::all_enabled();
491 if !custom_commission_collector {
492 feature_set.deactivate(&agave_feature_set::custom_commission_collector::id());
493 }
494 bank.feature_set = Arc::new(feature_set);
495
496 let pubkey = genesis.voting_keypair.pubkey();
497 let deposit_amount = 500;
498 let pre_lamports = bank.get_balance(&pubkey);
499 assert_eq!(
500 expected.map(|_| pre_lamports.saturating_add(deposit_amount)),
501 bank.deposit_fees(&pubkey, deposit_amount)
502 );
503 }
504
505 #[test]
506 fn test_deposit_fees_reserved_account() {
507 let initial_balance = 1000;
508 let genesis = create_genesis_config_with_leader(0, &pubkey::new_rand(), initial_balance);
509 let bank = Bank::new_for_tests(&genesis.genesis_config);
510 let deposit_amount = 500;
511
512 for id in bank.get_reserved_account_keys() {
513 assert!(matches!(
514 bank.deposit_fees(id, deposit_amount),
515 Err(DepositFeeError::ReservedCollector) | Err(DepositFeeError::InvalidAccountOwner),
516 ));
517 }
518 }
519
520 #[test]
521 fn test_deposit_fees_to_nonexistent_account_rent_exempt() {
522 let mut genesis = create_genesis_config(0);
523 let rent = Rent::default();
524 genesis.genesis_config.rent = rent.clone();
525 let bank = Bank::new_for_tests(&genesis.genesis_config);
526 let nonexistent_pubkey = Pubkey::new_unique();
527
528 let deposit_amount = rent.minimum_balance(0);
530
531 assert!(
532 bank.get_account(&nonexistent_pubkey).is_none(),
533 "Account should not exist before deposit"
534 );
535
536 assert_eq!(
537 bank.deposit_fees(&nonexistent_pubkey, deposit_amount),
538 Ok(deposit_amount),
539 "Deposit should succeed when fee is sufficient for rent-exemption"
540 );
541
542 let account = bank.get_account(&nonexistent_pubkey).unwrap();
543 assert_eq!(account.lamports(), deposit_amount);
544 assert_eq!(account.owner(), &system_program::id());
545 }
546
547 #[test]
548 fn test_deposit_fees_to_nonexistent_account_not_rent_exempt() {
549 let mut genesis = create_genesis_config(0);
550 let rent = Rent::default();
551 genesis.genesis_config.rent = rent.clone();
552 let bank = Bank::new_for_tests(&genesis.genesis_config);
553 let nonexistent_pubkey = Pubkey::new_unique();
554
555 let deposit_amount = rent.minimum_balance(0) - 1;
557
558 assert!(
559 bank.get_account(&nonexistent_pubkey).is_none(),
560 "Account should not exist before deposit"
561 );
562
563 assert_eq!(
564 bank.deposit_fees(&nonexistent_pubkey, deposit_amount),
565 Err(DepositFeeError::InvalidRentPayingAccount),
566 "Deposit should fail when fee is insufficient for rent-exemption"
567 );
568
569 assert!(
570 bank.get_account(&nonexistent_pubkey).is_none(),
571 "Account should still not exist after failed deposit"
572 );
573 }
574
575 #[test_case(true; "custom_commission_collector")]
576 #[test_case(false; "no_custom_commission_collector")]
577 fn test_deposit_or_burn_fee_respects_relaxed_post_exec_min_balance_check(
578 custom_commission_collector: bool,
579 ) {
580 enum CollectorState {
581 InitializedToSubRentExemptMinimum,
582 UninitializedToSubRentExemptMinimum,
583 UninitializedToRentExempt,
584 }
585
586 for collector_state in [
587 CollectorState::InitializedToSubRentExemptMinimum,
588 CollectorState::UninitializedToSubRentExemptMinimum,
589 CollectorState::UninitializedToRentExempt,
590 ] {
591 for relax_post_exec_min_balance_check in [false, true] {
592 let mut genesis = create_genesis_config_with_leader(0, &pubkey::new_rand(), 1000);
593 let rent = Rent::default();
594 genesis.genesis_config.rent = rent.clone();
595
596 let initialized_data_len = 64;
597 let rent_exempt_minimum = rent.minimum_balance(initialized_data_len);
598 assert!(rent_exempt_minimum > 1);
599
600 let (pre_balance, deposit, should_succeed) = match collector_state {
601 CollectorState::InitializedToSubRentExemptMinimum => (
602 rent_exempt_minimum - 2,
603 1,
604 relax_post_exec_min_balance_check,
605 ),
606 CollectorState::UninitializedToSubRentExemptMinimum => (0, 1, false),
607 CollectorState::UninitializedToRentExempt => (0, rent_exempt_minimum, true),
608 };
609 let post_balance = pre_balance + deposit;
610 let maybe_collector_id = if custom_commission_collector {
611 let mut maybe_collector_id = None;
612 for account in genesis.genesis_config.accounts.values_mut() {
613 if account.owner == solana_sdk_ids::vote::id() {
614 let mut vote_state =
615 VoteStateV4::deserialize(account.data(), &Pubkey::default())
616 .unwrap();
617 let collector_id = Pubkey::new_unique();
618 vote_state.block_revenue_collector = collector_id;
619 maybe_collector_id = Some(collector_id);
620 let versioned = VoteStateVersions::V4(Box::new(vote_state));
621 account.set_state(&versioned).unwrap();
622 }
623 }
624 maybe_collector_id
625 } else {
626 None
627 };
628
629 let mut bank = Bank::new_for_tests(&genesis.genesis_config);
630
631 let mut feature_set = FeatureSet::all_enabled();
632 if !custom_commission_collector {
633 feature_set.deactivate(&agave_feature_set::custom_commission_collector::id());
634 }
635 if !relax_post_exec_min_balance_check {
636 feature_set
637 .deactivate(&agave_feature_set::relax_post_exec_min_balance_check::id());
638 }
639 bank.feature_set = Arc::new(feature_set);
640
641 let collector_id = maybe_collector_id.unwrap_or(*bank.leader_id());
642 let account = AccountSharedData::new(
643 pre_balance,
644 initialized_data_len,
645 &system_program::id(),
646 );
647 bank.store_account(&collector_id, &account);
648
649 let burned = bank.deposit_or_burn_fee(deposit);
650 let rewards = bank.rewards.read().unwrap();
651
652 if should_succeed {
658 assert_eq!(burned, 0);
659 assert_eq!(bank.get_balance(&collector_id), post_balance);
660 assert_eq!(rewards.len(), 1, "fee should be distributed to the leader");
661 assert_eq!(rewards[0].1.post_balance, post_balance);
662 } else {
663 assert_eq!(burned, deposit);
664 assert_eq!(bank.get_balance(&collector_id), pre_balance);
665 assert!(
666 rewards.is_empty(),
667 "fee should be burned when the rent transition is invalid"
668 );
669 }
670 }
671 }
672 }
673
674 #[test]
675 fn test_distribute_transaction_fee_details_normal() {
676 let initial_balance = 1000;
677 let genesis = create_genesis_config_with_leader(0, &pubkey::new_rand(), initial_balance);
678 let mut bank = Bank::new_for_tests(&genesis.genesis_config);
679 let transaction_fee = 100;
680 let priority_fee = 200;
681 bank.collector_fee_details = RwLock::new(CollectorFeeDetails {
682 transaction_fee,
683 priority_fee,
684 });
685 let expected_burn = transaction_fee * bank.burn_percent() / 100;
686 let expected_rewards = transaction_fee - expected_burn + priority_fee;
687
688 let collector_id = *bank.leader_id();
689
690 let initial_capitalization = bank.capitalization();
691 let initial_collector_balance = bank.get_balance(&collector_id);
692 bank.distribute_transaction_fee_details();
693 let new_collector_balance = bank.get_balance(&collector_id);
694
695 assert_eq!(
696 initial_collector_balance + expected_rewards,
697 new_collector_balance
698 );
699 assert_eq!(
700 initial_capitalization - expected_burn,
701 bank.capitalization()
702 );
703 let locked_rewards = bank.rewards.read().unwrap();
704 assert_eq!(
705 locked_rewards.len(),
706 1,
707 "There should be one reward distributed"
708 );
709
710 let reward_info = &locked_rewards[0];
711 assert_eq!(
712 reward_info.1.lamports, expected_rewards as i64,
713 "The reward amount should match the expected deposit"
714 );
715 assert_eq!(
716 reward_info.1.reward_type,
717 RewardType::Fee,
718 "The reward type should be Fee"
719 );
720 }
721
722 #[test]
723 fn test_distribute_transaction_fee_details_zero() {
724 let genesis = create_genesis_config(0);
725 let bank = Bank::new_for_tests(&genesis.genesis_config);
726 assert_eq!(
727 *bank.collector_fee_details.read().unwrap(),
728 CollectorFeeDetails::default()
729 );
730
731 let initial_capitalization = bank.capitalization();
732 let initial_leader_id_balance = bank.get_balance(bank.leader_id());
733 bank.distribute_transaction_fee_details();
734 let new_leader_id_balance = bank.get_balance(bank.leader_id());
735
736 assert_eq!(initial_leader_id_balance, new_leader_id_balance);
737 assert_eq!(initial_capitalization, bank.capitalization());
738 let locked_rewards = bank.rewards.read().unwrap();
739 assert!(
740 locked_rewards.is_empty(),
741 "There should be no rewards distributed"
742 );
743 }
744
745 #[test]
746 fn test_distribute_transaction_fee_details_overflow_failure() {
747 let initial_balance = 1000;
748 let genesis = create_genesis_config_with_leader(0, &pubkey::new_rand(), initial_balance);
749 let mut bank = Bank::new_for_tests(&genesis.genesis_config);
750 let transaction_fee = 100;
751 let priority_fee = 200;
752 bank.collector_fee_details = RwLock::new(CollectorFeeDetails {
753 transaction_fee,
754 priority_fee,
755 });
756
757 let collector_id = *bank.leader_id();
758
759 let mut account = bank.get_account(&collector_id).unwrap_or_default();
761 account.set_lamports(u64::MAX);
762 bank.store_account(&collector_id, &account);
763
764 let initial_capitalization = bank.capitalization();
765 let initial_collector_balance = bank.get_balance(&collector_id);
766 bank.distribute_transaction_fee_details();
767 let new_collector_balance = bank.get_balance(&collector_id);
768
769 assert_eq!(initial_collector_balance, new_collector_balance);
770 assert_eq!(
771 initial_capitalization - transaction_fee - priority_fee,
772 bank.capitalization()
773 );
774 let locked_rewards = bank.rewards.read().unwrap();
775 assert!(
776 locked_rewards.is_empty(),
777 "There should be no rewards distributed"
778 );
779 }
780}