diff --git a/sdk/pinocchio/Cargo.toml b/sdk/pinocchio/Cargo.toml index 854ccda1..b69f1bd0 100644 --- a/sdk/pinocchio/Cargo.toml +++ b/sdk/pinocchio/Cargo.toml @@ -19,6 +19,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ [features] std = [] +syscalls-stubs = ["std"] [dev-dependencies] five8_const = { workspace = true } diff --git a/sdk/pinocchio/src/cpi.rs b/sdk/pinocchio/src/cpi.rs index cdecc68d..64fdb867 100644 --- a/sdk/pinocchio/src/cpi.rs +++ b/sdk/pinocchio/src/cpi.rs @@ -127,10 +127,14 @@ pub fn invoke_signed( account_infos: &[&AccountInfo; ACCOUNTS], signers_seeds: &[Signer], ) -> ProgramResult { + #[cfg(feature = "syscalls-stubs")] + return crate::syscalls_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds); + // SAFETY: The array of `AccountInfo`s will be checked to ensure that it has // the same number of accounts as the instruction – this indirectly validates // that the stack allocated account storage `ACCOUNTS` is sufficient for the // number of accounts expected by the instruction. + #[cfg(not(feature = "syscalls-stubs"))] unsafe { inner_invoke_signed_with_bounds::(instruction, account_infos, signers_seeds) } @@ -450,7 +454,10 @@ pub fn set_return_data(data: &[u8]) { crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64) }; - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_set_return_data(data); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box(data); } @@ -510,7 +517,10 @@ pub fn get_return_data() -> Option { } } - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + return crate::syscalls_stubs::sol_get_return_data(); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box(None) } diff --git a/sdk/pinocchio/src/lib.rs b/sdk/pinocchio/src/lib.rs index 4693036c..92162c70 100644 --- a/sdk/pinocchio/src/lib.rs +++ b/sdk/pinocchio/src/lib.rs @@ -236,6 +236,8 @@ pub mod program { pub mod program_error; pub mod pubkey; pub mod syscalls; +#[cfg(feature = "syscalls-stubs")] +pub mod syscalls_stubs; pub mod sysvars; #[deprecated(since = "0.7.0", note = "Use the `entrypoint` module instead")] diff --git a/sdk/pinocchio/src/log.rs b/sdk/pinocchio/src/log.rs index d978cbec..a2c9d24a 100644 --- a/sdk/pinocchio/src/log.rs +++ b/sdk/pinocchio/src/log.rs @@ -99,7 +99,10 @@ pub fn sol_log(message: &str) { crate::syscalls::sol_log_(message.as_ptr(), message.len() as u64); } - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_log(message); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box(message); } @@ -111,7 +114,10 @@ pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { crate::syscalls::sol_log_64_(arg1, arg2, arg3, arg4, arg5); } - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_log_64(arg1, arg2, arg3, arg4, arg5); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box((arg1, arg2, arg3, arg4, arg5)); } @@ -122,7 +128,10 @@ pub fn sol_log_data(data: &[&[u8]]) { crate::syscalls::sol_log_data(data as *const _ as *const u8, data.len() as u64) }; - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_log_data(data); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box(data); } diff --git a/sdk/pinocchio/src/memory.rs b/sdk/pinocchio/src/memory.rs index 3051c102..c38af823 100644 --- a/sdk/pinocchio/src/memory.rs +++ b/sdk/pinocchio/src/memory.rs @@ -37,7 +37,10 @@ pub unsafe fn sol_memcpy(dst: &mut [u8], src: &[u8], n: usize) { #[cfg(target_os = "solana")] syscalls::sol_memcpy_(dst.as_mut_ptr(), src.as_ptr(), n as u64); - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_memcpy(dst, src, n); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box((dst, src, n)); } @@ -103,7 +106,10 @@ pub unsafe fn sol_memmove(dst: *mut u8, src: *const u8, n: usize) { #[cfg(target_os = "solana")] syscalls::sol_memmove_(dst, src, n as u64); - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_memmove(dst, src, n); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box((dst, src, n)); } @@ -137,7 +143,10 @@ pub unsafe fn sol_memcmp(s1: &[u8], s2: &[u8], n: usize) -> i32 { #[cfg(target_os = "solana")] syscalls::sol_memcmp_(s1.as_ptr(), s2.as_ptr(), n as u64, &mut result as *mut i32); - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_memcmp(s1, s2, n, &mut result); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box((s1, s2, n, result)); result @@ -170,6 +179,9 @@ pub unsafe fn sol_memset(s: &mut [u8], c: u8, n: usize) { #[cfg(target_os = "solana")] syscalls::sol_memset_(s.as_mut_ptr(), c, n as u64); - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_memset(s, c, n); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box((s, c, n)); } diff --git a/sdk/pinocchio/src/pubkey.rs b/sdk/pinocchio/src/pubkey.rs index 7a8480fb..c5115bb6 100644 --- a/sdk/pinocchio/src/pubkey.rs +++ b/sdk/pinocchio/src/pubkey.rs @@ -29,7 +29,10 @@ pub fn log(pubkey: &Pubkey) { crate::syscalls::sol_log_pubkey(pubkey as *const _ as *const u8) }; - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + crate::syscalls_stubs::sol_log_pubkey(pubkey); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] core::hint::black_box(pubkey); } @@ -144,7 +147,10 @@ pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option< } } - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + return crate::syscalls_stubs::sol_try_find_program_address(seeds, program_id); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] { core::hint::black_box((seeds, program_id)); None @@ -197,7 +203,10 @@ pub fn create_program_address( } } - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + return crate::syscalls_stubs::sol_create_program_address(seeds, program_id); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] { core::hint::black_box((seeds, program_id)); panic!("create_program_address is only available on target `solana`") @@ -271,7 +280,10 @@ pub fn create_with_seed( Ok(unsafe { bytes.assume_init() }) } - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + return crate::syscalls_stubs::sol_create_with_seed(base, seed, program_id); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] { core::hint::black_box((base, seed, program_id)); panic!("create_with_seed is only available on target `solana`") diff --git a/sdk/pinocchio/src/syscalls_stubs.rs b/sdk/pinocchio/src/syscalls_stubs.rs new file mode 100644 index 00000000..053ad408 --- /dev/null +++ b/sdk/pinocchio/src/syscalls_stubs.rs @@ -0,0 +1,130 @@ +//! Custom syscall to use when pinocchio is built for non-SBF targets with `syscalls-stubs` feature +//! flag. Requires "std". + +#![cfg(not(target_os = "solana"))] + +use { + crate::{ + account_info::AccountInfo, + instruction::{Instruction, Signer}, + program::ReturnData, + program_error::ProgramError, + pubkey::Pubkey, + ProgramResult, SUCCESS, + }, + std::{ + boxed::Box, + sync::{LazyLock, RwLock}, + }, +}; + +#[allow(clippy::incompatible_msrv)] +static SYSCALL_STUBS: LazyLock>> = + LazyLock::new(|| RwLock::new(Box::new(DefaultSyscallStubs {}))); + +pub fn set_syscall_stubs(syscall_stubs: Box) -> Box { + std::mem::replace(&mut SYSCALL_STUBS.write().unwrap(), syscall_stubs) +} + +pub trait SyscallStubs: Sync + Send { + fn sol_log(&self, _message: &str) {} + + fn sol_log_64(&self, _arg1: u64, _arg2: u64, _arg3: u64, _arg4: u64, _arg5: u64) {} + + fn sol_log_data(&self, _data: &[&[u8]]) {} + + fn sol_log_pubkey(&self, _pubkey: &Pubkey) {} + + fn sol_try_find_program_address( + &self, + _seeds: &[&[u8]], + _program_id: &Pubkey, + ) -> Option<(Pubkey, u8)> { + None + } + + fn sol_create_program_address( + &self, + _seeds: &[&[u8]], + _program_id: &Pubkey, + ) -> Result { + Err(ProgramError::UnsupportedSysvar) + } + + fn sol_create_with_seed( + &self, + _base: &Pubkey, + _seed: &[u8], + _program_id: &Pubkey, + ) -> Result { + Err(ProgramError::UnsupportedSysvar) + } + + fn sol_invoke_signed( + &self, + _instruction: &Instruction, + _account_infos: &[&AccountInfo], + _signers_seeds: &[Signer], + ) -> ProgramResult { + Err(ProgramError::UnsupportedSysvar) + } + + fn sol_set_return_data(&self, _data: &[u8]) {} + + fn sol_get_return_data(&self) -> Option { + None + } + + fn sol_get_clock_sysvar(&self, _var_addr: *mut u8) -> u64 { + !SUCCESS + } + + fn sol_get_fees_sysvar(&self, _var_addr: *mut u8) -> u64 { + !SUCCESS + } + + fn sol_get_rent_sysvar(&self, _var_addr: *mut u8) -> u64 { + !SUCCESS + } + + fn sol_memcpy(&self, _dst: &mut [u8], _src: &[u8], _n: usize) {} + + fn sol_memmove(&self, _dst: *mut u8, _src: *const u8, _n: usize) {} + + fn sol_memcmp(&self, _s1: &[u8], _s2: &[u8], _n: usize, _result: &mut i32) {} + + fn sol_memset(&self, _s: &mut [u8], _c: u8, _n: usize) {} +} + +struct DefaultSyscallStubs {} +impl SyscallStubs for DefaultSyscallStubs {} + +macro_rules! define_stub { + (fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => { + pub(crate) fn $name($($arg: $typ),*) -> $ret { + SYSCALL_STUBS.read().unwrap().$name($($arg),*) + } + }; + + (fn $name:ident($($arg:ident: $typ:ty),*)) => { + define_stub!(fn $name($($arg: $typ),*) -> ()); + }; +} + +define_stub!(fn sol_log(message: &str)); +define_stub!(fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64)); +define_stub!(fn sol_log_data(data: &[&[u8]])); +define_stub!(fn sol_log_pubkey(pubkey: &Pubkey)); +define_stub!(fn sol_try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)>); +define_stub!(fn sol_create_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Result); +define_stub!(fn sol_create_with_seed(base: &Pubkey, seed: &[u8], program_id: &Pubkey) -> Result); +define_stub!(fn sol_invoke_signed(instruction: &Instruction, account_infos: &[&AccountInfo], signers_seeds: &[Signer]) -> ProgramResult); +define_stub!(fn sol_set_return_data(data: &[u8])); +define_stub!(fn sol_get_return_data() -> Option); +define_stub!(fn sol_get_clock_sysvar(var_addr: *mut u8) -> u64); +define_stub!(fn sol_get_fees_sysvar(var_addr: *mut u8) -> u64); +define_stub!(fn sol_get_rent_sysvar(var_addr: *mut u8) -> u64); +define_stub!(fn sol_memcpy(dst: &mut [u8], src: &[u8], n: usize)); +define_stub!(fn sol_memmove(dst: *mut u8, src: *const u8, n: usize)); +define_stub!(fn sol_memcmp(s1: &[u8], s2: &[u8], n: usize, result: &mut i32)); +define_stub!(fn sol_memset(s: &mut [u8], c: u8, n: usize)); diff --git a/sdk/pinocchio/src/sysvars/mod.rs b/sdk/pinocchio/src/sysvars/mod.rs index 9d759c2e..7abeeae7 100644 --- a/sdk/pinocchio/src/sysvars/mod.rs +++ b/sdk/pinocchio/src/sysvars/mod.rs @@ -51,7 +51,10 @@ macro_rules! impl_sysvar_get { #[cfg(target_os = "solana")] let result = unsafe { $crate::syscalls::$syscall_name(var_addr) }; - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "syscalls-stubs"))] + let result = $crate::syscalls_stubs::$syscall_name(var_addr); + + #[cfg(all(not(target_os = "solana"), not(feature = "syscalls-stubs")))] let result = core::hint::black_box(var_addr as *const _ as u64); match result {