1#![allow(clippy::arithmetic_side_effects)]
6#![no_std]
7#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
8#[cfg(feature = "frozen-abi")]
9extern crate std;
10
11#[cfg(feature = "sysvar")]
12pub mod sysvar;
13
14use clone_solana_sdk_macro::CloneZeroed;
15
16const DEFAULT_SLOTS_PER_EPOCH: u64 = 432_000;
18#[cfg(test)]
19static_assertions::const_assert_eq!(
20 DEFAULT_SLOTS_PER_EPOCH,
21 clone_solana_clock::DEFAULT_SLOTS_PER_EPOCH
22);
23
24#[repr(C)]
26#[cfg_attr(
27 feature = "frozen-abi",
28 derive(clone_solana_frozen_abi_macro::AbiExample)
29)]
30#[cfg_attr(
31 feature = "serde",
32 derive(serde_derive::Deserialize, serde_derive::Serialize)
33)]
34#[derive(PartialEq, CloneZeroed, Debug)]
35pub struct Rent {
36 pub lamports_per_byte_year: u64,
38
39 pub exemption_threshold: f64,
42
43 pub burn_percent: u8,
48}
49
50pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 1_000_000_000 / 100 * 365 / (1024 * 1024);
58
59pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0;
62
63pub const DEFAULT_BURN_PERCENT: u8 = 50;
68
69pub const ACCOUNT_STORAGE_OVERHEAD: u64 = 128;
74
75impl Default for Rent {
76 fn default() -> Self {
77 Self {
78 lamports_per_byte_year: DEFAULT_LAMPORTS_PER_BYTE_YEAR,
79 exemption_threshold: DEFAULT_EXEMPTION_THRESHOLD,
80 burn_percent: DEFAULT_BURN_PERCENT,
81 }
82 }
83}
84
85impl Rent {
86 pub fn calculate_burn(&self, rent_collected: u64) -> (u64, u64) {
91 let burned_portion = (rent_collected * u64::from(self.burn_percent)) / 100;
92 (burned_portion, rent_collected - burned_portion)
93 }
94
95 pub fn minimum_balance(&self, data_len: usize) -> u64 {
97 let bytes = data_len as u64;
98 (((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
99 * self.exemption_threshold) as u64
100 }
101
102 pub fn is_exempt(&self, balance: u64, data_len: usize) -> bool {
104 balance >= self.minimum_balance(data_len)
105 }
106
107 pub fn due(&self, balance: u64, data_len: usize, years_elapsed: f64) -> RentDue {
109 if self.is_exempt(balance, data_len) {
110 RentDue::Exempt
111 } else {
112 RentDue::Paying(self.due_amount(data_len, years_elapsed))
113 }
114 }
115
116 pub fn due_amount(&self, data_len: usize, years_elapsed: f64) -> u64 {
118 let actual_data_len = data_len as u64 + ACCOUNT_STORAGE_OVERHEAD;
119 let lamports_per_year = self.lamports_per_byte_year * actual_data_len;
120 (lamports_per_year as f64 * years_elapsed) as u64
121 }
122
123 pub fn free() -> Self {
127 Self {
128 lamports_per_byte_year: 0,
129 ..Rent::default()
130 }
131 }
132
133 pub fn with_slots_per_epoch(slots_per_epoch: u64) -> Self {
137 let ratio = slots_per_epoch as f64 / DEFAULT_SLOTS_PER_EPOCH as f64;
138 let exemption_threshold = DEFAULT_EXEMPTION_THRESHOLD * ratio;
139 let lamports_per_byte_year = (DEFAULT_LAMPORTS_PER_BYTE_YEAR as f64 / ratio) as u64;
140 Self {
141 lamports_per_byte_year,
142 exemption_threshold,
143 ..Self::default()
144 }
145 }
146}
147
148#[derive(Debug, Copy, Clone, Eq, PartialEq)]
150pub enum RentDue {
151 Exempt,
153 Paying(u64),
155}
156
157impl RentDue {
158 pub fn lamports(&self) -> u64 {
160 match self {
161 RentDue::Exempt => 0,
162 RentDue::Paying(x) => *x,
163 }
164 }
165
166 pub fn is_exempt(&self) -> bool {
168 match self {
169 RentDue::Exempt => true,
170 RentDue::Paying(_) => false,
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_due() {
181 let default_rent = Rent::default();
182
183 assert_eq!(
184 default_rent.due(0, 2, 1.2),
185 RentDue::Paying(
186 (((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64 * 1.2)
187 as u64
188 ),
189 );
190 assert_eq!(
191 default_rent.due(
192 (((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
193 * DEFAULT_EXEMPTION_THRESHOLD) as u64,
194 2,
195 1.2
196 ),
197 RentDue::Exempt,
198 );
199
200 let custom_rent = Rent {
201 lamports_per_byte_year: 5,
202 exemption_threshold: 2.5,
203 ..Rent::default()
204 };
205
206 assert_eq!(
207 custom_rent.due(0, 2, 1.2),
208 RentDue::Paying(
209 (((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64 * 1.2)
210 as u64,
211 )
212 );
213
214 assert_eq!(
215 custom_rent.due(
216 (((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64
217 * custom_rent.exemption_threshold) as u64,
218 2,
219 1.2
220 ),
221 RentDue::Exempt
222 );
223 }
224
225 #[test]
226 fn test_rent_due_lamports() {
227 assert_eq!(RentDue::Exempt.lamports(), 0);
228
229 let amount = 123;
230 assert_eq!(RentDue::Paying(amount).lamports(), amount);
231 }
232
233 #[test]
234 fn test_rent_due_is_exempt() {
235 assert!(RentDue::Exempt.is_exempt());
236 assert!(!RentDue::Paying(0).is_exempt());
237 }
238
239 #[test]
240 fn test_clone() {
241 let rent = Rent {
242 lamports_per_byte_year: 1,
243 exemption_threshold: 2.2,
244 burn_percent: 3,
245 };
246 #[allow(clippy::clone_on_copy)]
247 let cloned_rent = rent.clone();
248 assert_eq!(cloned_rent, rent);
249 }
250}