pinocchio/entrypoint/mod.rs
1//! Macros and functions for defining the program entrypoint and setting up
2//! global handlers.
3
4pub mod lazy;
5
6#[cfg(feature = "alloc")]
7pub use alloc::BumpAllocator;
8pub use lazy::{InstructionContext, MaybeAccount};
9use {
10 crate::{
11 account::{AccountView, RuntimeAccount, MAX_PERMITTED_DATA_INCREASE},
12 Address, ProgramResult, BPF_ALIGN_OF_U128, MAX_TX_ACCOUNTS, SUCCESS,
13 },
14 core::{
15 alloc::{GlobalAlloc, Layout},
16 cmp::min,
17 mem::{size_of, MaybeUninit},
18 ptr::with_exposed_provenance_mut,
19 slice::from_raw_parts,
20 },
21};
22
23/// Start address of the memory region used for program heap.
24pub const HEAP_START_ADDRESS: u64 = 0x300000000;
25
26/// Length of the heap memory region used for program heap.
27#[deprecated(since = "0.10.0", note = "Use `MAX_HEAP_LENGTH` instead")]
28pub const HEAP_LENGTH: usize = 32 * 1024;
29
30/// Maximum heap length in bytes that a program can request.
31pub const MAX_HEAP_LENGTH: u32 = 256 * 1024;
32
33/// Value used to indicate that a serialized account is not a duplicate.
34pub const NON_DUP_MARKER: u8 = u8::MAX;
35
36/// The "static" size of an account in the input buffer.
37///
38/// This is the size of the account header plus the maximum permitted data
39/// increase.
40const STATIC_ACCOUNT_DATA: usize = size_of::<RuntimeAccount>() + MAX_PERMITTED_DATA_INCREASE;
41
42/// Declare the program entrypoint and set up global handlers.
43///
44/// The main difference from the standard (SDK) [`entrypoint`] macro is that
45/// this macro represents an entrypoint that does not perform allocations or
46/// copies when reading the input buffer.
47///
48/// [`entrypoint`]: https://docs.rs/solana-program-entrypoint/latest/solana_program_entrypoint/macro.entrypoint.html
49///
50/// This macro emits the common boilerplate necessary to begin program
51/// execution, calling a provided function to process the program instruction
52/// supplied by the runtime, and reporting its result to the runtime.
53///
54/// It also sets up a [global allocator] and [panic handler], using the
55/// [`crate::default_allocator!`] and [`crate::default_panic_handler!`] macros.
56///
57/// The first argument is the name of a function with this type signature:
58///
59/// ```ignore
60/// fn process_instruction(
61/// program_id: &Address, // Address of the account the program was loaded into
62/// accounts: &[AccountView], // All accounts required to process the instruction
63/// instruction_data: &[u8], // Serialized instruction-specific data
64/// ) -> ProgramResult;
65/// ```
66/// The argument is defined as an `expr`, which allows the use of any function
67/// pointer not just identifiers in the current scope.
68///
69/// There is a second optional argument that allows to specify the maximum
70/// number of accounts expected by instructions of the program. This is useful
71/// to reduce the stack size requirement for the entrypoint, as the default is
72/// set to [`crate::MAX_TX_ACCOUNTS`]. If the program receives more accounts
73/// than the specified maximum, these accounts will be ignored.
74///
75/// [global allocator]: https://doc.rust-lang.org/stable/alloc/alloc/trait.GlobalAlloc.html
76/// [maximum number of accounts]: https://github.com/anza-xyz/agave/blob/ccabfcf84921977202fd06d3197cbcea83742133/runtime/src/bank.rs#L3207-L3219
77/// [panic handler]: https://doc.rust-lang.org/stable/core/panic/trait.PanicHandler.html
78///
79/// # Examples
80///
81/// Defining an entrypoint conditional on the `bpf-entrypoint` feature. Although
82/// the `entrypoint` module is written inline in this example, it is common to
83/// put it into its own file.
84///
85/// ```no_run
86/// #[cfg(feature = "bpf-entrypoint")]
87/// pub mod entrypoint {
88///
89/// use pinocchio::{
90/// AccountView,
91/// entrypoint,
92/// Address,
93/// ProgramResult
94/// };
95///
96/// entrypoint!(process_instruction);
97///
98/// pub fn process_instruction(
99/// program_id: &Address,
100/// accounts: &[AccountView],
101/// instruction_data: &[u8],
102/// ) -> ProgramResult {
103/// Ok(())
104/// }
105///
106/// }
107/// ```
108///
109/// # Important
110///
111/// The panic handler set up is different depending on whether the `std` library
112/// is available to the linker or not. The `entrypoint` macro will set up a
113/// default panic "hook", that works with the `#[panic_handler]` set by the
114/// `std`. Therefore, this macro should be used when the program or any of its
115/// dependencies are dependent on the `std` library.
116///
117/// When the program and all its dependencies are `no_std`, it is necessary to
118/// set a `#[panic_handler]` to handle panics. This is done by the
119/// [`crate::nostd_panic_handler`] macro. In this case, it is not possible to
120/// use the `entrypoint` macro. Use the [`crate::program_entrypoint!`] macro
121/// instead and set up the allocator and panic handler manually.
122///
123/// The compiler may inline the instruction handler (and its call tree) into the
124/// generated `entrypoint`, which can significantly increase the entrypoint
125/// stack frame. If your program has large instruction dispatch logic or builds
126/// sizable CPI account arrays, consider adding `#[inline(never)]` to the
127/// instruction handler to keep it out of the entrypoint stack frame and avoid
128/// BPF stack overflows.
129///
130/// [`crate::nostd_panic_handler`]: https://docs.rs/pinocchio/latest/pinocchio/macro.nostd_panic_handler.html
131#[cfg(feature = "alloc")]
132#[macro_export]
133macro_rules! entrypoint {
134 ( $process_instruction:expr ) => {
135 $crate::entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
136 };
137 ( $process_instruction:expr, $maximum:expr ) => {
138 $crate::program_entrypoint!($process_instruction, $maximum);
139 $crate::default_allocator!();
140 $crate::default_panic_handler!();
141 };
142}
143
144/// Declare the program entrypoint.
145///
146/// This macro is similar to the [`crate::entrypoint!`] macro, but it does not
147/// set up a global allocator nor a panic handler. This is useful when the
148/// program will set up its own allocator and panic handler.
149///
150/// The first argument is the name of a function with this type signature:
151///
152/// ```ignore
153/// fn process_instruction(
154/// program_id: &Address, // Address of the account the program was loaded into
155/// accounts: &[AccountView], // All accounts required to process the instruction
156/// instruction_data: &[u8], // Serialized instruction-specific data
157/// ) -> ProgramResult;
158/// ```
159/// The argument is defined as an `expr`, which allows the use of any function
160/// pointer not just identifiers in the current scope.
161///
162/// There is a second optional argument that allows to specify the maximum
163/// number of accounts expected by instructions of the program. This is useful
164/// to reduce the stack size requirement for the entrypoint, as the default is
165/// set to [`MAX_TX_ACCOUNTS`]. If the program receives more accounts than the
166/// specified maximum, these accounts will be ignored.
167#[macro_export]
168macro_rules! program_entrypoint {
169 ( $process_instruction:expr ) => {
170 $crate::program_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
171 };
172 ( $process_instruction:expr, $maximum:expr ) => {
173 /// Program entrypoint.
174 #[no_mangle]
175 pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
176 $crate::entrypoint::process_entrypoint::<$maximum>(input, $process_instruction)
177 }
178 };
179}
180
181/// Entrypoint deserialization.
182///
183/// This function inlines entrypoint deserialization for use in the
184/// `program_entrypoint!` macro.
185///
186/// # Safety
187///
188/// The caller must ensure that the `input` buffer is valid, i.e., it represents
189/// the program input parameters serialized by the SVM loader. Additionally, the
190/// `input` should last for the lifetime of the program execution since the
191/// returned values reference the `input`.
192#[inline(always)]
193pub unsafe fn process_entrypoint<const MAX_ACCOUNTS: usize>(
194 input: *mut u8,
195 process_instruction: fn(&Address, &[AccountView], &[u8]) -> ProgramResult,
196) -> u64 {
197 const UNINIT: MaybeUninit<AccountView> = MaybeUninit::<AccountView>::uninit();
198 // Create an array of uninitialized account views.
199 let mut accounts = [UNINIT; MAX_ACCOUNTS];
200
201 let (program_id, count, instruction_data) =
202 unsafe { deserialize::<MAX_ACCOUNTS>(input, &mut accounts) };
203
204 // Call the program's entrypoint passing `count` account views; we know that
205 // they are initialized so we cast the pointer to a slice of `[AccountView]`.
206 match process_instruction(
207 program_id,
208 unsafe { from_raw_parts(accounts.as_ptr() as _, count) },
209 instruction_data,
210 ) {
211 Ok(()) => SUCCESS,
212 Err(error) => error.into(),
213 }
214}
215
216/// Align a pointer to the BPF alignment of [`u128`].
217macro_rules! align_pointer {
218 ($ptr:ident) => {
219 // Integer-to-pointer cast: first compute the aligned address as a `usize`,
220 // since this is more CU-efficient than using `ptr::align_offset()` or the
221 // strict provenance API (e.g., `ptr::with_addr()`). Then cast the result
222 // back to a pointer. The resulting pointer is guaranteed to be valid
223 // because it follows the layout serialized by the runtime.
224 with_exposed_provenance_mut(
225 ($ptr.expose_provenance() + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1),
226 )
227 };
228}
229
230/// Advance the input pointer in relation to a non-duplicated account.
231///
232/// The macro will add `STATIC_ACCOUNT_DATA` and the account length to
233/// the input pointer and align its address using [`align_pointer`].
234macro_rules! advance_input_with_account {
235 ($input:ident, $account:expr) => {{
236 $input = $input.add(STATIC_ACCOUNT_DATA);
237 $input = $input.add((*$account).data_len as usize);
238 $input = align_pointer!($input);
239 }};
240}
241
242/// A macro to repeat a pattern to process an account `n` times, where `n` is
243/// the number of `_` tokens in the input.
244///
245/// The main advantage of this macro is to inline the code to process `n`
246/// accounts, which is useful to reduce the number of jumps required. As a
247/// result, it reduces the number of CUs required to process each account.
248///
249/// Note that this macro emits code to update both the `input` and `accounts`
250/// pointers.
251macro_rules! process_n_accounts {
252 // Base case: no tokens left.
253 ( () => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {};
254
255 // Recursive case: one `_` token per repetition.
256 ( ( _ $($rest:tt)* ) => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
257 process_n_accounts!(@process_account => ($input, $accounts, $accounts_slice));
258 process_n_accounts!(($($rest)*) => ($input, $accounts, $accounts_slice));
259 };
260
261 // Process one account.
262 ( @process_account => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
263 // Increment the `accounts` pointer to the next account.
264 $accounts = $accounts.add(1);
265
266 // Read the next account.
267 let account: *mut RuntimeAccount = $input as *mut RuntimeAccount;
268 // Adds an 8-bytes offset for:
269 // - rent epoch in case of a non-duplicated account
270 // - duplicated marker + 7 bytes of padding in case of a duplicated account
271 $input = $input.add(size_of::<u64>());
272
273 if (*account).borrow_state != NON_DUP_MARKER {
274 clone_account_view($accounts, $accounts_slice, (*account).borrow_state);
275 } else {
276 $accounts.write(AccountView::new_unchecked(account));
277 advance_input_with_account!($input, account);
278 }
279 };
280}
281
282/// Convenience macro to transform the number of accounts to process into a
283/// pattern of `_` tokens for the [`process_n_accounts`] macro.
284macro_rules! process_accounts {
285 ( 1 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
286 process_n_accounts!( (_) => ( $input, $accounts, $accounts_slice ));
287 };
288 ( 2 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
289 process_n_accounts!( (_ _) => ( $input, $accounts, $accounts_slice ));
290 };
291 ( 3 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
292 process_n_accounts!( (_ _ _) => ( $input, $accounts, $accounts_slice ));
293 };
294 ( 4 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
295 process_n_accounts!( (_ _ _ _) => ( $input, $accounts, $accounts_slice ));
296 };
297 ( 5 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
298 process_n_accounts!( (_ _ _ _ _) => ( $input, $accounts, $accounts_slice ));
299 };
300}
301
302/// Create an [`AccountView`] referencing the same account referenced by the
303/// [`AccountView`] at the specified `index`.
304///
305/// # Safety
306///
307/// The caller must ensure that:
308/// - `accounts` pointer must point to an array of [`AccountView`]s where the
309/// new [`AccountView`] will be written.
310/// - `accounts_slice` pointer must point to a slice of [`AccountView`]s
311/// already initialized.
312/// - `index` is a valid index in the `accounts_slice`.
313//
314// Note: The function is marked as `cold` to stop the compiler from optimizing the parsing of
315// duplicated accounts, which leads to an overall increase in CU consumption.
316#[allow(clippy::clone_on_copy)]
317#[cold]
318#[inline(always)]
319unsafe fn clone_account_view(
320 accounts: *mut AccountView,
321 accounts_slice: *const AccountView,
322 index: u8,
323) {
324 accounts.write((*accounts_slice.add(index as usize)).clone());
325}
326
327/// Parse the arguments from the runtime input buffer.
328///
329/// This function parses the `accounts`, `instruction_data` and `program_id`
330/// from the input buffer. The `MAX_ACCOUNTS` constant defines the maximum
331/// number of accounts that can be parsed from the input buffer. If the number
332/// of accounts in the input buffer exceeds `MAX_ACCOUNTS`, the excess
333/// accounts will be skipped (ignored).
334///
335/// # Safety
336///
337/// The caller must ensure that the `input` buffer is valid, i.e., it represents
338/// the program input parameters serialized by the SVM loader. Additionally, the
339/// `input` should last for the lifetime of the program execution since the
340/// returned values reference the `input`.
341#[inline(always)]
342pub unsafe fn deserialize<const MAX_ACCOUNTS: usize>(
343 mut input: *mut u8,
344 accounts: &mut [MaybeUninit<AccountView>; MAX_ACCOUNTS],
345) -> (&'static Address, usize, &'static [u8]) {
346 // Ensure that MAX_ACCOUNTS is less than or equal to the maximum number of
347 // accounts (MAX_TX_ACCOUNTS) that can be processed in a transaction.
348 const {
349 assert!(
350 MAX_ACCOUNTS <= MAX_TX_ACCOUNTS,
351 "MAX_ACCOUNTS must be less than or equal to MAX_TX_ACCOUNTS"
352 );
353 }
354
355 // Number of accounts to process.
356 let mut processed = *(input as *const u64) as usize;
357 // Skip the number of accounts (8 bytes).
358 input = input.add(size_of::<u64>());
359
360 if processed > 0 {
361 let mut accounts = accounts.as_mut_ptr() as *mut AccountView;
362 // Represents the beginning of the accounts slice.
363 let accounts_slice = accounts;
364
365 // The first account is always non-duplicated, so process
366 // it directly as such.
367 let account: *mut RuntimeAccount = input as *mut RuntimeAccount;
368 accounts.write(AccountView::new_unchecked(account));
369
370 input = input.add(size_of::<u64>());
371 advance_input_with_account!(input, account);
372
373 if processed > 1 {
374 // The number of accounts to process (`to_process_plus_one`) is limited to
375 // `MAX_ACCOUNTS`, which is the capacity of the accounts array. When there are
376 // more accounts to process than the maximum, we still need to skip
377 // the remaining accounts (`to_skip`) to move the input pointer to
378 // the instruction data. At the end, we return the number of
379 // accounts processed (`processed`), which represents the accounts
380 // initialized in the `accounts` slice.
381 //
382 // Note that `to_process_plus_one` includes the first (already processed)
383 // account to avoid decrementing the value. The actual number of
384 // remaining accounts to process is `to_process_plus_one - 1`.
385 let mut to_process_plus_one = if MAX_ACCOUNTS < MAX_TX_ACCOUNTS {
386 min(processed, MAX_ACCOUNTS)
387 } else {
388 processed
389 };
390
391 let mut to_skip = processed - to_process_plus_one;
392 processed = to_process_plus_one;
393
394 // This is an optimization to reduce the number of jumps required to process the
395 // accounts. The macro `process_accounts` will generate inline code to process
396 // the specified number of accounts.
397 if to_process_plus_one == 2 {
398 process_accounts!(1 => (input, accounts, accounts_slice));
399 } else {
400 while to_process_plus_one > 5 {
401 // Process 5 accounts at a time.
402 process_accounts!(5 => (input, accounts, accounts_slice));
403 to_process_plus_one -= 5;
404 }
405
406 // There might be remaining accounts to process.
407 match to_process_plus_one {
408 5 => {
409 process_accounts!(4 => (input, accounts, accounts_slice));
410 }
411 4 => {
412 process_accounts!(3 => (input, accounts, accounts_slice));
413 }
414 3 => {
415 process_accounts!(2 => (input, accounts, accounts_slice));
416 }
417 2 => {
418 process_accounts!(1 => (input, accounts, accounts_slice));
419 }
420 1 => (),
421 _ => {
422 // SAFETY: `while` loop above makes sure that `to_process_plus_one`
423 // has 1 to 5 entries left.
424 unsafe { core::hint::unreachable_unchecked() }
425 }
426 }
427 }
428
429 // Process any remaining accounts to move the offset to the instruction data
430 // (there is a duplication of logic but we avoid testing whether we
431 // have space for the account or not).
432 //
433 // There might be accounts to skip only when `MAX_ACCOUNTS < MAX_TX_ACCOUNTS` so
434 // this allows the compiler to optimize the code and avoid the loop
435 // when `MAX_ACCOUNTS == MAX_TX_ACCOUNTS`.
436 if MAX_ACCOUNTS < MAX_TX_ACCOUNTS {
437 while to_skip > 0 {
438 // Marks the account as skipped.
439 to_skip -= 1;
440
441 // Read the next account.
442 let account: *mut RuntimeAccount = input as *mut RuntimeAccount;
443 // Adds an 8-bytes offset for:
444 // - rent epoch in case of a non-duplicated account
445 // - duplicated marker + 7 bytes of padding in case of a duplicated account
446 input = input.add(size_of::<u64>());
447
448 if (*account).borrow_state == NON_DUP_MARKER {
449 advance_input_with_account!(input, account);
450 }
451 }
452 }
453 }
454 }
455
456 // instruction data
457 let instruction_data_len = *(input as *const u64) as usize;
458 input = input.add(size_of::<u64>());
459
460 let instruction_data = { from_raw_parts(input, instruction_data_len) };
461 let input = input.add(instruction_data_len);
462
463 // program id
464 let program_id: &Address = &*(input as *const Address);
465
466 (program_id, processed, instruction_data)
467}
468
469/// Default panic hook.
470///
471/// This macro sets up a default panic hook that logs the file where the panic
472/// occurred. It acts as a hook after Rust runtime panics; syscall `abort()`
473/// will be called after it returns.
474#[macro_export]
475macro_rules! default_panic_handler {
476 () => {
477 /// Default panic handler.
478 #[cfg(any(target_os = "solana", target_arch = "bpf"))]
479 #[no_mangle]
480 fn custom_panic(info: &core::panic::PanicInfo<'_>) {
481 if let Some(location) = info.location() {
482 let location = location.file();
483 unsafe { $crate::syscalls::sol_log_(location.as_ptr(), location.len() as u64) };
484 }
485 // Panic reporting.
486 const PANICKED: &str = "** PANICKED **";
487 unsafe { $crate::syscalls::sol_log_(PANICKED.as_ptr(), PANICKED.len() as u64) };
488 }
489 };
490}
491
492/// A global `#[panic_handler]` for `no_std` programs.
493///
494/// This macro sets up a default panic handler that logs the location (file,
495/// line and column) where the panic occurred and then calls the syscall
496/// `abort()`.
497///
498/// This macro should be used when all crates are `no_std`.
499#[macro_export]
500macro_rules! nostd_panic_handler {
501 () => {
502 /// A panic handler for `no_std`.
503 #[cfg(any(target_os = "solana", target_arch = "bpf"))]
504 #[panic_handler]
505 fn handler(info: &core::panic::PanicInfo<'_>) -> ! {
506 if let Some(location) = info.location() {
507 unsafe {
508 $crate::syscalls::sol_panic_(
509 location.file().as_ptr(),
510 location.file().len() as u64,
511 location.line() as u64,
512 location.column() as u64,
513 )
514 }
515 } else {
516 // Panic reporting.
517 const PANICKED: &str = "** PANICKED **";
518 unsafe {
519 $crate::syscalls::sol_log_(PANICKED.as_ptr(), PANICKED.len() as u64);
520 $crate::syscalls::abort();
521 }
522 }
523 }
524
525 /// A panic handler for when the program is compiled on a target different than
526 /// `"solana"`.
527 ///
528 /// This links the `std` library, which will set up a default panic handler.
529 #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
530 mod __private_panic_handler {
531 extern crate std as __std;
532 }
533 };
534}
535
536/// Default global allocator.
537///
538/// This macro sets up a default global allocator that uses a bump allocator to
539/// allocate memory.
540#[cfg(feature = "alloc")]
541#[macro_export]
542macro_rules! default_allocator {
543 () => {
544 #[cfg(any(target_os = "solana", target_arch = "bpf"))]
545 #[global_allocator]
546 static A: $crate::entrypoint::BumpAllocator = unsafe {
547 $crate::entrypoint::BumpAllocator::new_unchecked(
548 $crate::entrypoint::HEAP_START_ADDRESS as usize,
549 // Use the maximum heap length allowed. Programs can request heap sizes up
550 // to this value using the `ComputeBudget`.
551 $crate::entrypoint::MAX_HEAP_LENGTH as usize,
552 )
553 };
554
555 /// A default allocator for when the program is compiled on a target different
556 /// than `"solana"`.
557 ///
558 /// This links the `std` library, which will set up a default global allocator.
559 #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
560 mod __private_alloc {
561 extern crate std as __std;
562 }
563 };
564}
565
566/// A global allocator that does not dynamically allocate memory.
567///
568/// This macro sets up a global allocator that denies all dynamic allocations,
569/// while allowing static ("manual") allocations. This is useful when the
570/// program does not need to dynamically allocate memory and manages their own
571/// allocations.
572///
573/// The program will panic if it tries to dynamically allocate memory.
574///
575/// This is used when the `"alloc"` feature is disabled.
576#[macro_export]
577macro_rules! no_allocator {
578 () => {
579 #[cfg(any(target_os = "solana", target_arch = "bpf"))]
580 #[global_allocator]
581 static A: $crate::entrypoint::NoAllocator = $crate::entrypoint::NoAllocator;
582
583 /// Allocates memory for the given type `T` at the specified offset in the heap
584 /// reserved address space.
585 ///
586 /// # Safety
587 ///
588 /// It is the caller's responsibility to ensure that the offset does not overlap
589 /// with previous allocations and that type `T` can hold the bit-pattern `0` as
590 /// a valid value.
591 ///
592 /// For types that cannot hold the bit-pattern `0` as a valid value, use
593 /// [`core::mem::MaybeUninit<T>`] to allocate memory for the type and initialize
594 /// it later.
595 //
596 // Make this `const` once `const_mut_refs` is stable for the platform-tools toolchain Rust
597 // version.
598 #[inline(always)]
599 pub unsafe fn allocate_unchecked<T: Sized>(offset: usize) -> &'static mut T {
600 // SAFETY: The pointer is within a valid range and aligned to `T`.
601 unsafe { &mut *(calculate_offset::<T>(offset) as *mut T) }
602 }
603
604 #[inline(always)]
605 const fn calculate_offset<T: Sized>(offset: usize) -> usize {
606 let start = $crate::entrypoint::HEAP_START_ADDRESS as usize + offset;
607 let end = start + core::mem::size_of::<T>();
608
609 // Assert if the allocation does not exceed the heap size.
610 assert!(
611 end <= $crate::entrypoint::HEAP_START_ADDRESS as usize
612 + $crate::entrypoint::MAX_HEAP_LENGTH as usize,
613 "allocation exceeds heap size"
614 );
615
616 // Assert if the pointer is aligned to `T`.
617 assert!(
618 start % core::mem::align_of::<T>() == 0,
619 "offset is not aligned"
620 );
621
622 start
623 }
624
625 /// A default allocator for when the program is compiled on a target different
626 /// than `"solana"`.
627 ///
628 /// This links the `std` library, which will set up a default global allocator.
629 #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
630 mod __private_alloc {
631 extern crate std as __std;
632 }
633 };
634}
635
636/// An allocator that does not allocate memory.
637#[cfg_attr(feature = "copy", derive(Copy))]
638#[derive(Clone, Debug)]
639pub struct NoAllocator;
640
641unsafe impl GlobalAlloc for NoAllocator {
642 #[inline]
643 unsafe fn alloc(&self, _: Layout) -> *mut u8 {
644 panic!("** NoAllocator::alloc() does not allocate memory **");
645 }
646
647 #[inline]
648 unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
649 // I deny all allocations, so I don't need to free.
650 }
651}
652
653#[cfg(feature = "alloc")]
654mod alloc {
655 use {
656 crate::{entrypoint::MAX_HEAP_LENGTH, hint::unlikely},
657 core::{
658 alloc::{GlobalAlloc, Layout},
659 mem::size_of,
660 ptr::null_mut,
661 },
662 };
663
664 /// The bump allocator used as the default Rust heap when running programs.
665 ///
666 /// The allocator uses a forward bump allocation strategy, where memory is
667 /// allocated by moving a pointer forward in a pre-allocated memory
668 /// region. The current position of the heap pointer is stored at the
669 /// start of the memory region.
670 ///
671 /// This implementation relies on the runtime to zero out memory and to
672 /// enforce the limit of the heap memory region. Use of memory outside
673 /// the allocated region will result in a runtime error.
674 #[cfg_attr(feature = "copy", derive(Copy))]
675 #[derive(Clone, Debug)]
676 pub struct BumpAllocator {
677 start: usize,
678 end: usize,
679 }
680
681 impl BumpAllocator {
682 /// Creates the allocator tied to specific range of addresses.
683 ///
684 /// # Safety
685 ///
686 /// This is unsafe in most situations, unless you are totally sure that
687 /// the provided start address and length can be written to by the
688 /// allocator, and that the memory will be usable for the
689 /// lifespan of the allocator. The start address must be aligned
690 /// to `usize` and the length must be
691 /// at least `size_of::<usize>()` bytes.
692 ///
693 /// For Solana on-chain programs, a certain address range is reserved,
694 /// so the allocator can be given those addresses. In general,
695 /// the `len` is set to the maximum heap length allowed by the
696 /// runtime. The runtime will enforce the actual heap size
697 /// requested by the program.
698 pub const unsafe fn new_unchecked(start: usize, len: usize) -> Self {
699 Self {
700 start,
701 end: start + len,
702 }
703 }
704 }
705
706 // Integer arithmetic in this global allocator implementation is safe when
707 // operating on the prescribed `BumpAllocator::start` and
708 // `BumpAllocator::end`. Any other use may overflow and is thus unsupported
709 // and at one's own risk.
710 #[allow(clippy::arithmetic_side_effects)]
711 unsafe impl GlobalAlloc for BumpAllocator {
712 /// Allocates memory as described by the given `layout` using a forward
713 /// bump allocator.
714 ///
715 /// Returns a pointer to newly-allocated memory, or `null` to indicate
716 /// allocation failure.
717 ///
718 /// # Safety
719 ///
720 /// `layout` must have non-zero size. Attempting to allocate for a
721 /// zero-sized layout will result in undefined behavior.
722 #[inline]
723 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
724 // Reads the current position of the heap pointer.
725 //
726 // Integer-to-pointer cast: the caller guarantees that `self.start` is a valid
727 // address for the lifetime of the allocator and aligned to `usize`.
728 let pos_ptr = self.start as *mut usize;
729 let mut pos = *pos_ptr;
730
731 if unlikely(pos == 0) {
732 // First time, set starting position.
733 pos = self.start + size_of::<usize>();
734 }
735
736 // Determines the allocation address, adjusting the alignment for the
737 // type being allocated.
738 let allocation = (pos + layout.align() - 1) & !(layout.align() - 1);
739
740 if unlikely(layout.size() > MAX_HEAP_LENGTH as usize)
741 || unlikely(self.end < allocation + layout.size())
742 {
743 return null_mut();
744 }
745
746 // Updates the heap pointer.
747 *pos_ptr = allocation + layout.size();
748
749 allocation as *mut u8
750 }
751
752 /// Behaves like `alloc`, but also ensures that the contents are set to
753 /// zero before being returned.
754 ///
755 /// This method relies on the runtime to zero out the memory when
756 /// reserving the heap region, so it simply calls `alloc`.
757 #[inline]
758 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
759 self.alloc(layout)
760 }
761
762 /// This method has no effect since the bump allocator does not free
763 /// memory.
764 #[inline]
765 unsafe fn dealloc(&self, _: *mut u8, _: Layout) {}
766 }
767}
768
769#[cfg(test)]
770mod tests {
771 use {
772 super::*,
773 ::alloc::{
774 alloc::{alloc, dealloc, handle_alloc_error},
775 vec,
776 },
777 core::{
778 alloc::Layout,
779 ptr::{copy_nonoverlapping, null_mut},
780 },
781 };
782
783 /// The mock program ID used for testing.
784 const MOCK_PROGRAM_ID: Address = Address::new_from_array([5u8; 32]);
785
786 /// An uninitialized account view.
787 const UNINIT: MaybeUninit<AccountView> = MaybeUninit::<AccountView>::uninit();
788
789 /// Struct representing a memory region with a specific alignment.
790 struct AlignedMemory {
791 ptr: *mut u8,
792 layout: Layout,
793 }
794
795 impl AlignedMemory {
796 pub fn new(len: usize) -> Self {
797 let layout = Layout::from_size_align(len, BPF_ALIGN_OF_U128).unwrap();
798 // SAFETY: `align` is set to `BPF_ALIGN_OF_U128`.
799 unsafe {
800 let ptr = alloc(layout);
801 if ptr.is_null() {
802 handle_alloc_error(layout);
803 }
804 AlignedMemory { ptr, layout }
805 }
806 }
807
808 /// Write data to the memory region at the specified offset.
809 ///
810 /// # Safety
811 ///
812 /// The caller must ensure that the `data` length does not exceed the
813 /// remaining space in the memory region starting from the
814 /// `offset`.
815 pub unsafe fn write(&mut self, data: &[u8], offset: usize) {
816 copy_nonoverlapping(data.as_ptr(), self.ptr.add(offset), data.len());
817 }
818
819 /// Return a mutable pointer to the memory region.
820 pub fn as_mut_ptr(&mut self) -> *mut u8 {
821 self.ptr
822 }
823 }
824
825 impl Drop for AlignedMemory {
826 fn drop(&mut self) {
827 unsafe {
828 dealloc(self.ptr, self.layout);
829 }
830 }
831 }
832
833 /// Creates an input buffer with a specified number of accounts and
834 /// instruction data.
835 ///
836 /// This function mimics the input buffer created by the SVM loader. Each
837 /// account created has zeroed data, apart from the `data_len` field,
838 /// which is set to the index of the account.
839 ///
840 /// # Safety
841 ///
842 /// The returned `AlignedMemory` should only be used within the test
843 /// context.
844 unsafe fn create_input(accounts: usize, instruction_data: &[u8]) -> AlignedMemory {
845 let mut input = AlignedMemory::new(1_000_000_000);
846 // Number of accounts.
847 input.write(&(accounts as u64).to_le_bytes(), 0);
848 let mut offset = size_of::<u64>();
849
850 for i in 0..accounts {
851 // Account data.
852 let mut account = [0u8; STATIC_ACCOUNT_DATA + size_of::<u64>()];
853 account[0] = NON_DUP_MARKER;
854 // Set the accounts data length. The actual account data is zeroed.
855 account[80..88].copy_from_slice(&i.to_le_bytes());
856 input.write(&account, offset);
857 offset += account.len();
858 // Padding for the account data to align to `BPF_ALIGN_OF_U128`.
859 let padding_for_data = (i + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1);
860 input.write(&vec![0u8; padding_for_data], offset);
861 offset += padding_for_data;
862 }
863
864 // Instruction data length.
865 input.write(&instruction_data.len().to_le_bytes(), offset);
866 offset += size_of::<u64>();
867 // Instruction data.
868 input.write(instruction_data, offset);
869 offset += instruction_data.len();
870 // Program ID (mock).
871 input.write(MOCK_PROGRAM_ID.as_array(), offset);
872
873 input
874 }
875
876 /// Creates an input buffer with a specified number of accounts, including
877 /// duplicated accounts, and instruction data.
878 ///
879 /// This function differs from `create_input` in that it creates accounts
880 /// with a marker indicating that they are duplicated. There will be
881 /// `accounts - duplicated` unique accounts, and the remaining
882 /// `duplicated` accounts will be duplicates of the last unique account.
883 ///
884 /// This function mimics the input buffer created by the SVM loader. Each
885 /// account created has zeroed data, apart from the `data_len` field,
886 /// which is set to the index of the account.
887 ///
888 /// # Safety
889 ///
890 /// The returned `AlignedMemory` should only be used within the test
891 /// context.
892 unsafe fn create_input_with_duplicates(
893 accounts: usize,
894 instruction_data: &[u8],
895 duplicated: usize,
896 ) -> AlignedMemory {
897 let mut input = AlignedMemory::new(1_000_000_000);
898 // Number of accounts.
899 input.write(&(accounts as u64).to_le_bytes(), 0);
900 let mut offset = size_of::<u64>();
901
902 if accounts > 0 {
903 assert!(
904 duplicated < accounts,
905 "Duplicated accounts must be less than total accounts"
906 );
907 let unique = accounts - duplicated;
908
909 for i in 0..unique {
910 // Account data.
911 let mut account = [0u8; STATIC_ACCOUNT_DATA + size_of::<u64>()];
912 account[0] = NON_DUP_MARKER;
913 // Set the accounts data length. The actual account data is zeroed.
914 account[80..88].copy_from_slice(&i.to_le_bytes());
915 input.write(&account, offset);
916 offset += account.len();
917 // Padding for the account data to align to `BPF_ALIGN_OF_U128`.
918 let padding_for_data = (i + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1);
919 input.write(&vec![0u8; padding_for_data], offset);
920 offset += padding_for_data;
921 }
922
923 // Remaining accounts are duplicated of the last unique account.
924 for _ in unique..accounts {
925 input.write(&[(unique - 1) as u8, 0, 0, 0, 0, 0, 0, 0], offset);
926 offset += size_of::<u64>();
927 }
928 }
929
930 // Instruction data length.
931 input.write(&instruction_data.len().to_le_bytes(), offset);
932 offset += size_of::<u64>();
933 // Instruction data.
934 input.write(instruction_data, offset);
935 offset += instruction_data.len();
936 // Program ID (mock).
937 input.write(MOCK_PROGRAM_ID.as_array(), offset);
938
939 input
940 }
941
942 /// Asserts that the accounts slice contains the expected number of accounts
943 /// and that each account's data length matches its index.
944 fn assert_accounts(accounts: &[MaybeUninit<AccountView>]) {
945 for (i, account) in accounts.iter().enumerate() {
946 let account_view = unsafe { account.assume_init_ref() };
947 assert_eq!(account_view.data_len(), i);
948 }
949 }
950
951 /// Asserts that the accounts slice contains the expected number of accounts
952 /// and all accounts are duplicated, apart from the first one.
953 fn assert_duplicated_accounts(accounts: &[MaybeUninit<AccountView>], duplicated: usize) {
954 assert!(accounts.len() > duplicated);
955
956 let unique = accounts.len() - duplicated;
957
958 // Unique accounts should have `data_len` equal to their index.
959 for (i, account) in accounts[..unique].iter().enumerate() {
960 let account_view = unsafe { account.assume_init_ref() };
961 assert_eq!(account_view.data_len(), i);
962 }
963
964 // Last unique account.
965 let duplicated = unsafe { accounts[unique - 1].assume_init_ref() };
966 // No mutable borrow active at this point.
967 assert!(duplicated.try_borrow_mut().is_ok());
968
969 // Duplicated accounts should reference (share) the account pointer
970 // to the last unique account.
971 for account in accounts[unique..].iter() {
972 let account_view = unsafe { account.assume_init_ref() };
973
974 assert_eq!(account_view, duplicated);
975 assert_eq!(account_view.data_len(), duplicated.data_len());
976
977 let borrowed = account_view.try_borrow_mut().unwrap();
978 // Only one mutable borrow at the same time should be allowed
979 // on the duplicated account.
980 assert!(duplicated.try_borrow_mut().is_err());
981 drop(borrowed);
982 }
983
984 // There should not be any mutable borrow on the duplicated account
985 // at this point.
986 assert!(duplicated.try_borrow_mut().is_ok());
987 }
988
989 #[test]
990 fn test_deserialize() {
991 let ix_data = [3u8; 100];
992
993 // Input with 0 accounts.
994
995 let mut input = unsafe { create_input(0, &ix_data) };
996 let mut accounts = [UNINIT; 1];
997
998 let (program_id, count, parsed_ix_data) =
999 unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
1000
1001 assert_eq!(count, 0);
1002 assert!(program_id == &MOCK_PROGRAM_ID);
1003 assert_eq!(&ix_data, parsed_ix_data);
1004
1005 // Input with 3 accounts but the accounts array has only space
1006 // for 1.
1007
1008 let mut input = unsafe { create_input(3, &ix_data) };
1009 let mut accounts = [UNINIT; 1];
1010
1011 let (program_id, count, parsed_ix_data) =
1012 unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
1013
1014 assert_eq!(count, 1);
1015 assert!(program_id == &MOCK_PROGRAM_ID);
1016 assert_eq!(&ix_data, parsed_ix_data);
1017 assert_accounts(&accounts[..count]);
1018
1019 // Input with `MAX_TX_ACCOUNTS` accounts but accounts array has
1020 // only space for 64.
1021
1022 let mut input = unsafe { create_input(MAX_TX_ACCOUNTS, &ix_data) };
1023 let mut accounts = [UNINIT; 64];
1024
1025 let (program_id, count, parsed_ix_data) =
1026 unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
1027
1028 assert_eq!(count, 64);
1029 assert!(program_id == &MOCK_PROGRAM_ID);
1030 assert_eq!(&ix_data, parsed_ix_data);
1031 assert_accounts(&accounts);
1032 }
1033
1034 #[test]
1035 fn test_deserialize_duplicated() {
1036 let ix_data = [3u8; 100];
1037
1038 // Input with 0 accounts.
1039
1040 let mut input = unsafe { create_input_with_duplicates(0, &ix_data, 0) };
1041 let mut accounts = [UNINIT; 1];
1042
1043 let (program_id, count, parsed_ix_data) =
1044 unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
1045
1046 assert_eq!(count, 0);
1047 assert!(program_id == &MOCK_PROGRAM_ID);
1048 assert_eq!(&ix_data, parsed_ix_data);
1049
1050 // Input with 3 (1 + 2 duplicated) accounts but the accounts array has only
1051 // space for 2. The assert checks that the second account is a duplicate
1052 // of the first one and the first one is unique.
1053
1054 let mut input = unsafe { create_input_with_duplicates(3, &ix_data, 2) };
1055 let mut accounts = [UNINIT; 2];
1056
1057 let (program_id, count, parsed_ix_data) =
1058 unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
1059
1060 assert_eq!(count, 2);
1061 assert!(program_id == &MOCK_PROGRAM_ID);
1062 assert_eq!(&ix_data, parsed_ix_data);
1063 assert_duplicated_accounts(&accounts[..count], 1);
1064
1065 // Input with `MAX_TX_ACCOUNTS` accounts (only 32 unique ones) but accounts
1066 // array has only space for 64. The assert checks that the first 32
1067 // accounts are unique and the rest are duplicates of the account at
1068 // index 31.
1069
1070 let mut input = unsafe {
1071 create_input_with_duplicates(MAX_TX_ACCOUNTS, &ix_data, MAX_TX_ACCOUNTS - 32)
1072 };
1073 let mut accounts = [UNINIT; 64];
1074
1075 let (program_id, count, parsed_ix_data) =
1076 unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
1077
1078 assert_eq!(count, 64);
1079 assert!(program_id == &MOCK_PROGRAM_ID);
1080 assert_eq!(&ix_data, parsed_ix_data);
1081 assert_duplicated_accounts(&accounts, 32);
1082 }
1083
1084 #[test]
1085 fn test_bump_allocator() {
1086 // alloc the entire
1087 {
1088 let mut heap = AlignedMemory::new(128);
1089 unsafe { heap.write(&[0; 128], 0) };
1090
1091 let allocator = unsafe {
1092 BumpAllocator::new_unchecked(heap.as_mut_ptr() as usize, heap.layout.size())
1093 };
1094
1095 for i in 0..128 - size_of::<*mut u8>() {
1096 let ptr = unsafe {
1097 allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
1098 };
1099 assert_eq!(
1100 ptr as usize,
1101 heap.as_mut_ptr() as usize + size_of::<*mut u8>() + i
1102 );
1103 }
1104 assert_eq!(null_mut(), unsafe {
1105 allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
1106 });
1107 }
1108 // check alignment
1109 {
1110 let mut heap = AlignedMemory::new(128);
1111 unsafe { heap.write(&[0; 128], 0) };
1112
1113 let allocator = unsafe {
1114 BumpAllocator::new_unchecked(heap.as_mut_ptr() as usize, heap.layout.size())
1115 };
1116 let ptr =
1117 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap()) };
1118 assert_eq!(0, ptr.align_offset(size_of::<u8>()));
1119 let ptr =
1120 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u16>()).unwrap()) };
1121 assert_eq!(0, ptr.align_offset(size_of::<u16>()));
1122 let ptr =
1123 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u32>()).unwrap()) };
1124 assert_eq!(0, ptr.align_offset(size_of::<u32>()));
1125 let ptr =
1126 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u64>()).unwrap()) };
1127 assert_eq!(0, ptr.align_offset(size_of::<u64>()));
1128 let ptr =
1129 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u128>()).unwrap()) };
1130 assert_eq!(0, ptr.align_offset(size_of::<u128>()));
1131 let ptr = unsafe { allocator.alloc(Layout::from_size_align(1, 64).unwrap()) };
1132 assert_eq!(0, ptr.align_offset(64));
1133 }
1134 // alloc entire block (minus the pos ptr)
1135 {
1136 let mut heap = AlignedMemory::new(128);
1137 unsafe { heap.write(&[0; 128], 0) };
1138
1139 let allocator = unsafe {
1140 BumpAllocator::new_unchecked(heap.as_mut_ptr() as usize, heap.layout.size())
1141 };
1142 let ptr = unsafe {
1143 allocator.alloc(
1144 Layout::from_size_align(
1145 heap.layout.size() - size_of::<usize>(),
1146 size_of::<u8>(),
1147 )
1148 .unwrap(),
1149 )
1150 };
1151 assert_ne!(ptr, null_mut());
1152 assert_eq!(0, ptr.align_offset(size_of::<u64>()));
1153 }
1154 }
1155}