solana_program_entrypoint/
lib.rs

1//! The Rust-based BPF program entrypoint supported by the latest BPF loader.
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4extern crate alloc;
5use {
6    alloc::vec::Vec,
7    solana_account_info::AccountInfo,
8    solana_pubkey::Pubkey,
9    std::{
10        alloc::Layout,
11        mem::{size_of, MaybeUninit},
12        ptr::null_mut,
13        slice::{from_raw_parts, from_raw_parts_mut},
14    },
15};
16// need to re-export `AccountInfo` and `Pubkey` for entrypoint_no_alloc macro
17pub use {
18    solana_account_info::AccountInfo as __AccountInfo,
19    solana_account_info::MAX_PERMITTED_DATA_INCREASE,
20    // Re-exporting for custom_panic
21    solana_define_syscall::definitions::{sol_log_ as __log, sol_panic_ as __panic},
22    solana_program_error::ProgramResult,
23    solana_pubkey::Pubkey as __Pubkey,
24};
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/// Start address of the memory region used for program heap.
37pub const HEAP_START_ADDRESS: u64 = 0x300000000;
38/// Length of the heap memory region used for program heap.
39pub const HEAP_LENGTH: usize = 32 * 1024;
40
41/// Value used to indicate that a serialized account is not a duplicate
42pub const NON_DUP_MARKER: u8 = u8::MAX;
43
44/// Declare the program entrypoint and set up global handlers.
45///
46/// This macro emits the common boilerplate necessary to begin program
47/// execution, calling a provided function to process the program instruction
48/// supplied by the runtime, and reporting its result to the runtime.
49///
50/// It also sets up a [global allocator] and [panic handler], using the
51/// [`custom_heap_default`] and [`custom_panic_default`] macros.
52///
53/// [`custom_heap_default`]: crate::custom_heap_default
54/// [`custom_panic_default`]: crate::custom_panic_default
55///
56/// [global allocator]: https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html
57/// [panic handler]: https://doc.rust-lang.org/nomicon/panic-handler.html
58///
59/// The argument is the name of a function with this type signature:
60///
61/// ```ignore
62/// fn process_instruction(
63///     program_id: &Pubkey,      // Public key of the account the program was loaded into
64///     accounts: &[AccountInfo], // All accounts required to process the instruction
65///     instruction_data: &[u8],  // Serialized instruction-specific data
66/// ) -> ProgramResult;
67/// ```
68///
69/// # Cargo features
70///
71/// This macro emits symbols and definitions that may only be defined once
72/// globally. As such, if linked to other Rust crates it will cause compiler
73/// errors. To avoid this, it is common for Solana programs to define an
74/// optional [Cargo feature] called `no-entrypoint`, and use it to conditionally
75/// disable the `entrypoint` macro invocation, as well as the
76/// `process_instruction` function. See a typical pattern for this in the
77/// example below.
78///
79/// [Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html
80///
81/// The code emitted by this macro can be customized by adding cargo features
82/// _to your own crate_ (the one that calls this macro) and enabling them:
83///
84/// - If the `custom-heap` feature is defined then the macro will not set up the
85///   global allocator, allowing `entrypoint` to be used with your own
86///   allocator. See documentation for the [`custom_heap_default`] macro for
87///   details of customizing the global allocator.
88///
89/// - If the `custom-panic` feature is defined then the macro will not define a
90///   panic handler, allowing `entrypoint` to be used with your own panic
91///   handler. See documentation for the [`custom_panic_default`] macro for
92///   details of customizing the panic handler.
93///
94/// # Examples
95///
96/// Defining an entrypoint and making it conditional on the `no-entrypoint`
97/// feature. Although the `entrypoint` module is written inline in this example,
98/// it is common to put it into its own file.
99///
100/// ```no_run
101/// #[cfg(not(feature = "no-entrypoint"))]
102/// pub mod entrypoint {
103///
104///     use solana_account_info::AccountInfo;
105///     use solana_program_entrypoint::entrypoint;
106///     use solana_program_entrypoint::ProgramResult;
107///     use solana_msg::msg;
108///     use solana_pubkey::Pubkey;
109///
110///     entrypoint!(process_instruction);
111///
112///     pub fn process_instruction(
113///         program_id: &Pubkey,
114///         accounts: &[AccountInfo],
115///         instruction_data: &[u8],
116///     ) -> ProgramResult {
117///         msg!("Hello world");
118///
119///         Ok(())
120///     }
121///
122/// }
123/// ```
124#[macro_export]
125macro_rules! entrypoint {
126    ($process_instruction:ident) => {
127        /// # Safety
128        #[no_mangle]
129        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
130            let (program_id, accounts, instruction_data) = unsafe { $crate::deserialize(input) };
131            match $process_instruction(program_id, &accounts, instruction_data) {
132                Ok(()) => $crate::SUCCESS,
133                Err(error) => error.into(),
134            }
135        }
136        $crate::custom_heap_default!();
137        $crate::custom_panic_default!();
138    };
139}
140
141/// Declare the program entrypoint and set up global handlers.
142///
143/// This is similar to the `entrypoint!` macro, except that it does not perform
144/// any dynamic allocations, and instead writes the input accounts into a pre-
145/// allocated array.
146///
147/// This version reduces compute unit usage by 20-30 compute units per unique
148/// account in the instruction. It may become the default option in a future
149/// release.
150///
151/// For more information about how the program entrypoint behaves and what it
152/// does, please see the documentation for [`entrypoint!`].
153///
154/// NOTE: This entrypoint has a hard-coded limit of 64 input accounts.
155#[macro_export]
156macro_rules! entrypoint_no_alloc {
157    ($process_instruction:ident) => {
158        /// # Safety
159        #[no_mangle]
160        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
161            use std::mem::MaybeUninit;
162            // Clippy complains about this because a `const` with interior
163            // mutability `RefCell` should use `static` instead to make it
164            // clear that it can change.
165            // In our case, however, we want to create an array of `AccountInfo`s,
166            // and the only way to do it is through a `const` expression, and
167            // we don't expect to mutate the internals of this `const` type.
168            #[allow(clippy::declare_interior_mutable_const)]
169            const UNINIT_ACCOUNT_INFO: MaybeUninit<$crate::__AccountInfo> =
170                MaybeUninit::<$crate::__AccountInfo>::uninit();
171            const MAX_ACCOUNT_INFOS: usize = 64;
172            let mut accounts = [UNINIT_ACCOUNT_INFO; MAX_ACCOUNT_INFOS];
173            let (program_id, num_accounts, instruction_data) =
174                unsafe { $crate::deserialize_into(input, &mut accounts) };
175            // Use `slice_assume_init_ref` once it's stabilized
176            let accounts = &*(&accounts[..num_accounts]
177                as *const [MaybeUninit<$crate::__AccountInfo<'_>>]
178                as *const [$crate::__AccountInfo<'_>]);
179
180            #[inline(never)]
181            fn call_program(
182                program_id: &$crate::__Pubkey,
183                accounts: &[$crate::__AccountInfo],
184                data: &[u8],
185            ) -> u64 {
186                match $process_instruction(program_id, accounts, data) {
187                    Ok(()) => $crate::SUCCESS,
188                    Err(error) => error.into(),
189                }
190            }
191
192            call_program(&program_id, accounts, &instruction_data)
193        }
194        $crate::custom_heap_default!();
195        $crate::custom_panic_default!();
196    };
197}
198
199/// Define the default global allocator.
200///
201/// The default global allocator is enabled only if the calling crate has not
202/// disabled it using [Cargo features] as described below. It is only defined
203/// for [BPF] targets.
204///
205/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
206/// [BPF]: https://solana.com/docs/programs/faq#berkeley-packet-filter-bpf
207///
208/// # Cargo features
209///
210/// A crate that calls this macro can provide its own custom heap
211/// implementation, or allow others to provide their own custom heap
212/// implementation, by adding a `custom-heap` feature to its `Cargo.toml`. After
213/// enabling the feature, one may define their own [global allocator] in the
214/// standard way.
215///
216/// [global allocator]: https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html
217///
218#[macro_export]
219macro_rules! custom_heap_default {
220    () => {
221        #[cfg(all(not(feature = "custom-heap"), target_os = "solana"))]
222        #[global_allocator]
223        static A: $crate::BumpAllocator = unsafe {
224            $crate::BumpAllocator::with_fixed_address_range(
225                $crate::HEAP_START_ADDRESS as usize,
226                $crate::HEAP_LENGTH,
227            )
228        };
229    };
230}
231
232/// Define the default global panic handler.
233///
234/// This must be used if the [`entrypoint`] macro is not used, and no other
235/// panic handler has been defined; otherwise a program will crash without an
236/// explicit panic message.
237///
238/// The default global allocator is enabled only if the calling crate has not
239/// disabled it using [Cargo features] as described below. It is only defined
240/// for [BPF] targets.
241///
242/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
243/// [BPF]: https://solana.com/docs/programs/faq#berkeley-packet-filter-bpf
244///
245/// # Cargo features
246///
247/// A crate that calls this macro can provide its own custom panic handler, or
248/// allow others to provide their own custom panic handler, by adding a
249/// `custom-panic` feature to its `Cargo.toml`. After enabling the feature, one
250/// may define their own panic handler.
251///
252/// A good way to reduce the final size of the program is to provide a
253/// `custom_panic` implementation that does nothing. Doing so will cut ~25kb
254/// from a noop program. That number goes down the more the programs pulls in
255/// Rust's standard library for other purposes.
256///
257/// # Defining a panic handler for Solana
258///
259/// _The mechanism for defining a Solana panic handler is different [from most
260/// Rust programs][rpanic]._
261///
262/// [rpanic]: https://doc.rust-lang.org/nomicon/panic-handler.html
263///
264/// To define a panic handler one must define a `custom_panic` function
265/// with the `#[no_mangle]` attribute, as below:
266///
267/// ```ignore
268/// #[cfg(all(feature = "custom-panic", target_os = "solana"))]
269/// #[no_mangle]
270/// fn custom_panic(info: &core::panic::PanicInfo<'_>) {
271///     $crate::msg!("{}", info);
272/// }
273/// ```
274#[macro_export]
275macro_rules! custom_panic_default {
276    () => {
277        #[cfg(all(not(feature = "custom-panic"), target_os = "solana"))]
278        #[no_mangle]
279        fn custom_panic(info: &core::panic::PanicInfo<'_>) {
280            if let Some(mm) = info.message().as_str() {
281                unsafe {
282                    $crate::__log(mm.as_ptr(), mm.len() as u64);
283                }
284            }
285
286            if let Some(loc) = info.location() {
287                unsafe {
288                    $crate::__panic(
289                        loc.file().as_ptr(),
290                        loc.file().len() as u64,
291                        loc.line() as u64,
292                        loc.column() as u64,
293                    )
294                }
295            }
296        }
297    };
298}
299
300/// The bump allocator used as the default rust heap when running programs.
301pub struct BumpAllocator {
302    start: usize,
303    len: usize,
304}
305
306impl BumpAllocator {
307    /// Creates the allocator tied to a provided slice.
308    /// This will not initialize the provided memory, except for the first
309    /// bytes where the pointer is stored.
310    ///
311    /// # Safety
312    /// As long as BumpAllocator or any of its allocations are alive,
313    /// writing into or deallocating the arena will cause UB.
314    ///
315    /// Integer arithmetic in this global allocator implementation is safe when
316    /// operating on the prescribed `HEAP_START_ADDRESS` and `HEAP_LENGTH`. Any
317    /// other use may overflow and is thus unsupported and at one's own risk.
318    #[inline]
319    #[allow(clippy::arithmetic_side_effects)]
320    pub unsafe fn new(arena: &mut [u8]) -> Self {
321        debug_assert!(
322            arena.len() > size_of::<usize>(),
323            "Arena should be larger than usize"
324        );
325
326        // create a pointer to the start of the arena
327        // that will hold an address of the byte following free space
328        let pos_ptr = arena.as_mut_ptr() as *mut usize;
329        // initialize the data there
330        *pos_ptr = pos_ptr as usize + arena.len();
331
332        Self {
333            start: pos_ptr as usize,
334            len: arena.len(),
335        }
336    }
337
338    /// Creates the allocator tied to specific range of addresses.
339    ///
340    /// # Safety
341    /// This is unsafe in most situations, unless you are totally sure that the
342    /// provided start address and length can be written to by the allocator,
343    /// and that the memory will be usable for the lifespan of the allocator.
344    ///
345    /// For Solana on-chain programs, a certain address range is reserved, so
346    /// the allocator can be given those addresses.
347    pub const unsafe fn with_fixed_address_range(start: usize, len: usize) -> Self {
348        Self { start, len }
349    }
350}
351
352/// Integer arithmetic in this global allocator implementation is safe when
353/// operating on the prescribed `HEAP_START_ADDRESS` and `HEAP_LENGTH`. Any
354/// other use may overflow and is thus unsupported and at one's own risk.
355#[allow(clippy::arithmetic_side_effects)]
356unsafe impl std::alloc::GlobalAlloc for BumpAllocator {
357    #[inline]
358    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
359        let pos_ptr = self.start as *mut usize;
360        let mut pos = *pos_ptr;
361        if pos == 0 {
362            // First time, set starting position
363            pos = self.start + self.len;
364        }
365        pos = pos.saturating_sub(layout.size());
366        pos &= !(layout.align().wrapping_sub(1));
367        if pos < self.start + size_of::<*mut u8>() {
368            return null_mut();
369        }
370        *pos_ptr = pos;
371        pos as *mut u8
372    }
373    #[inline]
374    unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
375        // I'm a bump allocator, I don't free
376    }
377}
378
379/// `assert_eq(std::mem::align_of::<u128>(), 8)` is true for BPF but not for some host machines
380pub const BPF_ALIGN_OF_U128: usize = 8;
381
382#[allow(clippy::arithmetic_side_effects)]
383#[inline(always)] // this reduces CU usage
384unsafe fn deserialize_instruction_data<'a>(input: *mut u8, mut offset: usize) -> (&'a [u8], usize) {
385    #[allow(clippy::cast_ptr_alignment)]
386    let instruction_data_len = *(input.add(offset) as *const u64) as usize;
387    offset += size_of::<u64>();
388
389    let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
390    offset += instruction_data_len;
391
392    (instruction_data, offset)
393}
394
395#[allow(clippy::arithmetic_side_effects)]
396#[inline(always)] // this reduces CU usage by half!
397unsafe fn deserialize_account_info<'a>(
398    input: *mut u8,
399    mut offset: usize,
400) -> (AccountInfo<'a>, usize) {
401    #[allow(clippy::cast_ptr_alignment)]
402    let is_signer = *(input.add(offset) as *const u8) != 0;
403    offset += size_of::<u8>();
404
405    #[allow(clippy::cast_ptr_alignment)]
406    let is_writable = *(input.add(offset) as *const u8) != 0;
407    offset += size_of::<u8>();
408
409    #[allow(clippy::cast_ptr_alignment)]
410    let executable = *(input.add(offset) as *const u8) != 0;
411    offset += size_of::<u8>();
412
413    // The original data length is stored here because these 4 bytes were
414    // originally only used for padding and served as a good location to
415    // track the original size of the account data in a compatible way.
416    let original_data_len_offset = offset;
417    offset += size_of::<u32>();
418
419    let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
420    offset += size_of::<Pubkey>();
421
422    let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
423    offset += size_of::<Pubkey>();
424
425    #[allow(clippy::cast_ptr_alignment)]
426    let lamports = &mut *(input.add(offset) as *mut u64);
427    offset += size_of::<u64>();
428
429    #[allow(clippy::cast_ptr_alignment)]
430    let data_len = *(input.add(offset) as *const u64) as usize;
431    offset += size_of::<u64>();
432
433    // Store the original data length for detecting invalid reallocations and
434    // requires that MAX_PERMITTED_DATA_LENGTH fits in a u32
435    *(input.add(original_data_len_offset) as *mut u32) = data_len as u32;
436
437    let data = from_raw_parts_mut(input.add(offset), data_len);
438    // rent epoch is not deserialized, so skip it
439    offset += data_len + MAX_PERMITTED_DATA_INCREASE + size_of::<u64>();
440    offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); // padding
441
442    (
443        AccountInfo::new(
444            key,
445            is_signer,
446            is_writable,
447            lamports,
448            data,
449            owner,
450            executable,
451        ),
452        offset,
453    )
454}
455
456/// Deserialize the input arguments
457///
458/// The integer arithmetic in this method is safe when called on a buffer that was
459/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
460/// done at one's own risk.
461///
462/// # Safety
463#[allow(clippy::arithmetic_side_effects)]
464pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
465    let mut offset: usize = 0;
466
467    // Number of accounts present
468
469    #[allow(clippy::cast_ptr_alignment)]
470    let num_accounts = *(input.add(offset) as *const u64) as usize;
471    offset += size_of::<u64>();
472
473    // Account Infos
474
475    let mut accounts = Vec::with_capacity(num_accounts);
476    for _ in 0..num_accounts {
477        let dup_info = *(input.add(offset) as *const u8);
478        offset += size_of::<u8>();
479        if dup_info == NON_DUP_MARKER {
480            let (account_info, new_offset) = deserialize_account_info(input, offset);
481            offset = new_offset;
482            accounts.push(account_info);
483        } else {
484            offset += 7; // padding
485
486            // Duplicate account, clone the original
487            accounts.push(accounts[dup_info as usize].clone());
488        }
489    }
490
491    // Instruction data
492
493    let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
494    offset = new_offset;
495
496    // Program Id
497
498    let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
499
500    (program_id, accounts, instruction_data)
501}
502
503/// Deserialize the input arguments
504///
505/// Differs from `deserialize` by writing the account infos into an uninitialized
506/// slice, which provides better performance, roughly 30 CUs per unique account
507/// provided to the instruction.
508///
509/// Panics if the input slice is not large enough.
510///
511/// The integer arithmetic in this method is safe when called on a buffer that was
512/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
513/// done at one's own risk.
514///
515/// # Safety
516#[allow(clippy::arithmetic_side_effects)]
517pub unsafe fn deserialize_into<'a>(
518    input: *mut u8,
519    accounts: &mut [MaybeUninit<AccountInfo<'a>>],
520) -> (&'a Pubkey, usize, &'a [u8]) {
521    let mut offset: usize = 0;
522
523    // Number of accounts present
524
525    #[allow(clippy::cast_ptr_alignment)]
526    let num_accounts = *(input.add(offset) as *const u64) as usize;
527    offset += size_of::<u64>();
528
529    if num_accounts > accounts.len() {
530        panic!(
531            "{} accounts provided, but only {} are supported",
532            num_accounts,
533            accounts.len()
534        );
535    }
536
537    // Account Infos
538
539    for i in 0..num_accounts {
540        let dup_info = *(input.add(offset) as *const u8);
541        offset += size_of::<u8>();
542        if dup_info == NON_DUP_MARKER {
543            let (account_info, new_offset) = deserialize_account_info(input, offset);
544            offset = new_offset;
545            accounts[i].write(account_info);
546        } else {
547            offset += 7; // padding
548
549            // Duplicate account, clone the original
550            accounts[i].write(accounts[dup_info as usize].assume_init_ref().clone());
551        }
552    }
553
554    // Instruction data
555
556    let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
557    offset = new_offset;
558
559    // Program Id
560
561    let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
562
563    (program_id, num_accounts, instruction_data)
564}
565
566#[cfg(test)]
567mod test {
568    use {super::*, std::alloc::GlobalAlloc};
569
570    #[test]
571    fn test_bump_allocator() {
572        // alloc the entire
573        {
574            let mut heap = [0u8; 128];
575            let allocator = unsafe { BumpAllocator::new(&mut heap) };
576            for i in 0..128 - size_of::<*mut u8>() {
577                let ptr = unsafe {
578                    allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
579                };
580                assert_eq!(ptr as usize, heap.as_ptr() as usize + heap.len() - 1 - i);
581            }
582            assert_eq!(null_mut(), unsafe {
583                allocator.alloc(Layout::from_size_align(1, 1).unwrap())
584            });
585        }
586        // check alignment
587        {
588            let mut heap = [0u8; 128];
589            let allocator = unsafe { BumpAllocator::new(&mut heap) };
590            let ptr =
591                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap()) };
592            assert_eq!(0, ptr.align_offset(size_of::<u8>()));
593            let ptr =
594                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u16>()).unwrap()) };
595            assert_eq!(0, ptr.align_offset(size_of::<u16>()));
596            let ptr =
597                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u32>()).unwrap()) };
598            assert_eq!(0, ptr.align_offset(size_of::<u32>()));
599            let ptr =
600                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u64>()).unwrap()) };
601            assert_eq!(0, ptr.align_offset(size_of::<u64>()));
602            let ptr =
603                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u128>()).unwrap()) };
604            assert_eq!(0, ptr.align_offset(size_of::<u128>()));
605            let ptr = unsafe { allocator.alloc(Layout::from_size_align(1, 64).unwrap()) };
606            assert_eq!(0, ptr.align_offset(64));
607        }
608        // alloc entire block (minus the pos ptr)
609        {
610            let mut heap = [0u8; 128];
611            let allocator = unsafe { BumpAllocator::new(&mut heap) };
612            let ptr = unsafe {
613                allocator.alloc(
614                    Layout::from_size_align(heap.len() - size_of::<usize>(), size_of::<u8>())
615                        .unwrap(),
616                )
617            };
618            assert_ne!(ptr, null_mut());
619            assert_eq!(0, ptr.align_offset(size_of::<u64>()));
620        }
621    }
622}