diff --git a/define-syscall/src/definitions.rs b/define-syscall/src/definitions.rs index 76e91df5c..da614c6e0 100644 --- a/define-syscall/src/definitions.rs +++ b/define-syscall/src/definitions.rs @@ -49,12 +49,12 @@ define_syscall!(fn sol_get_return_data(data: *mut u8, length: u64, program_id: * // - `is_writable` (`u8`): `true` if the instruction requires the account to be writable define_syscall!(fn sol_get_processed_sibling_instruction(index: u64, meta: *mut u8, program_id: *mut u8, data: *mut u8, accounts: *mut u8) -> u64); -// these are to be deprecated once they are superceded by sol_get_sysvar -define_syscall!(fn sol_get_clock_sysvar(addr: *mut u8) -> u64); -define_syscall!(fn sol_get_epoch_schedule_sysvar(addr: *mut u8) -> u64); -define_syscall!(fn sol_get_rent_sysvar(addr: *mut u8) -> u64); -define_syscall!(fn sol_get_last_restart_slot(addr: *mut u8) -> u64); -define_syscall!(fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64); +// these are deprecated - use sol_get_sysvar instead +define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `Clock` sysvar address instead")] fn sol_get_clock_sysvar(addr: *mut u8) -> u64); +define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `EpochSchedule` sysvar address instead")] fn sol_get_epoch_schedule_sysvar(addr: *mut u8) -> u64); +define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `Rent` sysvar address instead")] fn sol_get_rent_sysvar(addr: *mut u8) -> u64); +define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `LastRestartSlot` sysvar address instead")] fn sol_get_last_restart_slot(addr: *mut u8) -> u64); +define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `EpochRewards` sysvar address instead")] fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64); // this cannot go through sol_get_sysvar but can be removed once no longer in use define_syscall!(fn sol_get_fees_sysvar(addr: *mut u8) -> u64); diff --git a/define-syscall/src/lib.rs b/define-syscall/src/lib.rs index 47f8e5ea6..3d8e956c9 100644 --- a/define-syscall/src/lib.rs +++ b/define-syscall/src/lib.rs @@ -9,7 +9,8 @@ pub mod definitions; ))] #[macro_export] macro_rules! define_syscall { - (fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => { + ($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => { + $(#[$attr])* #[inline] pub unsafe fn $name($($arg: $typ),*) -> $ret { // this enum is used to force the hash to be computed in a const context @@ -23,8 +24,8 @@ macro_rules! define_syscall { } }; - (fn $name:ident($($arg:ident: $typ:ty),*)) => { - define_syscall!(fn $name($($arg: $typ),*) -> ()); + ($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*)) => { + define_syscall!($(#[$attr])* fn $name($($arg: $typ),*) -> ()); } } @@ -34,13 +35,14 @@ macro_rules! define_syscall { )))] #[macro_export] macro_rules! define_syscall { - (fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => { + ($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => { extern "C" { + $(#[$attr])* pub fn $name($($arg: $typ),*) -> $ret; } }; - (fn $name:ident($($arg:ident: $typ:ty),*)) => { - define_syscall!(fn $name($($arg: $typ),*) -> ()); + ($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*)) => { + define_syscall!($(#[$attr])* fn $name($($arg: $typ),*) -> ()); } } diff --git a/epoch-rewards/src/lib.rs b/epoch-rewards/src/lib.rs index 5d581891a..c7ea303c8 100644 --- a/epoch-rewards/src/lib.rs +++ b/epoch-rewards/src/lib.rs @@ -51,6 +51,9 @@ pub struct EpochRewards { /// Whether the rewards period (including calculation and distribution) is /// active + /// + /// SAFETY: upstream invariant: the sysvar data is created exclusively + /// by the Solana runtime and serializes bool as 0x00 or 0x01. pub active: bool, } diff --git a/sysvar/src/clock.rs b/sysvar/src/clock.rs index bde88bab2..dadb6335f 100644 --- a/sysvar/src/clock.rs +++ b/sysvar/src/clock.rs @@ -130,8 +130,64 @@ pub use { }; impl Sysvar for Clock { - impl_sysvar_get!(sol_get_clock_sysvar); + impl_sysvar_get!(id()); } #[cfg(feature = "bincode")] impl SysvarSerialize for Clock {} + +#[cfg(test)] +mod tests { + use {super::*, crate::tests::to_bytes, serial_test::serial}; + + #[test] + #[cfg(feature = "bincode")] + fn test_clock_size_matches_bincode() { + // Prove that Clock's in-memory layout matches its bincode serialization. + let clock = Clock::default(); + let in_memory_size = core::mem::size_of::(); + let bincode_size = bincode::serialized_size(&clock).unwrap() as usize; + + assert_eq!( + in_memory_size, bincode_size, + "Clock in-memory size ({in_memory_size}) must match bincode size ({bincode_size})", + ); + } + + #[test] + #[serial] + fn test_clock_get_uses_sysvar_syscall() { + let expected = Clock { + slot: 1, + epoch_start_timestamp: 2, + epoch: 3, + leader_schedule_epoch: 4, + unix_timestamp: 5, + }; + + let data = to_bytes(&expected); + crate::tests::mock_get_sysvar_syscall(&data); + + let got = Clock::get().unwrap(); + assert_eq!(got, expected); + } + + #[test] + #[serial] + fn test_clock_get_passes_correct_sysvar_id() { + let expected = Clock { + slot: 11, + epoch_start_timestamp: 22, + epoch: 33, + leader_schedule_epoch: 44, + unix_timestamp: 55, + }; + let data = to_bytes(&expected); + let prev = crate::tests::mock_get_sysvar_syscall_with_id(&data, &id()); + + let got = Clock::get().unwrap(); + assert_eq!(got, expected); + + let _ = crate::program_stubs::set_syscall_stubs(prev); + } +} diff --git a/sysvar/src/epoch_rewards.rs b/sysvar/src/epoch_rewards.rs index ae741e4ef..3e87254e9 100755 --- a/sysvar/src/epoch_rewards.rs +++ b/sysvar/src/epoch_rewards.rs @@ -163,8 +163,39 @@ pub use { }; impl Sysvar for EpochRewards { - impl_sysvar_get!(sol_get_epoch_rewards_sysvar); + // SAFETY: upstream invariant: the sysvar data is created exclusively + // by the Solana runtime and serializes bool as 0x00 or 0x01, so the final + // `bool` field of `EpochRewards` can be re-aligned with padding and read + // directly without validation. + impl_sysvar_get!(id(), 15); } #[cfg(feature = "bincode")] impl SysvarSerialize for EpochRewards {} + +#[cfg(test)] +mod tests { + use {super::*, crate::Sysvar, serial_test::serial}; + + #[test] + #[serial] + #[cfg(feature = "bincode")] + fn test_epoch_rewards_get() { + let expected = EpochRewards { + distribution_starting_block_height: 42, + num_partitions: 7, + parent_blockhash: solana_hash::Hash::new_unique(), + total_points: 1234567890, + total_rewards: 100, + distributed_rewards: 10, + active: true, + }; + + let data = bincode::serialize(&expected).unwrap(); + assert_eq!(data.len(), 81); + + crate::tests::mock_get_sysvar_syscall(&data); + let got = EpochRewards::get().unwrap(); + assert_eq!(got, expected); + } +} diff --git a/sysvar/src/epoch_schedule.rs b/sysvar/src/epoch_schedule.rs index d189434a3..2dca37de5 100644 --- a/sysvar/src/epoch_schedule.rs +++ b/sysvar/src/epoch_schedule.rs @@ -119,17 +119,126 @@ //! # //! # Ok::<(), anyhow::Error>(()) //! ``` +use crate::Sysvar; #[cfg(feature = "bincode")] use crate::SysvarSerialize; -use crate::{impl_sysvar_get, Sysvar}; pub use { solana_epoch_schedule::EpochSchedule, solana_sdk_ids::sysvar::epoch_schedule::{check_id, id, ID}, }; +/// Pod (Plain Old Data) representation of [`EpochSchedule`] with no padding. +/// +/// This type can be safely loaded via `sol_get_sysvar` without undefined behavior. +/// Provides performant zero-copy accessors as an alternative to the `EpochSchedule` type. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PodEpochSchedule { + slots_per_epoch: [u8; 8], + leader_schedule_slot_offset: [u8; 8], + warmup: u8, + first_normal_epoch: [u8; 8], + first_normal_slot: [u8; 8], +} + +const POD_EPOCH_SCHEDULE_SIZE: usize = 33; +const _: () = assert!(core::mem::size_of::() == POD_EPOCH_SCHEDULE_SIZE); + +impl PodEpochSchedule { + /// Fetch the sysvar data using the `sol_get_sysvar` syscall. + /// This provides an alternative to `EpochSchedule` which provides zero-copy accessors. + pub fn fetch() -> Result { + let mut pod = core::mem::MaybeUninit::::uninit(); + // Safety: `get_sysvar_unchecked` will initialize `pod` with the sysvar data, + // and error if unsuccessful. + unsafe { + crate::get_sysvar_unchecked( + pod.as_mut_ptr() as *mut u8, + (&id()) as *const _ as *const u8, + 0, + POD_EPOCH_SCHEDULE_SIZE as u64, + )?; + Ok(pod.assume_init()) + } + } + + pub fn slots_per_epoch(&self) -> u64 { + u64::from_le_bytes(self.slots_per_epoch) + } + + pub fn leader_schedule_slot_offset(&self) -> u64 { + u64::from_le_bytes(self.leader_schedule_slot_offset) + } + + pub fn warmup(&self) -> bool { + // SAFETY: upstream invariant: the sysvar data is created exclusively + // by the Solana runtime and serializes bool as 0x00 or 0x01. + self.warmup > 0 + } + + pub fn first_normal_epoch(&self) -> u64 { + u64::from_le_bytes(self.first_normal_epoch) + } + + pub fn first_normal_slot(&self) -> u64 { + u64::from_le_bytes(self.first_normal_slot) + } +} + +impl From for EpochSchedule { + fn from(pod: PodEpochSchedule) -> Self { + Self { + slots_per_epoch: pod.slots_per_epoch(), + leader_schedule_slot_offset: pod.leader_schedule_slot_offset(), + warmup: pod.warmup(), + first_normal_epoch: pod.first_normal_epoch(), + first_normal_slot: pod.first_normal_slot(), + } + } +} + impl Sysvar for EpochSchedule { - impl_sysvar_get!(sol_get_epoch_schedule_sysvar); + fn get() -> Result { + Ok(PodEpochSchedule::fetch()?.into()) + } } #[cfg(feature = "bincode")] impl SysvarSerialize for EpochSchedule {} + +#[cfg(test)] +mod tests { + use {super::*, crate::Sysvar, serial_test::serial}; + + #[test] + fn test_pod_epoch_schedule_conversion() { + let pod = PodEpochSchedule { + slots_per_epoch: 432000u64.to_le_bytes(), + leader_schedule_slot_offset: 432000u64.to_le_bytes(), + warmup: 1, + first_normal_epoch: 14u64.to_le_bytes(), + first_normal_slot: 524256u64.to_le_bytes(), + }; + + let epoch_schedule = EpochSchedule::from(pod); + + assert_eq!(epoch_schedule.slots_per_epoch, 432000); + assert_eq!(epoch_schedule.leader_schedule_slot_offset, 432000); + assert!(epoch_schedule.warmup); + assert_eq!(epoch_schedule.first_normal_epoch, 14); + assert_eq!(epoch_schedule.first_normal_slot, 524256); + } + + #[test] + #[serial] + #[cfg(feature = "bincode")] + fn test_epoch_schedule_get() { + let expected = EpochSchedule::custom(1234, 5678, false); + let data = bincode::serialize(&expected).unwrap(); + assert_eq!(data.len(), 33); + + crate::tests::mock_get_sysvar_syscall(&data); + let got = EpochSchedule::get().unwrap(); + assert_eq!(got, expected); + } +} diff --git a/sysvar/src/fees.rs b/sysvar/src/fees.rs index cf40e48ed..1a634e598 100644 --- a/sysvar/src/fees.rs +++ b/sysvar/src/fees.rs @@ -64,7 +64,7 @@ impl SysvarSerialize for Fees {} #[cfg(test)] mod tests { - use super::*; + use {super::*, serial_test::serial}; #[test] fn test_clone() { @@ -76,4 +76,50 @@ mod tests { let cloned_fees = fees.clone(); assert_eq!(cloned_fees, fees); } + + struct MockFeesSyscall; + impl crate::program_stubs::SyscallStubs for MockFeesSyscall { + fn sol_get_fees_sysvar(&self, var_addr: *mut u8) -> u64 { + let fees = Fees { + fee_calculator: FeeCalculator { + lamports_per_signature: 42, + }, + }; + unsafe { + std::ptr::copy_nonoverlapping( + &fees as *const _ as *const u8, + var_addr, + core::mem::size_of::(), + ); + } + solana_program_entrypoint::SUCCESS + } + } + + #[test] + #[serial] + fn test_fees_get_deprecated_syscall_path() { + let _ = crate::program_stubs::set_syscall_stubs(Box::new(MockFeesSyscall)); + let got = Fees::get().unwrap(); + assert_eq!(got.fee_calculator.lamports_per_signature, 42); + } + + struct FailFeesSyscall; + impl crate::program_stubs::SyscallStubs for FailFeesSyscall { + fn sol_get_fees_sysvar(&self, _var_addr: *mut u8) -> u64 { + 9999 + } + } + + #[test] + #[serial] + fn test_fees_get_deprecated_non_success_maps_to_unsupported() { + let prev = crate::program_stubs::set_syscall_stubs(Box::new(FailFeesSyscall)); + let got = Fees::get(); + assert_eq!( + got, + Err(solana_program_error::ProgramError::UnsupportedSysvar) + ); + let _ = crate::program_stubs::set_syscall_stubs(prev); + } } diff --git a/sysvar/src/last_restart_slot.rs b/sysvar/src/last_restart_slot.rs index 5ee10b621..5689ddfb5 100644 --- a/sysvar/src/last_restart_slot.rs +++ b/sysvar/src/last_restart_slot.rs @@ -45,8 +45,40 @@ pub use { }; impl Sysvar for LastRestartSlot { - impl_sysvar_get!(sol_get_last_restart_slot); + impl_sysvar_get!(id()); } #[cfg(feature = "bincode")] impl SysvarSerialize for LastRestartSlot {} + +#[cfg(test)] +mod tests { + use {super::*, crate::tests::to_bytes, serial_test::serial}; + + #[test] + #[cfg(feature = "bincode")] + fn test_last_restart_slot_size_matches_bincode() { + // Prove that LastRestartSlot's in-memory layout matches its bincode serialization. + let slot = LastRestartSlot::default(); + let in_memory_size = core::mem::size_of::(); + let bincode_size = bincode::serialized_size(&slot).unwrap() as usize; + + assert_eq!( + in_memory_size, bincode_size, + "LastRestartSlot in-memory size ({in_memory_size}) must match bincode size ({bincode_size})", + ); + } + + #[test] + #[serial] + fn test_last_restart_slot_get_uses_sysvar_syscall() { + let expected = LastRestartSlot { + last_restart_slot: 9999, + }; + let data = to_bytes(&expected); + crate::tests::mock_get_sysvar_syscall(&data); + + let got = LastRestartSlot::get().unwrap(); + assert_eq!(got, expected); + } +} diff --git a/sysvar/src/lib.rs b/sysvar/src/lib.rs index 0e522f658..e9288281d 100644 --- a/sysvar/src/lib.rs +++ b/sysvar/src/lib.rs @@ -161,6 +161,9 @@ pub trait SysvarSerialize: /// Implements the [`Sysvar::get`] method for both SBF and host targets. #[macro_export] macro_rules! impl_sysvar_get { + // DEPRECATED: This variant is only for the deprecated Fees sysvar and should be + // removed once Fees is no longer in use. It uses the old-style direct syscall + // approach instead of the new sol_get_sysvar syscall. ($syscall_name:ident) => { fn get() -> Result { let mut var = Self::default(); @@ -174,11 +177,43 @@ macro_rules! impl_sysvar_get { match result { $crate::__private::SUCCESS => Ok(var), - // Unexpected errors are folded into `UnsupportedSysvar`. _ => Err($crate::__private::ProgramError::UnsupportedSysvar), } } }; + // Variant for sysvars with padding at the end. Loads bincode-serialized data + // (size - padding bytes) and zeros the padding to avoid undefined behavior. + // Only supports sysvars where padding is at the end of the layout. Caller + // must supply the correct number of padding bytes. + ($sysvar_id:expr, $padding:literal) => { + fn get() -> Result { + let mut var = core::mem::MaybeUninit::::uninit(); + let var_addr = var.as_mut_ptr() as *mut u8; + let length = core::mem::size_of::().saturating_sub($padding); + let sysvar_id_ptr = (&$sysvar_id) as *const _ as *const u8; + // SAFETY: The allocation is valid for `size_of::()`. We load + // `(size - padding)` bytes from the syscall, which matches bincode + // serialization. The remaining `padding` bytes are then zeroed. + let result = + unsafe { $crate::get_sysvar_unchecked(var_addr, sysvar_id_ptr, 0, length as u64) }; + match result { + Ok(()) => { + // SAFETY: All bytes now initialized: syscall filled data + // bytes and we zeroed padding. + unsafe { + var_addr.add(length).write_bytes(0, $padding); + Ok(var.assume_init()) + } + } + // Unexpected errors are folded into `UnsupportedSysvar`. + Err(_) => Err($crate::__private::ProgramError::UnsupportedSysvar), + } + } + }; + // Variant for sysvars without padding (struct size matches bincode size). + ($sysvar_id:expr) => { + $crate::impl_sysvar_get!($sysvar_id, 0); + }; } /// Handler for retrieving a slice of sysvar data from the `sol_get_sysvar` @@ -206,6 +241,35 @@ pub fn get_sysvar( #[cfg(not(target_os = "solana"))] let result = crate::program_stubs::sol_get_sysvar(sysvar_id, var_addr, offset, length); + match result { + solana_program_entrypoint::SUCCESS => Ok(()), + OFFSET_LENGTH_EXCEEDS_SYSVAR => Err(solana_program_error::ProgramError::InvalidArgument), + _ => Err(solana_program_error::ProgramError::UnsupportedSysvar), + } +} + +/// Internal helper for retrieving sysvar data directly into a raw buffer. +/// +/// # Safety +/// +/// This function bypasses the slice-length check that `get_sysvar` performs. +/// The caller must ensure that `var_addr` points to a writable buffer of at +/// least `length` bytes. This is typically used with `MaybeUninit` to load +/// compact representations of sysvars. +#[doc(hidden)] +pub unsafe fn get_sysvar_unchecked( + var_addr: *mut u8, + sysvar_id: *const u8, + offset: u64, + length: u64, +) -> Result<(), solana_program_error::ProgramError> { + #[cfg(target_os = "solana")] + let result = + solana_define_syscall::definitions::sol_get_sysvar(sysvar_id, var_addr, offset, length); + + #[cfg(not(target_os = "solana"))] + let result = crate::program_stubs::sol_get_sysvar(sysvar_id, var_addr, offset, length); + match result { solana_program_entrypoint::SUCCESS => Ok(()), OFFSET_LENGTH_EXCEEDS_SYSVAR => Err(solana_program_error::ProgramError::InvalidArgument), @@ -245,10 +309,13 @@ mod tests { impl Sysvar for TestSysvar {} impl SysvarSerialize for TestSysvar {} - // NOTE tests that use this mock MUST carry the #[serial] attribute + // NOTE: Tests using these mocks MUST carry the #[serial] attribute + // because they modify global SYSCALL_STUBS state. + struct MockGetSysvarSyscall { data: Vec, } + impl SyscallStubs for MockGetSysvarSyscall { #[allow(clippy::arithmetic_side_effects)] fn sol_get_sysvar( @@ -263,12 +330,65 @@ mod tests { SUCCESS } } + + /// Mock syscall stub for tests. Requires `#[serial]` attribute. pub fn mock_get_sysvar_syscall(data: &[u8]) { set_syscall_stubs(Box::new(MockGetSysvarSyscall { data: data.to_vec(), })); } + struct ValidateIdSyscall { + data: Vec, + expected_id: Pubkey, + } + + impl SyscallStubs for ValidateIdSyscall { + #[allow(clippy::arithmetic_side_effects)] + fn sol_get_sysvar( + &self, + sysvar_id_addr: *const u8, + var_addr: *mut u8, + offset: u64, + length: u64, + ) -> u64 { + // Validate that the correct sysvar id pointer was passed + let passed_id = unsafe { *(sysvar_id_addr as *const Pubkey) }; + assert_eq!(passed_id, self.expected_id); + + let slice = unsafe { std::slice::from_raw_parts_mut(var_addr, length as usize) }; + slice.copy_from_slice(&self.data[offset as usize..(offset + length) as usize]); + SUCCESS + } + } + + /// Mock syscall stub that validates sysvar ID. Requires `#[serial]` attribute. + pub fn mock_get_sysvar_syscall_with_id( + data: &[u8], + expected_id: &Pubkey, + ) -> Box { + set_syscall_stubs(Box::new(ValidateIdSyscall { + data: data.to_vec(), + expected_id: *expected_id, + })) + } + + /// Convert a value to its in-memory byte representation. + /// + /// # Safety + /// + /// This function is only safe for plain-old-data types with no padding. + /// Intended for test use only. + pub fn to_bytes(value: &T) -> Vec { + let size = core::mem::size_of::(); + let ptr = (value as *const T) as *const u8; + let mut data = vec![0u8; size]; + unsafe { + std::ptr::copy_nonoverlapping(ptr, data.as_mut_ptr(), size); + } + data + } + #[test] fn test_sysvar_account_info_to_from() { let test_sysvar = TestSysvar::default(); diff --git a/sysvar/src/program_stubs.rs b/sysvar/src/program_stubs.rs index ba4180656..219cce02c 100644 --- a/sysvar/src/program_stubs.rs +++ b/sysvar/src/program_stubs.rs @@ -155,32 +155,6 @@ pub(crate) fn sol_get_sysvar( .sol_get_sysvar(sysvar_id_addr, var_addr, offset, length) } -pub(crate) fn sol_get_clock_sysvar(var_addr: *mut u8) -> u64 { - SYSCALL_STUBS.read().unwrap().sol_get_clock_sysvar(var_addr) -} - -pub(crate) fn sol_get_epoch_schedule_sysvar(var_addr: *mut u8) -> u64 { - SYSCALL_STUBS - .read() - .unwrap() - .sol_get_epoch_schedule_sysvar(var_addr) -} - -pub(crate) fn sol_get_fees_sysvar(var_addr: *mut u8) -> u64 { - SYSCALL_STUBS.read().unwrap().sol_get_fees_sysvar(var_addr) -} - -pub(crate) fn sol_get_rent_sysvar(var_addr: *mut u8) -> u64 { - SYSCALL_STUBS.read().unwrap().sol_get_rent_sysvar(var_addr) -} - -pub(crate) fn sol_get_last_restart_slot(var_addr: *mut u8) -> u64 { - SYSCALL_STUBS - .read() - .unwrap() - .sol_get_last_restart_slot(var_addr) -} - pub fn sol_get_epoch_stake(vote_address: *const u8) -> u64 { SYSCALL_STUBS .read() @@ -211,9 +185,6 @@ pub 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) +pub(crate) fn sol_get_fees_sysvar(var_addr: *mut u8) -> u64 { + SYSCALL_STUBS.read().unwrap().sol_get_fees_sysvar(var_addr) } diff --git a/sysvar/src/rent.rs b/sysvar/src/rent.rs index 84f894cf6..a994dfaf0 100644 --- a/sysvar/src/rent.rs +++ b/sysvar/src/rent.rs @@ -128,9 +128,33 @@ pub use { solana_rent::Rent, solana_sdk_ids::sysvar::rent::{check_id, id, ID}, }; + impl Sysvar for Rent { - impl_sysvar_get!(sol_get_rent_sysvar); + impl_sysvar_get!(id(), 7); } #[cfg(feature = "bincode")] impl SysvarSerialize for Rent {} + +#[cfg(test)] +mod tests { + use {super::*, crate::Sysvar, serial_test::serial}; + + #[test] + #[serial] + #[cfg(feature = "bincode")] + #[allow(deprecated)] + fn test_rent_get() { + let expected = Rent { + lamports_per_byte_year: 123, + exemption_threshold: 2.5, + burn_percent: 7, + }; + let data = bincode::serialize(&expected).unwrap(); + assert_eq!(data.len(), 17); + + crate::tests::mock_get_sysvar_syscall(&data); + let got = Rent::get().unwrap(); + assert_eq!(got, expected); + } +}