rialo_s_program_entrypoint/
lib.rs

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