Skip to content

Commit 2f93fd8

Browse files
authored
rent: Rent exempt calculation improvement (#46)
* Add u64 threshold * Add from_bytes helper * Add test
1 parent 1b6181c commit 2f93fd8

File tree

1 file changed

+80
-12
lines changed

1 file changed

+80
-12
lines changed

sdk/pinocchio/src/sysvars/rent.rs

+80-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 1_000_000_000 / 100 * 365 / (102
1818
/// account to be rent exempt.
1919
pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0;
2020

21+
/// Default amount of time (in years) the balance has to include rent for the
22+
/// account to be rent exempt as a `u64`.
23+
const DEFAULT_EXEMPTION_THRESHOLD_AS_U64: u64 = 2;
24+
25+
/// The `u64` representation of the default exemption threshold.
26+
///
27+
/// This is used to check whether the `f64` value can be safely cast to a `u64`.
28+
const F64_EXEMPTION_THRESHOLD_AS_U64: u64 = 4611686018427387904;
29+
2130
/// Default percentage of collected rent that is burned.
2231
///
2332
/// Valid values are in the range [0, 100]. The remaining percentage is
@@ -44,27 +53,29 @@ pub struct Rent {
4453
pub burn_percent: u8,
4554
}
4655

47-
/// Calculates the rent for a given number of bytes and duration
48-
///
49-
/// # Arguments
50-
///
51-
/// * `bytes` - The number of bytes to calculate rent for
52-
/// * `years` - The number of years to calculate rent for
53-
///
54-
/// # Returns
55-
///
56-
/// The total rent in lamports
5756
impl Rent {
57+
/// Return a `Rent` from the given bytes.
58+
///
59+
/// # Safety
60+
///
61+
/// The caller must ensure that `bytes` contains a valid representation of `Rent`.
62+
#[inline]
63+
pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
64+
&*(bytes.as_ptr() as *const Rent)
65+
}
66+
5867
/// Calculate how much rent to burn from the collected rent.
5968
///
6069
/// The first value returned is the amount burned. The second is the amount
6170
/// to distribute to validators.
71+
#[inline]
6272
pub fn calculate_burn(&self, rent_collected: u64) -> (u64, u64) {
6373
let burned_portion = (rent_collected * u64::from(self.burn_percent)) / 100;
6474
(burned_portion, rent_collected - burned_portion)
6575
}
6676

6777
/// Rent due on account's data length with balance.
78+
#[inline]
6879
pub fn due(&self, balance: u64, data_len: usize, years_elapsed: f64) -> RentDue {
6980
if self.is_exempt(balance, data_len) {
7081
RentDue::Exempt
@@ -74,6 +85,7 @@ impl Rent {
7485
}
7586

7687
/// Rent due for account that is known to be not exempt.
88+
#[inline]
7789
pub fn due_amount(&self, data_len: usize, years_elapsed: f64) -> u64 {
7890
let actual_data_len = data_len as u64 + ACCOUNT_STORAGE_OVERHEAD;
7991
let lamports_per_year = self.lamports_per_byte_year * actual_data_len;
@@ -82,17 +94,27 @@ impl Rent {
8294

8395
/// Calculates the minimum balance for rent exemption.
8496
///
97+
/// This method avoids floating-point operations when the `exemption_threshold`
98+
/// is the default value.
99+
///
85100
/// # Arguments
86101
///
87102
/// * `data_len` - The number of bytes in the account
88103
///
89104
/// # Returns
90105
///
91106
/// The minimum balance in lamports for rent exemption.
107+
#[inline]
92108
pub fn minimum_balance(&self, data_len: usize) -> u64 {
93109
let bytes = data_len as u64;
94-
(((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
95-
* self.exemption_threshold) as u64
110+
111+
if self.is_default_rent_threshold() {
112+
((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year)
113+
* DEFAULT_EXEMPTION_THRESHOLD_AS_U64
114+
} else {
115+
(((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
116+
* self.exemption_threshold) as u64
117+
}
96118
}
97119

98120
/// Determines if an account can be considered rent exempt.
@@ -105,9 +127,19 @@ impl Rent {
105127
/// # Returns
106128
///
107129
/// `true`` if the account is rent exempt, `false`` otherwise.
130+
#[inline]
108131
pub fn is_exempt(&self, lamports: u64, data_len: usize) -> bool {
109132
lamports >= self.minimum_balance(data_len)
110133
}
134+
135+
/// Determines if the `exemption_threshold` is the default value.
136+
///
137+
/// This is used to check whether the `f64` value can be safely cast to a `u64`
138+
/// to avoid floating-point operations.
139+
#[inline]
140+
fn is_default_rent_threshold(&self) -> bool {
141+
u64::from_le_bytes(self.exemption_threshold.to_le_bytes()) == F64_EXEMPTION_THRESHOLD_AS_U64
142+
}
111143
}
112144

113145
impl Sysvar for Rent {
@@ -140,3 +172,39 @@ impl RentDue {
140172
}
141173
}
142174
}
175+
176+
#[cfg(test)]
177+
mod tests {
178+
use crate::sysvars::rent::{
179+
ACCOUNT_STORAGE_OVERHEAD, DEFAULT_BURN_PERCENT, DEFAULT_EXEMPTION_THRESHOLD,
180+
DEFAULT_LAMPORTS_PER_BYTE_YEAR,
181+
};
182+
183+
#[test]
184+
pub fn test_minimum_balance() {
185+
let mut rent = super::Rent {
186+
lamports_per_byte_year: DEFAULT_LAMPORTS_PER_BYTE_YEAR,
187+
exemption_threshold: DEFAULT_EXEMPTION_THRESHOLD,
188+
burn_percent: DEFAULT_BURN_PERCENT,
189+
};
190+
191+
// Using the default exemption threshold.
192+
193+
let balance = rent.minimum_balance(100);
194+
let calculated = (((ACCOUNT_STORAGE_OVERHEAD + 100) * rent.lamports_per_byte_year) as f64
195+
* rent.exemption_threshold) as u64;
196+
197+
assert!(calculated > 0);
198+
assert_eq!(balance, calculated);
199+
200+
// Using a different exemption threshold.
201+
rent.exemption_threshold = 0.5;
202+
203+
let balance = rent.minimum_balance(100);
204+
let calculated = (((ACCOUNT_STORAGE_OVERHEAD + 100) * rent.lamports_per_byte_year) as f64
205+
* rent.exemption_threshold) as u64;
206+
207+
assert!(calculated > 0);
208+
assert_eq!(balance, calculated);
209+
}
210+
}

0 commit comments

Comments
 (0)