use crate::address::Address;
use crate::error::ProgramError;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Clock {
pub slot: u64,
pub epoch_start_timestamp: i64,
pub epoch: u64,
pub leader_schedule_epoch: u64,
pub unix_timestamp: i64,
}
#[inline]
pub fn get_clock() -> Result<Clock, ProgramError> {
#[allow(unused_mut)]
let mut clock = Clock::default();
#[cfg(target_os = "solana")]
{
let rc =
unsafe { crate::syscalls::sol_get_clock_sysvar(&mut clock as *mut Clock as *mut u8) };
if rc != 0 {
return Err(ProgramError::UnsupportedSysvar);
}
}
Ok(clock)
}
impl Clock {
#[inline]
pub fn get() -> Result<Self, ProgramError> {
get_clock()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Rent {
pub lamports_per_byte_year: u64,
pub exemption_threshold: f64,
pub burn_percent: u8,
}
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 rent_exempt_minimum(data_len: usize) -> u64 {
(data_len as u64 + ACCOUNT_STORAGE_OVERHEAD)
* LAMPORTS_PER_BYTE_YEAR
* EXEMPTION_THRESHOLD_YEARS
}
#[inline]
pub fn get_rent() -> Result<Rent, ProgramError> {
#[allow(unused_mut)]
let mut rent = Rent::default();
#[cfg(target_os = "solana")]
{
let rc = unsafe { crate::syscalls::sol_get_rent_sysvar(&mut rent as *mut Rent as *mut u8) };
if rc != 0 {
return Err(ProgramError::UnsupportedSysvar);
}
}
Ok(rent)
}
impl Rent {
#[inline]
pub fn get() -> Result<Self, ProgramError> {
get_rent()
}
#[inline]
pub fn minimum_balance(&self, data_len: usize) -> u64 {
let bytes = data_len as u64;
let integer_part = ACCOUNT_STORAGE_OVERHEAD
.saturating_add(bytes)
.wrapping_mul(self.lamports_per_byte_year);
scale_by_exemption_threshold(integer_part, self.exemption_threshold.to_bits())
}
}
#[inline(always)]
pub const fn saturating_mul_u64(a: u64, b: u64) -> u64 {
let (ah, al) = (a >> 32, a & 0xFFFF_FFFF);
let (bh, bl) = (b >> 32, b & 0xFFFF_FFFF);
if ah != 0 && bh != 0 {
return u64::MAX;
}
let cross = ah * bl + al * bh;
if cross >> 32 != 0 {
return u64::MAX;
}
match (cross << 32).checked_add(al * bl) {
Some(product) => product,
None => u64::MAX,
}
}
const THRESHOLD_ONE_BITS: u64 = 0x3FF0_0000_0000_0000;
const THRESHOLD_TWO_BITS: u64 = 0x4000_0000_0000_0000;
#[inline]
pub fn scale_by_exemption_threshold(integer_part: u64, threshold_bits: u64) -> u64 {
if threshold_bits == THRESHOLD_ONE_BITS {
return integer_part;
}
if threshold_bits == THRESHOLD_TWO_BITS {
return integer_part.saturating_mul(2);
}
saturating_mul_u64(integer_part, ceil_years(threshold_bits))
}
#[inline]
fn ceil_years(bits: u64) -> u64 {
if bits >> 63 == 1 {
return 0;
}
let exponent = ((bits >> 52) & 0x7FF) as i32;
let mantissa = bits & ((1u64 << 52) - 1);
if exponent == 0x7FF {
return if mantissa == 0 { u64::MAX } else { 0 };
}
if exponent == 0 {
return if mantissa == 0 { 0 } else { 1 };
}
let e = exponent - 1023;
if e < 0 {
return 1;
}
if e >= 64 {
return u64::MAX;
}
let significand = (1u64 << 52) | mantissa;
if e >= 52 {
return significand << (e - 52);
}
let shift = (52 - e) as u32;
let whole = significand >> shift;
let fraction = significand & ((1u64 << shift) - 1);
if fraction == 0 {
whole
} else {
whole + 1
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct EpochSchedule {
pub slots_per_epoch: u64,
pub leader_schedule_slot_offset: u64,
pub warmup: bool,
pub first_normal_epoch: u64,
pub first_normal_slot: u64,
}
const _: () = {
assert!(core::mem::size_of::<EpochSchedule>() == 40);
assert!(core::mem::align_of::<EpochSchedule>() == 8);
assert!(core::mem::offset_of!(EpochSchedule, slots_per_epoch) == 0);
assert!(core::mem::offset_of!(EpochSchedule, leader_schedule_slot_offset) == 8);
assert!(core::mem::offset_of!(EpochSchedule, warmup) == 16);
assert!(core::mem::offset_of!(EpochSchedule, first_normal_epoch) == 24);
assert!(core::mem::offset_of!(EpochSchedule, first_normal_slot) == 32);
};
const _: () = {
assert!(core::mem::size_of::<Clock>() == 40);
assert!(core::mem::offset_of!(Clock, slot) == 0);
assert!(core::mem::offset_of!(Clock, epoch_start_timestamp) == 8);
assert!(core::mem::offset_of!(Clock, epoch) == 16);
assert!(core::mem::offset_of!(Clock, leader_schedule_epoch) == 24);
assert!(core::mem::offset_of!(Clock, unix_timestamp) == 32);
};
#[inline]
pub fn get_epoch_schedule() -> Result<EpochSchedule, ProgramError> {
#[allow(unused_mut)]
let mut schedule = EpochSchedule::default();
#[cfg(target_os = "solana")]
{
let rc = unsafe {
crate::syscalls::sol_get_epoch_schedule_sysvar(
&mut schedule as *mut EpochSchedule as *mut u8,
)
};
if rc != 0 {
return Err(ProgramError::UnsupportedSysvar);
}
}
Ok(schedule)
}
impl EpochSchedule {
#[inline]
pub fn get_epoch(&self, slot: u64) -> u64 {
if slot < self.first_normal_slot {
if slot == 0 {
return 0;
}
let mut epoch_len: u64 = 32; let mut epoch: u64 = 0;
let mut slot_remaining = slot;
while slot_remaining >= epoch_len {
slot_remaining -= epoch_len;
epoch += 1;
epoch_len = epoch_len.saturating_mul(2);
}
epoch
} else {
let normal_slot_index = slot - self.first_normal_slot;
self.first_normal_epoch + normal_slot_index / self.slots_per_epoch
}
}
#[inline]
pub fn get_first_slot_in_epoch(&self, epoch: u64) -> u64 {
if epoch <= self.first_normal_epoch {
if epoch == 0 {
return 0;
}
let shift = epoch.min(63);
32_u64.saturating_mul((1_u64 << shift).saturating_sub(1))
} else {
let normal_epoch_index = epoch - self.first_normal_epoch;
self.first_normal_slot + normal_epoch_index * self.slots_per_epoch
}
}
}
pub const CLOCK_ID: Address = crate::address!("SysvarC1ock11111111111111111111111111111111");
pub const RENT_ID: Address = crate::address!("SysvarRent111111111111111111111111111111111");
pub const EPOCH_SCHEDULE_ID: Address =
crate::address!("SysvarEpochSchedu1e111111111111111111111111");
pub const SLOT_HASHES_ID: Address = crate::address!("SysvarS1otHashes111111111111111111111111111");
pub const STAKE_HISTORY_ID: Address =
crate::address!("SysvarStakeHistory1111111111111111111111111");
pub const INSTRUCTIONS_ID: Address = crate::address!("Sysvar1nstructions1111111111111111111111111");
pub const EPOCH_REWARDS_ID: Address =
crate::address!("SysvarEpochRewards1111111111111111111111111");
#[inline]
pub fn get_sysvar_into(
sysvar_id: &Address,
offset: u64,
dst: &mut [u8],
) -> Result<(), ProgramError> {
#[cfg(target_os = "solana")]
{
let rc = unsafe {
crate::syscalls::sol_get_sysvar(
sysvar_id.as_array().as_ptr(),
dst.as_mut_ptr(),
offset,
dst.len() as u64,
)
};
if rc != 0 {
return Err(ProgramError::UnsupportedSysvar);
}
}
#[cfg(not(target_os = "solana"))]
{
let _ = (sysvar_id, offset, dst);
}
Ok(())
}
#[inline]
pub fn get_epoch_stake(vote: &Address) -> u64 {
#[cfg(target_os = "solana")]
{
unsafe { crate::syscalls::sol_get_epoch_stake(vote.as_array().as_ptr()) }
}
#[cfg(not(target_os = "solana"))]
{
let _ = vote;
0
}
}
#[inline]
pub fn get_total_epoch_stake() -> u64 {
#[cfg(target_os = "solana")]
{
unsafe { crate::syscalls::sol_get_epoch_stake(core::ptr::null()) }
}
#[cfg(not(target_os = "solana"))]
{
0
}
}
pub const LAST_RESTART_SLOT_ID: Address =
crate::address!("SysvarLastRestartS1ot1111111111111111111111");
#[inline]
pub fn get_last_restart_slot() -> Result<u64, ProgramError> {
#[allow(unused_mut)]
let mut slot: u64 = 0;
#[cfg(target_os = "solana")]
{
let rc =
unsafe { crate::syscalls::sol_get_last_restart_slot(&mut slot as *mut u64 as *mut u8) };
if rc != 0 {
return Err(ProgramError::UnsupportedSysvar);
}
}
Ok(slot)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SlotHash {
pub slot: u64,
pub hash: [u8; 32],
}
#[inline]
pub fn slot_hashes_latest() -> Result<Option<SlotHash>, ProgramError> {
let mut count_buf = [0u8; 8];
get_sysvar_into(&SLOT_HASHES_ID, 0, &mut count_buf)?;
let count = u64::from_le_bytes(count_buf);
if count == 0 {
return Ok(None);
}
let mut entry = [0u8; 40];
get_sysvar_into(&SLOT_HASHES_ID, 8, &mut entry)?;
let slot = u64::from_le_bytes([
entry[0], entry[1], entry[2], entry[3], entry[4], entry[5], entry[6], entry[7],
]);
let mut hash = [0u8; 32];
hash.copy_from_slice(&entry[8..40]);
Ok(Some(SlotHash { slot, hash }))
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct StakeHistoryEntry {
pub epoch: u64,
pub effective: u64,
pub activating: u64,
pub deactivating: u64,
}
#[inline]
pub fn stake_history_latest() -> Result<Option<StakeHistoryEntry>, ProgramError> {
let mut count_buf = [0u8; 8];
get_sysvar_into(&STAKE_HISTORY_ID, 0, &mut count_buf)?;
let count = u64::from_le_bytes(count_buf);
if count == 0 {
return Ok(None);
}
let mut entry = [0u8; 32];
get_sysvar_into(&STAKE_HISTORY_ID, 8, &mut entry)?;
let rd = |o: usize| {
u64::from_le_bytes([
entry[o],
entry[o + 1],
entry[o + 2],
entry[o + 3],
entry[o + 4],
entry[o + 5],
entry[o + 6],
entry[o + 7],
])
};
Ok(Some(StakeHistoryEntry {
epoch: rd(0),
effective: rd(8),
activating: rd(16),
deactivating: rd(24),
}))
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct EpochRewards {
pub distribution_starting_block_height: u64,
pub num_partitions: u64,
pub parent_blockhash: [u8; 32],
pub total_points: u128,
pub total_rewards: u64,
pub distributed_rewards: u64,
pub active: bool,
}
const EPOCH_REWARDS_LEN: usize = 81;
#[inline]
fn decode_epoch_rewards(buf: &[u8; EPOCH_REWARDS_LEN]) -> EpochRewards {
let rd8 = |o: usize| {
u64::from_le_bytes([
buf[o],
buf[o + 1],
buf[o + 2],
buf[o + 3],
buf[o + 4],
buf[o + 5],
buf[o + 6],
buf[o + 7],
])
};
let mut parent_blockhash = [0u8; 32];
parent_blockhash.copy_from_slice(&buf[16..48]);
let mut points = [0u8; 16];
points.copy_from_slice(&buf[48..64]);
EpochRewards {
distribution_starting_block_height: rd8(0),
num_partitions: rd8(8),
parent_blockhash,
total_points: u128::from_le_bytes(points),
total_rewards: rd8(64),
distributed_rewards: rd8(72),
active: buf[80] != 0,
}
}
#[inline]
pub fn get_epoch_rewards() -> Result<EpochRewards, ProgramError> {
let mut buf = [0u8; EPOCH_REWARDS_LEN];
get_sysvar_into(&EPOCH_REWARDS_ID, 0, &mut buf)?;
Ok(decode_epoch_rewards(&buf))
}
#[cfg(test)]
mod abi_tests {
use super::*;
#[test]
fn epoch_schedule_reads_canonical_byte_image() {
let mut buf = [0u8; 40];
buf[0..8].copy_from_slice(&432_000u64.to_le_bytes()); buf[8..16].copy_from_slice(&432_000u64.to_le_bytes()); buf[16] = 0; buf[24..32].copy_from_slice(&0u64.to_le_bytes()); buf[32..40].copy_from_slice(&0u64.to_le_bytes());
let sched: EpochSchedule = unsafe { core::ptr::read(buf.as_ptr() as *const EpochSchedule) };
assert_eq!(sched.slots_per_epoch, 432_000);
assert_eq!(sched.leader_schedule_slot_offset, 432_000);
assert!(!sched.warmup);
assert_eq!(sched.first_normal_epoch, 0);
assert_eq!(sched.first_normal_slot, 0);
}
#[test]
fn clock_reads_canonical_byte_image() {
let mut buf = [0u8; 40];
buf[0..8].copy_from_slice(&123u64.to_le_bytes()); buf[8..16].copy_from_slice(&1_600_000_000i64.to_le_bytes()); buf[16..24].copy_from_slice(&7u64.to_le_bytes()); buf[24..32].copy_from_slice(&8u64.to_le_bytes()); buf[32..40].copy_from_slice(&1_600_000_500i64.to_le_bytes());
let clock: Clock = unsafe { core::ptr::read(buf.as_ptr() as *const Clock) };
assert_eq!(clock.slot, 123);
assert_eq!(clock.epoch_start_timestamp, 1_600_000_000);
assert_eq!(clock.epoch, 7);
assert_eq!(clock.leader_schedule_epoch, 8);
assert_eq!(clock.unix_timestamp, 1_600_000_500);
}
#[test]
fn epoch_rewards_decodes_canonical_byte_image() {
let mut buf = [0u8; EPOCH_REWARDS_LEN];
buf[0..8].copy_from_slice(&100u64.to_le_bytes()); buf[8..16].copy_from_slice(&8u64.to_le_bytes()); buf[16..48].copy_from_slice(&[7u8; 32]); buf[48..64].copy_from_slice(&123_456_789u128.to_le_bytes()); buf[64..72].copy_from_slice(&5_000_000u64.to_le_bytes()); buf[72..80].copy_from_slice(&1_250_000u64.to_le_bytes()); buf[80] = 1;
let er = decode_epoch_rewards(&buf);
assert_eq!(er.distribution_starting_block_height, 100);
assert_eq!(er.num_partitions, 8);
assert_eq!(er.parent_blockhash, [7u8; 32]);
assert_eq!(er.total_points, 123_456_789);
assert_eq!(er.total_rewards, 5_000_000);
assert_eq!(er.distributed_rewards, 1_250_000);
assert!(er.active);
}
#[test]
fn epoch_rewards_off_chain_is_zeroed_default() {
let er = get_epoch_rewards().unwrap();
assert_eq!(er, EpochRewards::default());
assert!(!er.active);
}
}
#[cfg(test)]
mod rent_tests {
use super::*;
const LOADER_MAX_DATA_LEN: usize = 10_485_760;
fn solana_reference_minimum_balance(data_len: usize, lpby: u64, threshold: f64) -> u64 {
let bytes = data_len as u64;
let integer_part = (ACCOUNT_STORAGE_OVERHEAD + bytes).saturating_mul(lpby);
if threshold == 1.0 {
integer_part
} else if threshold == 2.0 {
integer_part.saturating_mul(2)
} else {
(integer_part as f64 * threshold) as u64
}
}
fn rent_with(lpby: u64, threshold: f64) -> Rent {
Rent {
lamports_per_byte_year: lpby,
exemption_threshold: threshold,
burn_percent: 0,
}
}
#[test]
fn const_rent_exempt_minimum_no_overflow_at_extremes() {
assert_eq!(
rent_exempt_minimum(0),
ACCOUNT_STORAGE_OVERHEAD * LAMPORTS_PER_BYTE_YEAR * EXEMPTION_THRESHOLD_YEARS
);
let max = rent_exempt_minimum(LOADER_MAX_DATA_LEN);
let expected = (LOADER_MAX_DATA_LEN as u64 + ACCOUNT_STORAGE_OVERHEAD)
* LAMPORTS_PER_BYTE_YEAR
* EXEMPTION_THRESHOLD_YEARS;
assert_eq!(max, expected);
assert!(max < u64::MAX / 2);
}
#[test]
fn sysvar_minimum_balance_no_overflow_at_extremes() {
let rent = rent_with(1u64 << 40, 2.0);
let _ = rent.minimum_balance(LOADER_MAX_DATA_LEN);
assert_eq!(
rent.minimum_balance(0),
solana_reference_minimum_balance(0, 1u64 << 40, 2.0)
);
}
#[test]
fn const_and_sysvar_agree_at_launch_snapshot() {
let rent = rent_with(LAMPORTS_PER_BYTE_YEAR, EXEMPTION_THRESHOLD_YEARS as f64);
for &dl in &[
0usize,
1,
127,
128,
1024,
10_240,
1_000_000,
LOADER_MAX_DATA_LEN,
] {
assert_eq!(
rent_exempt_minimum(dl),
rent.minimum_balance(dl),
"const vs sysvar mismatch at data_len={dl}"
);
}
}
#[test]
fn sysvar_minimum_balance_byte_matches_solana_reference() {
let cases: &[(usize, u64, f64)] = &[
(0, 6_333, 1.0),
(167_829, 6_333, 1.0),
(0, 3_480, 2.0),
(165, 3_480, 2.0),
(10_240, 3_480, 2.0),
(1_000_000, 6_960, 2.0), (500_000, 3_480, 3.0), (10_485_760, 3_480, 2.0), (1_024, 9_007_199_254_740_993, 2.0),
];
for &(dl, lpby, threshold) in cases {
let rent = rent_with(lpby, threshold);
assert_eq!(
rent.minimum_balance(dl),
solana_reference_minimum_balance(dl, lpby, threshold),
"sysvar minimum_balance != Solana reference at dl={dl}, lpby={lpby}, threshold={threshold}"
);
}
}
#[test]
fn saturating_mul_u64_matches_the_library_operator() {
let samples = [
0u64,
1,
2,
3,
128,
153,
3_480,
5_080,
6_333,
10_485_888,
u32::MAX as u64,
u32::MAX as u64 + 1,
1 << 40,
u64::MAX / 3,
u64::MAX / 2,
u64::MAX / 2 + 1,
u64::MAX - 1,
u64::MAX,
];
for &a in &samples {
for &b in &samples {
assert_eq!(saturating_mul_u64(a, b), a.saturating_mul(b), "{a} * {b}");
}
}
}
#[test]
fn threshold_scaling_is_float_free_and_never_underfunds() {
fn reference(integer_part: u64, threshold: f64) -> u64 {
(integer_part as f64 * threshold) as u64
}
let amounts: [u64; 8] = [
0,
1,
7,
128 * 3_480,
890_880,
1_740_445_440,
1 << 40,
1 << 52,
];
let whole_thresholds: [f64; 4] = [1.0, 2.0, 3.0, 4.0];
let fractional: [(f64, u64); 10] = [
(0.5, 1),
(1.5, 2),
(2.5, 3),
(0.25, 1),
(0.75, 1),
(1.1, 2),
(1.7, 2),
(0.3, 1),
(2.9, 3),
(3.33, 4),
];
for &amount in &amounts {
for &threshold in &whole_thresholds {
assert_eq!(
scale_by_exemption_threshold(amount, threshold.to_bits()),
reference(amount, threshold),
"amount {amount} threshold {threshold}"
);
}
for &(threshold, years) in &fractional {
let ours = scale_by_exemption_threshold(amount, threshold.to_bits());
assert_eq!(ours, amount.saturating_mul(years), "threshold {threshold}");
assert!(
ours >= reference(amount, threshold),
"amount {amount} threshold {threshold}: {ours} underfunds"
);
}
assert_eq!(scale_by_exemption_threshold(amount, 0.0f64.to_bits()), 0);
assert_eq!(scale_by_exemption_threshold(amount, (-1.0f64).to_bits()), 0);
assert_eq!(scale_by_exemption_threshold(amount, f64::NAN.to_bits()), 0);
assert_eq!(
scale_by_exemption_threshold(amount, f64::MIN_POSITIVE.to_bits() >> 1),
amount
);
let saturated = if amount == 0 { 0 } else { u64::MAX };
assert_eq!(
scale_by_exemption_threshold(amount, f64::INFINITY.to_bits()),
saturated
);
}
let huge: f64 = (1u64 << 63) as f64;
assert_eq!(scale_by_exemption_threshold(2, huge.to_bits()), u64::MAX);
let astronomical: f64 = 1e300;
assert_eq!(
scale_by_exemption_threshold(1, astronomical.to_bits()),
u64::MAX
);
let exactly_2_pow_60: f64 = (1u64 << 60) as f64;
assert_eq!(
scale_by_exemption_threshold(1, exactly_2_pow_60.to_bits()),
1 << 60
);
assert_eq!(
scale_by_exemption_threshold(u64::MAX, 1.0f64.to_bits()),
u64::MAX
);
assert_eq!(
scale_by_exemption_threshold(u64::MAX, 2.0f64.to_bits()),
u64::MAX
);
}
#[test]
fn repriced_sysvar_exceeds_const_underestimate() {
let dl = 4_096;
let const_min = rent_exempt_minimum(dl);
let repriced = rent_with(LAMPORTS_PER_BYTE_YEAR * 2, EXEMPTION_THRESHOLD_YEARS as f64);
let sysvar_min = repriced.minimum_balance(dl);
assert!(
sysvar_min > const_min,
"expected repriced sysvar minimum ({sysvar_min}) > const minimum ({const_min})"
);
assert_eq!(sysvar_min, const_min * 2);
}
}
#[cfg(kani)]
mod kani_rent_proofs {
use super::*;
const LOADER_MAX_DATA_LEN: usize = 10_485_760;
const MAX_LAMPORTS_PER_BYTE_YEAR: u64 = 1 << 40;
#[kani::proof]
fn const_rent_exempt_minimum_never_overflows() {
let data_len: usize = kani::any();
kani::assume(data_len <= LOADER_MAX_DATA_LEN);
let sum = (data_len as u64)
.checked_add(ACCOUNT_STORAGE_OVERHEAD)
.unwrap();
let per_year = sum.checked_mul(LAMPORTS_PER_BYTE_YEAR).unwrap();
let total = per_year.checked_mul(EXEMPTION_THRESHOLD_YEARS).unwrap();
assert_eq!(rent_exempt_minimum(data_len), total);
}
#[kani::proof]
fn sysvar_minimum_balance_integer_part_never_overflows() {
let data_len: usize = kani::any();
let lpby: u64 = kani::any();
kani::assume(data_len <= LOADER_MAX_DATA_LEN);
kani::assume(lpby <= MAX_LAMPORTS_PER_BYTE_YEAR);
let bytes = data_len as u64;
let checked = ACCOUNT_STORAGE_OVERHEAD
.checked_add(bytes)
.and_then(|s| s.checked_mul(lpby));
assert!(checked.is_some());
let saturating = ACCOUNT_STORAGE_OVERHEAD
.saturating_add(bytes)
.saturating_mul(lpby);
assert_eq!(saturating, checked.unwrap());
}
#[kani::proof]
fn const_and_sysvar_agree_at_launch_snapshot() {
let data_len: usize = kani::any();
kani::assume(data_len <= LOADER_MAX_DATA_LEN);
let rent = Rent {
lamports_per_byte_year: LAMPORTS_PER_BYTE_YEAR,
exemption_threshold: EXEMPTION_THRESHOLD_YEARS as f64,
burn_percent: 0,
};
assert_eq!(
rent_exempt_minimum(data_len),
rent.minimum_balance(data_len)
);
}
}