rialo_s_program_entrypoint/lib.rs
1//! The Rust-based BPF program entrypoint supported by the latest BPF loader.
2#![allow(unsafe_code)]
3#![allow(clippy::ptr_as_ptr)]
4
5extern crate alloc;
6
7/// Not part of the public API. Used by macros.
8#[cfg(target_arch = "riscv64")]
9#[doc(hidden)]
10pub mod __private {
11 pub use polkavm_derive::{self, default_abi, polkavm_export};
12}
13use alloc::vec::Vec;
14use std::{
15 cell::RefCell,
16 mem::{size_of, MaybeUninit},
17 rc::Rc,
18 slice::{from_raw_parts, from_raw_parts_mut},
19};
20
21use rialo_s_account_info::AccountInfo;
22use rialo_s_pubkey::Pubkey;
23// need to re-export msg for custom_heap_default macro
24pub use {
25 crate::allocator::BumpAllocator, rialo_s_account_info::MAX_PERMITTED_DATA_INCREASE,
26 rialo_s_msg::msg as __msg, rialo_s_program_error::ProgramResult,
27};
28
29pub mod allocator;
30
31/// User implemented function to process an instruction
32///
33/// program_id: Program ID of the currently executing program accounts: Accounts
34/// passed as part of the instruction instruction_data: Instruction data
35pub type ProcessInstruction =
36 fn(program_id: &Pubkey, accounts: &[AccountInfo<'_>], instruction_data: &[u8]) -> ProgramResult;
37
38/// Programs indicate success with a return value of 0
39pub const SUCCESS: u64 = 0;
40
41/// Length of the heap memory region used for program heap.
42pub const HEAP_LENGTH: usize = 32 * 1024;
43
44/// Value used to indicate that a serialized account is not a duplicate
45pub const NON_DUP_MARKER: u8 = u8::MAX;
46
47/// Declare the program entrypoint and set up global handlers.
48///
49/// This macro emits the common boilerplate necessary to begin program
50/// execution, calling a provided function to process the program instruction
51/// supplied by the runtime, and reporting its result to the runtime.
52///
53/// It also sets up a [global allocator] and [panic handler], using the
54/// [`custom_heap_default`] and [`custom_panic_default`] macros.
55///
56/// [`custom_heap_default`]: crate::custom_heap_default
57/// [`custom_panic_default`]: crate::custom_panic_default
58///
59/// [global allocator]: https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html
60/// [panic handler]: https://doc.rust-lang.org/nomicon/panic-handler.html
61///
62/// The argument is the name of a function with this type signature:
63///
64/// ```ignore
65/// fn process_instruction(
66/// program_id: &Pubkey, // Public key of the account the program was loaded into
67/// accounts: &[AccountInfo], // All accounts required to process the instruction
68/// instruction_data: &[u8], // Serialized instruction-specific data
69/// ) -> ProgramResult;
70/// ```
71///
72/// # Cargo features
73///
74/// This macro emits symbols and definitions that may only be defined once
75/// globally. As such, if linked to other Rust crates it will cause compiler
76/// errors. To avoid this, it is common for Solana programs to define an
77/// optional [Cargo feature] called `no-entrypoint`, and use it to conditionally
78/// disable the `entrypoint` macro invocation, as well as the
79/// `process_instruction` function. See a typical pattern for this in the
80/// example below.
81///
82/// [Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html
83///
84/// The code emitted by this macro can be customized by adding cargo features
85/// _to your own crate_ (the one that calls this macro) and enabling them:
86///
87/// - If the `custom-heap` feature is defined then the macro will not set up the
88/// global allocator, allowing `entrypoint` to be used with your own
89/// allocator. See documentation for the [`custom_heap_default`] macro for
90/// details of customizing the global allocator.
91///
92/// - If the `custom-panic` feature is defined then the macro will not define a
93/// panic handler, allowing `entrypoint` to be used with your own panic
94/// handler. See documentation for the [`custom_panic_default`] macro for
95/// details of customizing the panic handler.
96///
97/// # Examples
98///
99/// Defining an entrypoint and making it conditional on the `no-entrypoint`
100/// feature. Although the `entrypoint` module is written inline in this example,
101/// it is common to put it into its own file.
102///
103/// ```no_run
104/// #[cfg(not(feature = "no-entrypoint"))]
105/// pub mod entrypoint {
106///
107/// use rialo_s_account_info::AccountInfo;
108/// use rialo_s_program_entrypoint::entrypoint;
109/// use rialo_s_program_entrypoint::ProgramResult;
110/// use rialo_s_msg::msg;
111/// use rialo_s_pubkey::Pubkey;
112///
113/// entrypoint!(process_instruction);
114///
115/// pub fn process_instruction(
116/// program_id: &Pubkey,
117/// accounts: &[AccountInfo],
118/// instruction_data: &[u8],
119/// ) -> ProgramResult {
120/// msg!("Hello world");
121///
122/// Ok(())
123/// }
124///
125/// }
126/// ```
127#[cfg(not(target_arch = "riscv64"))]
128#[macro_export]
129macro_rules! entrypoint {
130 ($process_instruction:ident) => {
131 /// # Safety
132 #[no_mangle]
133 pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
134 let (program_id, accounts, instruction_data) = unsafe { $crate::deserialize(input) };
135 match $process_instruction(program_id, &accounts, instruction_data) {
136 Ok(()) => $crate::SUCCESS,
137 Err(error) => error.into(),
138 }
139 }
140 $crate::custom_heap_default!();
141 $crate::custom_panic_default!();
142 };
143}
144
145#[cfg(target_arch = "riscv64")]
146#[macro_export]
147macro_rules! entrypoint {
148 ($process_instruction:expr) => {
149 #[$crate::__private::polkavm_export(abi = $crate::__private::polkavm_derive::default_abi)]
150 extern "C" fn entrypoint(addr: u32, _len: u32) -> u64 {
151 let input = addr as usize as *mut u8;
152 let (program_id, accounts, instruction_data) =
153 unsafe { $crate::deserialize(addr as usize as *mut u8) };
154
155 match $process_instruction(program_id, &accounts, instruction_data) {
156 Ok(()) => $crate::SUCCESS,
157 Err(error) => error.into(),
158 }
159 }
160 $crate::custom_heap_default!();
161 $crate::custom_panic_default!();
162 };
163}
164
165/// Declare the program entrypoint and set up global handlers.
166///
167/// This is similar to the `entrypoint!` macro, except that it does not perform
168/// any dynamic allocations, and instead writes the input accounts into a pre-
169/// allocated array.
170///
171/// This version reduces compute unit usage by 20-30 compute units per unique
172/// account in the instruction. It may become the default option in a future
173/// release.
174///
175/// For more information about how the program entrypoint behaves and what it
176/// does, please see the documentation for [`entrypoint!`].
177///
178/// NOTE: This entrypoint has a hard-coded limit of 64 input accounts.
179#[cfg(not(target_arch = "riscv64"))]
180#[macro_export]
181macro_rules! entrypoint_no_alloc {
182 ($process_instruction:ident) => {
183 /// # Safety
184 #[no_mangle]
185 pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
186 use std::mem::MaybeUninit;
187 // Clippy complains about this because a `const` with interior
188 // mutability `RefCell` should use `static` instead to make it
189 // clear that it can change.
190 // In our case, however, we want to create an array of `AccountInfo`s,
191 // and the only way to do it is through a `const` expression, and
192 // we don't expect to mutate the internals of this `const` type.
193 #[allow(clippy::declare_interior_mutable_const)]
194 const UNINIT_ACCOUNT_INFO: MaybeUninit<AccountInfo> =
195 MaybeUninit::<AccountInfo>::uninit();
196 const MAX_ACCOUNT_INFOS: usize = 64;
197 let mut accounts = [UNINIT_ACCOUNT_INFO; MAX_ACCOUNT_INFOS];
198 let (program_id, num_accounts, instruction_data) =
199 unsafe { $crate::deserialize_into(input, &mut accounts) };
200 // Use `slice_assume_init_ref` once it's stabilized
201 let accounts = &*(&accounts[..num_accounts] as *const [MaybeUninit<AccountInfo<'_>>]
202 as *const [AccountInfo<'_>]);
203
204 #[inline(never)]
205 fn call_program(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> u64 {
206 match $process_instruction(program_id, accounts, data) {
207 Ok(()) => $crate::SUCCESS,
208 Err(error) => error.into(),
209 }
210 }
211
212 call_program(&program_id, accounts, &instruction_data)
213 }
214 $crate::custom_heap_default!();
215 $crate::custom_panic_default!();
216 };
217}
218
219#[cfg(target_arch = "riscv64")]
220#[macro_export]
221macro_rules! entrypoint_no_alloc {
222 ($process_instruction:expr) => {
223 #[$crate::__private::polkavm_export(abi = $crate::__private::polkavm_derive::default_abi)]
224 pub unsafe extern "C" fn entrypoint(addr: u32, len: u32) -> u64 {
225 use std::mem::MaybeUninit;
226 // Clippy complains about this because a `const` with interior
227 // mutability `RefCell` should use `static` instead to make it
228 // clear that it can change.
229 // In our case, however, we want to create an array of `AccountInfo`s,
230 // and the only way to do it is through a `const` expression, and
231 // we don't expect to mutate the internals of this `const` type.
232 #[allow(clippy::declare_interior_mutable_const)]
233 const UNINIT_ACCOUNT_INFO: MaybeUninit<AccountInfo> =
234 MaybeUninit::<AccountInfo>::uninit();
235 const MAX_ACCOUNT_INFOS: usize = 64;
236 let mut accounts = [UNINIT_ACCOUNT_INFO; MAX_ACCOUNT_INFOS];
237 let input = addr as usize as *mut u8;
238 let (program_id, num_accounts, instruction_data) =
239 unsafe { $crate::deserialize_into(input, &mut accounts) };
240 // Use `slice_assume_init_ref` once it's stabilized
241 let accounts = unsafe {
242 &*(&accounts[..num_accounts] as *const [MaybeUninit<AccountInfo<'_>>]
243 as *const [AccountInfo<'_>])
244 };
245
246 #[inline(never)]
247 fn call_program(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> u64 {
248 match $process_instruction(program_id, accounts, data) {
249 Ok(()) => $crate::SUCCESS,
250 Err(error) => error.into(),
251 }
252 }
253
254 call_program(&program_id, accounts, &instruction_data)
255 }
256 $crate::custom_heap_default!();
257 $crate::custom_panic_default!();
258 };
259}
260
261/// Define the default global allocator.
262///
263/// The default global allocator is enabled only if the calling crate has not
264/// disabled it using [Cargo features] as described below. It is only defined
265/// for [BPF] targets.
266///
267/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
268/// [BPF]: https://solana.com/docs/programs/faq#berkeley-packet-filter-bpf
269///
270/// # Cargo features
271///
272/// A crate that calls this macro can provide its own custom heap
273/// implementation, or allow others to provide their own custom heap
274/// implementation, by adding a `custom-heap` feature to its `Cargo.toml`. After
275/// enabling the feature, one may define their own [global allocator] in the
276/// standard way.
277///
278/// [global allocator]: https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html
279///
280#[macro_export]
281macro_rules! custom_heap_default {
282 () => {
283 #[cfg(all(not(feature = "custom-heap"), target_os = "solana"))]
284 #[global_allocator]
285 static A: $crate::allocator::BumpAllocator<()> =
286 unsafe { $crate::allocator::BumpAllocator::new() };
287 };
288}
289
290/// Define the default global panic handler.
291///
292/// This must be used if the [`entrypoint`] macro is not used, and no other
293/// panic handler has been defined; otherwise compilation will fail with a
294/// missing `custom_panic` symbol.
295///
296/// The default global allocator is enabled only if the calling crate has not
297/// disabled it using [Cargo features] as described below. It is only defined
298/// for [BPF] targets.
299///
300/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
301/// [BPF]: https://solana.com/docs/programs/faq#berkeley-packet-filter-bpf
302///
303/// # Cargo features
304///
305/// A crate that calls this macro can provide its own custom panic handler, or
306/// allow others to provide their own custom panic handler, by adding a
307/// `custom-panic` feature to its `Cargo.toml`. After enabling the feature, one
308/// may define their own panic handler.
309///
310/// A good way to reduce the final size of the program is to provide a
311/// `custom_panic` implementation that does nothing. Doing so will cut ~25kb
312/// from a noop program. That number goes down the more the programs pulls in
313/// Rust's standard library for other purposes.
314///
315/// # Defining a panic handler for Solana
316///
317/// _The mechanism for defining a Solana panic handler is different [from most
318/// Rust programs][rpanic]._
319///
320/// [rpanic]: https://doc.rust-lang.org/nomicon/panic-handler.html
321///
322/// To define a panic handler one must define a `custom_panic` function
323/// with the `#[no_mangle]` attribute, as below:
324///
325/// ```ignore
326/// #[cfg(all(feature = "custom-panic", target_os = "solana"))]
327/// #[no_mangle]
328/// fn custom_panic(info: &core::panic::PanicInfo<'_>) {
329/// $crate::msg!("{}", info);
330/// }
331/// ```
332///
333/// The above is how Solana defines the default panic handler.
334#[macro_export]
335macro_rules! custom_panic_default {
336 () => {
337 #[cfg(all(not(feature = "custom-panic"), target_os = "solana"))]
338 #[no_mangle]
339 fn custom_panic(info: &core::panic::PanicInfo<'_>) {
340 // Full panic reporting
341 $crate::__msg!("{}", info);
342 }
343 };
344}
345
346/// `assert_eq(std::mem::align_of::<u128>(), 8)` is true for BPF but not for some host machines
347pub const BPF_ALIGN_OF_U128: usize = 8;
348
349#[allow(clippy::arithmetic_side_effects)]
350#[inline(always)] // this reduces CU usage
351unsafe fn deserialize_instruction_data<'a>(input: *mut u8, mut offset: usize) -> (&'a [u8], usize) {
352 #[allow(clippy::cast_ptr_alignment)]
353 let instruction_data_len = *(input.add(offset) as *const u64) as usize;
354 offset += size_of::<u64>();
355
356 let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
357 offset += instruction_data_len;
358
359 (instruction_data, offset)
360}
361
362#[allow(clippy::arithmetic_side_effects)]
363#[inline(always)] // this reduces CU usage by half!
364unsafe fn deserialize_account_info<'a>(
365 input: *mut u8,
366 mut offset: usize,
367) -> (AccountInfo<'a>, usize) {
368 #[allow(clippy::cast_ptr_alignment)]
369 let is_signer = *(input.add(offset) as *const u8) != 0;
370 offset += size_of::<u8>();
371
372 #[allow(clippy::cast_ptr_alignment)]
373 let is_writable = *(input.add(offset) as *const u8) != 0;
374 offset += size_of::<u8>();
375
376 #[allow(clippy::cast_ptr_alignment)]
377 let executable = *(input.add(offset) as *const u8) != 0;
378 offset += size_of::<u8>();
379
380 // The original data length is stored here because these 4 bytes were
381 // originally only used for padding and served as a good location to
382 // track the original size of the account data in a compatible way.
383 let original_data_len_offset = offset;
384 offset += size_of::<u32>();
385
386 let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
387 offset += size_of::<Pubkey>();
388
389 let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
390 offset += size_of::<Pubkey>();
391
392 #[allow(clippy::cast_ptr_alignment)]
393 let kelvins = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
394 offset += size_of::<u64>();
395
396 #[allow(clippy::cast_ptr_alignment)]
397 let data_len = *(input.add(offset) as *const u64) as usize;
398 offset += size_of::<u64>();
399
400 // Store the original data length for detecting invalid reallocations and
401 // requires that MAX_PERMITTED_DATA_LENGTH fits in a u32
402 *(input.add(original_data_len_offset) as *mut u32) = data_len as u32;
403
404 let data = Rc::new(RefCell::new({
405 from_raw_parts_mut(input.add(offset), data_len)
406 }));
407 offset += data_len + MAX_PERMITTED_DATA_INCREASE;
408 offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); // padding
409
410 #[allow(clippy::cast_ptr_alignment)]
411 let rent_epoch = *(input.add(offset) as *const u64);
412 offset += size_of::<u64>();
413
414 (
415 AccountInfo {
416 key,
417 is_signer,
418 is_writable,
419 kelvins,
420 data,
421 owner,
422 executable,
423 rent_epoch,
424 },
425 offset,
426 )
427}
428
429/// Deserialize the input arguments
430///
431/// The integer arithmetic in this method is safe when called on a buffer that was
432/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
433/// done at one's own risk.
434///
435/// # Safety
436#[allow(clippy::arithmetic_side_effects)]
437pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
438 let mut offset: usize = 0;
439
440 // Number of accounts present
441
442 #[allow(clippy::cast_ptr_alignment)]
443 let num_accounts = *(input.add(offset) as *const u64) as usize;
444 offset += size_of::<u64>();
445
446 // Account Infos
447
448 let mut accounts = Vec::with_capacity(num_accounts);
449 for _ in 0..num_accounts {
450 let dup_info = *(input.add(offset) as *const u8);
451 offset += size_of::<u8>();
452 if dup_info == NON_DUP_MARKER {
453 let (account_info, new_offset) = deserialize_account_info(input, offset);
454 offset = new_offset;
455 accounts.push(account_info);
456 } else {
457 offset += 7; // padding
458
459 // Duplicate account, clone the original
460 accounts.push(accounts[dup_info as usize].clone());
461 }
462 }
463
464 // Instruction data
465
466 let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
467 offset = new_offset;
468
469 // Program Id
470
471 let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
472
473 (program_id, accounts, instruction_data)
474}
475
476/// Deserialize the input arguments
477///
478/// Differs from `deserialize` by writing the account infos into an uninitialized
479/// slice, which provides better performance, roughly 30 CUs per unique account
480/// provided to the instruction.
481///
482/// Panics if the input slice is not large enough.
483///
484/// The integer arithmetic in this method is safe when called on a buffer that was
485/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
486/// done at one's own risk.
487///
488/// # Safety
489#[allow(clippy::arithmetic_side_effects)]
490pub unsafe fn deserialize_into<'a>(
491 input: *mut u8,
492 accounts: &mut [MaybeUninit<AccountInfo<'a>>],
493) -> (&'a Pubkey, usize, &'a [u8]) {
494 let mut offset: usize = 0;
495
496 // Number of accounts present
497
498 #[allow(clippy::cast_ptr_alignment)]
499 let num_accounts = *(input.add(offset) as *const u64) as usize;
500 offset += size_of::<u64>();
501
502 if num_accounts > accounts.len() {
503 panic!(
504 "{} accounts provided, but only {} are supported",
505 num_accounts,
506 accounts.len()
507 );
508 }
509
510 // Account Infos
511
512 for i in 0..num_accounts {
513 let dup_info = *(input.add(offset) as *const u8);
514 offset += size_of::<u8>();
515 if dup_info == NON_DUP_MARKER {
516 let (account_info, new_offset) = deserialize_account_info(input, offset);
517 offset = new_offset;
518 accounts[i].write(account_info);
519 } else {
520 offset += 7; // padding
521
522 // Duplicate account, clone the original
523 accounts[i].write(accounts[dup_info as usize].assume_init_ref().clone());
524 }
525 }
526
527 // Instruction data
528
529 let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
530 offset = new_offset;
531
532 // Program Id
533
534 let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
535
536 (program_id, num_accounts, instruction_data)
537}