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}