Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rent: Rent exempt calculation improvement #46

Merged
merged 3 commits into from
Nov 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}