Add epoch_reward sysvar (#32049)

* Add epoch_reward sysvar

* Apply suggestions from code review

Co-authored-by: Brooks <brooks@prumo.org>

* remove new from EpochRewards

* remove copy test

* Apply suggestions from code review

Co-authored-by: Jon Cinque <joncinque@pm.me>
Co-authored-by: Tyera <teulberg@gmail.com>

* reviews

---------

Co-authored-by: HaoranYi <haoran.yi@solana.com>
Co-authored-by: Brooks <brooks@prumo.org>
Co-authored-by: Jon Cinque <joncinque@pm.me>
Co-authored-by: Tyera <teulberg@gmail.com>
This commit is contained in:
HaoranYi 2023-06-14 08:41:26 -05:00 committed by GitHub
parent 3b0b0ba07d
commit ffe4c06a19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 245 additions and 0 deletions

View File

@ -22,6 +22,7 @@ The following sysvars support `get`:
- EpochSchedule
- Fees
- Rent
- EpochRewards
The second is to pass the sysvar to the program as an account by including its address as one of the accounts in the `Instruction` and then deserializing the data during execution. Access to sysvars accounts is
always _readonly_.
@ -140,3 +141,18 @@ and de-activations per epoch. It is updated at the start of every epoch.
- Address: `SysvarStakeHistory1111111111111111111111111`
- Layout:
[StakeHistory](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/stake_history/struct.StakeHistory.html)
## EpochRewards
The EpochRewards sysvar tracks the progress of epoch rewards distribution. The
sysvar is created in the first block of the epoch, and lasts for several blocks
while paying out the rewards. When all rewards have been distributed, the sysvar
is deleted. Unlike other sysvars, which almost always exist on-chain,
EpochRewards sysvar only exists during the reward period. Therefore, calling
`EpochRewards::get()` on blocks that are outside of the reward period will
return an error, i.e. `UnsupportedSysvar`. This can serve as a method for
determining whether epoch rewards distribution has finished.
- Address: `SysvarEpochRewards1111111111111111111111111`
- Layout:
[EpochRewards](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/epoch_rewards/struct.EpochRewards.html)

View File

@ -0,0 +1,75 @@
//! A type to hold data for the [`EpochRewards` sysvar][sv].
//!
//! [sv]: https://docs.solana.com/developing/runtime-facilities/sysvars#EpochRewards
//!
//! The sysvar ID is declared in [`sysvar::epoch_rewards`].
//!
//! [`sysvar::epoch_rewards`]: crate::sysvar::epoch_rewards
use std::ops::AddAssign;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone, Copy, AbiExample)]
pub struct EpochRewards {
/// total rewards for the current epoch, in lamports
pub total_rewards: u64,
/// distributed rewards for the current epoch, in lamports
pub distributed_rewards: u64,
/// distribution of all staking rewards for the current
/// epoch will be completed at this block height
pub distribution_complete_block_height: u64,
}
impl EpochRewards {
pub fn distribute(&mut self, amount: u64) {
assert!(self.distributed_rewards.saturating_add(amount) <= self.total_rewards);
self.distributed_rewards.add_assign(amount);
}
}
#[cfg(test)]
mod tests {
use super::*;
impl EpochRewards {
pub fn new(
total_rewards: u64,
distributed_rewards: u64,
distribution_complete_block_height: u64,
) -> Self {
Self {
total_rewards,
distributed_rewards,
distribution_complete_block_height,
}
}
}
#[test]
fn test_epoch_rewards_new() {
let epoch_rewards = EpochRewards::new(100, 0, 64);
assert_eq!(epoch_rewards.total_rewards, 100);
assert_eq!(epoch_rewards.distributed_rewards, 0);
assert_eq!(epoch_rewards.distribution_complete_block_height, 64);
}
#[test]
fn test_epoch_rewards_distribute() {
let mut epoch_rewards = EpochRewards::new(100, 0, 64);
epoch_rewards.distribute(100);
assert_eq!(epoch_rewards.total_rewards, 100);
assert_eq!(epoch_rewards.distributed_rewards, 100);
}
#[test]
#[should_panic(
expected = "assertion failed: self.distributed_rewards.saturating_add(amount) <= self.total_rewards"
)]
fn test_epoch_rewards_distribute_panic() {
let mut epoch_rewards = EpochRewards::new(100, 0, 64);
epoch_rewards.distribute(200);
}
}

