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#[macro_export]
123macro_rules! entrypoint {
124 ($process_instruction:ident) => {
125 /// # Safety
126 #[no_mangle]
127 pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
128 let (program_id, accounts, instruction_data) = unsafe { $crate::deserialize(input) };
129 match $process_instruction(program_id, &accounts, instruction_data) {
130 Ok(()) => $crate::SUCCESS,
131 Err(error) => error.into(),
132 }
133 }
134 $crate::custom_heap_default!();
135 $crate::custom_panic_default!();
136 };
137}
138
139/// Declare the program entrypoint and set up global handlers.
140///
141/// This is similar to the `entrypoint!` macro, except that it does not perform
142/// any dynamic allocations, and instead writes the input accounts into a pre-
143/// allocated array.
144///
145/// This version reduces compute unit usage by 20-30 compute units per unique
146/// account in the instruction. It may become the default option in a future
147/// release.
148///
149/// For more information about how the program entrypoint behaves and what it
150/// does, please see the documentation for [`entrypoint!`].
151///
152/// NOTE: This entrypoint has a hard-coded limit of 64 input accounts.
153#[macro_export]
154macro_rules! entrypoint_no_alloc {
155 ($process_instruction:ident) => {
156 /// # Safety
157 #[no_mangle]
158 pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
159 use std::mem::MaybeUninit;
160 // Clippy complains about this because a `const` with interior
161 // mutability `RefCell` should use `static` instead to make it
162 // clear that it can change.
163 // In our case, however, we want to create an array of `AccountInfo`s,
164 // and the only way to do it is through a `const` expression, and
165 // we don't expect to mutate the internals of this `const` type.
166 #[allow(clippy::declare_interior_mutable_const)]
167 const UNINIT_ACCOUNT_INFO: MaybeUninit<AccountInfo> =
168 MaybeUninit::<AccountInfo>::uninit();
169 const MAX_ACCOUNT_INFOS: usize = 64;
170 let mut accounts = [UNINIT_ACCOUNT_INFO; MAX_ACCOUNT_INFOS];
171 let (program_id, num_accounts, instruction_data) =
172 unsafe { $crate::deserialize_into(input, &mut accounts) };
173 // Use `slice_assume_init_ref` once it's stabilized
174 let accounts = &*(&accounts[..num_accounts] as *const [MaybeUninit<AccountInfo<'_>>]
175 as *const [AccountInfo<'_>]);
176
177 #[inline(never)]
178 fn call_program(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> u64 {
179 match $process_instruction(program_id, accounts, data) {
180 Ok(()) => $crate::SUCCESS,
181 Err(error) => error.into(),
182 }
183 }
184
185 call_program(&program_id, accounts, &instruction_data)
186 }
187 $crate::custom_heap_default!();
188 $crate::custom_panic_default!();
189 };
190}
191
192/// Define the default global allocator.
193///
194/// The default global allocator is enabled only if the calling crate has not
195/// disabled it using [Cargo features] as described below. It is only defined
196/// for [BPF] targets.
197///
198/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
199/// [BPF]: https://solana.com/docs/programs/faq#berkeley-packet-filter-bpf
200///
201/// # Cargo features
202///
203/// A crate that calls this macro can provide its own custom heap
204/// implementation, or allow others to provide their own custom heap
205/// implementation, by adding a `custom-heap` feature to its `Cargo.toml`. After
206/// enabling the feature, one may define their own [global allocator] in the
207/// standard way.
208///
209/// [global allocator]: https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html
210///
211#[macro_export]
212macro_rules! custom_heap_default {
213 () => {
214 #[cfg(all(not(feature = "custom-heap"), target_os = "solana"))]
215 #[global_allocator]
216 static A: $crate::allocator::BumpAllocator<()> =
217 unsafe { $crate::allocator::BumpAllocator::new() };
218 };
219}
220
221/// Define the default global panic handler.
222///
223/// This must be used if the [`entrypoint`] macro is not used, and no other
224/// panic handler has been defined; otherwise compilation will fail with a
225/// missing `custom_panic` symbol.
226///
227/// The default global allocator is enabled only if the calling crate has not
228/// disabled it using [Cargo features] as described below. It is only defined
229/// for [BPF] targets.
230///
231/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
232/// [BPF]: https://solana.com/docs/programs/faq#berkeley-packet-filter-bpf
233///
234/// # Cargo features
235///
236/// A crate that calls this macro can provide its own custom panic handler, or
237/// allow others to provide their own custom panic handler, by adding a
238/// `custom-panic` feature to its `Cargo.toml`. After enabling the feature, one
239/// may define their own panic handler.
240///
241/// A good way to reduce the final size of the program is to provide a
242/// `custom_panic` implementation that does nothing. Doing so will cut ~25kb
243/// from a noop program. That number goes down the more the programs pulls in
244/// Rust's standard library for other purposes.
245///
246/// # Defining a panic handler for Solana
247///
248/// _The mechanism for defining a Solana panic handler is different [from most
249/// Rust programs][rpanic]._
250///
251/// [rpanic]: https://doc.rust-lang.org/nomicon/panic-handler.html
252///
253/// To define a panic handler one must define a `custom_panic` function
254/// with the `#[no_mangle]` attribute, as below:
255///
256/// ```ignore
257/// #[cfg(all(feature = "custom-panic", target_os = "solana"))]
258/// #[no_mangle]
259/// fn custom_panic(info: &core::panic::PanicInfo<'_>) {
260/// $crate::msg!("{}", info);
261/// }
262/// ```
263///
264/// The above is how Solana defines the default panic handler.
265#[macro_export]
266macro_rules! custom_panic_default {
267 () => {
268 #[cfg(all(not(feature = "custom-panic"), target_os = "solana"))]
269 #[no_mangle]
270 fn custom_panic(info: &core::panic::PanicInfo<'_>) {
271 // Full panic reporting
272 $crate::__msg!("{}", info);
273 }
274 };
275}
276
277/// `assert_eq(std::mem::align_of::<u128>(), 8)` is true for BPF but not for some host machines
278pub const BPF_ALIGN_OF_U128: usize = 8;
279
280#[allow(clippy::arithmetic_side_effects)]
281#[inline(always)] // this reduces CU usage
282unsafe fn deserialize_instruction_data<'a>(input: *mut u8, mut offset: usize) -> (&'a [u8], usize) {
283 #[allow(clippy::cast_ptr_alignment)]
284 let instruction_data_len = *(input.add(offset) as *const u64) as usize;
285 offset += size_of::<u64>();
286
287 let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
288 offset += instruction_data_len;
289
290 (instruction_data, offset)
291}
292
293#[allow(clippy::arithmetic_side_effects)]
294#[inline(always)] // this reduces CU usage by half!
295unsafe fn deserialize_account_info<'a>(
296 input: *mut u8,
297 mut offset: usize,
298) -> (AccountInfo<'a>, usize) {
299 #[allow(clippy::cast_ptr_alignment)]
300 let is_signer = *(input.add(offset) as *const u8) != 0;
301 offset += size_of::<u8>();
302
303 #[allow(clippy::cast_ptr_alignment)]
304 let is_writable = *(input.add(offset) as *const u8) != 0;
305 offset += size_of::<u8>();
306
307 #[allow(clippy::cast_ptr_alignment)]
308 let executable = *(input.add(offset) as *const u8) != 0;
309 offset += size_of::<u8>();
310
311 // The original data length is stored here because these 4 bytes were
312 // originally only used for padding and served as a good location to
313 // track the original size of the account data in a compatible way.
314 let original_data_len_offset = offset;
315 offset += size_of::<u32>();
316
317 let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
318 offset += size_of::<Pubkey>();
319
320 let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
321 offset += size_of::<Pubkey>();
322
323 #[allow(clippy::cast_ptr_alignment)]
324 let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
325 offset += size_of::<u64>();
326
327 #[allow(clippy::cast_ptr_alignment)]
328 let data_len = *(input.add(offset) as *const u64) as usize;
329 offset += size_of::<u64>();
330
331 // Store the original data length for detecting invalid reallocations and
332 // requires that MAX_PERMITTED_DATA_LENGTH fits in a u32
333 *(input.add(original_data_len_offset) as *mut u32) = data_len as u32;
334
335 let data = Rc::new(RefCell::new({
336 from_raw_parts_mut(input.add(offset), data_len)
337 }));
338 offset += data_len + MAX_PERMITTED_DATA_INCREASE;
339 offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); // padding
340
341 #[allow(clippy::cast_ptr_alignment)]
342 let rent_epoch = *(input.add(offset) as *const u64);
343 offset += size_of::<u64>();
344
345 (
346 AccountInfo {
347 key,
348 is_signer,
349 is_writable,
350 lamports,
351 data,
352 owner,
353 executable,
354 rent_epoch,
355 },
356 offset,
357 )
358}
359
360/// Deserialize the input arguments
361///
362/// The integer arithmetic in this method is safe when called on a buffer that was
363/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
364/// done at one's own risk.
365///
366/// # Safety
367#[allow(clippy::arithmetic_side_effects)]
368pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
369 let mut offset: usize = 0;
370
371 // Number of accounts present
372
373 #[allow(clippy::cast_ptr_alignment)]
374 let num_accounts = *(input.add(offset) as *const u64) as usize;
375 offset += size_of::<u64>();
376
377 // Account Infos
378
379 let mut accounts = Vec::with_capacity(num_accounts);
380 for _ in 0..num_accounts {
381 let dup_info = *(input.add(offset) as *const u8);
382 offset += size_of::<u8>();
383 if dup_info == NON_DUP_MARKER {
384 let (account_info, new_offset) = deserialize_account_info(input, offset);
385 offset = new_offset;
386 accounts.push(account_info);
387 } else {
388 offset += 7; // padding
389
390 // Duplicate account, clone the original
391 accounts.push(accounts[dup_info as usize].clone());
392 }
393 }
394
395 // Instruction data
396
397 let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
398 offset = new_offset;
399
400 // Program Id
401
402 let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
403
404 (program_id, accounts, instruction_data)
405}
406
407/// Deserialize the input arguments
408///
409/// Differs from `deserialize` by writing the account infos into an uninitialized
410/// slice, which provides better performance, roughly 30 CUs per unique account
411/// provided to the instruction.
412///
413/// Panics if the input slice is not large enough.
414///
415/// The integer arithmetic in this method is safe when called on a buffer that was
416/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
417/// done at one's own risk.
418///
419/// # Safety
420#[allow(clippy::arithmetic_side_effects)]
421pub unsafe fn deserialize_into<'a>(
422 input: *mut u8,
423 accounts: &mut [MaybeUninit<AccountInfo<'a>>],
424) -> (&'a Pubkey, usize, &'a [u8]) {
425 let mut offset: usize = 0;
426
427 // Number of accounts present
428
429 #[allow(clippy::cast_ptr_alignment)]
430 let num_accounts = *(input.add(offset) as *const u64) as usize;
431 offset += size_of::<u64>();
432
433 if num_accounts > accounts.len() {
434 panic!(
435 "{} accounts provided, but only {} are supported",
436 num_accounts,
437 accounts.len()
438 );
439 }
440
441 // Account Infos
442
443 for i in 0..num_accounts {
444 let dup_info = *(input.add(offset) as *const u8);
445 offset += size_of::<u8>();
446 if dup_info == NON_DUP_MARKER {
447 let (account_info, new_offset) = deserialize_account_info(input, offset);
448 offset = new_offset;
449 accounts[i].write(account_info);
450 } else {
451 offset += 7; // padding
452
453 // Duplicate account, clone the original
454 accounts[i].write(accounts[dup_info as usize].assume_init_ref().clone());
455 }
456 }
457
458 // Instruction data
459
460 let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
461 offset = new_offset;
462
463 // Program Id
464
465 let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
466
467 (program_id, num_accounts, instruction_data)
468}