Skip to content

Commit 8949e2f

Browse files
committed
safe sol_get_sysvar from reprs
1 parent 2b06ec1 commit 8949e2f

File tree

10 files changed

+540
-55
lines changed

10 files changed

+540
-55
lines changed

define-syscall/src/definitions.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ define_syscall!(fn sol_get_return_data(data: *mut u8, length: u64, program_id: *
4949
// - `is_writable` (`u8`): `true` if the instruction requires the account to be writable
5050
define_syscall!(fn sol_get_processed_sibling_instruction(index: u64, meta: *mut u8, program_id: *mut u8, data: *mut u8, accounts: *mut u8) -> u64);
5151

52-
// these are to be deprecated once they are superceded by sol_get_sysvar
53-
define_syscall!(fn sol_get_clock_sysvar(addr: *mut u8) -> u64);
54-
define_syscall!(fn sol_get_epoch_schedule_sysvar(addr: *mut u8) -> u64);
55-
define_syscall!(fn sol_get_rent_sysvar(addr: *mut u8) -> u64);
56-
define_syscall!(fn sol_get_last_restart_slot(addr: *mut u8) -> u64);
57-
define_syscall!(fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64);
52+
// these are deprecated - use sol_get_sysvar instead
53+
define_syscall!(#[deprecated(since = "3.0.0", note = "Use `sol_get_sysvar` with `Clock` sysvar address instead")] fn sol_get_clock_sysvar(addr: *mut u8) -> u64);
54+
define_syscall!(#[deprecated(since = "3.0.0", note = "Use `sol_get_sysvar` with `EpochSchedule` sysvar address instead")] fn sol_get_epoch_schedule_sysvar(addr: *mut u8) -> u64);
55+
define_syscall!(#[deprecated(since = "3.0.0", note = "Use `sol_get_sysvar` with `Rent` sysvar address instead")] fn sol_get_rent_sysvar(addr: *mut u8) -> u64);
56+
define_syscall!(#[deprecated(since = "3.0.0", note = "Use `sol_get_sysvar` with `LastRestartSlot` sysvar address instead")] fn sol_get_last_restart_slot(addr: *mut u8) -> u64);
57+
define_syscall!(#[deprecated(since = "3.0.0", note = "Use `sol_get_sysvar` with `EpochRewards` sysvar address instead")] fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64);
5858

5959
// this cannot go through sol_get_sysvar but can be removed once no longer in use
6060
define_syscall!(fn sol_get_fees_sysvar(addr: *mut u8) -> u64);

define-syscall/src/lib.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ pub mod definitions;
99
))]
1010
#[macro_export]
1111
macro_rules! define_syscall {
12-
(fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
12+
($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
13+
$(#[$attr])*
1314
#[inline]
1415
pub unsafe fn $name($($arg: $typ),*) -> $ret {
1516
// this enum is used to force the hash to be computed in a const context
@@ -23,8 +24,8 @@ macro_rules! define_syscall {
2324
}
2425

2526
};
26-
(fn $name:ident($($arg:ident: $typ:ty),*)) => {
27-
define_syscall!(fn $name($($arg: $typ),*) -> ());
27+
($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*)) => {
28+
define_syscall!($(#[$attr])* fn $name($($arg: $typ),*) -> ());
2829
}
2930
}
3031

@@ -34,13 +35,14 @@ macro_rules! define_syscall {
3435
)))]
3536
#[macro_export]
3637
macro_rules! define_syscall {
37-
(fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
38+
($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
3839
extern "C" {
40+
$(#[$attr])*
3941
pub fn $name($($arg: $typ),*) -> $ret;
4042
}
4143
};
42-
(fn $name:ident($($arg:ident: $typ:ty),*)) => {
43-
define_syscall!(fn $name($($arg: $typ),*) -> ());
44+
($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*)) => {
45+
define_syscall!($(#[$attr])* fn $name($($arg: $typ),*) -> ());
4446
}
4547
}
4648

sysvar/src/clock.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,74 @@ pub use {
130130
};
131131

132132
impl Sysvar for Clock {
133-
impl_sysvar_get!(sol_get_clock_sysvar);
133+
impl_sysvar_get!(id());
134134
}
135135

136136
#[cfg(feature = "bincode")]
137137
impl SysvarSerialize for Clock {}
138+
139+
#[cfg(test)]
140+
mod tests {
141+
use {super::*, crate::tests::to_bytes, serial_test::serial};
142+
143+
#[test]
144+
#[serial]
145+
fn test_clock_get_uses_sysvar_syscall() {
146+
let expected = Clock {
147+
slot: 1,
148+
epoch_start_timestamp: 2,
149+
epoch: 3,
150+
leader_schedule_epoch: 4,
151+
unix_timestamp: 5,
152+
};
153+
154+
let data = to_bytes(&expected);
155+
crate::tests::mock_get_sysvar_syscall(&data);
156+
157+
let got = Clock::get().unwrap();
158+
assert_eq!(got, expected);
159+
}
160+
161+
struct ValidateIdSyscall {
162+
data: Vec<u8>,
163+
}
164+
165+
impl crate::program_stubs::SyscallStubs for ValidateIdSyscall {
166+
fn sol_get_sysvar(
167+
&self,
168+
sysvar_id_addr: *const u8,
169+
var_addr: *mut u8,
170+
offset: u64,
171+
length: u64,
172+
) -> u64 {
173+
// Validate that the macro passed the correct sysvar id pointer
174+
let passed_id = unsafe { *(sysvar_id_addr as *const solana_pubkey::Pubkey) };
175+
assert_eq!(passed_id, id());
176+
177+
let slice = unsafe { std::slice::from_raw_parts_mut(var_addr, length as usize) };
178+
slice.copy_from_slice(
179+
&self.data[offset as usize..(offset.saturating_add(length)) as usize],
180+
);
181+
solana_program_entrypoint::SUCCESS
182+
}
183+
}
184+
185+
#[test]
186+
#[serial]
187+
fn test_clock_get_passes_correct_sysvar_id() {
188+
let expected = Clock {
189+
slot: 11,
190+
epoch_start_timestamp: 22,
191+
epoch: 33,
192+
leader_schedule_epoch: 44,
193+
unix_timestamp: 55,
194+
};
195+
let data = to_bytes(&expected);
196+
let prev = crate::program_stubs::set_syscall_stubs(Box::new(ValidateIdSyscall { data }));
197+
198+
let got = Clock::get().unwrap();
199+
assert_eq!(got, expected);
200+
201+
let _ = crate::program_stubs::set_syscall_stubs(prev);
202+
}
203+
}

sysvar/src/epoch_rewards.rs

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,17 +154,127 @@
154154
//! # Ok::<(), anyhow::Error>(())
155155
//! ```
156156
157+
use crate::impl_sysvar_from_repr;
157158
#[cfg(feature = "bincode")]
158159
use crate::SysvarSerialize;
159-
use crate::{impl_sysvar_get, Sysvar};
160160
pub use {
161161
solana_epoch_rewards::EpochRewards,
162162
solana_sdk_ids::sysvar::epoch_rewards::{check_id, id, ID},
163163
};
164164

165-
impl Sysvar for EpochRewards {
166-
impl_sysvar_get!(sol_get_epoch_rewards_sysvar);
165+
/// Compact 1-byte-aligned representation of [`EpochRewards`] matching its serialized form.
166+
///
167+
/// The on-chain encoding is 81 bytes: u64 + u64 + Hash(32) + u128 + u64 + u64 + bool.
168+
/// The canonical `EpochRewards` struct uses `#[repr(C)]` and contains padding,
169+
/// so we define this compact layout to avoid undefined behavior when loading.
170+
#[repr(C, packed)]
171+
#[derive(Clone, Copy, Default)]
172+
struct EpochRewardsRepr {
173+
distribution_starting_block_height: u64,
174+
num_partitions: u64,
175+
parent_blockhash: [u8; 32],
176+
total_points: u128,
177+
total_rewards: u64,
178+
distributed_rewards: u64,
179+
active: u8, // bool serialized as u8
167180
}
168181

182+
impl From<EpochRewardsRepr> for EpochRewards {
183+
fn from(r: EpochRewardsRepr) -> Self {
184+
Self {
185+
distribution_starting_block_height: r.distribution_starting_block_height,
186+
num_partitions: r.num_partitions,
187+
parent_blockhash: solana_hash::Hash::new_from_array(r.parent_blockhash),
188+
total_points: r.total_points,
189+
total_rewards: r.total_rewards,
190+
distributed_rewards: r.distributed_rewards,
191+
active: r.active != 0,
192+
}
193+
}
194+
}
195+
196+
impl_sysvar_from_repr!(EpochRewards, EpochRewardsRepr, id());
197+
169198
#[cfg(feature = "bincode")]
170199
impl SysvarSerialize for EpochRewards {}
200+
201+
#[cfg(test)]
202+
mod tests {
203+
use {super::*, crate::Sysvar, serial_test::serial};
204+
205+
#[test]
206+
fn test_epoch_rewards_repr_size() {
207+
assert_eq!(core::mem::size_of::<EpochRewardsRepr>(), 81);
208+
}
209+
210+
#[test]
211+
fn test_epoch_rewards_repr_conversion() {
212+
let hash_bytes = [42u8; 32];
213+
let repr = EpochRewardsRepr {
214+
distribution_starting_block_height: 42,
215+
num_partitions: 7,
216+
parent_blockhash: hash_bytes,
217+
total_points: 1234567890,
218+
total_rewards: 100,
219+
distributed_rewards: 10,
220+
active: 1, // true
221+
};
222+
let rewards = EpochRewards::from(repr);
223+
assert_eq!(rewards.distribution_starting_block_height, 42);
224+
assert_eq!(rewards.num_partitions, 7);
225+
assert_eq!(
226+
rewards.parent_blockhash,
227+
solana_hash::Hash::new_from_array(hash_bytes)
228+
);
229+
assert_eq!(rewards.total_points, 1234567890);
230+
assert_eq!(rewards.total_rewards, 100);
231+
assert_eq!(rewards.distributed_rewards, 10);
232+
assert_eq!(rewards.active, true);
233+
}
234+
235+
#[test]
236+
#[serial]
237+
#[cfg(feature = "bincode")]
238+
fn test_epoch_rewards_get_with_bincode_serialized_data() {
239+
use {
240+
crate::program_stubs::{set_syscall_stubs, SyscallStubs},
241+
solana_program_entrypoint::SUCCESS,
242+
};
243+
244+
let expected = EpochRewards {
245+
distribution_starting_block_height: 42,
246+
num_partitions: 7,
247+
parent_blockhash: solana_hash::Hash::new_unique(),
248+
total_points: 1234567890,
249+
total_rewards: 100,
250+
distributed_rewards: 10,
251+
active: true,
252+
};
253+
254+
let data = bincode::serialize(&expected).unwrap();
255+
assert_eq!(data.len(), 81, "Bincode serialization should be 81 bytes");
256+
257+
struct MockSyscall {
258+
data: Vec<u8>,
259+
}
260+
impl SyscallStubs for MockSyscall {
261+
fn sol_get_sysvar(
262+
&self,
263+
_sysvar_id_addr: *const u8,
264+
var_addr: *mut u8,
265+
offset: u64,
266+
length: u64,
267+
) -> u64 {
268+
unsafe {
269+
let slice = core::slice::from_raw_parts_mut(var_addr, length as usize);
270+
slice.copy_from_slice(&self.data[offset as usize..(offset + length) as usize]);
271+
}
272+
SUCCESS
273+
}
274+
}
275+
276+
set_syscall_stubs(Box::new(MockSyscall { data }));
277+
let got = EpochRewards::get().unwrap();
278+
assert_eq!(got, expected);
279+
}
280+
}

sysvar/src/epoch_schedule.rs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,107 @@
119119
//! #
120120
//! # Ok::<(), anyhow::Error>(())
121121
//! ```
122+
use crate::impl_sysvar_from_repr;
122123
#[cfg(feature = "bincode")]
123124
use crate::SysvarSerialize;
124-
use crate::{impl_sysvar_get, Sysvar};
125125
pub use {
126126
solana_epoch_schedule::EpochSchedule,
127127
solana_sdk_ids::sysvar::epoch_schedule::{check_id, id, ID},
128128
};
129129

130-
impl Sysvar for EpochSchedule {
131-
impl_sysvar_get!(sol_get_epoch_schedule_sysvar);
130+
/// Compact 1-byte-aligned representation of [`EpochSchedule`] matching its serialized form.
131+
///
132+
/// The on-chain encoding is 33 bytes: u64 + u64 + bool + u64 + u64.
133+
/// The canonical `EpochSchedule` struct uses `#[repr(C)]` and contains padding,
134+
/// so we define this compact layout to avoid undefined behavior when loading.
135+
#[repr(C, packed)]
136+
#[derive(Clone, Copy, Default)]
137+
struct EpochScheduleRepr {
138+
slots_per_epoch: u64,
139+
leader_schedule_slot_offset: u64,
140+
warmup: u8, // bool serialized as u8
141+
first_normal_epoch: u64,
142+
first_normal_slot: u64,
132143
}
133144

145+
impl From<EpochScheduleRepr> for EpochSchedule {
146+
fn from(r: EpochScheduleRepr) -> Self {
147+
Self {
148+
slots_per_epoch: r.slots_per_epoch,
149+
leader_schedule_slot_offset: r.leader_schedule_slot_offset,
150+
warmup: r.warmup != 0,
151+
first_normal_epoch: r.first_normal_epoch,
152+
first_normal_slot: r.first_normal_slot,
153+
}
154+
}
155+
}
156+
157+
impl_sysvar_from_repr!(EpochSchedule, EpochScheduleRepr, id());
158+
134159
#[cfg(feature = "bincode")]
135160
impl SysvarSerialize for EpochSchedule {}
161+
162+
#[cfg(test)]
163+
mod tests {
164+
use {super::*, crate::Sysvar, serial_test::serial};
165+
166+
#[test]
167+
fn test_epoch_schedule_repr_size() {
168+
assert_eq!(core::mem::size_of::<EpochScheduleRepr>(), 33);
169+
}
170+
171+
#[test]
172+
fn test_epoch_schedule_repr_conversion() {
173+
let repr = EpochScheduleRepr {
174+
slots_per_epoch: 1234,
175+
leader_schedule_slot_offset: 5678,
176+
warmup: 0, // false
177+
first_normal_epoch: 10,
178+
first_normal_slot: 20,
179+
};
180+
let schedule = EpochSchedule::from(repr);
181+
assert_eq!(schedule.slots_per_epoch, 1234);
182+
assert_eq!(schedule.leader_schedule_slot_offset, 5678);
183+
assert_eq!(schedule.warmup, false);
184+
assert_eq!(schedule.first_normal_epoch, 10);
185+
assert_eq!(schedule.first_normal_slot, 20);
186+
}
187+
188+
#[test]
189+
#[serial]
190+
#[cfg(feature = "bincode")]
191+
fn test_epoch_schedule_get_with_bincode_serialized_data() {
192+
use {
193+
crate::program_stubs::{set_syscall_stubs, SyscallStubs},
194+
solana_program_entrypoint::SUCCESS,
195+
};
196+
197+
let expected = EpochSchedule::custom(1234, 5678, false);
198+
199+
let data = bincode::serialize(&expected).unwrap();
200+
assert_eq!(data.len(), 33, "Bincode serialization should be 33 bytes");
201+
202+
struct MockSyscall {
203+
data: Vec<u8>,
204+
}
205+
impl SyscallStubs for MockSyscall {
206+
fn sol_get_sysvar(
207+
&self,
208+
_sysvar_id_addr: *const u8,
209+
var_addr: *mut u8,
210+
offset: u64,
211+
length: u64,
212+
) -> u64 {
213+
unsafe {
214+
let slice = core::slice::from_raw_parts_mut(var_addr, length as usize);
215+
slice.copy_from_slice(&self.data[offset as usize..(offset + length) as usize]);
216+
}
217+
SUCCESS
218+
}
219+
}
220+
221+
set_syscall_stubs(Box::new(MockSyscall { data }));
222+
let got = EpochSchedule::get().unwrap();
223+
assert_eq!(got, expected);
224+
}
225+
}

0 commit comments

Comments
 (0)