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}