Skip to content

Commit f892e68

Browse files
committed
Implement space-efficient panic
1 parent 45c1069 commit f892e68

File tree

8 files changed

+162
-2
lines changed

8 files changed

+162
-2
lines changed

programs/sbf/Cargo.lock

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

programs/sbf/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ members = [
161161
"rust/deprecated_loader",
162162
"rust/divide_by_zero",
163163
"rust/dup_accounts",
164+
"rust/efficient_panic",
164165
"rust/error_handling",
165166
"rust/external_spend",
166167
"rust/finalize",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "solana-sbf-rust-efficient-panic"
3+
version = { workspace = true }
4+
description = { workspace = true }
5+
authors = { workspace = true }
6+
repository = { workspace = true }
7+
homepage = { workspace = true }
8+
license = { workspace = true }
9+
edition = { workspace = true }
10+
11+
[dependencies]
12+
solana-program = { workspace = true }
13+
14+
[features]
15+
default = ["efficient-panic"]
16+
efficient-panic = []
17+
18+
[lib]
19+
crate-type = ["cdylib"]
20+
21+
[lints]
22+
workspace = true
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Cargo clippy runs with 1.81, but cargo-build-sbf is on 1.79.
2+
#![allow(stable_features)]
3+
#![feature(panic_info_message)]
4+
5+
use solana_program::{
6+
account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
7+
pubkey::Pubkey,
8+
};
9+
10+
solana_program::entrypoint!(process_instruction);
11+
12+
fn process_instruction(
13+
_program_id: &Pubkey,
14+
_accounts: &[AccountInfo],
15+
instruction_data: &[u8],
16+
) -> ProgramResult {
17+
match instruction_data[0] {
18+
0 => {
19+
// Accessing an invalid index creates a dynamic generated panic message.
20+
let a = instruction_data[92341];
21+
return Err(ProgramError::Custom(a as u32));
22+
}
23+
1 => {
24+
// Unwrap on a None emit a compiler constant panic message.
25+
let a: Option<u64> = None;
26+
#[allow(clippy::unnecessary_literal_unwrap)]
27+
let b = a.unwrap();
28+
return Err(ProgramError::Custom(b as u32));
29+
}
30+
31+
_ => (),
32+
}
33+
Ok(())
34+
}

programs/sbf/tests/programs.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5662,3 +5662,59 @@ fn test_mem_syscalls_overlap_account_begin_or_end() {
56625662
}
56635663
}
56645664
}
5665+
5666+
#[test]
5667+
#[cfg(feature = "sbf_rust")]
5668+
fn test_efficient_panic() {
5669+
let GenesisConfigInfo {
5670+
genesis_config,
5671+
mint_keypair,
5672+
..
5673+
} = create_genesis_config(50);
5674+
5675+
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
5676+
let mut bank_client = BankClient::new_shared(bank.clone());
5677+
let authority_keypair = Keypair::new();
5678+
5679+
let (bank, program_id) = load_upgradeable_program_and_advance_slot(
5680+
&mut bank_client,
5681+
bank_forks.as_ref(),
5682+
&mint_keypair,
5683+
&authority_keypair,
5684+
"solana_sbf_rust_efficient_panic",
5685+
);
5686+
5687+
bank.freeze();
5688+
5689+
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
5690+
let instruction = Instruction::new_with_bytes(program_id, &[0, 2], account_metas.clone());
5691+
5692+
let blockhash = bank.last_blockhash();
5693+
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
5694+
let transaction = Transaction::new(&[&mint_keypair], message, blockhash);
5695+
let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);
5696+
5697+
let result = bank.simulate_transaction(&sanitized_tx, false);
5698+
assert!(result.logs.contains(&format!(
5699+
"Program {} \
5700+
failed: SBF program Panicked in rust/efficient_panic/src/lib.rs at 22:21",
5701+
program_id
5702+
)));
5703+
5704+
let instruction = Instruction::new_with_bytes(program_id, &[1, 2], account_metas);
5705+
5706+
let blockhash = bank.last_blockhash();
5707+
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
5708+
let transaction = Transaction::new(&[&mint_keypair], message, blockhash);
5709+
let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);
5710+
5711+
let result = bank.simulate_transaction(&sanitized_tx, false);
5712+
assert!(result
5713+
.logs
5714+
.contains(&"Program log: called `Option::unwrap()` on a `None` value".to_string()));
5715+
assert!(result.logs.contains(&format!(
5716+
"Program {} \
5717+
failed: SBF program Panicked in rust/efficient_panic/src/lib.rs at 28:23",
5718+
program_id
5719+
)));
5720+
}

