Skip to content

Commit 2127efd

Browse files
committed
get-sysvar: extract sol_get_sysvar from solana-sysvar
1 parent 336d2e3 commit 2127efd

11 files changed

Lines changed: 212 additions & 97 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ members = [
3535
"frozen-abi",
3636
"frozen-abi-macro",
3737
"genesis-config",
38+
"get-sysvar",
3839
"hard-forks",
3940
"hash",
4041
"hash-512",
@@ -262,6 +263,7 @@ solana-file-download = { path = "file-download", version = "3.0.0" }
262263
solana-frozen-abi = { path = "frozen-abi", version = "3.5.0" }
263264
solana-frozen-abi-macro = { path = "frozen-abi-macro", version = "3.5.0", default-features = false }
264265
solana-genesis-config = { path = "genesis-config", version = "4.0.0" }
266+
solana-get-sysvar = { path = "get-sysvar", version = "0.0.0" }
265267
solana-hard-forks = { path = "hard-forks", version = "3.1.0", default-features = false }
266268
solana-hash = { path = "hash", version = "4.4.0", default-features = false }
267269
solana-hash-512 = { path = "hash-512", version = "1.1.0", default-features = false }

get-sysvar/Cargo.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "solana-get-sysvar"
3+
description = "Reads Solana sysvar account data from the runtime."
4+
documentation = "https://docs.rs/solana-get-sysvar"
5+
version = "0.0.0"
6+
authors = { workspace = true }
7+
repository = { workspace = true }
8+
homepage = { workspace = true }
9+
license = { workspace = true }
10+
edition = { workspace = true }
11+
12+
[package.metadata.docs.rs]
13+
targets = ["x86_64-unknown-linux-gnu"]
14+
all-features = true
15+
16+
[features]
17+
default = []
18+
std = []
19+
20+
[dependencies]
21+
solana-address = { workspace = true }
22+
solana-program-error = { workspace = true }
23+
24+
[target.'cfg(target_os = "solana")'.dependencies]
25+
solana-define-syscall = { workspace = true }
26+
27+
[lints]
28+
workspace = true

get-sysvar/src/lib.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! Access to the `sol_get_sysvar` syscall, used to fetch sysvar data from the runtime.
2+
//!
3+
//! On-chain calls go directly to the syscall. Off-chain calls are routed through a
4+
//! stub which test harnesses can install with [`set_get_sysvar_stub`] to serve mock
5+
//! sysvar data.
6+
#![no_std]
7+
#![cfg_attr(docsrs, feature(doc_cfg))]
8+
9+
#[cfg(all(not(target_os = "solana"), feature = "std"))]
10+
extern crate std;
11+
12+
use {solana_address::Address, solana_program_error::ProgramError};
13+
14+
#[cfg(all(not(target_os = "solana"), feature = "std"))]
15+
mod stubs;
16+
17+
#[cfg(all(not(target_os = "solana"), feature = "std"))]
18+
pub use stubs::{clear_get_sysvar_stub, set_get_sysvar_stub, GetSysvarStub};
19+
20+
/// Syscall success code.
21+
//
22+
// Defined in solana-program-entrypoint as [`SUCCESS`](https://github.com/anza-xyz/solana-sdk/blob/program-entrypoint@v2.2.1/program-entrypoint/src/lib.rs#L35).
23+
const SUCCESS: u64 = 0;
24+
/// Return value indicating that the `offset + length` is greater than the length of
25+
/// the sysvar data.
26+
//
27+
// Defined in the Agave syscalls crate as [`OFFSET_LENGTH_EXCEEDS_SYSVAR`](https://github.com/anza-xyz/agave/blob/v4.0.2/syscalls/src/sysvar.rs#L180).
28+
const OFFSET_LENGTH_EXCEEDS_SYSVAR: u64 = 1;
29+
30+
/// Return value indicating that the sysvar was not found.
31+
//
32+
// Defined in the Agave syscalls crate as [`SYSVAR_NOT_FOUND`](https://github.com/anza-xyz/agave/blob/v4.0.2/syscalls/src/sysvar.rs#L179).
33+
const SYSVAR_NOT_FOUND: u64 = 2;
34+
35+
/// Handler for retrieving a slice of sysvar data from the `sol_get_sysvar`
36+
/// syscall.
37+
pub fn get_sysvar(
38+
dst: &mut [u8],
39+
sysvar_id: &Address,
40+
offset: u64,
41+
length: u64,
42+
) -> Result<(), ProgramError> {
43+
// Check that the provided destination buffer is large enough to hold the requested data
44+
if dst.len() < length as usize {
45+
return Err(ProgramError::InvalidArgument);
46+
}
47+
48+
let sysvar_id = sysvar_id as *const _ as *const u8;
49+
let var_addr = dst as *mut _ as *mut u8;
50+
51+
match sol_get_sysvar(sysvar_id, var_addr, offset, length) {
52+
SUCCESS => Ok(()),
53+
OFFSET_LENGTH_EXCEEDS_SYSVAR => Err(ProgramError::InvalidArgument),
54+
_ => Err(ProgramError::UnsupportedSysvar),
55+
}
56+
}
57+
58+
/// Internal helper for retrieving sysvar data directly into a raw buffer.
59+
///
60+
/// # Safety
61+
///
62+
/// This function bypasses the slice-length check that `get_sysvar` performs.
63+
/// The caller must ensure that `var_addr` points to a writable buffer of at
64+
/// least `length` bytes. This is typically used with `MaybeUninit` to load
65+
/// compact representations of sysvars.
66+
#[doc(hidden)]
67+
pub unsafe fn get_sysvar_unchecked(
68+
var_addr: *mut u8,
69+
sysvar_id: *const u8,
70+
offset: u64,
71+
length: u64,
72+
) -> Result<(), ProgramError> {
73+
match sol_get_sysvar(sysvar_id, var_addr, offset, length) {
74+
SUCCESS => Ok(()),
75+
OFFSET_LENGTH_EXCEEDS_SYSVAR => Err(ProgramError::InvalidArgument),
76+
SYSVAR_NOT_FOUND => Err(ProgramError::UnsupportedSysvar),
77+
// Unexpected errors are folded into `UnsupportedSysvar`.
78+
_ => Err(ProgramError::UnsupportedSysvar),
79+
}
80+
}
81+
82+
// Dispatch to the syscall backend available for this target and feature set
83+
fn sol_get_sysvar(sysvar_id: *const u8, var_addr: *mut u8, offset: u64, length: u64) -> u64 {
84+
// On-chain programs call the runtime syscall directly
85+
#[cfg(target_os = "solana")]
86+
unsafe {
87+
solana_define_syscall::definitions::sol_get_sysvar(sysvar_id, var_addr, offset, length)
88+
}
89+
90+
// Off-chain std builds route through the host-test stub registry
91+
#[cfg(all(not(target_os = "solana"), feature = "std"))]
92+
{
93+
stubs::sol_get_sysvar(sysvar_id, var_addr, offset, length)
94+
}
95+
96+
// Off-chain no-std builds have neither runtime syscalls nor host stubs.
97+
#[cfg(all(not(target_os = "solana"), not(feature = "std")))]
98+
{
99+
let _ = (sysvar_id, var_addr, offset, length);
100+
solana_program_error::UNSUPPORTED_SYSVAR
101+
}
102+
}

get-sysvar/src/stubs.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use std::sync::{Arc, RwLock};
2+
3+
/// Off-chain stub implementation of the `sol_get_sysvar` syscall.
4+
pub trait GetSysvarStub: Sync + Send {
5+
/// Write `length` bytes from the sysvar identified by `sysvar_id_addr`,
6+
/// starting at `offset`, into `var_addr`.
7+
///
8+
/// Return `0` on success. Return the runtime syscall error code for
9+
/// unsupported sysvars or invalid ranges.
10+
fn sol_get_sysvar(
11+
&self,
12+
sysvar_id_addr: *const u8,
13+
var_addr: *mut u8,
14+
offset: u64,
15+
length: u64,
16+
) -> u64;
17+
}
18+
19+
static GET_SYSVAR_STUB: RwLock<Option<Arc<dyn GetSysvarStub>>> = RwLock::new(None);
20+
21+
/// Install the process-global `sol_get_sysvar` stub, returning the previous one.
22+
pub fn set_get_sysvar_stub(stub: Arc<dyn GetSysvarStub>) -> Option<Arc<dyn GetSysvarStub>> {
23+
replace_get_sysvar_stub(Some(stub))
24+
}
25+
26+
/// Clear the process-global `sol_get_sysvar` stub.
27+
pub fn clear_get_sysvar_stub() -> Option<Arc<dyn GetSysvarStub>> {
28+
replace_get_sysvar_stub(None)
29+
}
30+
31+
fn replace_get_sysvar_stub(stub: Option<Arc<dyn GetSysvarStub>>) -> Option<Arc<dyn GetSysvarStub>> {
32+
std::mem::replace(&mut *GET_SYSVAR_STUB.write().unwrap(), stub)
33+
}
34+
35+
pub(crate) fn sol_get_sysvar(
36+
sysvar_id_addr: *const u8,
37+
var_addr: *mut u8,
38+
offset: u64,
39+
length: u64,
40+
) -> u64 {
41+
let stub = GET_SYSVAR_STUB.read().unwrap().clone();
42+
match stub {
43+
Some(stub) => stub.sol_get_sysvar(sysvar_id_addr, var_addr, offset, length),
44+
None => solana_program_error::UNSUPPORTED_SYSVAR,
45+
}
46+
}

scripts/check-no-std.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ no_std_crates=(
2020
-p solana-epoch-schedule
2121
-p solana-epoch-stake
2222
-p solana-fee-calculator
23+
-p solana-get-sysvar
2324
-p solana-hash
2425
-p solana-instruction-view
2526
-p solana-keccak-hasher

scripts/patch-crates-functions.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ all_crate_dirs=(
3939
frozen-abi
4040
frozen-abi-macro
4141
genesis-config
42+
get-sysvar
4243
hard-forks
4344
hash
4445
hash-512

sysvar/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ solana-epoch-schedule = { workspace = true, features = ["sysvar"] }
6161
solana-fee-calculator = { workspace = true }
6262
solana-frozen-abi = { workspace = true, optional = true, features = ["frozen-abi"] }
6363
solana-frozen-abi-macro = { workspace = true, optional = true }
64+
solana-get-sysvar = { workspace = true, features = ["std"] }
6465
solana-hash = { workspace = true, features = ["bytemuck"] }
6566
solana-last-restart-slot = { workspace = true, features = ["sysvar"] }
6667
solana-program-entrypoint = { workspace = true }

sysvar/src/clock.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ mod tests {
188188
let got = Clock::get().unwrap();
189189
assert_eq!(got, expected);
190190

191-
let _ = crate::program_stubs::set_syscall_stubs(prev);
191+
// Restore the process-global stub to the state this test replaced.
192+
match prev {
193+
Some(prev) => {
194+
solana_get_sysvar::set_get_sysvar_stub(prev);
195+
}
196+
None => {
197+
solana_get_sysvar::clear_get_sysvar_stub();
198+
}
199+
}
192200
}
193201
}

0 commit comments

Comments
 (0)