1#![allow(clippy::integer_arithmetic)]
6use {
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 pub lamports_per_byte_year: u64,
18
19 pub exemption_threshold: f64,
21
22 pub burn_percent: u8,
24}
25
26pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 1_000_000_000 / 100 * 365 / (1024 * 1024);
32
33pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0;
35
36pub const DEFAULT_BURN_PERCENT: u8 = 50;
38
39pub 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 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 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 pub fn is_exempt(&self, balance: u64, data_len: usize) -> bool {
84 balance >= self.minimum_balance(data_len)
85 }
86
87 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 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#[derive(Debug, Copy, Clone, Eq, PartialEq)]
124pub enum RentDue {
125 Exempt,
127 Paying(u64),
129}
130
131impl RentDue {
132 pub fn lamports(&self) -> u64 {
134 match self {
135 RentDue::Exempt => 0,
136 RentDue::Paying(x) => *x,
137 }
138 }
139
140 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}