sdk/define-syscall/src/definitions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ define_syscall!(fn sol_remaining_compute_units() -> u64, SOL_REMAINING_COMPUTE_U
3333
define_syscall!(fn sol_alt_bn128_compression(op: u64, input: *const u8, input_size: u64, result: *mut u8) -> u64, SOL_ALT_BN128_COMPRESSION);
3434
define_syscall!(fn sol_get_sysvar(sysvar_id_addr: *const u8, result: *mut u8, offset: u64, length: u64) -> u64, SOL_GET_SYSVAR);
3535
define_syscall!(fn sol_get_epoch_stake(vote_address: *const u8) -> u64, SOL_GET_EPOCH_STAKE);
36+
define_syscall!(fn sol_panic_(filename: *const u8, filename_len: u64, line: u64, column: u64), SOL_PANIC_);
3637

3738
// these are to be deprecated once they are superceded by sol_get_sysvar
3839
define_syscall!(fn sol_get_clock_sysvar(addr: *mut u8) -> u64, SOL_GET_CLOCK_SYSVAR);

sdk/program-entrypoint/src/lib.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ macro_rules! entrypoint {
136136
}
137137
$crate::custom_heap_default!();
138138
$crate::custom_panic_default!();
139+
$crate::efficient_panic!();
139140
};
140141
}
141142

@@ -189,6 +190,7 @@ macro_rules! entrypoint_no_alloc {
189190
}
190191
$crate::custom_heap_default!();
191192
$crate::custom_panic_default!();
193+
$crate::efficient_panic!();
192194
};
193195
}
194196

@@ -270,7 +272,10 @@ macro_rules! custom_heap_default {
270272
#[macro_export]
271273
macro_rules! custom_panic_default {
272274
() => {
273-
#[cfg(all(not(feature = "custom-panic"), target_os = "solana"))]
275+
#[cfg(all(
276+
not(any(feature = "custom-panic", feature = "efficient-panic")),
277+
target_os = "solana"
278+
))]
274279
#[no_mangle]
275280
fn custom_panic(info: &core::panic::PanicInfo<'_>) {
276281
// Full panic reporting
@@ -279,6 +284,40 @@ macro_rules! custom_panic_default {
279284
};
280285
}
281286

287+
/// This is an efficient implementation fo custom panic. It contains two syscalls and has a size
288+
/// of about 264 bytes. This is an alternative of the existing panic to decrease programs size and
289+
/// requires Rust 1.84.
290+
#[macro_export]
291+
macro_rules! efficient_panic {
292+
() => {
293+
#[cfg(all(
294+
not(feature = "custom-panic"),
295+
feature = "efficient-panic",
296+
target_os = "solana"
297+
))]
298+
#[no_mangle]
299+
fn custom_panic(info: &core::panic::PanicInfo<'_>) {
300+
if let Some(Some(mm)) = info.message().map(|mes| mes.as_str()) {
301+
let mes = mm.as_bytes();
302+
unsafe {
303+
solana_program::syscalls::sol_log_(mes.as_ptr(), mes.len() as u64);
304+
}
305+
}
306+
307+
if let Some(loc) = info.location() {
308+
unsafe {
309+
solana_program::syscalls::sol_panic_(
310+
loc.file().as_ptr(),
311+
loc.file().len() as u64,
312+
loc.line() as u64,
313+
loc.column() as u64,
314+
);
315+
}
316+
}
317+
}
318+
};
319+
}
320+
282321
/// The bump allocator used as the default rust heap when running programs.
283322
pub struct BumpAllocator {
284323
pub start: usize,

sdk/program/src/syscalls/definitions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub use solana_define_syscall::definitions::{
1111
sol_curve_group_op, sol_curve_multiscalar_mul, sol_curve_pairing_map, sol_curve_validate_point,
1212
sol_get_clock_sysvar, sol_get_epoch_rewards_sysvar, sol_get_epoch_schedule_sysvar,
1313
sol_get_epoch_stake, sol_get_fees_sysvar, sol_get_last_restart_slot, sol_get_rent_sysvar,
14-
sol_get_sysvar, sol_keccak256, sol_remaining_compute_units,
14+
sol_get_sysvar, sol_keccak256, sol_panic_, sol_remaining_compute_units,
1515
};
1616
#[deprecated(since = "2.1.0", note = "Use `solana_instruction::syscalls` instead")]
1717
pub use solana_instruction::syscalls::{

0 commit comments

Comments
 (0)