2019-08-23 14:04:53 -07:00
|
|
|
//! calculate and collect rent from Accounts
|
2022-12-19 08:18:02 -08:00
|
|
|
use solana_sdk::{
|
|
|
|
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
|
|
|
clock::Epoch,
|
|
|
|
epoch_schedule::EpochSchedule,
|
|
|
|
genesis_config::GenesisConfig,
|
|
|
|
incinerator,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
rent::{Rent, RentDue},
|
2019-11-18 12:01:27 -08:00
|
|
|
};
|
2019-08-23 14:04:53 -07:00
|
|
|
|
2020-08-11 08:04:32 -07:00
|
|
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, AbiExample)]
|
2019-08-23 14:04:53 -07:00
|
|
|
pub struct RentCollector {
|
|
|
|
pub epoch: Epoch,
|
|
|
|
pub epoch_schedule: EpochSchedule,
|
|
|
|
pub slots_per_year: f64,
|
2019-10-30 16:25:12 -07:00
|
|
|
pub rent: Rent,
|
2019-08-23 14:04:53 -07:00
|
|
|
}
|
|
|
|
|
2020-08-11 08:04:32 -07:00
|
|
|
impl Default for RentCollector {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
epoch: Epoch::default(),
|
|
|
|
epoch_schedule: EpochSchedule::default(),
|
|
|
|
// derive default value using GenesisConfig::default()
|
|
|
|
slots_per_year: GenesisConfig::default().slots_per_year(),
|
|
|
|
rent: Rent::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-02 07:51:08 -08:00
|
|
|
/// When rent is collected from an exempt account, rent_epoch is set to this
|
|
|
|
/// value. The idea is to have a fixed, consistent value for rent_epoch for all accounts that do not collect rent.
|
|
|
|
/// This enables us to get rid of the field completely.
|
|
|
|
pub const RENT_EXEMPT_RENT_EPOCH: Epoch = Epoch::MAX;
|
|
|
|
|
2022-01-27 16:48:04 -08:00
|
|
|
/// when rent is collected for this account, this is the action to apply to the account
|
2022-02-09 16:07:36 -08:00
|
|
|
#[derive(Debug)]
|
2022-10-31 13:37:36 -07:00
|
|
|
enum RentResult {
|
2022-12-08 19:48:42 -08:00
|
|
|
/// this account will never have rent collected from it
|
|
|
|
Exempt,
|
|
|
|
/// maybe we collect rent later, but not now
|
|
|
|
NoRentCollectionNow,
|
2022-01-27 16:48:04 -08:00
|
|
|
/// collect rent
|
2022-07-06 13:01:16 -07:00
|
|
|
CollectRent {
|
|
|
|
new_rent_epoch: Epoch,
|
2022-12-08 19:48:42 -08:00
|
|
|
rent_due: u64, // lamports, could be 0
|
2022-07-06 13:01:16 -07:00
|
|
|
},
|
2022-01-27 16:48:04 -08:00
|
|
|
}
|
|
|
|
|
2019-08-23 14:04:53 -07:00
|
|
|
impl RentCollector {
|
2022-07-06 13:01:16 -07:00
|
|
|
pub(crate) fn new(
|
2019-08-23 14:04:53 -07:00
|
|
|
epoch: Epoch,
|
2022-07-06 13:01:16 -07:00
|
|
|
epoch_schedule: EpochSchedule,
|
2019-08-23 14:04:53 -07:00
|
|
|
slots_per_year: f64,
|
2022-07-06 13:01:16 -07:00
|
|
|
rent: Rent,
|
2019-08-23 14:04:53 -07:00
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
epoch,
|
2022-07-06 13:01:16 -07:00
|
|
|
epoch_schedule,
|
2019-08-23 14:04:53 -07:00
|
|
|
slots_per_year,
|
2022-07-06 13:01:16 -07:00
|
|
|
rent,
|
2019-08-23 14:04:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-06 13:01:16 -07:00
|
|
|
pub(crate) fn clone_with_epoch(&self, epoch: Epoch) -> Self {
|
2019-08-23 14:04:53 -07:00
|
|
|
Self {
|
|
|
|
epoch,
|
|
|
|
..self.clone()
|
|
|
|
}
|
|
|
|
}
|
2020-08-16 22:22:16 -07:00
|
|
|
|
2021-11-04 04:28:41 -07:00
|
|
|
/// true if it is easy to determine this account should consider having rent collected from it
|
2022-07-08 13:04:08 -07:00
|
|
|
pub(crate) fn should_collect_rent(
|
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
account: &impl ReadableAccount,
|
|
|
|
) -> bool {
|
2021-11-04 04:28:41 -07:00
|
|
|
!(account.executable() // executable accounts must be rent-exempt balance
|
|
|
|
|| *address == incinerator::id())
|
2021-11-03 12:44:52 -07:00
|
|
|
}
|
|
|
|
|
2021-11-04 04:28:41 -07:00
|
|
|
/// given an account that 'should_collect_rent'
|
|
|
|
/// returns (amount rent due, is_exempt_from_rent)
|
2022-07-06 13:01:16 -07:00
|
|
|
pub(crate) fn get_rent_due(&self, account: &impl ReadableAccount) -> RentDue {
|
2022-02-28 07:42:42 -08:00
|
|
|
if self
|
|
|
|
.rent
|
|
|
|
.is_exempt(account.lamports(), account.data().len())
|
|
|
|
{
|
|
|
|
RentDue::Exempt
|
2021-11-03 12:44:52 -07:00
|
|
|
} else {
|
2022-03-03 07:09:31 -08:00
|
|
|
let account_rent_epoch = account.rent_epoch();
|
|
|
|
let slots_elapsed: u64 = (account_rent_epoch..=self.epoch)
|
2022-02-28 07:42:42 -08:00
|
|
|
.map(|epoch| self.epoch_schedule.get_slots_in_epoch(epoch + 1))
|
|
|
|
.sum();
|
2021-11-03 12:44:52 -07:00
|
|
|
|
2022-02-28 07:42:42 -08:00
|
|
|
// avoid infinite rent in rust 1.45
|
|
|
|
let years_elapsed = if self.slots_per_year != 0.0 {
|
|
|
|
slots_elapsed as f64 / self.slots_per_year
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
|
|
|
|
|
|
|
// we know this account is not exempt
|
2022-03-03 07:09:31 -08:00
|
|
|
let due = self.rent.due_amount(account.data().len(), years_elapsed);
|
|
|
|
RentDue::Paying(due)
|
2022-02-28 07:42:42 -08:00
|
|
|
}
|
2021-11-03 12:44:52 -07:00
|
|
|
}
|
|
|
|
|
2022-01-08 07:03:46 -08:00
|
|
|
// Updates the account's lamports and status, and returns the amount of rent collected, if any.
|
|
|
|
// This is NOT thread safe at some level. If we try to collect from the same account in
|
|
|
|
// parallel, we may collect twice.
|
2020-08-11 08:04:32 -07:00
|
|
|
#[must_use = "add to Bank::collected_rent"]
|
2022-07-06 13:01:16 -07:00
|
|
|
pub(crate) fn collect_from_existing_account(
|
2021-03-09 13:06:07 -08:00
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
account: &mut AccountSharedData,
|
2021-10-11 10:46:27 -07:00
|
|
|
filler_account_suffix: Option<&Pubkey>,
|
2023-01-02 07:51:08 -08:00
|
|
|
set_exempt_rent_epoch_max: bool,
|
2022-01-11 15:20:28 -08:00
|
|
|
) -> CollectedInfo {
|
2022-11-01 06:36:59 -07:00
|
|
|
match self.calculate_rent_result(address, account, filler_account_suffix) {
|
2023-01-02 07:51:08 -08:00
|
|
|
RentResult::Exempt => {
|
|
|
|
if set_exempt_rent_epoch_max {
|
|
|
|
account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
|
|
|
|
}
|
|
|
|
CollectedInfo::default()
|
|
|
|
}
|
|
|
|
RentResult::NoRentCollectionNow => CollectedInfo::default(),
|
2022-07-06 13:01:16 -07:00
|
|
|
RentResult::CollectRent {
|
|
|
|
new_rent_epoch,
|
|
|
|
rent_due,
|
|
|
|
} => match account.lamports().checked_sub(rent_due) {
|
|
|
|
None | Some(0) => {
|
|
|
|
let account = std::mem::take(account);
|
|
|
|
CollectedInfo {
|
|
|
|
rent_amount: account.lamports(),
|
|
|
|
account_data_len_reclaimed: account.data().len() as u64,
|
|
|
|
}
|
2022-01-27 16:48:04 -08:00
|
|
|
}
|
2022-07-06 13:01:16 -07:00
|
|
|
Some(lamports) => {
|
|
|
|
account.set_lamports(lamports);
|
|
|
|
account.set_rent_epoch(new_rent_epoch);
|
|
|
|
CollectedInfo {
|
|
|
|
rent_amount: rent_due,
|
|
|
|
account_data_len_reclaimed: 0u64,
|
|
|
|
}
|
2022-01-27 16:48:04 -08:00
|
|
|
}
|
2022-07-06 13:01:16 -07:00
|
|
|
},
|
2022-01-27 16:48:04 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// determine what should happen to collect rent from this account
|
|
|
|
#[must_use]
|
2022-10-31 13:37:36 -07:00
|
|
|
fn calculate_rent_result(
|
2022-01-27 16:48:04 -08:00
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
account: &impl ReadableAccount,
|
|
|
|
filler_account_suffix: Option<&Pubkey>,
|
|
|
|
) -> RentResult {
|
2023-01-02 07:51:08 -08:00
|
|
|
if account.rent_epoch() == RENT_EXEMPT_RENT_EPOCH || account.rent_epoch() > self.epoch {
|
|
|
|
// potentially rent paying account (or known and already marked exempt)
|
2022-12-08 19:48:42 -08:00
|
|
|
// Maybe collect rent later, leave account alone for now.
|
|
|
|
return RentResult::NoRentCollectionNow;
|
|
|
|
}
|
|
|
|
if !self.should_collect_rent(address, account)
|
|
|
|
|| crate::accounts_db::AccountsDb::is_filler_account_helper(
|
|
|
|
address,
|
|
|
|
filler_account_suffix,
|
|
|
|
)
|
|
|
|
{
|
|
|
|
// easy to determine this account should not consider having rent collected from it
|
|
|
|
return RentResult::Exempt;
|
2022-01-08 07:03:46 -08:00
|
|
|
}
|
2022-07-06 13:01:16 -07:00
|
|
|
match self.get_rent_due(account) {
|
2022-12-08 19:48:42 -08:00
|
|
|
// account will not have rent collected ever
|
|
|
|
RentDue::Exempt => RentResult::Exempt,
|
|
|
|
// potentially rent paying account
|
|
|
|
// Maybe collect rent later, leave account alone for now.
|
|
|
|
RentDue::Paying(0) => RentResult::NoRentCollectionNow,
|
2022-07-06 13:01:16 -07:00
|
|
|
// Rent is collected for next epoch.
|
|
|
|
RentDue::Paying(rent_due) => RentResult::CollectRent {
|
|
|
|
new_rent_epoch: self.epoch + 1,
|
|
|
|
rent_due,
|
|
|
|
},
|
2019-08-23 14:04:53 -07:00
|
|
|
}
|
|
|
|
}
|
2020-08-11 08:04:32 -07:00
|
|
|
}
|
|
|
|
|
2022-01-11 15:20:28 -08:00
|
|
|
/// Information computed during rent collection
|
|
|
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
2022-07-08 13:04:08 -07:00
|
|
|
pub(crate) struct CollectedInfo {
|
2022-01-11 15:20:28 -08:00
|
|
|
/// Amount of rent collected from account
|
2022-07-08 13:04:08 -07:00
|
|
|
pub(crate) rent_amount: u64,
|
2022-01-11 15:20:28 -08:00
|
|
|
/// Size of data reclaimed from account (happens when account's lamports go to zero)
|
2022-07-08 13:04:08 -07:00
|
|
|
pub(crate) account_data_len_reclaimed: u64,
|
2022-01-11 15:20:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::Add for CollectedInfo {
|
|
|
|
type Output = Self;
|
|
|
|
fn add(self, other: Self) -> Self {
|
|
|
|
Self {
|
|
|
|
rent_amount: self.rent_amount + other.rent_amount,
|
|
|
|
account_data_len_reclaimed: self.account_data_len_reclaimed
|
|
|
|
+ other.account_data_len_reclaimed,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::AddAssign for CollectedInfo {
|
|
|
|
fn add_assign(&mut self, other: Self) {
|
|
|
|
*self = *self + other;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 08:04:32 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-01-14 04:34:09 -08:00
|
|
|
use {
|
|
|
|
super::*,
|
|
|
|
solana_sdk::{account::Account, sysvar},
|
|
|
|
};
|
2020-08-11 08:04:32 -07:00
|
|
|
|
2022-02-08 20:11:47 -08:00
|
|
|
fn default_rent_collector_clone_with_epoch(epoch: Epoch) -> RentCollector {
|
|
|
|
RentCollector::default().clone_with_epoch(epoch)
|
|
|
|
}
|
|
|
|
|
2022-11-01 11:18:02 -07:00
|
|
|
impl RentCollector {
|
|
|
|
#[must_use = "add to Bank::collected_rent"]
|
|
|
|
fn collect_from_created_account(
|
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
account: &mut AccountSharedData,
|
2023-01-02 11:59:50 -08:00
|
|
|
set_exempt_rent_epoch_max: bool,
|
2022-11-01 11:18:02 -07:00
|
|
|
) -> CollectedInfo {
|
|
|
|
// initialize rent_epoch as created at this epoch
|
|
|
|
account.set_rent_epoch(self.epoch);
|
|
|
|
self.collect_from_existing_account(
|
2023-01-02 07:51:08 -08:00
|
|
|
address,
|
|
|
|
account,
|
|
|
|
/*filler_account_suffix:*/ None,
|
2023-01-02 11:59:50 -08:00
|
|
|
set_exempt_rent_epoch_max,
|
2022-11-01 11:18:02 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-08 19:48:42 -08:00
|
|
|
#[test]
|
|
|
|
fn test_calculate_rent_result() {
|
2023-01-02 07:51:08 -08:00
|
|
|
for set_exempt_rent_epoch_max in [false, true] {
|
|
|
|
let mut rent_collector = RentCollector::default();
|
|
|
|
|
|
|
|
let mut account = AccountSharedData::default();
|
|
|
|
assert!(matches!(
|
|
|
|
rent_collector.calculate_rent_result(&Pubkey::default(), &account, None,),
|
|
|
|
RentResult::NoRentCollectionNow,
|
|
|
|
));
|
|
|
|
{
|
|
|
|
let mut account_clone = account.clone();
|
|
|
|
assert_eq!(
|
|
|
|
rent_collector.collect_from_existing_account(
|
|
|
|
&Pubkey::default(),
|
|
|
|
&mut account_clone,
|
|
|
|
None,
|
|
|
|
set_exempt_rent_epoch_max
|
|
|
|
),
|
|
|
|
CollectedInfo::default()
|
|
|
|
);
|
|
|
|
assert_eq!(account_clone, account);
|
|
|
|
}
|
2022-12-08 19:48:42 -08:00
|
|
|
|
2023-01-02 07:51:08 -08:00
|
|
|
account.set_executable(true);
|
|
|
|
assert!(matches!(
|
|
|
|
rent_collector.calculate_rent_result(&Pubkey::default(), &account, None,),
|
|
|
|
RentResult::Exempt
|
|
|
|
));
|
|
|
|
{
|
|
|
|
let mut account_clone = account.clone();
|
|
|
|
let mut account_expected = account.clone();
|
|
|
|
if set_exempt_rent_epoch_max {
|
|
|
|
account_expected.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
rent_collector.collect_from_existing_account(
|
|
|
|
&Pubkey::default(),
|
|
|
|
&mut account_clone,
|
|
|
|
None,
|
|
|
|
set_exempt_rent_epoch_max
|
|
|
|
),
|
|
|
|
CollectedInfo::default()
|
|
|
|
);
|
|
|
|
assert_eq!(account_clone, account_expected);
|
|
|
|
}
|
2022-12-08 19:48:42 -08:00
|
|
|
|
2023-01-02 07:51:08 -08:00
|
|
|
account.set_executable(false);
|
|
|
|
assert!(matches!(
|
|
|
|
rent_collector.calculate_rent_result(&incinerator::id(), &account, None,),
|
|
|
|
RentResult::Exempt
|
|
|
|
));
|
|
|
|
{
|
|
|
|
let mut account_clone = account.clone();
|
|
|
|
let mut account_expected = account.clone();
|
|
|
|
if set_exempt_rent_epoch_max {
|
|
|
|
account_expected.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
rent_collector.collect_from_existing_account(
|
|
|
|
&incinerator::id(),
|
|
|
|
&mut account_clone,
|
|
|
|
None,
|
|
|
|
set_exempt_rent_epoch_max
|
2022-12-08 19:48:42 -08:00
|
|
|
),
|
2023-01-02 07:51:08 -08:00
|
|
|
CollectedInfo::default()
|
2022-12-08 19:48:42 -08:00
|
|
|
);
|
2023-01-02 07:51:08 -08:00
|
|
|
assert_eq!(account_clone, account_expected);
|
|
|
|
}
|
2022-12-08 19:48:42 -08:00
|
|
|
|
2023-01-02 07:51:08 -08:00
|
|
|
// try a few combinations of rent collector rent epoch and collecting rent with and without filler accounts specified (but we aren't a filler)
|
|
|
|
let filler_account = solana_sdk::pubkey::new_rand();
|
|
|
|
|
|
|
|
for filler_accounts in [None, Some(&filler_account)] {
|
|
|
|
for (rent_epoch, rent_due_expected) in [(2, 2), (3, 5)] {
|
|
|
|
rent_collector.epoch = rent_epoch;
|
|
|
|
account.set_lamports(10);
|
|
|
|
account.set_rent_epoch(1);
|
|
|
|
let new_rent_epoch_expected = rent_collector.epoch + 1;
|
|
|
|
assert!(
|
|
|
|
matches!(
|
|
|
|
rent_collector.calculate_rent_result(&Pubkey::default(), &account, filler_accounts),
|
|
|
|
RentResult::CollectRent{ new_rent_epoch, rent_due} if new_rent_epoch == new_rent_epoch_expected && rent_due == rent_due_expected,
|
2022-12-08 19:48:42 -08:00
|
|
|
),
|
2023-01-02 07:51:08 -08:00
|
|
|
"{:?}",
|
|
|
|
rent_collector.calculate_rent_result(&Pubkey::default(), &account, None,)
|
2022-12-08 19:48:42 -08:00
|
|
|
);
|
2023-01-02 07:51:08 -08:00
|
|
|
|
|
|
|
{
|
|
|
|
let mut account_clone = account.clone();
|
|
|
|
assert_eq!(
|
|
|
|
rent_collector.collect_from_existing_account(
|
|
|
|
&Pubkey::default(),
|
|
|
|
&mut account_clone,
|
|
|
|
filler_accounts,
|
|
|
|
set_exempt_rent_epoch_max
|
|
|
|
),
|
|
|
|
CollectedInfo {
|
|
|
|
rent_amount: rent_due_expected,
|
|
|
|
account_data_len_reclaimed: 0
|
|
|
|
}
|
|
|
|
);
|
|
|
|
let mut account_expected = account.clone();
|
|
|
|
account_expected.set_lamports(account.lamports() - rent_due_expected);
|
|
|
|
account_expected.set_rent_epoch(new_rent_epoch_expected);
|
|
|
|
assert_eq!(account_clone, account_expected);
|
|
|
|
}
|
2022-12-08 19:48:42 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-02 07:51:08 -08:00
|
|
|
// enough lamports to make us exempt
|
|
|
|
account.set_lamports(1_000_000);
|
|
|
|
let result = rent_collector.calculate_rent_result(&Pubkey::default(), &account, None);
|
|
|
|
assert!(
|
|
|
|
matches!(result, RentResult::Exempt),
|
2023-01-05 10:06:00 -08:00
|
|
|
"{result:?}, set_exempt_rent_epoch_max: {set_exempt_rent_epoch_max}",
|
2022-12-08 19:48:42 -08:00
|
|
|
);
|
2023-01-02 07:51:08 -08:00
|
|
|
{
|
|
|
|
let mut account_clone = account.clone();
|
|
|
|
let mut account_expected = account.clone();
|
|
|
|
if set_exempt_rent_epoch_max {
|
|
|
|
account_expected.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
rent_collector.collect_from_existing_account(
|
|
|
|
&Pubkey::default(),
|
|
|
|
&mut account_clone,
|
|
|
|
None,
|
|
|
|
set_exempt_rent_epoch_max
|
|
|
|
),
|
|
|
|
CollectedInfo::default()
|
|
|
|
);
|
|
|
|
assert_eq!(account_clone, account_expected);
|
|
|
|
}
|
2022-12-08 19:48:42 -08:00
|
|
|
|
2023-01-02 07:51:08 -08:00
|
|
|
// enough lamports to make us exempt
|
|
|
|
// but, our rent_epoch is set in the future, so we can't know if we are exempt yet or not.
|
|
|
|
// We don't calculate rent amount vs data if the rent_epoch is already in the future.
|
|
|
|
account.set_rent_epoch(1_000_000);
|
|
|
|
assert!(matches!(
|
|
|
|
rent_collector.calculate_rent_result(&Pubkey::default(), &account, None,),
|
|
|
|
RentResult::NoRentCollectionNow,
|
|
|
|
));
|
|
|
|
{
|
|
|
|
let mut account_clone = account.clone();
|
|
|
|
assert_eq!(
|
|
|
|
rent_collector.collect_from_existing_account(
|
|
|
|
&Pubkey::default(),
|
|
|
|
&mut account_clone,
|
|
|
|
None,
|
|
|
|
set_exempt_rent_epoch_max
|
|
|
|
),
|
|
|
|
CollectedInfo::default()
|
|
|
|
);
|
|
|
|
assert_eq!(account_clone, account);
|
|
|
|
}
|
2022-12-08 19:48:42 -08:00
|
|
|
|
2023-01-02 07:51:08 -08:00
|
|
|
// filler accounts are exempt
|
|
|
|
account.set_rent_epoch(1);
|
|
|
|
account.set_lamports(10);
|
|
|
|
assert!(matches!(
|
|
|
|
rent_collector.calculate_rent_result(
|
2022-12-08 19:48:42 -08:00
|
|
|
&filler_account,
|
2023-01-02 07:51:08 -08:00
|
|
|
&account,
|
|
|
|
Some(&filler_account),
|
2022-12-08 19:48:42 -08:00
|
|
|
),
|
2023-01-02 07:51:08 -08:00
|
|
|
RentResult::Exempt,
|
|
|
|
));
|
|
|
|
{
|
|
|
|
let mut account_clone = account.clone();
|
|
|
|
let mut account_expected = account.clone();
|
|
|
|
if set_exempt_rent_epoch_max {
|
|
|
|
account_expected.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
rent_collector.collect_from_existing_account(
|
|
|
|
&filler_account,
|
|
|
|
&mut account_clone,
|
|
|
|
Some(&filler_account),
|
|
|
|
set_exempt_rent_epoch_max
|
|
|
|
),
|
|
|
|
CollectedInfo::default()
|
|
|
|
);
|
|
|
|
assert_eq!(account_clone, account_expected);
|
|
|
|
}
|
2022-12-08 19:48:42 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 08:04:32 -07:00
|
|
|
#[test]
|
|
|
|
fn test_collect_from_account_created_and_existing() {
|
2023-01-02 11:59:50 -08:00
|
|
|
for set_exempt_rent_epoch_max in [false, true] {
|
|
|
|
let old_lamports = 1000;
|
|
|
|
let old_epoch = 1;
|
|
|
|
let new_epoch = 2;
|
|
|
|
|
|
|
|
let (mut created_account, mut existing_account) = {
|
|
|
|
let account = AccountSharedData::from(Account {
|
|
|
|
lamports: old_lamports,
|
|
|
|
rent_epoch: old_epoch,
|
|
|
|
..Account::default()
|
|
|
|
});
|
|
|
|
|
|
|
|
(account.clone(), account)
|
|
|
|
};
|
2020-08-11 08:04:32 -07:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
let rent_collector = default_rent_collector_clone_with_epoch(new_epoch);
|
2020-08-11 08:04:32 -07:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
// collect rent on a newly-created account
|
|
|
|
let collected = rent_collector.collect_from_created_account(
|
|
|
|
&solana_sdk::pubkey::new_rand(),
|
|
|
|
&mut created_account,
|
|
|
|
set_exempt_rent_epoch_max,
|
|
|
|
);
|
|
|
|
assert!(created_account.lamports() < old_lamports);
|
|
|
|
assert_eq!(
|
|
|
|
created_account.lamports() + collected.rent_amount,
|
|
|
|
old_lamports
|
|
|
|
);
|
|
|
|
assert_ne!(created_account.rent_epoch(), old_epoch);
|
|
|
|
assert_eq!(collected.account_data_len_reclaimed, 0);
|
|
|
|
|
|
|
|
// collect rent on a already-existing account
|
|
|
|
let collected = rent_collector.collect_from_existing_account(
|
|
|
|
&solana_sdk::pubkey::new_rand(),
|
|
|
|
&mut existing_account,
|
|
|
|
None, // filler_account_suffix
|
|
|
|
set_exempt_rent_epoch_max,
|
|
|
|
);
|
|
|
|
assert!(existing_account.lamports() < old_lamports);
|
|
|
|
assert_eq!(
|
|
|
|
existing_account.lamports() + collected.rent_amount,
|
|
|
|
old_lamports
|
|
|
|
);
|
|
|
|
assert_ne!(existing_account.rent_epoch(), old_epoch);
|
|
|
|
assert_eq!(collected.account_data_len_reclaimed, 0);
|
2020-08-11 08:04:32 -07:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
// newly created account should be collected for less rent; thus more remaining balance
|
|
|
|
assert!(created_account.lamports() > existing_account.lamports());
|
|
|
|
assert_eq!(created_account.rent_epoch(), existing_account.rent_epoch());
|
|
|
|
}
|
2020-08-11 08:04:32 -07:00
|
|
|
}
|
2020-08-16 22:22:16 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_rent_exempt_temporal_escape() {
|
2023-01-02 14:32:09 -08:00
|
|
|
for set_exempt_rent_epoch_max in [false, true] {
|
|
|
|
for pass in 0..2 {
|
|
|
|
let mut account = AccountSharedData::default();
|
|
|
|
let epoch = 3;
|
|
|
|
let huge_lamports = 123_456_789_012;
|
|
|
|
let tiny_lamports = 789_012;
|
|
|
|
let pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
|
|
|
|
assert_eq!(account.rent_epoch(), 0);
|
|
|
|
|
|
|
|
// create a tested rent collector
|
|
|
|
let rent_collector = default_rent_collector_clone_with_epoch(epoch);
|
|
|
|
|
|
|
|
if pass == 0 {
|
|
|
|
account.set_lamports(huge_lamports);
|
|
|
|
// first mark account as being collected while being rent-exempt
|
|
|
|
let collected = rent_collector.collect_from_existing_account(
|
|
|
|
&pubkey,
|
|
|
|
&mut account,
|
|
|
|
None, // filler_account_suffix
|
|
|
|
set_exempt_rent_epoch_max,
|
|
|
|
);
|
|
|
|
assert_eq!(account.lamports(), huge_lamports);
|
|
|
|
assert_eq!(collected, CollectedInfo::default());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// decrease the balance not to be rent-exempt
|
|
|
|
// In a real validator, it is not legal to reduce an account's lamports such that the account becomes rent paying.
|
|
|
|
// So, pass == 0 above tests the case of rent that is exempt. pass == 1 tests the case where we are rent paying.
|
|
|
|
account.set_lamports(tiny_lamports);
|
|
|
|
|
|
|
|
// ... and trigger another rent collection on the same epoch and check that rent is working
|
|
|
|
let collected = rent_collector.collect_from_existing_account(
|
|
|
|
&pubkey,
|
|
|
|
&mut account,
|
|
|
|
None, // filler_account_suffix
|
|
|
|
set_exempt_rent_epoch_max,
|
|
|
|
);
|
|
|
|
assert_eq!(account.lamports(), tiny_lamports - collected.rent_amount);
|
|
|
|
assert_ne!(collected, CollectedInfo::default());
|
|
|
|
}
|
|
|
|
}
|
2020-08-16 22:22:16 -07:00
|
|
|
}
|
2021-05-17 08:30:48 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_rent_exempt_sysvar() {
|
2023-01-02 11:59:50 -08:00
|
|
|
for set_exempt_rent_epoch_max in [false, true] {
|
|
|
|
let tiny_lamports = 1;
|
|
|
|
let mut account = AccountSharedData::default();
|
|
|
|
account.set_owner(sysvar::id());
|
|
|
|
account.set_lamports(tiny_lamports);
|
2021-05-17 08:30:48 -07:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
let pubkey = solana_sdk::pubkey::new_rand();
|
2021-05-17 08:30:48 -07:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
assert_eq!(account.rent_epoch(), 0);
|
2021-05-17 08:30:48 -07:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
let epoch = 3;
|
|
|
|
let rent_collector = default_rent_collector_clone_with_epoch(epoch);
|
2021-05-17 08:30:48 -07:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
let collected = rent_collector.collect_from_existing_account(
|
|
|
|
&pubkey,
|
|
|
|
&mut account,
|
|
|
|
None, // filler_account_suffix
|
|
|
|
set_exempt_rent_epoch_max,
|
|
|
|
);
|
|
|
|
assert_eq!(account.lamports(), 0);
|
|
|
|
assert_eq!(collected.rent_amount, 1);
|
|
|
|
}
|
2022-01-11 15:20:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Ensure that when an account is "rent collected" away, its data len is returned.
|
|
|
|
#[test]
|
|
|
|
fn test_collect_cleans_up_account() {
|
2023-01-02 11:59:50 -08:00
|
|
|
for set_exempt_rent_epoch_max in [false, true] {
|
|
|
|
solana_logger::setup();
|
|
|
|
let account_lamports = 1; // must be *below* rent amount
|
|
|
|
let account_data_len = 567;
|
|
|
|
let account_rent_epoch = 11;
|
|
|
|
let mut account = AccountSharedData::from(Account {
|
|
|
|
lamports: account_lamports, // <-- must be below rent-exempt amount
|
|
|
|
data: vec![u8::default(); account_data_len],
|
|
|
|
rent_epoch: account_rent_epoch,
|
|
|
|
..Account::default()
|
|
|
|
});
|
|
|
|
let rent_collector = default_rent_collector_clone_with_epoch(account_rent_epoch + 1);
|
2022-01-11 15:20:28 -08:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
let collected = rent_collector.collect_from_existing_account(
|
|
|
|
&Pubkey::new_unique(),
|
|
|
|
&mut account,
|
|
|
|
None, // filler_account_suffix
|
|
|
|
set_exempt_rent_epoch_max,
|
|
|
|
);
|
2022-01-11 15:20:28 -08:00
|
|
|
|
2023-01-02 11:59:50 -08:00
|
|
|
assert_eq!(collected.rent_amount, account_lamports);
|
|
|
|
assert_eq!(
|
|
|
|
collected.account_data_len_reclaimed,
|
|
|
|
account_data_len as u64
|
|
|
|
);
|
|
|
|
assert_eq!(account, AccountSharedData::default());
|
|
|
|
}
|
2021-05-17 08:30:48 -07:00
|
|
|
}
|
2019-08-23 14:04:53 -07:00
|
|
|
}
|