Skip to content

Commit 2d0d5e6

Browse files
committed
safe sol_get_sysvar from packed reprs
1 parent 2b06ec1 commit 2d0d5e6

File tree

10 files changed

+555
-52
lines changed

10 files changed

+555
-52
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: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,94 @@ 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+
fn test_clock_size_matches_bincode() {
145+
// Prove that Clock's in-memory layout matches its bincode serialization,
146+
// so we do not need to use define_sysvar_wire.
147+
let clock = Clock::default();
148+
let in_memory_size = core::mem::size_of::<Clock>();
149+
150+
#[cfg(feature = "bincode")]
151+
{
152+
let bincode_size = bincode::serialized_size(&clock).unwrap() as usize;
153+
assert_eq!(
154+
in_memory_size, bincode_size,
155+
"Clock in-memory size ({in_memory_size}) must match bincode size ({bincode_size})",
156+
);
157+
}
158+
159+
// Clock is 5 u64s = 40 bytes
160+
assert_eq!(in_memory_size, 40);
161+
}
162+
163+
#[test]
164+
#[serial]
165+
fn test_clock_get_uses_sysvar_syscall() {
166+
let expected = Clock {
167+
slot: 1,
168+
epoch_start_timestamp: 2,
169+
epoch: 3,
170+
leader_schedule_epoch: 4,
171+
unix_timestamp: 5,
172+
};
173+
174+
let data = to_bytes(&expected);
175+
crate::tests::mock_get_sysvar_syscall(&data);
176+
177+
let got = Clock::get().unwrap();
178+
assert_eq!(got, expected);
179+
}
180+
181+
struct ValidateIdSyscall {
182+
data: Vec<u8>,
183+
}
184+
185+
impl crate::program_stubs::SyscallStubs for ValidateIdSyscall {
186+
fn sol_get_sysvar(
187+
&self,
188+
sysvar_id_addr: *const u8,
189+
var_addr: *mut u8,
190+
offset: u64,
191+
length: u64,
192+
) -> u64 {
193+
// Validate that the macro passed the correct sysvar id pointer
194+
let passed_id = unsafe { *(sysvar_id_addr as *const solana_pubkey::Pubkey) };
195+
assert_eq!(passed_id, id());
196+
197+
let slice = unsafe { std::slice::from_raw_parts_mut(var_addr, length as usize) };
198+
slice.copy_from_slice(
199+
&self.data[offset as usize..(offset.saturating_add(length)) as usize],
200+
);
201+
solana_program_entrypoint::SUCCESS
202+
}
203+
}
204+
205+
#[test]
206+
#[serial]
207+
fn test_clock_get_passes_correct_sysvar_id() {
208+
let expected = Clock {
209+
slot: 11,
210+
epoch_start_timestamp: 22,
211+
epoch: 33,
212+
leader_schedule_epoch: 44,
213+
unix_timestamp: 55,
214+
};
215+
let data = to_bytes(&expected);
216+
let prev = crate::program_stubs::set_syscall_stubs(Box::new(ValidateIdSyscall { data }));
217+
218+
let got = Clock::get().unwrap();
219+
assert_eq!(got, expected);
220+
221+
let _ = crate::program_stubs::set_syscall_stubs(prev);
222+
}
223+
}

sysvar/src/epoch_rewards.rs

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,99 @@
156156
157157
#[cfg(feature = "bincode")]
158158
use crate::SysvarSerialize;
159-
use crate::{impl_sysvar_get, Sysvar};
159+
use crate::{get_sysvar_via_packed, sysvar_packed_struct, Sysvar};
160160
pub use {
161161
solana_epoch_rewards::EpochRewards,
162162
solana_sdk_ids::sysvar::epoch_rewards::{check_id, id, ID},
163163
};
164164

165+
sysvar_packed_struct! {
166+
struct EpochRewardsPacked(81) {
167+
distribution_starting_block_height: u64,
168+
num_partitions: u64,
169+
parent_blockhash: [u8; 32],
170+
total_points: u128,
171+
total_rewards: u64,
172+
distributed_rewards: u64,
173+
active: u8, // bool as u8
174+
}
175+
}
176+
177+
impl From<EpochRewardsPacked> for EpochRewards {
178+
fn from(p: EpochRewardsPacked) -> Self {
179+
Self {
180+
distribution_starting_block_height: p.distribution_starting_block_height,
181+
num_partitions: p.num_partitions,
182+
parent_blockhash: solana_hash::Hash::new_from_array(p.parent_blockhash),
183+
total_points: p.total_points,
184+
total_rewards: p.total_rewards,
185+
distributed_rewards: p.distributed_rewards,
186+
active: p.active != 0,
187+
}
188+
}
189+
}
190+
165191
impl Sysvar for EpochRewards {
166-
impl_sysvar_get!(sol_get_epoch_rewards_sysvar);
192+
fn get() -> Result<Self, solana_program_error::ProgramError> {
193+
get_sysvar_via_packed::<Self, EpochRewardsPacked>(&id())
194+
}
167195
}
168196

