1#![allow(clippy::arithmetic_side_effects)]
6
7use {crate::clock::DEFAULT_SLOTS_PER_EPOCH, miraland_sdk_macro::CloneZeroed};
8
9#[repr(C)]
11#[derive(Serialize, Deserialize, PartialEq, CloneZeroed, Debug, AbiExample)]
12pub struct Rent {
13 pub lamports_per_byte_year: u64,
15
16 pub exemption_threshold: f64,
19
20 pub burn_percent: u8,
25}
26
27pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 1_000_000_000 / 100 * 365 / (1024 * 1024);
35
36pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0;
39
40pub const DEFAULT_BURN_PERCENT: u8 = 50;
45
46pub const ACCOUNT_STORAGE_OVERHEAD: u64 = 128;
51
52impl Default for Rent {
53 fn default() -> Self {
54 Self {
55 lamports_per_byte_year: DEFAULT_LAMPORTS_PER_BYTE_YEAR,
56 exemption_threshold: DEFAULT_EXEMPTION_THRESHOLD,
57 burn_percent: DEFAULT_BURN_PERCENT,
58 }
59 }
60}
61
62impl Rent {
63 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
72 pub fn minimum_balance(&self, data_len: usize) -> u64 {
74 let bytes = data_len as u64;
75 (((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
76 * self.exemption_threshold) as u64
77 }
78
79 pub fn is_exempt(&self, balance: u64, data_len: usize) -> bool {
81 balance >= self.minimum_balance(data_len)
82 }
83
84 pub fn due(&self, balance: u64, data_len: usize, years_elapsed: f64) -> RentDue {
86 if self.is_exempt(balance, data_len) {
87 RentDue::Exempt
88 } else {
89 RentDue::Paying(self.due_amount(data_len, years_elapsed))
90 }
91 }
92
93 pub fn due_amount(&self, data_len: usize, years_elapsed: f64) -> u64 {
95 let actual_data_len = data_len as u64 + ACCOUNT_STORAGE_OVERHEAD;
96 let lamports_per_year = self.lamports_per_byte_year * actual_data_len;
97 (lamports_per_year as f64 * years_elapsed) as u64
98 }
99
100 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 {
114 let ratio = slots_per_epoch as f64 / DEFAULT_SLOTS_PER_EPOCH as f64;
115 let exemption_threshold = DEFAULT_EXEMPTION_THRESHOLD * ratio;
116 let lamports_per_byte_year = (DEFAULT_LAMPORTS_PER_BYTE_YEAR as f64 / ratio) as u64;
117 Self {
118 lamports_per_byte_year,
119 exemption_threshold,
120 ..Self::default()
121 }
122 }
123}
124
125#[derive(Debug, Copy, Clone, Eq, PartialEq)]
127pub enum RentDue {
128 Exempt,
130 Paying(u64),
132}
133
134impl RentDue {
135 pub fn lamports(&self) -> u64 {
137 match self {
138 RentDue::Exempt => 0,
139 RentDue::Paying(x) => *x,
140 }
141 }
142
143 pub fn is_exempt(&self) -> bool {
145 match self {
146 RentDue::Exempt => true,
147 RentDue::Paying(_) => false,
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_due() {
158 let default_rent = Rent::default();
159
160 assert_eq!(
161 default_rent.due(0, 2, 1.2),
162 RentDue::Paying(
163 (((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64 * 1.2)
164 as u64
165 ),
166 );
167 assert_eq!(
168 default_rent.due(
169 (((2 + ACCOUNT_STORAGE_OVERHEAD) * DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
170 * DEFAULT_EXEMPTION_THRESHOLD) as u64,
171 2,
172 1.2
173 ),
174 RentDue::Exempt,
175 );
176
177 let custom_rent = Rent {
178 lamports_per_byte_year: 5,
179 exemption_threshold: 2.5,
180 ..Rent::default()
181 };
182
183 assert_eq!(
184 custom_rent.due(0, 2, 1.2),
185 RentDue::Paying(
186 (((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64 * 1.2)
187 as u64,
188 )
189 );
190
191 assert_eq!(
192 custom_rent.due(
193 (((2 + ACCOUNT_STORAGE_OVERHEAD) * custom_rent.lamports_per_byte_year) as f64
194 * custom_rent.exemption_threshold) as u64,
195 2,
196 1.2
197 ),
198 RentDue::Exempt
199 );
200 }
201
202 #[test]
203 fn test_rent_due_lamports() {
204 assert_eq!(RentDue::Exempt.lamports(), 0);
205
206 let amount = 123;
207 assert_eq!(RentDue::Paying(amount).lamports(), amount);
208 }
209
210 #[test]
211 fn test_rent_due_is_exempt() {
212 assert!(RentDue::Exempt.is_exempt());
213 assert!(!RentDue::Paying(0).is_exempt());
214 }
215
216 #[test]
217 fn test_clone() {
218 let rent = Rent {
219 lamports_per_byte_year: 1,
220 exemption_threshold: 2.2,
221 burn_percent: 3,
222 };
223 #[allow(clippy::clone_on_copy)]
224 let cloned_rent = rent.clone();
225 assert_eq!(cloned_rent, rent);
226 }
227}