View File

@ -486,6 +486,7 @@ pub mod decode_error;
pub mod ed25519_program;
pub mod entrypoint;
pub mod entrypoint_deprecated;
pub mod epoch_rewards;
pub mod epoch_schedule;
pub mod feature;
pub mod fee_calculator;

View File

@ -51,6 +51,10 @@ pub trait SyscallStubs: Sync + Send {
fn sol_get_rent_sysvar(&self, _var_addr: *mut u8) -> u64 {
UNSUPPORTED_SYSVAR
}
fn sol_get_epoch_rewards_sysvar(&self, _var_addr: *mut u8) -> u64 {
UNSUPPORTED_SYSVAR
}
/// # Safety
unsafe fn sol_memcpy(&self, dst: *mut u8, src: *const u8, n: usize) {
// cannot be overlapping
@ -197,6 +201,13 @@ pub(crate) fn sol_get_stack_height() -> u64 {
SYSCALL_STUBS.read().unwrap().sol_get_stack_height()
}
pub(crate) fn sol_get_epoch_rewards_sysvar(var_addr: *mut u8) -> u64 {
SYSCALL_STUBS
.read()
.unwrap()
.sol_get_epoch_rewards_sysvar(var_addr)
}
/// Check that two regions do not overlap.
///
/// Hidden to share with bpf_loader without being part of the API surface.

View File

@ -67,6 +67,7 @@ define_syscall!(fn sol_curve_multiscalar_mul(curve_id: u64, scalars_addr: *const
define_syscall!(fn sol_curve_pairing_map(curve_id: u64, point: *const u8, result: *mut u8) -> u64);
define_syscall!(fn sol_alt_bn128_group_op(group_op: u64, input: *const u8, input_size: u64, result: *mut u8) -> u64);
define_syscall!(fn sol_big_mod_exp(params: *const u8, result: *mut u8) -> u64);
define_syscall!(fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64);
#[cfg(target_feature = "static-syscalls")]
pub const fn sys_hash(name: &str) -> usize {

View File

@ -0,0 +1,139 @@
//! Epoch rewards for current epoch
//!
//! The _epoch rewards_ sysvar provides access to the [`EpochRewards`] type,
//! which tracks the progress of epoch rewards distribution. It includes the
//! - total rewards for the current epoch, in lamports
//! - rewards for the current epoch distributed so far, in lamports
//! - distribution completed block height, i.e. distribution of all staking rewards for the current
//! epoch will be completed at this block height
//!
//! [`EpochRewards`] implements [`Sysvar::get`] and can be loaded efficiently without
//! passing the sysvar account ID to the program.
//!
//! See also the Solana [documentation on the epoch rewards sysvar][sdoc].
//!
//! [sdoc]: https://docs.solana.com/developing/runtime-facilities/sysvars#epochrewards
//!
//! # Examples
//!
//! Accessing via on-chain program directly:
//!
//! ```no_run
//! # use solana_program::{
//! # account_info::{AccountInfo, next_account_info},
//! # entrypoint::ProgramResult,
//! # msg,
//! # program_error::ProgramError,
//! # pubkey::Pubkey,
//! # sysvar::epoch_rewards::{self, EpochRewards},
//! # sysvar::Sysvar,
//! # };
//! #
//! fn process_instruction(
//! program_id: &Pubkey,
//! accounts: &[AccountInfo],
//! instruction_data: &[u8],
//! ) -> ProgramResult {
//!
//! let epoch_rewards = EpochRewards::get()?;
//! msg!("epoch_rewards: {:#?}", epoch_rewards);
//!
//! Ok(())
//! }
//! #
//! # use solana_program::sysvar::SysvarId;
//! # let p = EpochRewards::id();
//! # let l = &mut 1120560;
//! # let d = &mut vec![0, 202, 154, 59, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0];
//! # let a = AccountInfo::new(&p, false, false, l, d, &p, false, 0);
//! # let accounts = &[a.clone(), a];
//! # process_instruction(
//! # &Pubkey::new_unique(),
//! # accounts,
//! # &[],
//! # )?;
//! # Ok::<(), ProgramError>(())
//! ```
//!
//! Accessing via on-chain program's account parameters:
//!
//! ```
//! # use solana_program::{
//! # account_info::{AccountInfo, next_account_info},
//! # entrypoint::ProgramResult,
//! # msg,
//! # pubkey::Pubkey,
//! # sysvar::epoch_rewards::{self, EpochRewards},
//! # sysvar::Sysvar,
//! # };
//! # use solana_program::program_error::ProgramError;
//! #
//! fn process_instruction(
//! program_id: &Pubkey,
//! accounts: &[AccountInfo],
//! instruction_data: &[u8],
//! ) -> ProgramResult {
//! let account_info_iter = &mut accounts.iter();
//! let epoch_rewards_account_info = next_account_info(account_info_iter)?;
//!
//! assert!(epoch_rewards::check_id(epoch_rewards_account_info.key));
//!
//! let epoch_rewards = EpochRewards::from_account_info(epoch_rewards_account_info)?;
//! msg!("epoch_rewards: {:#?}", epoch_rewards);
//!
//! Ok(())
//! }
//! #
//! # use solana_program::sysvar::SysvarId;
//! # let p = EpochRewards::id();
//! # let l = &mut 1120560;
//! # let d = &mut vec![0, 202, 154, 59, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0];
//! # let a = AccountInfo::new(&p, false, false, l, d, &p, false, 0);
//! # let accounts = &[a.clone(), a];
//! # process_instruction(
//! # &Pubkey::new_unique(),
//! # accounts,
//! # &[],
//! # )?;
//! # Ok::<(), ProgramError>(())
//! ```
//!
//! Accessing via the RPC client:
//!
//! ```
//! # use solana_program::example_mocks::solana_sdk;
//! # use solana_program::example_mocks::solana_rpc_client;
//! # use solana_sdk::account::Account;
//! # use solana_rpc_client::rpc_client::RpcClient;
//! # use solana_sdk::sysvar::epoch_rewards::{self, EpochRewards};
//! # use anyhow::Result;
//! #
//! fn print_sysvar_epoch_rewards(client: &RpcClient) -> Result<()> {
//! # client.set_get_account_response(epoch_rewards::ID, Account {
//! # lamports: 1120560,
//! # data: vec![0, 202, 154, 59, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0],
//! # owner: solana_sdk::system_program::ID,
//! # executable: false,
//! # rent_epoch: 307,
//! # });
//! #
//! let epoch_rewards = client.get_account(&epoch_rewards::ID)?;
//! let data: EpochRewards = bincode::deserialize(&epoch_rewards.data)?;
//!
//! Ok(())
//! }
//! #
//! # let client = RpcClient::new(String::new());
//! # print_sysvar_epoch_rewards(&client)?;
//! #
//! # Ok::<(), anyhow::Error>(())
//! ```
pub use crate::epoch_rewards::EpochRewards;
use crate::{impl_sysvar_get, program_error::ProgramError, sysvar::Sysvar};
crate::declare_sysvar_id!("SysvarEpochRewards1111111111111111111111111", EpochRewards);
impl Sysvar for EpochRewards {
impl_sysvar_get!(sol_get_epoch_rewards_sysvar);
}

View File

@ -87,6 +87,7 @@ use {
};
pub mod clock;
pub mod epoch_rewards;
pub mod epoch_schedule;
pub mod fees;
pub mod instructions;
@ -111,6 +112,7 @@ lazy_static! {
slot_history::id(),
stake_history::id(),
instructions::id(),
epoch_rewards::id(),
];
}