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