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 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 solana_clock::DEFAULT_SLOTS_PER_EPOCH
22);
23
24#[repr(C)]
26#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
27#[cfg_attr(
28 feature = "serde",
29 derive(serde_derive::Deserialize, serde_derive::Serialize)
30)]
31#[derive(PartialEq, CloneZeroed, Debug)]
32pub struct Rent {
33 pub lamports_per_byte_year: u64,
35
36 pub exemption_threshold: f64,
39
40 pub burn_percent: u8,
45}
46
47pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 1_000_000_000 / 100 * 365 / (1024 * 1024);
55
56pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0;
59
60pub const DEFAULT_BURN_PERCENT: u8 = 50;
65
66pub const ACCOUNT_STORAGE_OVERHEAD: u64 = 128;
71
72impl Default for Rent {
73 fn default() -> Self {
74 Self {
75 lamports_per_byte_year: DEFAULT_LAMPORTS_PER_BYTE_YEAR,
76 exemption_threshold: DEFAULT_EXEMPTION_THRESHOLD,
77 burn_percent: DEFAULT_BURN_PERCENT,
78 }
79 }
80}
81
82impl Rent {
83 pub fn calculate_burn(&self, rent_collected: u64) -> (u64, u64) {
88 let burned_portion = (rent_collected * u64::from(self.burn_percent)) / 100;
89 (burned_portion, rent_collected - burned_portion)
90 }
91
92 pub fn minimum_balance(&self, data_len: usize) -> u64 {
94 let bytes = data_len as u64;
95 (((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
96 * self.exemption_threshold) as u64
97 }
98
99 pub fn is_exempt(&self, balance: u64, data_len: usize) -> bool {
101 balance >= self.minimum_balance(data_len)
102 }
103
104 pub fn due(&self, balance: u64, data_len: usize, years_elapsed: f64) -> RentDue {
106 if self.is_exempt(balance, data_len) {
107 RentDue::Exempt
108 } else {
109 RentDue::Paying(self.due_amount(data_len, years_elapsed))
110 }
111 }
112
113 pub fn due_amount(&self, data_len: usize, years_elapsed: f64) -> u64 {
115 let actual_data_len = data_len as u64 + ACCOUNT_STORAGE_OVERHEAD;
116 let lamports_per_year = self.lamports_per_byte_year * actual_data_len;
117 (lamports_per_year as f64 * years_elapsed) as u64
118 }
119
120 pub fn free() -> Self {
124 Self {
125 lamports_per_byte_year: 0,
126 ..Rent::default()
127 }
128 }
129
130 pub fn with_slots_per_epoch(slots_per_epoch: u64) -> Self {
134 let ratio = slots_per_epoch as f64 / DEFAULT_SLOTS_PER_EPOCH as f64;
135 let exemption_threshold = DEFAULT_EXEMPTION_THRESHOLD * ratio;
136 let lamports_per_byte_year = (DEFAULT_LAMPORTS_PER_BYTE_YEAR as f64 / ratio) as u64;
137 Self {
138 lamports_per_byte_year,
139 exemption_threshold,
140 ..Self::default()
141 }
142 }
143}
144
145#[derive(Debug, Copy, Clone, Eq, PartialEq)]
147pub enum RentDue {
148 Exempt,
150 Paying(u64),
152}
153
154impl RentDue {
155 pub fn lamports(&self) -> u64 {
157 match self {
158 RentDue::Exempt => 0,
159 RentDue::Paying(x) => *x,
160 }
161 }
162
163 pub fn is_exempt(&self) -> bool {
165 match self {
166 RentDue::Exempt => true,
167 RentDue::Paying(_) => false,
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_due() {
178 let default_rent = Rent::default();
179
180 assert_eq!(
181 default_rent.due(0, 2, 1.2),
182 RentDue::Paying(
183 (((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64 * 1.2)
184 as u64
185 ),
186 );
187 assert_eq!(
188 default_rent.due(
189 (((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
190 * DEFAULT_EXEMPTION_THRESHOLD) as u64,
191 2,
192 1.2
193 ),
194 RentDue::Exempt,
195 );
196
197 let custom_rent = Rent {
198 lamports_per_byte_year: 5,
199 exemption_threshold: 2.5,
200 ..Rent::default()
201 };
202
203 assert_eq!(
204 custom_rent.due(0, 2, 1.2),
205 RentDue::Paying(
206 (((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64 * 1.2)
207 as u64,
208 )
209 );
210
211 assert_eq!(
212 custom_rent.due(
213 (((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64
214 * custom_rent.exemption_threshold) as u64,
215 2,
216 1.2
217 ),
218 RentDue::Exempt
219 );
220 }
221
222 #[test]
223 fn test_rent_due_lamports() {
224 assert_eq!(RentDue::Exempt.lamports(), 0);
225
226 let amount = 123;
227 assert_eq!(RentDue::Paying(amount).lamports(), amount);
228 }
229
230 #[test]
231 fn test_rent_due_is_exempt() {
232 assert!(RentDue::Exempt.is_exempt());
233 assert!(!RentDue::Paying(0).is_exempt());
234 }
235
236 #[test]
237 fn test_clone() {
238 let rent = Rent {
239 lamports_per_byte_year: 1,
240 exemption_threshold: 2.2,
241 burn_percent: 3,
242 };
243 #[allow(clippy::clone_on_copy)]
244 let cloned_rent = rent.clone();
245 assert_eq!(cloned_rent, rent);
246 }
247}