2019-08-23 14:04:53 -07:00
|
|
|
//! calculate and collect rent from Accounts
|
2019-11-18 12:01:27 -08:00
|
|
|
use solana_sdk::{
|
2021-04-28 06:52:20 -07:00
|
|
|
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
2021-03-09 14:31:33 -08:00
|
|
|
clock::Epoch,
|
|
|
|
epoch_schedule::EpochSchedule,
|
|
|
|
genesis_config::GenesisConfig,
|
|
|
|
incinerator,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
rent::Rent,
|
|
|
|
sysvar,
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-23 14:04:53 -07:00
|
|
|
impl RentCollector {
|
|
|
|
pub fn new(
|
|
|
|
epoch: Epoch,
|
|
|
|
epoch_schedule: &EpochSchedule,
|
|
|
|
slots_per_year: f64,
|
2019-10-30 16:25:12 -07:00
|
|
|
rent: &Rent,
|
2019-08-23 14:04:53 -07:00
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
epoch,
|
|
|
|
epoch_schedule: *epoch_schedule,
|
|
|
|
slots_per_year,
|
2019-10-30 16:25:12 -07:00
|
|
|
rent: *rent,
|
2019-08-23 14:04:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-21 06:19:25 -07:00
|
|
|
pub 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
|
|
|
|
pub fn should_collect_rent(
|
2021-11-03 12:44:52 -07:00
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
account: &impl ReadableAccount,
|
|
|
|
rent_for_sysvars: bool,
|
|
|
|
) -> bool {
|
2021-11-04 04:28:41 -07:00
|
|
|
!(account.executable() // executable accounts must be rent-exempt balance
|
2021-11-03 12:44:52 -07:00
|
|
|
|| (!rent_for_sysvars && sysvar::check_id(account.owner()))
|
2021-11-04 04:28:41 -07:00
|
|
|
|| *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)
|
2021-11-03 12:44:52 -07:00
|
|
|
pub fn get_rent_due(&self, account: &impl ReadableAccount) -> (u64, bool) {
|
|
|
|
let slots_elapsed: u64 = (account.rent_epoch()..=self.epoch)
|
|
|
|
.map(|epoch| self.epoch_schedule.get_slots_in_epoch(epoch + 1))
|
|
|
|
.sum();
|
|
|
|
|
|
|
|
// 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
|
|
|
|
};
|
|
|
|
|
|
|
|
self.rent
|
|
|
|
.due(account.lamports(), account.data().len(), years_elapsed)
|
|
|
|
}
|
|
|
|
|
2019-08-23 14:04:53 -07:00
|
|
|
// updates this account's lamports and status and returns
|
|
|
|
// the account rent collected, if any
|
2021-09-08 17:25:20 -07:00
|
|
|
// 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"]
|
2021-03-09 13:06:07 -08:00
|
|
|
pub fn collect_from_existing_account(
|
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
account: &mut AccountSharedData,
|
2021-05-17 08:30:48 -07:00
|
|
|
rent_for_sysvars: bool,
|
2021-10-11 10:46:27 -07:00
|
|
|
filler_account_suffix: Option<&Pubkey>,
|
2021-03-09 13:06:07 -08:00
|
|
|
) -> u64 {
|
2021-11-04 04:28:41 -07:00
|
|
|
if !self.should_collect_rent(address, account, rent_for_sysvars)
|
2021-11-04 09:17:19 -07:00
|
|
|
|| account.rent_epoch() > self.epoch
|
2021-11-03 12:44:52 -07:00
|
|
|
|| crate::accounts_db::AccountsDb::is_filler_account_helper(
|
|
|
|
address,
|
|
|
|
filler_account_suffix,
|
|
|
|
)
|
2020-03-27 08:28:18 -07:00
|
|
|
{
|
2019-11-13 21:26:49 -08:00
|
|
|
0
|
2019-08-23 14:04:53 -07:00
|
|
|
} else {
|
2021-11-03 12:44:52 -07:00
|
|
|
let (rent_due, exempt) = self.get_rent_due(account);
|
2019-08-23 14:04:53 -07:00
|
|
|
|
|
|
|
if exempt || rent_due != 0 {
|
2021-04-22 13:04:55 -07:00
|
|
|
if account.lamports() > rent_due {
|
2021-04-28 06:52:20 -07:00
|
|
|
account.set_rent_epoch(
|
|
|
|
self.epoch
|
|
|
|
+ if exempt {
|
|
|
|
// Rent isn't collected for the next epoch
|
|
|
|
// Make sure to check exempt status later in current epoch again
|
|
|
|
0
|
|
|
|
} else {
|
|
|
|
// Rent is collected for next epoch
|
|
|
|
1
|
|
|
|
},
|
|
|
|
);
|
2021-04-29 13:04:28 -07:00
|
|
|
let _ = account.checked_sub_lamports(rent_due); // will not fail. We check above.
|
2019-11-13 21:26:49 -08:00
|
|
|
rent_due
|
2019-08-23 14:04:53 -07:00
|
|
|
} else {
|
2021-04-29 08:44:46 -07:00
|
|
|
let rent_charged = account.lamports();
|
2021-03-09 13:06:07 -08:00
|
|
|
*account = AccountSharedData::default();
|
2019-11-13 21:26:49 -08:00
|
|
|
rent_charged
|
2019-08-23 14:04:53 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// maybe collect rent later, leave account alone
|
2019-11-13 21:26:49 -08:00
|
|
|
0
|
2019-08-23 14:04:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-11 08:04:32 -07:00
|
|
|
|
|
|
|
#[must_use = "add to Bank::collected_rent"]
|
2021-03-09 13:06:07 -08:00
|
|
|
pub fn collect_from_created_account(
|
|
|
|
&self,
|
|
|
|
address: &Pubkey,
|
|
|
|
account: &mut AccountSharedData,
|
2021-05-17 08:30:48 -07:00
|
|
|
rent_for_sysvars: bool,
|
2021-03-09 13:06:07 -08:00
|
|
|
) -> u64 {
|
2020-08-11 08:04:32 -07:00
|
|
|
// initialize rent_epoch as created at this epoch
|
2021-04-28 06:52:20 -07:00
|
|
|
account.set_rent_epoch(self.epoch);
|
2021-10-11 10:46:27 -07:00
|
|
|
self.collect_from_existing_account(address, account, rent_for_sysvars, None)
|
2020-08-11 08:04:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2021-03-11 16:09:04 -08:00
|
|
|
use solana_sdk::account::Account;
|
2020-08-11 08:04:32 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_collect_from_account_created_and_existing() {
|
|
|
|
let old_lamports = 1000;
|
|
|
|
let old_epoch = 1;
|
|
|
|
let new_epoch = 3;
|
|
|
|
|
|
|
|
let (mut created_account, mut existing_account) = {
|
2021-03-11 16:09:04 -08:00
|
|
|
let account = AccountSharedData::from(Account {
|
2020-12-13 17:26:34 -08:00
|
|
|
lamports: old_lamports,
|
|
|
|
rent_epoch: old_epoch,
|
2021-03-11 16:09:04 -08:00
|
|
|
..Account::default()
|
|
|
|
});
|
2020-08-11 08:04:32 -07:00
|
|
|
|
|
|
|
(account.clone(), account)
|
|
|
|
};
|
|
|
|
|
2020-10-21 06:19:25 -07:00
|
|
|
let rent_collector = RentCollector::default().clone_with_epoch(new_epoch);
|
2020-08-11 08:04:32 -07:00
|
|
|
|
2020-08-16 22:22:16 -07:00
|
|
|
// collect rent on a newly-created account
|
2021-05-17 08:30:48 -07:00
|
|
|
let collected = rent_collector.collect_from_created_account(
|
|
|
|
&solana_sdk::pubkey::new_rand(),
|
|
|
|
&mut created_account,
|
|
|
|
true,
|
|
|
|
);
|
2021-05-03 08:45:54 -07:00
|
|
|
assert!(created_account.lamports() < old_lamports);
|
|
|
|
assert_eq!(created_account.lamports() + collected, old_lamports);
|
2021-04-27 11:51:13 -07:00
|
|
|
assert_ne!(created_account.rent_epoch(), old_epoch);
|
2020-08-11 08:04:32 -07:00
|
|
|
|
2020-08-16 22:22:16 -07:00
|
|
|
// collect rent on a already-existing account
|
2021-05-17 08:30:48 -07:00
|
|
|
let collected = rent_collector.collect_from_existing_account(
|
|
|
|
&solana_sdk::pubkey::new_rand(),
|
|
|
|
&mut existing_account,
|
|
|
|
true,
|
2021-10-11 10:46:27 -07:00
|
|
|
None,
|
2021-05-17 08:30:48 -07:00
|
|
|
);
|
2021-05-03 08:45:54 -07:00
|
|
|
assert!(existing_account.lamports() < old_lamports);
|
|
|
|
assert_eq!(existing_account.lamports() + collected, old_lamports);
|
2021-04-27 11:51:13 -07:00
|
|
|
assert_ne!(existing_account.rent_epoch(), old_epoch);
|
2020-08-11 08:04:32 -07:00
|
|
|
|
2020-08-16 22:22:16 -07:00
|
|
|
// newly created account should be collected for less rent; thus more remaining balance
|
2021-05-03 08:45:54 -07:00
|
|
|
assert!(created_account.lamports() > existing_account.lamports());
|
2021-04-27 11:51:13 -07:00
|
|
|
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() {
|
2021-03-09 13:06:07 -08:00
|
|
|
let mut account = AccountSharedData::default();
|
2020-08-16 22:22:16 -07:00
|
|
|
let epoch = 3;
|
|
|
|
let huge_lamports = 123_456_789_012;
|
|
|
|
let tiny_lamports = 789_012;
|
|
|
|
let mut collected;
|
2020-10-19 12:12:08 -07:00
|
|
|
let pubkey = solana_sdk::pubkey::new_rand();
|
2020-08-16 22:22:16 -07:00
|
|
|
|
2021-04-30 11:20:54 -07:00
|
|
|
account.set_lamports(huge_lamports);
|
2021-04-27 11:51:13 -07:00
|
|
|
assert_eq!(account.rent_epoch(), 0);
|
2020-08-16 22:22:16 -07:00
|
|
|
|
|
|
|
// create a tested rent collector
|
2020-10-21 06:19:25 -07:00
|
|
|
let rent_collector = RentCollector::default().clone_with_epoch(epoch);
|
2020-08-16 22:22:16 -07:00
|
|
|
|
|
|
|
// first mark account as being collected while being rent-exempt
|
2021-10-11 10:46:27 -07:00
|
|
|
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true, None);
|
2021-05-03 08:45:54 -07:00
|
|
|
assert_eq!(account.lamports(), huge_lamports);
|
2020-08-16 22:22:16 -07:00
|
|
|
assert_eq!(collected, 0);
|
|
|
|
|
|
|
|
// decrease the balance not to be rent-exempt
|
2021-04-30 11:20:54 -07:00
|
|
|
account.set_lamports(tiny_lamports);
|
2020-08-16 22:22:16 -07:00
|
|
|
|
|
|
|
// ... and trigger another rent collection on the same epoch and check that rent is working
|
2021-10-11 10:46:27 -07:00
|
|
|
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true, None);
|
2021-05-03 08:45:54 -07:00
|
|
|
assert_eq!(account.lamports(), tiny_lamports - collected);
|
2020-08-16 22:22:16 -07:00
|
|
|
assert_ne!(collected, 0);
|
|
|
|
}
|
2021-05-17 08:30:48 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_rent_exempt_sysvar() {
|
|
|
|
let tiny_lamports = 1;
|
|
|
|
let mut account = AccountSharedData::default();
|
|
|
|
account.set_owner(sysvar::id());
|
|
|
|
account.set_lamports(tiny_lamports);
|
|
|
|
|
|
|
|
let pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
|
|
|
|
assert_eq!(account.rent_epoch(), 0);
|
|
|
|
|
|
|
|
let epoch = 3;
|
|
|
|
let rent_collector = RentCollector::default().clone_with_epoch(epoch);
|
|
|
|
|
|
|
|
// old behavior: sysvars are special-cased
|
2021-10-11 10:46:27 -07:00
|
|
|
let collected =
|
|
|
|
rent_collector.collect_from_existing_account(&pubkey, &mut account, false, None);
|
2021-05-17 08:30:48 -07:00
|
|
|
assert_eq!(account.lamports(), tiny_lamports);
|
|
|
|
assert_eq!(collected, 0);
|
|
|
|
|
|
|
|
// new behavior: sysvars are NOT special-cased
|
2021-10-11 10:46:27 -07:00
|
|
|
let collected =
|
|
|
|
rent_collector.collect_from_existing_account(&pubkey, &mut account, true, None);
|
2021-05-17 08:30:48 -07:00
|
|
|
assert_eq!(account.lamports(), 0);
|
|
|
|
assert_eq!(collected, 1);
|
|
|
|
}
|
2019-08-23 14:04:53 -07:00
|
|
|
}
|