169197
#[cfg(feature = "bincode")]
170198
impl SysvarSerialize for EpochRewards {}
199+
200+
#[cfg(test)]
201+
mod tests {
202+
use {super::*, crate::Sysvar, serial_test::serial};
203+
204+
#[test]
205+
fn test_epoch_rewards_packed_size() {
206+
assert_eq!(core::mem::size_of::<EpochRewardsPacked>(), 81);
207+
}
208+
209+
#[test]
210+
#[serial]
211+
#[cfg(feature = "bincode")]
212+
fn test_epoch_rewards_get() {
213+
use {
214+
crate::program_stubs::{set_syscall_stubs, SyscallStubs},
215+
solana_program_entrypoint::SUCCESS,
216+
};
217+
218+
let expected = EpochRewards {
219+
distribution_starting_block_height: 42,
220+
num_partitions: 7,
221+
parent_blockhash: solana_hash::Hash::new_unique(),
222+
total_points: 1234567890,
223+
total_rewards: 100,
224+
distributed_rewards: 10,
225+
active: true,
226+
};
227+
228+
let data = bincode::serialize(&expected).unwrap();
229+
assert_eq!(data.len(), 81);
230+
231+
struct MockSyscall {
232+
data: Vec<u8>,
233+
}
234+
impl SyscallStubs for MockSyscall {
235+
fn sol_get_sysvar(
236+
&self,
237+
_sysvar_id_addr: *const u8,
238+
var_addr: *mut u8,
239+
offset: u64,
240+
length: u64,
241+
) -> u64 {
242+
unsafe {
243+
let slice = core::slice::from_raw_parts_mut(var_addr, length as usize);
244+
slice.copy_from_slice(&self.data[offset as usize..(offset + length) as usize]);
245+
}
246+
SUCCESS
247+
}
248+
}
249+
250+
set_syscall_stubs(Box::new(MockSyscall { data }));
251+
let got = EpochRewards::get().unwrap();
252+
assert_eq!(got, expected);
253+
}
254+
}

sysvar/src/epoch_schedule.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,86 @@
121121
//! ```
122122
#[cfg(feature = "bincode")]
123123
use crate::SysvarSerialize;
124-
use crate::{impl_sysvar_get, Sysvar};
124+
use crate::{get_sysvar_via_packed, sysvar_packed_struct, Sysvar};
125125
pub use {
126126
solana_epoch_schedule::EpochSchedule,
127127
solana_sdk_ids::sysvar::epoch_schedule::{check_id, id, ID},
128128
};
129129

130+
sysvar_packed_struct! {
131+
struct EpochSchedulePacked(33) {
132+
slots_per_epoch: u64,
133+
leader_schedule_slot_offset: u64,
134+
warmup: u8, // bool as u8
135+
first_normal_epoch: u64,
136+
first_normal_slot: u64,
137+
}
138+
}
139+
140+
impl From<EpochSchedulePacked> for EpochSchedule {
141+
fn from(p: EpochSchedulePacked) -> Self {
142+
Self {
143+
slots_per_epoch: p.slots_per_epoch,
144+
leader_schedule_slot_offset: p.leader_schedule_slot_offset,
145+
warmup: p.warmup != 0,
146+
first_normal_epoch: p.first_normal_epoch,
147+
first_normal_slot: p.first_normal_slot,
148+
}
149+
}
150+
}
151+
130152
impl Sysvar for EpochSchedule {
131-
impl_sysvar_get!(sol_get_epoch_schedule_sysvar);
153+
fn get() -> Result<Self, solana_program_error::ProgramError> {
154+
get_sysvar_via_packed::<Self, EpochSchedulePacked>(&id())
155+
}
132156
}
133157

134158
#[cfg(feature = "bincode")]
135159
impl SysvarSerialize for EpochSchedule {}
160+
161+
#[cfg(test)]
162+
mod tests {
163+
use {super::*, crate::Sysvar, serial_test::serial};
164+
165+
#[test]
166+
fn test_epoch_schedule_packed_size() {
167+
assert_eq!(core::mem::size_of::<EpochSchedulePacked>(), 33);
168+
}
169+
170+
#[test]
171+
#[serial]
172+
#[cfg(feature = "bincode")]
173+
fn test_epoch_schedule_get() {
174+
use {
175+
crate::program_stubs::{set_syscall_stubs, SyscallStubs},
176+
solana_program_entrypoint::SUCCESS,
177+
};
178+
179+
let expected = EpochSchedule::custom(1234, 5678, false);
180+
let data = bincode::serialize(&expected).unwrap();
181+
assert_eq!(data.len(), 33);
182+
183+
struct MockSyscall {
184+
data: Vec<u8>,
185+
}
186+
impl SyscallStubs for MockSyscall {
187+
fn sol_get_sysvar(
188+
&self,
189+
_sysvar_id_addr: *const u8,
190+
var_addr: *mut u8,
191+
offset: u64,
192+
length: u64,
193+
) -> u64 {
194+
unsafe {
195+
let slice = core::slice::from_raw_parts_mut(var_addr, length as usize);
196+
slice.copy_from_slice(&self.data[offset as usize..(offset + length) as usize]);
197+
}
198+
SUCCESS
199+
}
200+
}
201+
202+
set_syscall_stubs(Box::new(MockSyscall { data }));
203+
let got = EpochSchedule::get().unwrap();
204+
assert_eq!(got, expected);
205+
}
206+
}

0 commit comments

Comments
 (0)