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}