solana_program/
rent.rs

1//! Configuration for network [rent].
2//!
3//! [rent]: https://docs.solana.com/implemented-proposals/rent
4
5#![allow(clippy::integer_arithmetic)]
6//! configuration for network rent
7
8use {
9    crate::{clock::DEFAULT_SLOTS_PER_EPOCH, clone_zeroed, copy_field},
10    std::mem::MaybeUninit,
11};
12
13#[repr(C)]
14#[derive(Serialize, Deserialize, PartialEq, Copy, Debug, AbiExample)]
15pub struct Rent {
16    /// Rental rate
17    pub lamports_per_byte_year: u64,
18
19    /// exemption threshold, in years
20    pub exemption_threshold: f64,
21
22    // What portion of collected rent are to be destroyed, percentage-wise
23    pub burn_percent: u8,
24}
25
26/// default rental rate in lamports/byte-year, based on:
27///  10^9 lamports per SAFE
28///  $1 per SAFE
29///  $0.01 per megabyte day
30///  $3.65 per megabyte year
31pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 1_000_000_000 / 100 * 365 / (1024 * 1024);
32
33/// default amount of time (in years) the balance has to include rent for
34pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0;
35
36/// default percentage of rent to burn (Valid values are 0 to 100)
37pub const DEFAULT_BURN_PERCENT: u8 = 50;
38
39/// account storage overhead for calculation of base rent
40pub const ACCOUNT_STORAGE_OVERHEAD: u64 = 128;
41
42impl Default for Rent {
43    fn default() -> Self {
44        Self {
45            lamports_per_byte_year: DEFAULT_LAMPORTS_PER_BYTE_YEAR,
46            exemption_threshold: DEFAULT_EXEMPTION_THRESHOLD,
47            burn_percent: DEFAULT_BURN_PERCENT,
48        }
49    }
50}
51
52impl Clone for Rent {
53    fn clone(&self) -> Self {
54        clone_zeroed(|cloned: &mut MaybeUninit<Self>| {
55            let ptr = cloned.as_mut_ptr();
56            unsafe {
57                copy_field!(ptr, self, lamports_per_byte_year);
58                copy_field!(ptr, self, exemption_threshold);
59                copy_field!(ptr, self, burn_percent);
60            }
61        })
62    }
63}
64
65impl Rent {
66    /// calculate how much rent to burn from the collected rent
67    pub fn calculate_burn(&self, rent_collected: u64) -> (u64, u64) {
68        let burned_portion = (rent_collected * u64::from(self.burn_percent)) / 100;
69        (burned_portion, rent_collected - burned_portion)
70    }
71    /// minimum balance due for rent-exemption of a given size Account::data.len()
72    ///
73    /// Note: a stripped-down version of this calculation is used in
74    /// calculate_split_rent_exempt_reserve in the stake program. When this function is updated, --
75    /// eg. when making rent variable -- the stake program will need to be refactored
76    pub fn minimum_balance(&self, data_len: usize) -> u64 {
77        let bytes = data_len as u64;
78        (((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
79            * self.exemption_threshold) as u64
80    }
81
82    /// whether a given balance and data_len would be exempt
83    pub fn is_exempt(&self, balance: u64, data_len: usize) -> bool {
84        balance >= self.minimum_balance(data_len)
85    }
86
87    /// rent due on account's data_len with balance
88    pub fn due(&self, balance: u64, data_len: usize, years_elapsed: f64) -> RentDue {
89        if self.is_exempt(balance, data_len) {
90            RentDue::Exempt
91        } else {
92            RentDue::Paying(self.due_amount(data_len, years_elapsed))
93        }
94    }
95
96    /// rent due for account that is known to be not exempt
97    pub fn due_amount(&self, data_len: usize, years_elapsed: f64) -> u64 {
98        let actual_data_len = data_len as u64 + ACCOUNT_STORAGE_OVERHEAD;
99        let lamports_per_year = self.lamports_per_byte_year * actual_data_len;
100        (lamports_per_year as f64 * years_elapsed) as u64
101    }
102
103    pub fn free() -> Self {
104        Self {
105            lamports_per_byte_year: 0,
106            ..Rent::default()
107        }
108    }
109
110    pub fn with_slots_per_epoch(slots_per_epoch: u64) -> Self {
111        let ratio = slots_per_epoch as f64 / DEFAULT_SLOTS_PER_EPOCH as f64;
112        let exemption_threshold = DEFAULT_EXEMPTION_THRESHOLD as f64 * ratio;
113        let lamports_per_byte_year = (DEFAULT_LAMPORTS_PER_BYTE_YEAR as f64 / ratio) as u64;
114        Self {
115            lamports_per_byte_year,
116            exemption_threshold,
117            ..Self::default()
118        }
119    }
120}
121
122/// Enumerate return values from `Rent::due()`
123#[derive(Debug, Copy, Clone, Eq, PartialEq)]
124pub enum RentDue {
125    /// Used to indicate the account is rent exempt
126    Exempt,
127    /// The account owes rent, and the amount is the field
128    Paying(u64),
129}
130
131impl RentDue {
132    /// Return the lamports due for rent
133    pub fn lamports(&self) -> u64 {
134        match self {
135            RentDue::Exempt => 0,
136            RentDue::Paying(x) => *x,
137        }
138    }
139
140    /// Return 'true' if rent exempt
141    pub fn is_exempt(&self) -> bool {
142        match self {
143            RentDue::Exempt => true,
144            RentDue::Paying(_) => false,
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_due() {
155        let default_rent = Rent::default();
156
157        assert_eq!(
158            default_rent.due(0, 2, 1.2),
159            RentDue::Paying(
160                (((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64 * 1.2)
161                    as u64
162            ),
163        );
164        assert_eq!(
165            default_rent.due(
166                (((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
167                    * DEFAULT_EXEMPTION_THRESHOLD) as u64,
168                2,
169                1.2
170            ),
171            RentDue::Exempt,
172        );
173
174        let custom_rent = Rent {
175            lamports_per_byte_year: 5,
176            exemption_threshold: 2.5,
177            ..Rent::default()
178        };
179
180        assert_eq!(
181            custom_rent.due(0, 2, 1.2),
182            RentDue::Paying(
183                (((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64 * 1.2)
184                    as u64,
185            )
186        );
187
188        assert_eq!(
189            custom_rent.due(
190                (((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64
191                    * custom_rent.exemption_threshold) as u64,
192                2,
193                1.2
194            ),
195            RentDue::Exempt
196        );
197    }
198
199    #[test]
200    fn test_rent_due_lamports() {
201        assert_eq!(RentDue::Exempt.lamports(), 0);
202
203        let amount = 123;
204        assert_eq!(RentDue::Paying(amount).lamports(), amount);
205    }
206
207    #[test]
208    fn test_rent_due_is_exempt() {
209        assert!(RentDue::Exempt.is_exempt());
210        assert!(!RentDue::Paying(0).is_exempt());
211    }
212
213    #[test]
214    fn test_clone() {
215        let rent = Rent {
216            lamports_per_byte_year: 1,
217            exemption_threshold: 2.2,
218            burn_percent: 3,
219        };
220        #[allow(clippy::clone_on_copy)]
221        let cloned_rent = rent.clone();
222        assert_eq!(cloned_rent, rent);
223    }
224}