hbros_solana_program_entrypoint/lib.rs
1//! The Rust-based BPF program entrypoint supported by the latest BPF loader.
2//!
3//! For more information see the [`bpf_loader`] module.
4//!
5//! [`bpf_loader`]: crate::bpf_loader
6
7extern crate alloc;
8use {
9 alloc::vec::Vec,
10 solana_account_info::AccountInfo,
11 solana_pubkey::Pubkey,
12 std::{
13 alloc::Layout,
14 cell::RefCell,
15 mem::{size_of, MaybeUninit},
16 ptr::null_mut,
17 rc::Rc,
18 slice::{from_raw_parts, from_raw_parts_mut},
19 },
20};
21// need to re-export msg for custom_heap_default macro
22pub use {
23 solana_account_info::MAX_PERMITTED_DATA_INCREASE, solana_msg::msg as __msg,
24 solana_program_error::ProgramResult,
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 hbros_solana_program_entrypoint::entrypoint;
107/// use hbros_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<AccountInfo> =
171 MaybeUninit::<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] as *const [MaybeUninit<AccountInfo<'_>>]
178 as *const [AccountInfo<'_>]);
179
180 #[inline(never)]
181 fn call_program(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> u64 {
182 match $process_instruction(program_id, accounts, data) {
183 Ok(()) => $crate::SUCCESS,
184 Err(error) => error.into(),
185 }
186 }
187
188 call_program(&program_id, accounts, &instruction_data)
189 }
190 $crate::custom_heap_default!();
191 $crate::custom_panic_default!();
192 };
193}
194
195/// Define the default global allocator.
196///
197/// The default global allocator is enabled only if the calling crate has not
198/// disabled it using [Cargo features] as described below. It is only defined
199/// for [BPF] targets.
200///
201/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
202/// [BPF]: https://solana.com/docs/programs/faq#berkeley-packet-filter-bpf
203///
204/// # Cargo features
205///
206/// A crate that calls this macro can provide its own custom heap
207/// implementation, or allow others to provide their own custom heap
208/// implementation, by adding a `custom-heap` feature to its `Cargo.toml`. After
209/// enabling the feature, one may define their own [global allocator] in the
210/// standard way.
211///
212/// [global allocator]: https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html
213///
214#[macro_export]
215macro_rules! custom_heap_default {
216 () => {
217 #[cfg(all(not(feature = "custom-heap"), target_os = "solana"))]
218 #[global_allocator]
219 static A: $crate::BumpAllocator = $crate::BumpAllocator {
220 start: $crate::HEAP_START_ADDRESS as usize,
221 len: $crate::HEAP_LENGTH,
222 };
223 };
224}
225
226/// Define the default global panic handler.
227///
228/// This must be used if the [`entrypoint`] macro is not used, and no other
229/// panic handler has been defined; otherwise compilation will fail with a
230/// missing `custom_panic` symbol.
231///
232/// The default global allocator is enabled only if the calling crate has not
233/// disabled it using [Cargo features] as described below. It is only defined
234/// for [BPF] targets.
235///
236/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
237/// [BPF]: https://solana.com/docs/programs/faq#berkeley-packet-filter-bpf
238///
239/// # Cargo features
240///
241/// A crate that calls this macro can provide its own custom panic handler, or
242/// allow others to provide their own custom panic handler, by adding a
243/// `custom-panic` feature to its `Cargo.toml`. After enabling the feature, one
244/// may define their own panic handler.
245///
246/// A good way to reduce the final size of the program is to provide a
247/// `custom_panic` implementation that does nothing. Doing so will cut ~25kb
248/// from a noop program. That number goes down the more the programs pulls in
249/// Rust's standard library for other purposes.
250///
251/// # Defining a panic handler for Solana
252///
253/// _The mechanism for defining a Solana panic handler is different [from most
254/// Rust programs][rpanic]._
255///
256/// [rpanic]: https://doc.rust-lang.org/nomicon/panic-handler.html
257///
258/// To define a panic handler one must define a `custom_panic` function
259/// with the `#[no_mangle]` attribute, as below:
260///
261/// ```ignore
262/// #[cfg(all(feature = "custom-panic", target_os = "solana"))]
263/// #[no_mangle]
264/// fn custom_panic(info: &core::panic::PanicInfo<'_>) {
265/// $crate::msg!("{}", info);
266/// }
267/// ```
268///
269/// The above is how Solana defines the default panic handler.
270#[macro_export]
271macro_rules! custom_panic_default {
272 () => {
273 #[cfg(all(not(feature = "custom-panic"), target_os = "solana"))]
274 #[no_mangle]
275 fn custom_panic(info: &core::panic::PanicInfo<'_>) {
276 // Full panic reporting
277 $crate::__msg!("{}", info);
278 }
279 };
280}
281
282/// The bump allocator used as the default rust heap when running programs.
283pub struct BumpAllocator {
284 pub start: usize,
285 pub len: usize,
286}
287impl BumpAllocator {
288 const RESERVED_MEM: usize = 1 * size_of::<*mut u8>();
289
290 /// Return heap position as of this call
291 pub unsafe fn pos(&self) -> usize {
292 let pos_ptr = self.start as *mut usize;
293 *pos_ptr
294 }
295
296 /// Reset heap start cursor to position.
297 /// ### This is very unsafe, use wisely
298 pub unsafe fn move_cursor(&self, pos: usize) {
299 let pos_ptr = self.start as *mut usize;
300 *pos_ptr = pos;
301 }
302}
303/// Integer arithmetic in this global allocator implementation is safe when
304/// operating on the prescribed `HEAP_START_ADDRESS` and `HEAP_LENGTH`. Any
305/// other use may overflow and is thus unsupported and at one's own risk.
306#[allow(clippy::arithmetic_side_effects)]
307unsafe impl std::alloc::GlobalAlloc for BumpAllocator {
308 #[inline]
309 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
310 let pos_ptr = self.start as *mut usize;
311
312 let mut pos = *pos_ptr;
313 if pos == 0 {
314 // First time, set starting position
315 pos = self.start + self.len;
316 }
317 pos = pos.saturating_sub(layout.size());
318 pos &= !(layout.align().wrapping_sub(1));
319 if pos < self.start + size_of::<*mut u8>() {
320 return null_mut();
321 }
322 *pos_ptr = pos;
323 pos as *mut u8
324 }
325 #[inline]
326 unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
327 // I'm a bump allocator, I don't free
328 }
329}
330
331/// `assert_eq(std::mem::align_of::<u128>(), 8)` is true for BPF but not for some host machines
332pub const BPF_ALIGN_OF_U128: usize = 8;
333
334#[allow(clippy::arithmetic_side_effects)]
335#[inline(always)] // this reduces CU usage
336unsafe fn deserialize_instruction_data<'a>(input: *mut u8, mut offset: usize) -> (&'a [u8], usize) {
337 #[allow(clippy::cast_ptr_alignment)]
338 let instruction_data_len = *(input.add(offset) as *const u64) as usize;
339 offset += size_of::<u64>();
340
341 let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
342 offset += instruction_data_len;
343
344 (instruction_data, offset)
345}
346
347#[allow(clippy::arithmetic_side_effects)]
348#[inline(always)] // this reduces CU usage by half!
349unsafe fn deserialize_account_info<'a>(
350 input: *mut u8,
351 mut offset: usize,
352) -> (AccountInfo<'a>, usize) {
353 #[allow(clippy::cast_ptr_alignment)]
354 let is_signer = *(input.add(offset) as *const u8) != 0;
355 offset += size_of::<u8>();
356
357 #[allow(clippy::cast_ptr_alignment)]
358 let is_writable = *(input.add(offset) as *const u8) != 0;
359 offset += size_of::<u8>();
360
361 #[allow(clippy::cast_ptr_alignment)]
362 let executable = *(input.add(offset) as *const u8) != 0;
363 offset += size_of::<u8>();
364
365 // The original data length is stored here because these 4 bytes were
366 // originally only used for padding and served as a good location to
367 // track the original size of the account data in a compatible way.
368 let original_data_len_offset = offset;
369 offset += size_of::<u32>();
370
371 let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
372 offset += size_of::<Pubkey>();
373
374 let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
375 offset += size_of::<Pubkey>();
376
377 #[allow(clippy::cast_ptr_alignment)]
378 let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
379 offset += size_of::<u64>();
380
381 #[allow(clippy::cast_ptr_alignment)]
382 let data_len = *(input.add(offset) as *const u64) as usize;
383 offset += size_of::<u64>();
384
385 // Store the original data length for detecting invalid reallocations and
386 // requires that MAX_PERMITTED_DATA_LENGTH fits in a u32
387 *(input.add(original_data_len_offset) as *mut u32) = data_len as u32;
388
389 let data = Rc::new(RefCell::new({
390 from_raw_parts_mut(input.add(offset), data_len)
391 }));
392 offset += data_len + MAX_PERMITTED_DATA_INCREASE;
393 offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); // padding
394
395 #[allow(clippy::cast_ptr_alignment)]
396 let rent_epoch = *(input.add(offset) as *const u64);
397 offset += size_of::<u64>();
398
399 (
400 AccountInfo {
401 key,
402 is_signer,
403 is_writable,
404 lamports,
405 data,
406 owner,
407 executable,
408 rent_epoch,
409 },
410 offset,
411 )
412}
413
414/// Deserialize the input arguments
415///
416/// The integer arithmetic in this method is safe when called on a buffer that was
417/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
418/// done at one's own risk.
419///
420/// # Safety
421#[allow(clippy::arithmetic_side_effects)]
422pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
423 let mut offset: usize = 0;
424
425 // Number of accounts present
426
427 #[allow(clippy::cast_ptr_alignment)]
428 let num_accounts = *(input.add(offset) as *const u64) as usize;
429 offset += size_of::<u64>();
430
431 // Account Infos
432
433 let mut accounts = Vec::with_capacity(num_accounts);
434 for _ in 0..num_accounts {
435 let dup_info = *(input.add(offset) as *const u8);
436 offset += size_of::<u8>();
437 if dup_info == NON_DUP_MARKER {
438 let (account_info, new_offset) = deserialize_account_info(input, offset);
439 offset = new_offset;
440 accounts.push(account_info);
441 } else {
442 offset += 7; // padding
443
444 // Duplicate account, clone the original
445 accounts.push(accounts[dup_info as usize].clone());
446 }
447 }
448
449 // Instruction data
450
451 let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
452 offset = new_offset;
453
454 // Program Id
455
456 let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
457
458 (program_id, accounts, instruction_data)
459}
460
461/// Deserialize the input arguments
462///
463/// Differs from `deserialize` by writing the account infos into an uninitialized
464/// slice, which provides better performance, roughly 30 CUs per unique account
465/// provided to the instruction.
466///
467/// Panics if the input slice is not large enough.
468///
469/// The integer arithmetic in this method is safe when called on a buffer that was
470/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
471/// done at one's own risk.
472///
473/// # Safety
474#[allow(clippy::arithmetic_side_effects)]
475pub unsafe fn deserialize_into<'a>(
476 input: *mut u8,
477 accounts: &mut [MaybeUninit<AccountInfo<'a>>],
478) -> (&'a Pubkey, usize, &'a [u8]) {
479 let mut offset: usize = 0;
480
481 // Number of accounts present
482
483 #[allow(clippy::cast_ptr_alignment)]
484 let num_accounts = *(input.add(offset) as *const u64) as usize;
485 offset += size_of::<u64>();
486
487 if num_accounts > accounts.len() {
488 panic!(
489 "{} accounts provided, but only {} are supported",
490 num_accounts,
491 accounts.len()
492 );
493 }
494
495 // Account Infos
496
497 for i in 0..num_accounts {
498 let dup_info = *(input.add(offset) as *const u8);
499 offset += size_of::<u8>();
500 if dup_info == NON_DUP_MARKER {
501 let (account_info, new_offset) = deserialize_account_info(input, offset);
502 offset = new_offset;
503 accounts[i].write(account_info);
504 } else {
505 offset += 7; // padding
506
507 // Duplicate account, clone the original
508 accounts[i].write(accounts[dup_info as usize].assume_init_ref().clone());
509 }
510 }
511
512 // Instruction data
513
514 let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
515 offset = new_offset;
516
517 // Program Id
518
519 let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
520
521 (program_id, num_accounts, instruction_data)
522}
523
524#[cfg(test)]
525mod test {
526 use {super::*, std::alloc::GlobalAlloc};
527
528 #[test]
529 fn test_bump_allocator() {
530 // alloc the entire
531 {
532 let heap = [0u8; 128];
533 let allocator = BumpAllocator {
534 start: heap.as_ptr() as *const _ as usize,
535 len: heap.len(),
536 };
537 for i in 0..128 - size_of::<*mut u8>() {
538 let ptr = unsafe {
539 allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
540 };
541 assert_eq!(
542 ptr as *const _ as usize,
543 heap.as_ptr() as *const _ as usize + heap.len() - 1 - i
544 );
545 }
546 assert_eq!(null_mut(), unsafe {
547 allocator.alloc(Layout::from_size_align(1, 1).unwrap())
548 });
549 }
550 // check alignment
551 {
552 let heap = [0u8; 128];
553 let allocator = BumpAllocator {
554 start: heap.as_ptr() as *const _ as usize,
555 len: heap.len(),
556 };
557 let ptr =
558 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap()) };
559 assert_eq!(0, ptr.align_offset(size_of::<u8>()));
560 let ptr =
561 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u16>()).unwrap()) };
562 assert_eq!(0, ptr.align_offset(size_of::<u16>()));
563 let ptr =
564 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u32>()).unwrap()) };
565 assert_eq!(0, ptr.align_offset(size_of::<u32>()));
566 let ptr =
567 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u64>()).unwrap()) };
568 assert_eq!(0, ptr.align_offset(size_of::<u64>()));
569 let ptr =
570 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u128>()).unwrap()) };
571 assert_eq!(0, ptr.align_offset(size_of::<u128>()));
572 let ptr = unsafe { allocator.alloc(Layout::from_size_align(1, 64).unwrap()) };
573 assert_eq!(0, ptr.align_offset(64));
574 }
575 // alloc entire block (minus the pos ptr)
576 {
577 let heap = [0u8; 128];
578 let allocator = BumpAllocator {
579 start: heap.as_ptr() as *const _ as usize,
580 len: heap.len(),
581 };
582 let ptr =
583 unsafe { allocator.alloc(Layout::from_size_align(120, size_of::<u8>()).unwrap()) };
584 assert_ne!(ptr, null_mut());
585 assert_eq!(0, ptr.align_offset(size_of::<u64>()));
586 }
587 }
588}