Skip to content

Commit c007ced

Browse files
authored
Support registering custom syscalls (#128)
1 parent fc1d66b commit c007ced

File tree

7 files changed

+160
-26
lines changed

7 files changed

+160
-26
lines changed

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.

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ audit:
2929
# Build test programs
3030
build-test-programs:
3131
@cargo build-sbf --manifest-path test-programs/cpi-target/Cargo.toml
32+
@cargo build-sbf --manifest-path test-programs/custom-syscall/Cargo.toml
3233
@cargo build-sbf --manifest-path test-programs/primary/Cargo.toml
3334

3435
# Pre-publish checks
@@ -84,6 +85,7 @@ check-features:
8485

8586
# Run tests
8687
test:
88+
@$(MAKE) build-test-programs
8789
@cargo test --all-features
8890

8991
# Run all checks in sequence

harness/src/lib.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ impl Default for Mollusk {
503503
solana_runtime::message_processor=debug,\
504504
solana_runtime::system_instruction_processor=trace",
505505
);
506+
let compute_budget = ComputeBudget::default();
506507
#[cfg(feature = "fuzz")]
507508
let feature_set = {
508509
// Omit "test features" (they have the same u64 ID).
@@ -515,12 +516,13 @@ impl Default for Mollusk {
515516
};
516517
#[cfg(not(feature = "fuzz"))]
517518
let feature_set = FeatureSet::all_enabled();
519+
let program_cache = ProgramCache::new(&feature_set, &compute_budget);
518520
Self {
519521
config: Config::default(),
520-
compute_budget: ComputeBudget::default(),
522+
compute_budget,
521523
feature_set,
522524
logger: None,
523-
program_cache: ProgramCache::default(),
525+
program_cache,
524526
sysvars: Sysvars::default(),
525527
#[cfg(feature = "fuzz-fd")]
526528
slot: 0,
@@ -573,13 +575,7 @@ impl Mollusk {
573575
elf: &[u8],
574576
loader_key: &Pubkey,
575577
) {
576-
self.program_cache.add_program(
577-
program_id,
578-
loader_key,
579-
elf,
580-
&self.compute_budget,
581-
&self.feature_set,
582-
);
578+
self.program_cache.add_program(program_id, loader_key, elf);
583579
}
584580

585581
/// Warp the test environment to a slot by updating sysvars.

harness/src/program.rs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ use {
88
solana_loader_v3_interface::state::UpgradeableLoaderState,
99
solana_loader_v4_interface::state::{LoaderV4State, LoaderV4Status},
1010
solana_program_runtime::{
11-
invoke_context::BuiltinFunctionWithContext,
11+
invoke_context::{BuiltinFunctionWithContext, InvokeContext},
1212
loaded_programs::{LoadProgramMetrics, ProgramCacheEntry, ProgramCacheForTxBatch},
13+
solana_sbpf::program::BuiltinProgram,
1314
},
1415
solana_pubkey::Pubkey,
1516
solana_rent::Rent,
@@ -57,13 +58,23 @@ pub struct ProgramCache {
5758
//
5859
// K: program ID, V: loader key
5960
entries_cache: Rc<RefCell<HashMap<Pubkey, Pubkey>>>,
61+
// The function registry (syscalls) to use for verifying and loading
62+
// program ELFs.
63+
pub program_runtime_environment: BuiltinProgram<InvokeContext<'static>>,
6064
}
6165

62-
impl Default for ProgramCache {
63-
fn default() -> Self {
66+
impl ProgramCache {
67+
pub fn new(feature_set: &FeatureSet, compute_budget: &ComputeBudget) -> Self {
6468
let me = Self {
6569
cache: Rc::new(RefCell::new(ProgramCacheForTxBatch::default())),
6670
entries_cache: Rc::new(RefCell::new(HashMap::new())),
71+
program_runtime_environment: create_program_runtime_environment_v1(
72+
feature_set,
73+
compute_budget,
74+
/* reject_deployment_of_broken_elfs */ false,
75+
/* debugging_features */ false,
76+
)
77+
.unwrap(),
6778
};
6879
BUILTINS.iter().for_each(|builtin| {
6980
let program_id = builtin.program_id;
@@ -72,9 +83,7 @@ impl Default for ProgramCache {
7283
});
7384
me
7485
}
75-
}
7686

77-
impl ProgramCache {
7887
pub(crate) fn cache(&self) -> RefMut<ProgramCacheForTxBatch> {
7988
self.cache.borrow_mut()
8089
}
@@ -94,18 +103,24 @@ impl ProgramCache {
94103
}
95104

96105
/// Add a program to the cache.
97-
pub fn add_program(
98-
&mut self,
99-
program_id: &Pubkey,
100-
loader_key: &Pubkey,
101-
elf: &[u8],
102-
compute_budget: &ComputeBudget,
103-
feature_set: &FeatureSet,
104-
) {
105-
let environment = Arc::new(
106-
create_program_runtime_environment_v1(feature_set, compute_budget, false, false)
107-
.unwrap(),
108-
);
106+
pub fn add_program(&mut self, program_id: &Pubkey, loader_key: &Pubkey, elf: &[u8]) {
107+
// This might look rough, but it's actually functionally the same as
108+
// calling `create_program_runtime_environment_v1` on every addition.
109+
let environment = {
110+
let config = self.program_runtime_environment.get_config().clone();
111+
let mut loader = BuiltinProgram::new_loader(config);
112+
113+
for (_key, (name, value)) in self
114+
.program_runtime_environment
115+
.get_function_registry()
116+
.iter()
117+
{
118+
let name = std::str::from_utf8(name).unwrap();
119+
loader.register_function(name, value).unwrap();
120+
}
121+
122+
Arc::new(loader)
123+
};
109124
self.replenish(
110125
*program_id,
111126
Arc::new(

harness/tests/custom_syscall.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use {
2+
mollusk_svm::{result::Check, Mollusk},
3+
solana_instruction::Instruction,
4+
solana_program_runtime::{
5+
invoke_context::InvokeContext,
6+
solana_sbpf::{declare_builtin_function, memory_region::MemoryMapping},
7+
},
8+
solana_pubkey::Pubkey,
9+
};
10+
11+
declare_builtin_function!(
12+
/// A custom syscall to burn CUs.
13+
SyscallBurnCus,
14+
fn rust(
15+
invoke_context: &mut InvokeContext,
16+
to_burn: u64,
17+
_arg2: u64,
18+
_arg3: u64,
19+
_arg4: u64,
20+
_arg5: u64,
21+
_memory_mapping: &mut MemoryMapping,
22+
) -> Result<u64, Box<dyn std::error::Error>> {
23+
invoke_context.consume_checked(to_burn)?;
24+
Ok(0)
25+
}
26+
);
27+
28+
fn instruction_burn_cus(program_id: &Pubkey, to_burn: u64) -> Instruction {
29+
Instruction::new_with_bytes(*program_id, &to_burn.to_le_bytes(), vec![])
30+
}
31+
32+
#[test]
33+
fn test_custom_syscall() {
34+
std::env::set_var("SBF_OUT_DIR", "../target/deploy");
35+
36+
let program_id = Pubkey::new_unique();
37+
38+
let mollusk = {
39+
let mut mollusk = Mollusk::default();
40+
mollusk
41+
.program_cache
42+
.program_runtime_environment
43+
.register_function("sol_burn_cus", SyscallBurnCus::vm)
44+
.unwrap();
45+
mollusk.add_program(
46+
&program_id,
47+
"custom_syscall_program",
48+
&mollusk_svm::program::loader_keys::LOADER_V3,
49+
);
50+
mollusk
51+
};
52+
53+
let base_cus = mollusk
54+
.process_and_validate_instruction(
55+
&instruction_burn_cus(&program_id, 0),
56+
&[],
57+
&[Check::success()],
58+
)
59+
.compute_units_consumed;
60+
61+
for to_burn in [100, 1_000, 10_000] {
62+
mollusk.process_and_validate_instruction(
63+
&instruction_burn_cus(&program_id, to_burn),
64+
&[],
65+
&[Check::success(), Check::compute_units(base_cus + to_burn)],
66+
);
67+
}
68+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "custom-syscall-program"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
solana-account-info = { workspace = true }
8+
solana-program-entrypoint = { workspace = true }
9+
solana-program-error = { workspace = true }
10+
solana-pubkey = { workspace = true }
11+
12+
[lib]
13+
crate-type = ["cdylib", "lib"]
14+
15+
[lints]
16+
workspace = true
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use {solana_account_info::AccountInfo, solana_program_error::ProgramError, solana_pubkey::Pubkey};
2+
3+
// Declare the custom syscall that we expect to be registered.
4+
// This matches the `sol_burn_cus` syscall from the test.
5+
extern "C" {
6+
fn sol_burn_cus(to_burn: u64) -> u64;
7+
}
8+
9+
solana_program_entrypoint::entrypoint!(process_instruction);
10+
11+
fn process_instruction(
12+
_program_id: &Pubkey,
13+
_accounts: &[AccountInfo],
14+
input: &[u8],
15+
) -> Result<(), ProgramError> {
16+
let to_burn = input
17+
.get(0..8)
18+
.and_then(|bytes| bytes.try_into().map(u64::from_le_bytes).ok())
19+
.ok_or(ProgramError::InvalidInstructionData)?;
20+
21+
// Call the custom syscall to burn CUs.
22+
unsafe {
23+
sol_burn_cus(to_burn);
24+
}
25+
26+
Ok(())
27+
}

0 commit comments

Comments
 (0)