Skip to main content

solana_rent/
lib.rs

1//! Configuration for network [rent].
2//!
3//! [rent]: https://docs.solanalabs.com/implemented-proposals/rent
4
5#![allow(clippy::arithmetic_side_effects)]
6#![no_std]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
9#[cfg(feature = "frozen-abi")]
10extern crate std;
11
12#[cfg(feature = "sysvar")]
13pub mod sysvar;
14
15#[cfg(feature = "frozen-abi")]
16use solana_frozen_abi_macro::{AbiExample, StableAbi, StableAbiSample};
17use solana_sdk_macro::CloneZeroed;
18
19/// Configuration of network rent.
20#[repr(C)]
21#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
22#[cfg_attr(
23    feature = "serde",
24    derive(serde_derive::Deserialize, serde_derive::Serialize)
25)]
26#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
27#[derive(PartialEq, CloneZeroed, Debug)]
28pub struct Rent {
29    /// Rental rate in lamports/byte.
30    pub lamports_per_byte: u64,
31
32    /// Formerly, the amount of time (in years) a balance must include rent for
33    /// the account to be rent exempt. Now it's just empty space.
34    #[deprecated(since = "4.1.0", note = "Use `Rent::minimum_balance()` directly")]
35    pub exemption_threshold: [u8; 8],
36
37    /// Formerly, the percentage of collected rent that is burned.
38    #[deprecated(since = "4.1.0", note = "Rent no longer exists")]
39    pub burn_percent: u8,
40}
41
42/// Serialized size of the `Rent` sysvar account.
43pub const SIZE: usize = size_of::<u64>() // lamports_per_byte
44    + size_of::<[u8; 8]>() // exemption_threshold
45    + size_of::<u8>(); // burn_percent
46const _: () = assert!(SIZE == 17);
47
48/// Maximum permitted size of account data (10 MiB).
49const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
50
51/// Default rental rate in lamports/byte.
52///
53/// This calculation is based on:
54/// - 10^9 lamports per SOL
55/// - $1 per SOL
56/// - $0.01 per megabyte day
57/// - $7.30 per megabyte
58pub const DEFAULT_LAMPORTS_PER_BYTE: u64 = 6_960;
59
60/// The `f64::to_le_bytes` representation of the SIMD-0194 exemption threshold.
61///
62/// This value is equivalent to `1.0f64`. It is only used to check whether
63/// the exemption threshold is the deprecated value to avoid performing
64/// floating-point operations on-chain.
65const SIMD0194_EXEMPTION_THRESHOLD: [u8; 8] = [0, 0, 0, 0, 0, 0, 240, 63];
66
67/// The `f64::to_le_bytes` representation of the default exemption threshold.
68///
69/// This value is equivalent to `2.0f64`. It is only used to check whether
70/// the exemption threshold is the default value to avoid performing
71/// floating-point operations on-chain.
72const CURRENT_EXEMPTION_THRESHOLD: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 64];
73
74/// Maximum lamports per byte for the SIMD-0194 exemption threshold.
75const SIMD0194_MAX_LAMPORTS_PER_BYTE: u64 = 1_759_197_129_867;
76
77/// Maximum lamports per byte for the current exemption threshold.
78const CURRENT_MAX_LAMPORTS_PER_BYTE: u64 = 879_598_564_933;
79
80const DEFAULT_BURN_PERCENT: u8 = 50;
81
82/// Account storage overhead for calculation of base rent.
83///
84/// This is the number of bytes required to store an account with no data. It is
85/// added to an accounts data length when calculating [`Rent::minimum_balance`].
86pub const ACCOUNT_STORAGE_OVERHEAD: u64 = 128;
87
88impl Default for Rent {
89    fn default() -> Self {
90        #[allow(deprecated)]
91        Self {
92            lamports_per_byte: DEFAULT_LAMPORTS_PER_BYTE,
93            exemption_threshold: SIMD0194_EXEMPTION_THRESHOLD,
94            burn_percent: DEFAULT_BURN_PERCENT,
95        }
96    }
97}
98
99impl Rent {
100    /// Calculates the minimum balance for rent exemption.
101    ///
102    /// This method avoids floating-point operations when the `exemption_threshold`
103    /// is the default value.
104    ///
105    /// # Arguments
106    ///
107    /// * `data_len` - The number of bytes in the account
108    ///
109    /// # Returns
110    ///
111    /// The minimum balance in lamports for rent exemption.
112    ///
113    /// # Panics
114    ///
115    /// Panics if `data_len` exceeds the maximum permitted data length or if the
116    /// `lamports_per_byte` is too large based on the `exemption_threshold`.
117    #[inline(always)]
118    pub fn minimum_balance(&self, data_len: usize) -> u64 {
119        self.try_minimum_balance(data_len)
120            .expect("Maximum permitted data length exceeded")
121    }
122
123    /// Calculates the minimum balance for rent exemption without performing
124    /// any validation.
125    ///
126    /// This method avoids floating-point operations when the `exemption_threshold`
127    /// is the default value.
128    ///
129    /// # Important
130    ///
131    /// The caller must ensure that `data_len` is within the permitted limit
132    /// and the `lamports_per_byte` is within the permitted limit based on
133    /// the `exemption_threshold` to avoid overflow.
134    ///
135    /// # Arguments
136    ///
137    /// * `data_len` - The number of bytes in the account
138    ///
139    /// # Returns
140    ///
141    /// The minimum balance in lamports for rent exemption.
142    #[inline(always)]
143    pub fn minimum_balance_unchecked(&self, data_len: usize) -> u64 {
144        let bytes = data_len as u64;
145
146        // There are two cases where it is possible to avoid floating-point
147        // operations:
148        //
149        //   1)  exemption threshold is `1.0` (the SIMD-0194 default)
150        //   2)  exemption threshold is `2.0` (the current default)
151        //
152        // In all other cases, perform the full calculation using floating-point
153        // operations. Note that on BPF targets, floating-point operations are
154        // not supported, so panic in that case.
155        #[allow(deprecated)]
156        if self.exemption_threshold == SIMD0194_EXEMPTION_THRESHOLD {
157            (ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte
158        } else if self.exemption_threshold == CURRENT_EXEMPTION_THRESHOLD {
159            2 * (ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte
160        } else {
161            #[cfg(not(target_arch = "bpf"))]
162            {
163                (((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte) as f64
164                    * f64::from_le_bytes(self.exemption_threshold)) as u64
165            }
166            #[cfg(target_arch = "bpf")]
167            panic!("Floating-point operations are not supported on BPF targets");
168        }
169    }
170
171    /// Calculates the minimum balance for rent exemption.
172    ///
173    /// This method avoids floating-point operations when the `exemption_threshold`
174    /// is the default value.
175    ///
176    /// # Arguments
177    ///
178    /// * `data_len` - The number of bytes in the account
179    ///
180    /// # Returns
181    ///
182    /// * `Some(u64)` - The minimum balance in lamports for rent exemption, if all checks pass.
183    /// * `None` - If `data_len` exceeds the maximum permitted data length, or if the
184    ///   `lamports_per_byte` is too large based on the `exemption_threshold`, which
185    ///   would cause an overflow.
186    #[inline(always)]
187    pub fn try_minimum_balance(&self, data_len: usize) -> Option<u64> {
188        if data_len as u64 > MAX_PERMITTED_DATA_LENGTH {
189            return None;
190        }
191
192        // Validate `lamports_per_byte` based on `exemption_threshold`
193        // to prevent overflow.
194
195        #[allow(deprecated)]
196        if (self.lamports_per_byte > CURRENT_MAX_LAMPORTS_PER_BYTE
197            && self.exemption_threshold == CURRENT_EXEMPTION_THRESHOLD)
198            || (self.lamports_per_byte > SIMD0194_MAX_LAMPORTS_PER_BYTE
199                && self.exemption_threshold == SIMD0194_EXEMPTION_THRESHOLD)
200        {
201            return None;
202        }
203
204        Some(self.minimum_balance_unchecked(data_len))
205    }
206
207    /// Whether a given balance and data length would be exempt.
208    pub fn is_exempt(&self, balance: u64, data_len: usize) -> bool {
209        balance >= self.minimum_balance(data_len)
210    }
211
212    /// Creates a `Rent` that charges no lamports.
213    ///
214    /// This is used for testing.
215    pub fn free() -> Self {
216        Self {
217            lamports_per_byte: 0,
218            ..Rent::default()
219        }
220    }
221
222    /// Creates a `Rent` with lamports per byte
223    pub fn with_lamports_per_byte(lamports_per_byte: u64) -> Self {
224        Self {
225            lamports_per_byte,
226            ..Self::default()
227        }
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use {super::*, proptest::proptest};
234
235    #[test]
236    fn test_size_of() {
237        assert_eq!(
238            wincode::serialized_size(&Rent::default()).unwrap() as usize,
239            SIZE,
240        );
241    }
242
243    #[test]
244    fn test_clone() {
245        #[allow(deprecated)]
246        let rent = Rent {
247            lamports_per_byte: 1,
248            exemption_threshold: 2.2f64.to_le_bytes(),
249            burn_percent: 3,
250        };
251        #[allow(clippy::clone_on_copy)]
252        let cloned_rent = rent.clone();
253        assert_eq!(cloned_rent, rent);
254    }
255
256    #[test]
257    fn test_exemption_threshold() {
258        assert_eq!(1f64.to_le_bytes(), SIMD0194_EXEMPTION_THRESHOLD);
259        assert_eq!(2f64.to_le_bytes(), CURRENT_EXEMPTION_THRESHOLD);
260    }
261
262    proptest! {
263        #[test]
264        fn test_minimum_balance(bytes in 0usize..=MAX_PERMITTED_DATA_LENGTH as usize) {
265            let default_rent = Rent::default();
266            #[allow(deprecated)]
267            let previous_rent = Rent {
268                lamports_per_byte: DEFAULT_LAMPORTS_PER_BYTE / 2,
269                exemption_threshold: 2.0f64.to_le_bytes(),
270                ..Default::default()
271            };
272            let default_calc = default_rent.minimum_balance(bytes);
273            assert_eq!(default_calc, previous_rent.minimum_balance(bytes));
274
275            // check that the calculation gives the same result using floats
276            #[allow(deprecated)]
277            let float_calc = (((ACCOUNT_STORAGE_OVERHEAD + bytes as u64) * previous_rent.lamports_per_byte) as f64
278                * f64::from_le_bytes(previous_rent.exemption_threshold)) as u64;
279            assert_eq!(default_calc, float_calc);
280        }
281    }
282}