diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index b71e60ca7d4161..3f3157c393cc24 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -38,8 +38,9 @@ use { disable_deploy_of_alloc_free_syscall, disable_fees_sysvar, enable_alt_bn128_compression_syscall, enable_alt_bn128_syscall, enable_big_mod_exp_syscall, enable_partitioned_epoch_reward, enable_poseidon_syscall, - error_on_syscall_bpf_function_hash_collisions, last_restart_slot_sysvar, - reject_callx_r10, remaining_compute_units_syscall_enabled, switch_to_new_elf_parser, + enable_syscall_get_epoch_stake, error_on_syscall_bpf_function_hash_collisions, + last_restart_slot_sysvar, reject_callx_r10, remaining_compute_units_syscall_enabled, + switch_to_new_elf_parser, }, hash::{Hash, Hasher}, instruction::{AccountMeta, InstructionError, ProcessedSiblingInstruction}, @@ -278,6 +279,8 @@ pub fn create_program_runtime_environment_v1<'a>( let enable_poseidon_syscall = feature_set.is_active(&enable_poseidon_syscall::id()); let remaining_compute_units_syscall_enabled = feature_set.is_active(&remaining_compute_units_syscall_enabled::id()); + let enable_syscall_get_epoch_stake = + feature_set.is_active(&enable_syscall_get_epoch_stake::id()); // !!! ATTENTION !!! // When adding new features for RBPF here, // also add them to `Bank::apply_builtin_program_feature_transitions()`. @@ -464,6 +467,14 @@ pub fn create_program_runtime_environment_v1<'a>( SyscallAltBn128Compression::vm, )?; + // Get Epoch Stake + register_feature_gated_function!( + result, + enable_syscall_get_epoch_stake, + *b"sol_syscall_get_epoch_stake", + SyscallGetEpochStake::vm, + )?; + // Log data result.register_function_hashed(*b"sol_log_data", SyscallLogData::vm)?; @@ -1997,6 +2008,59 @@ declare_builtin_function!( } ); +declare_builtin_function!( + // Get Epoch Stake Syscall + SyscallGetEpochStake, + fn rust( + invoke_context: &mut InvokeContext, + vote_address: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &mut MemoryMapping, + ) -> Result { + // Compute units, as specified by SIMD-0133. + // cu = syscall_base_cost + // + floor(32/cpi_bytes_per_unit) + // + mem_op_base_cost + let compute_budget = invoke_context.get_compute_budget(); + let compute_units = compute_budget + .syscall_base_cost + .saturating_add( + 32u64 + .checked_div(compute_budget.cpi_bytes_per_unit) + .unwrap_or(u64::MAX), + ) + .saturating_add(compute_budget.mem_op_base_cost); + + consume_compute_meter(invoke_context, compute_units)?; + + // Control flow, as specified by SIMD-0133. + // * The syscall aborts the virtual machine if not all bytes in VM + // memory range `[vote_addr, vote_addr + 32)` are readable. + // * Otherwise, the syscall returns a `u64` integer representing the + // total active stake delegated to the vote account at the provided + // address. + // * If the provided vote address corresponds to an account that is + // not a vote account or does not exist, the syscall will return + // `0` for active stake. + let check_aligned = invoke_context.get_check_aligned(); + let vote_address = translate_type::(memory_mapping, vote_address, check_aligned)?; + + Ok( + if let Some(vote_accounts) = invoke_context.get_vote_accounts() { + vote_accounts + .get(vote_address) + .map(|(stake, _)| *stake) + .unwrap_or(0) + } else { + 0 + }, + ) + } +); + #[cfg(test)] #[allow(clippy::arithmetic_side_effects)] #[allow(clippy::indexing_slicing)] diff --git a/sdk/program/src/epoch_stake.rs b/sdk/program/src/epoch_stake.rs new file mode 100644 index 00000000000000..868230821c6944 --- /dev/null +++ b/sdk/program/src/epoch_stake.rs @@ -0,0 +1,17 @@ +use crate::pubkey::Pubkey; + +/// Get the current epoch stake for a given vote address. +/// +/// If the provided vote address corresponds to an account that is not a vote +/// account or does not exist, returns `0` for active stake. +pub fn get_epoch_stake(vote_address: &Pubkey) -> u64 { + let vote_address = vote_address as *const _ as *const u8; + + #[cfg(target_os = "solana")] + let result = unsafe { crate::syscalls::sol_syscall_get_epoch_stake(vote_address) }; + + #[cfg(not(target_os = "solana"))] + let result = crate::program_stubs::sol_syscall_get_epoch_stake(vote_address); + + result +} diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 1f464337028287..b5a3840f84de28 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -491,6 +491,7 @@ pub mod entrypoint; pub mod entrypoint_deprecated; pub mod epoch_rewards; pub mod epoch_schedule; +pub mod epoch_stake; pub mod feature; pub mod fee_calculator; pub mod hash; diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index cf890659fa68a1..5fd117932f65be 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -61,6 +61,9 @@ pub trait SyscallStubs: Sync + Send { fn sol_get_last_restart_slot(&self, _var_addr: *mut u8) -> u64 { UNSUPPORTED_SYSVAR } + fn sol_syscall_get_epoch_stake(&self, _vote_address: *const u8) -> u64 { + UNSUPPORTED_SYSVAR + } /// # Safety unsafe fn sol_memcpy(&self, dst: *mut u8, src: *const u8, n: usize) { // cannot be overlapping @@ -171,6 +174,13 @@ pub(crate) fn sol_get_last_restart_slot(var_addr: *mut u8) -> u64 { .sol_get_last_restart_slot(var_addr) } +pub(crate) fn sol_syscall_get_epoch_stake(vote_address: *const u8) -> u64 { + SYSCALL_STUBS + .read() + .unwrap() + .sol_syscall_get_epoch_stake(vote_address) +} + pub(crate) fn sol_memcpy(dst: *mut u8, src: *const u8, n: usize) { unsafe { SYSCALL_STUBS.read().unwrap().sol_memcpy(dst, src, n); diff --git a/sdk/program/src/syscalls/definitions.rs b/sdk/program/src/syscalls/definitions.rs index b2dedceba953a0..b58887501e8fc2 100644 --- a/sdk/program/src/syscalls/definitions.rs +++ b/sdk/program/src/syscalls/definitions.rs @@ -72,6 +72,7 @@ define_syscall!(fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64); define_syscall!(fn sol_poseidon(parameters: u64, endianness: u64, vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64); define_syscall!(fn sol_remaining_compute_units() -> u64); define_syscall!(fn sol_alt_bn128_compression(op: u64, input: *const u8, input_size: u64, result: *mut u8) -> u64); +define_syscall!(fn sol_syscall_get_epoch_stake(vote_address: *const u8) -> u64); #[cfg(target_feature = "static-syscalls")] pub const fn sys_hash(name: &str) -> usize {