Skip to content

Commit

Permalink
rent: Rent exempt calculation improvement (#46)
Browse files Browse the repository at this point in the history
* Add u64 threshold

* Add from_bytes helper

* Add test
  • Loading branch information
febo authored Nov 25, 2024
1 parent 1b6181c commit 2f93fd8
Showing 1 changed file with 80 additions and 12 deletions.
92 changes: 80 additions & 12 deletions sdk/pinocchio/src/sysvars/rent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 1_000_000_000 / 100 * 365 / (102
/// account to be rent exempt.
pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0;

/// Default amount of time (in years) the balance has to include rent for the
/// account to be rent exempt as a `u64`.
const DEFAULT_EXEMPTION_THRESHOLD_AS_U64: u64 = 2;

/// The `u64` representation of the default exemption threshold.
///
/// This is used to check whether the `f64` value can be safely cast to a `u64`.
const F64_EXEMPTION_THRESHOLD_AS_U64: u64 = 4611686018427387904;

/// Default percentage of collected rent that is burned.
///
/// Valid values are in the range [0, 100]. The remaining percentage is
Expand All @@ -44,27 +53,29 @@ pub struct Rent {
pub burn_percent: u8,
}

/// Calculates the rent for a given number of bytes and duration
///
/// # Arguments
///
/// * `bytes` - The number of bytes to calculate rent for
/// * `years` - The number of years to calculate rent for
///
/// # Returns
///
/// The total rent in lamports
impl Rent {
/// Return a `Rent` from the given bytes.
///
/// # Safety
///
/// The caller must ensure that `bytes` contains a valid representation of `Rent`.
#[inline]
pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
&*(bytes.as_ptr() as *const Rent)
}

/// Calculate how much rent to burn from the collected rent.
///
/// The first value returned is the amount burned. The second is the amount
/// to distribute to validators.
#[inline]
pub fn calculate_burn(&self, rent_collected: u64) -> (u64, u64) {
let burned_portion = (rent_collected * u64::from(self.burn_percent)) / 100;
(burned_portion, rent_collected - burned_portion)
}

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

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

/// Calculates the minimum balance for rent exemption.
///
/// This method avoids floating-point operations when the `exemption_threshold`
/// is the default value.
///
/// # Arguments
///
/// * `data_len` - The number of bytes in the account
///
/// # Returns
///
/// The minimum balance in lamports for rent exemption.
#[inline]
pub fn minimum_balance(&self, data_len: usize) -> u64 {
let bytes = data_len as u64;
(((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
* self.exemption_threshold) as u64

if self.is_default_rent_threshold() {
((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year)
* DEFAULT_EXEMPTION_THRESHOLD_AS_U64
} else {
(((ACCOUNT_STORAGE_OVERHEAD + bytes) * self.lamports_per_byte_year) as f64
* self.exemption_threshold) as u64
}
}

/// Determines if an account can be considered rent exempt.
Expand All @@ -105,9 +127,19 @@ impl Rent {
/// # Returns
///
/// `true`` if the account is rent exempt, `false`` otherwise.
#[inline]
pub fn is_exempt(&self, lamports: u64, data_len: usize) -> bool {
lamports >= self.minimum_balance(data_len)
}

/// Determines if the `exemption_threshold` is the default value.
///
/// This is used to check whether the `f64` value can be safely cast to a `u64`
/// to avoid floating-point operations.
#[inline]
fn is_default_rent_threshold(&self) -> bool {
u64::from_le_bytes(self.exemption_threshold.to_le_bytes()) == F64_EXEMPTION_THRESHOLD_AS_U64
}
}

impl Sysvar for Rent {
Expand Down Expand Up @@ -140,3 +172,39 @@ impl RentDue {
}
}
}

#[cfg(test)]
mod tests {
use crate::sysvars::rent::{
ACCOUNT_STORAGE_OVERHEAD, DEFAULT_BURN_PERCENT, DEFAULT_EXEMPTION_THRESHOLD,
DEFAULT_LAMPORTS_PER_BYTE_YEAR,
};

#[test]
pub fn test_minimum_balance() {
let mut rent = super::Rent {
lamports_per_byte_year: DEFAULT_LAMPORTS_PER_BYTE_YEAR,
exemption_threshold: DEFAULT_EXEMPTION_THRESHOLD,
burn_percent: DEFAULT_BURN_PERCENT,
};

// Using the default exemption threshold.

let balance = rent.minimum_balance(100);
let calculated = (((ACCOUNT_STORAGE_OVERHEAD + 100) * rent.lamports_per_byte_year) as f64
* rent.exemption_threshold) as u64;

assert!(calculated > 0);
assert_eq!(balance, calculated);

// Using a different exemption threshold.
rent.exemption_threshold = 0.5;

let balance = rent.minimum_balance(100);
let calculated = (((ACCOUNT_STORAGE_OVERHEAD + 100) * rent.lamports_per_byte_year) as f64
* rent.exemption_threshold) as u64;

assert!(calculated > 0);
assert_eq!(balance, calculated);
}
}

0 comments on commit 2f93fd8

Please sign in to comment.