solana_program_entrypoint/
lib.rs

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