use {
super::Bank,
crate::inflation_rewards::points::PointValue,
log::info,
solana_account::{
ReadableAccount, WritableAccount, create_account_shared_data_with_fields as create_account,
from_account,
},
solana_clock::INITIAL_RENT_EPOCH,
solana_sysvar::{self as sysvar, epoch_rewards::EpochRewards},
};
impl Bank {
fn log_epoch_rewards_sysvar(&self, prefix: &str) {
if let Some(account) = self.get_account(&sysvar::epoch_rewards::id()) {
let epoch_rewards: EpochRewards = from_account(&account).unwrap();
info!("{prefix} epoch_rewards sysvar: {epoch_rewards:?}");
} else {
info!("{prefix} epoch_rewards sysvar: none");
}
}
pub(in crate::bank) fn create_epoch_rewards_sysvar(
&self,
distributed_rewards: u64,
distribution_starting_block_height: u64,
num_partitions: u64,
point_value: &PointValue,
block_rewards: u64,
) {
assert!(point_value.rewards >= distributed_rewards);
let parent_blockhash = self.last_blockhash();
let epoch_rewards = EpochRewards {
distribution_starting_block_height,
num_partitions,
parent_blockhash,
total_points: point_value.points,
total_rewards: point_value.rewards,
distributed_rewards,
active: true,
};
self.update_sysvar_account(&sysvar::epoch_rewards::id(), |account| {
create_account(
&epoch_rewards,
self.inherit_specially_retained_account_fields(account),
)
});
let mut account = self
.get_account_with_fixed_root(&sysvar::epoch_rewards::id())
.expect("created sysvar account exists");
account
.checked_add_lamports(block_rewards)
.expect("block rewards and sysvar account rent exemption must fit in a u64");
self.store_account(&sysvar::epoch_rewards::id(), &account);
self.log_epoch_rewards_sysvar("create");
}
pub(in crate::bank::partitioned_epoch_rewards) fn update_epoch_rewards_sysvar(
&self,
distributed: u64,
debit_block_reward_lamports: u64,
) {
let mut epoch_rewards = self.get_epoch_rewards_sysvar();
assert!(epoch_rewards.active);
epoch_rewards.distribute(distributed);
self.update_sysvar_account(&sysvar::epoch_rewards::id(), |account| {
create_account(
&epoch_rewards,
self.inherit_specially_retained_account_fields(account),
)
});
let mut account = self
.get_account_with_fixed_root(&sysvar::epoch_rewards::id())
.expect("created sysvar account exists");
account
.checked_sub_lamports(debit_block_reward_lamports)
.expect("epoch reward sysvar has enough lamports for distribution");
assert!(
account.lamports() >= self.get_minimum_balance_for_rent_exemption(account.data().len()),
"Sysvar account must have enough for rent exemption after debiting block rewards"
);
self.store_account(&sysvar::epoch_rewards::id(), &account);
self.log_epoch_rewards_sysvar("update");
}
pub(in crate::bank::partitioned_epoch_rewards) fn set_epoch_rewards_sysvar_to_inactive(&self) {
const RENT_UNADJUSTED_INITIAL_BALANCE: u64 = 1;
let mut epoch_rewards = self.get_epoch_rewards_sysvar();
assert!(epoch_rewards.total_rewards >= epoch_rewards.distributed_rewards);
epoch_rewards.active = false;
self.update_sysvar_account(&sysvar::epoch_rewards::id(), |account| {
create_account(
&epoch_rewards,
(
RENT_UNADJUSTED_INITIAL_BALANCE,
account
.as_ref()
.map(|a| a.rent_epoch())
.unwrap_or(INITIAL_RENT_EPOCH),
),
)
});
self.log_epoch_rewards_sysvar("set_inactive");
}
pub(in crate::bank::partitioned_epoch_rewards) fn get_epoch_rewards_sysvar(
&self,
) -> EpochRewards {
from_account(
&self
.get_account(&sysvar::epoch_rewards::id())
.unwrap_or_default(),
)
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::bank::{SlotLeader, tests::create_genesis_config},
solana_epoch_schedule::EpochSchedule,
solana_native_token::LAMPORTS_PER_SOL,
};
#[test]
fn test_epoch_rewards_sysvar() {
let (mut genesis_config, _mint_keypair) =
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
let (bank, bank_forks) =
Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
let total_rewards = 1_000_000_000;
let num_partitions = 2; let total_points = (total_rewards * 42) as u128; let point_value = PointValue {
rewards: total_rewards,
points: total_points,
};
let first_block_rewards = 5_000_000_000;
let expected_epoch_rewards = EpochRewards {
distribution_starting_block_height: 42,
num_partitions,
parent_blockhash: bank.last_blockhash(),
total_points,
total_rewards,
distributed_rewards: 10,
active: true,
};
let epoch_rewards = bank.get_epoch_rewards_sysvar();
assert_eq!(epoch_rewards, EpochRewards::default());
let pre_capitalization = bank.capitalization();
bank.create_epoch_rewards_sysvar(10, 42, num_partitions, &point_value, first_block_rewards);
let post_capitalization = bank.capitalization();
let account = bank.get_account(&sysvar::epoch_rewards::id()).unwrap();
let rent_exempt_reserve = bank.get_minimum_balance_for_rent_exemption(account.data().len());
let expected_balance = rent_exempt_reserve + first_block_rewards;
assert_eq!(account.lamports(), expected_balance);
assert_eq!(
post_capitalization,
pre_capitalization + rent_exempt_reserve
);
let epoch_rewards: EpochRewards = from_account(&account).unwrap();
assert_eq!(epoch_rewards, expected_epoch_rewards);
bank.set_epoch_rewards_sysvar_to_inactive();
assert_eq!(
post_capitalization - first_block_rewards,
bank.capitalization()
);
let parent_blockhash = bank.last_blockhash();
let parent_slot = bank.slot();
let bank = Bank::new_from_parent_with_bank_forks(
bank_forks.as_ref(),
bank,
SlotLeader::default(),
parent_slot + 1,
);
let second_block_rewards = 500_000_000;
let pre_capitalization = bank.capitalization();
bank.create_epoch_rewards_sysvar(
10,
42,
num_partitions,
&point_value,
second_block_rewards,
);
let post_capitalization = bank.capitalization();
assert_eq!(post_capitalization, pre_capitalization);
let expected_epoch_rewards = EpochRewards {
distribution_starting_block_height: 42,
num_partitions,
parent_blockhash,
total_points,
total_rewards,
distributed_rewards: 10,
active: true,
};
let account = bank.get_account(&sysvar::epoch_rewards::id()).unwrap();
let expected_balance = bank.get_minimum_balance_for_rent_exemption(account.data().len())
+ second_block_rewards;
assert_eq!(account.lamports(), expected_balance);
let epoch_rewards = bank.get_epoch_rewards_sysvar();
assert_eq!(epoch_rewards, expected_epoch_rewards);
let block_reward_distribution = 1_000_000;
bank.update_epoch_rewards_sysvar(10, block_reward_distribution);
let account = bank.get_account(&sysvar::epoch_rewards::id()).unwrap();
assert_eq!(
account.lamports(),
expected_balance - block_reward_distribution
);
assert_eq!(post_capitalization, bank.capitalization());
let epoch_rewards: EpochRewards = from_account(&account).unwrap();
let expected_epoch_rewards = EpochRewards {
distribution_starting_block_height: 42,
num_partitions,
parent_blockhash,
total_points,
total_rewards,
distributed_rewards: 20,
active: true,
};
assert_eq!(epoch_rewards, expected_epoch_rewards);
bank.set_epoch_rewards_sysvar_to_inactive();
assert_eq!(
bank.capitalization(),
post_capitalization + block_reward_distribution - second_block_rewards
);
let account = bank.get_account(&sysvar::epoch_rewards::id()).unwrap();
let epoch_rewards: EpochRewards = from_account(&account).unwrap();
let expected_epoch_rewards = EpochRewards {
distribution_starting_block_height: 42,
num_partitions,
parent_blockhash,
total_points,
total_rewards,
distributed_rewards: 20,
active: false,
};
assert_eq!(epoch_rewards, expected_epoch_rewards);
assert_eq!(account.lamports(), rent_exempt_reserve);
}
}