solana_svm_rent_collector/svm_rent_collector.rs
1//! Plugin trait for rent collection within the Solana SVM.
2
3use {
4 crate::rent_state::RentState,
5 solana_sdk::{
6 account::{AccountSharedData, ReadableAccount},
7 clock::Epoch,
8 pubkey::Pubkey,
9 rent::{Rent, RentDue},
10 rent_collector::CollectedInfo,
11 transaction::{Result, TransactionError},
12 transaction_context::{IndexOfAccount, TransactionContext},
13 },
14};
15
16mod rent_collector;
17
18/// Rent collector trait. Represents an entity that can evaluate the rent state
19/// of an account, determine rent due, and collect rent.
20///
21/// Implementors are responsible for evaluating rent due and collecting rent
22/// from accounts, if required. Methods for evaluating account rent state have
23/// default implementations, which can be overridden for customized rent
24/// management.
25pub trait SVMRentCollector {
26 /// Check rent state transition for an account in a transaction.
27 ///
28 /// This method has a default implementation that calls into
29 /// `check_rent_state_with_account`.
30 fn check_rent_state(
31 &self,
32 pre_rent_state: Option<&RentState>,
33 post_rent_state: Option<&RentState>,
34 transaction_context: &TransactionContext,
35 index: IndexOfAccount,
36 ) -> Result<()> {
37 if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) {
38 let expect_msg =
39 "account must exist at TransactionContext index if rent-states are Some";
40 self.check_rent_state_with_account(
41 pre_rent_state,
42 post_rent_state,
43 transaction_context
44 .get_key_of_account_at_index(index)
45 .expect(expect_msg),
46 &transaction_context
47 .get_account_at_index(index)
48 .expect(expect_msg)
49 .borrow(),
50 index,
51 )?;
52 }
53 Ok(())
54 }
55
56 /// Check rent state transition for an account directly.
57 ///
58 /// This method has a default implementation that checks whether the
59 /// transition is allowed and returns an error if it is not. It also
60 /// verifies that the account is not the incinerator.
61 fn check_rent_state_with_account(
62 &self,
63 pre_rent_state: &RentState,
64 post_rent_state: &RentState,
65 address: &Pubkey,
66 _account_state: &AccountSharedData,
67 account_index: IndexOfAccount,
68 ) -> Result<()> {
69 if !solana_sdk::incinerator::check_id(address)
70 && !self.transition_allowed(pre_rent_state, post_rent_state)
71 {
72 let account_index = account_index as u8;
73 Err(TransactionError::InsufficientFundsForRent { account_index })
74 } else {
75 Ok(())
76 }
77 }
78
79 /// Collect rent from an account.
80 fn collect_rent(&self, address: &Pubkey, account: &mut AccountSharedData) -> CollectedInfo;
81
82 /// Determine the rent state of an account.
83 ///
84 /// This method has a default implementation that treats accounts with zero
85 /// lamports as uninitialized and uses the implemented `get_rent` to
86 /// determine whether an account is rent-exempt.
87 fn get_account_rent_state(&self, account: &AccountSharedData) -> RentState {
88 if account.lamports() == 0 {
89 RentState::Uninitialized
90 } else if self
91 .get_rent()
92 .is_exempt(account.lamports(), account.data().len())
93 {
94 RentState::RentExempt
95 } else {
96 RentState::RentPaying {
97 data_size: account.data().len(),
98 lamports: account.lamports(),
99 }
100 }
101 }
102
103 /// Get the rent collector's rent instance.
104 fn get_rent(&self) -> &Rent;
105
106 /// Get the rent due for an account.
107 fn get_rent_due(&self, lamports: u64, data_len: usize, account_rent_epoch: Epoch) -> RentDue;
108
109 /// Check whether a transition from the pre_rent_state to the
110 /// post_rent_state is valid.
111 ///
112 /// This method has a default implementation that allows transitions from
113 /// any state to `RentState::Uninitialized` or `RentState::RentExempt`.
114 /// Pre-state `RentState::RentPaying` can only transition to
115 /// `RentState::RentPaying` if the data size remains the same and the
116 /// account is not credited.
117 fn transition_allowed(&self, pre_rent_state: &RentState, post_rent_state: &RentState) -> bool {
118 match post_rent_state {
119 RentState::Uninitialized | RentState::RentExempt => true,
120 RentState::RentPaying {
121 data_size: post_data_size,
122 lamports: post_lamports,
123 } => {
124 match pre_rent_state {
125 RentState::Uninitialized | RentState::RentExempt => false,
126 RentState::RentPaying {
127 data_size: pre_data_size,
128 lamports: pre_lamports,
129 } => {
130 // Cannot remain RentPaying if resized or credited.
131 post_data_size == pre_data_size && post_lamports <= pre_lamports
132 }
133 }
134 }
135 }
136 }
137}