use crate::account::AccountView;
use crate::error::ProgramError;
use crate::ProgramResult;
pub const LAMPORTS_PER_BYTE_YEAR: u64 = 3_480;
pub const EXEMPTION_THRESHOLD_YEARS: u64 = 2;
pub const ACCOUNT_STORAGE_OVERHEAD: u64 = 128;
#[inline]
pub const fn minimum_balance(data_len: usize) -> u64 {
(data_len as u64 + ACCOUNT_STORAGE_OVERHEAD)
* LAMPORTS_PER_BYTE_YEAR
* EXEMPTION_THRESHOLD_YEARS
}
#[inline]
pub fn minimum_balance_live(data_len: usize) -> Result<u64, ProgramError> {
#[cfg(target_os = "solana")]
{
Ok(live_rent()?.minimum_balance(data_len))
}
#[cfg(not(target_os = "solana"))]
{
Ok(minimum_balance(data_len))
}
}
#[cfg(target_os = "solana")]
#[repr(C)]
struct RentCache {
lamports_per_byte_year: u64,
threshold_bits: u64,
burn_percent: u64,
_spare: u64,
}
#[cfg(target_os = "solana")]
const _: () = assert!(core::mem::size_of::<RentCache>() == hopper_native::RENT_CACHE_BYTES);
#[inline]
pub fn live_rent() -> Result<hopper_native::sysvar::Rent, ProgramError> {
#[cfg(target_os = "solana")]
{
let cache = (hopper_native::HEAP_START_ADDRESS + hopper_native::RENT_CACHE_HEAP_OFFSET)
as *mut RentCache;
unsafe {
let rate = (*cache).lamports_per_byte_year;
if rate != 0 {
return Ok(hopper_native::sysvar::Rent {
lamports_per_byte_year: rate,
exemption_threshold: f64::from_bits((*cache).threshold_bits),
burn_percent: (*cache).burn_percent as u8,
});
}
let rent = hopper_native::sysvar::get_rent()?;
(*cache).threshold_bits = rent.exemption_threshold.to_bits();
(*cache).burn_percent = rent.burn_percent as u64;
(*cache).lamports_per_byte_year = rent.lamports_per_byte_year;
Ok(rent)
}
}
#[cfg(not(target_os = "solana"))]
{
Ok(hopper_native::sysvar::Rent {
lamports_per_byte_year: LAMPORTS_PER_BYTE_YEAR,
exemption_threshold: EXEMPTION_THRESHOLD_YEARS as f64,
burn_percent: 50,
})
}
}
#[inline]
pub fn check_rent_exempt(account: &AccountView<'_>) -> ProgramResult {
let data_len = account.data_len();
let required = minimum_balance_live(data_len)?;
if account.lamports() >= required {
Ok(())
} else {
Err(ProgramError::AccountNotRentExempt)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minimum_balance_matches_launch_snapshot() {
assert_eq!(minimum_balance(0), 890_880);
}
#[test]
fn minimum_balance_scales_linearly() {
let base = minimum_balance(0);
let with_100 = minimum_balance(100);
let with_200 = minimum_balance(200);
assert_eq!(with_100 - base, 696_000);
assert_eq!(with_200 - with_100, 696_000);
}
#[test]
fn minimum_balance_on_typical_vault_state() {
assert_eq!(minimum_balance(56), 1_280_640);
}
#[test]
fn host_live_minimum_uses_the_documented_snapshot() {
assert_eq!(minimum_balance_live(56), Ok(minimum_balance(56)));
}
}