atlas_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 atlas_account_info::AccountInfo,
7 atlas_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 atlas_account_info::AccountInfo as __AccountInfo,
19 atlas_account_info::MAX_PERMITTED_DATA_INCREASE,
20 // Re-exporting for custom_panic
21 atlas_define_syscall::definitions::{atlas_log_ as __log, sol_panic_ as __panic},
22 atlas_msg::msg as __msg,
23 atlas_program_error::ProgramResult,
24 atlas_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 Atlas 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 atlas_account_info::AccountInfo;
106/// use atlas_program_entrypoint::entrypoint;
107/// use atlas_program_entrypoint::ProgramResult;
108/// use atlas_msg::msg;
109/// use atlas_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://atlaschain.org/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 = "atlas"))]
223 #[global_allocator]
224 static A: $crate::BumpAllocator = unsafe {
225 $crate::BumpAllocator::with_fixed_address_range(
226 $crate::HEAP_START_ADDRESS as usize,
227 $crate::HEAP_LENGTH,
228 )
229 };
230 };
231}
232
233/// Define the default global panic handler.
234///
235/// This must be used if the [`entrypoint`] macro is not used, and no other
236/// panic handler has been defined; otherwise a program will crash without an
237/// explicit panic message.
238///
239/// The default global allocator is enabled only if the calling crate has not
240/// disabled it using [Cargo features] as described below. It is only defined
241/// for [BPF] targets.
242///
243/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
244/// [BPF]: https://atlaschain.org/docs/programs/faq#berkeley-packet-filter-bpf
245///
246/// # Cargo features
247///
248/// A crate that calls this macro can provide its own custom panic handler, or
249/// allow others to provide their own custom panic handler, by adding a
250/// `custom-panic` feature to its `Cargo.toml`. After enabling the feature, one
251/// may define their own panic handler.
252///
253/// A good way to reduce the final size of the program is to provide a
254/// `custom_panic` implementation that does nothing. Doing so will cut ~25kb
255/// from a noop program. That number goes down the more the programs pulls in
256/// Rust's standard library for other purposes.
257///
258/// # Defining a panic handler for Atlas
259///
260/// _The mechanism for defining a Atlas panic handler is different [from most
261/// Rust programs][rpanic]._
262///
263/// [rpanic]: https://doc.rust-lang.org/nomicon/panic-handler.html
264///
265/// To define a panic handler one must define a `custom_panic` function
266/// with the `#[no_mangle]` attribute, as below:
267///
268/// ```ignore
269/// #[cfg(all(feature = "custom-panic", target_os = "atlas"))]
270/// #[no_mangle]
271/// fn custom_panic(info: &core::panic::PanicInfo<'_>) {
272/// $crate::msg!("{}", info);
273/// }
274/// ```
275#[macro_export]
276macro_rules! custom_panic_default {
277 () => {
278 #[cfg(all(not(feature = "custom-panic"), target_os = "atlas"))]
279 #[no_mangle]
280 fn custom_panic(info: &core::panic::PanicInfo<'_>) {
281 if let Some(mm) = info.message().as_str() {
282 unsafe {
283 $crate::__log(mm.as_ptr(), mm.len() as u64);
284 }
285 }
286
287 if let Some(loc) = info.location() {
288 unsafe {
289 $crate::__panic(
290 loc.file().as_ptr(),
291 loc.file().len() as u64,
292 loc.line() as u64,
293 loc.column() as u64,
294 )
295 }
296 }
297 }
298 };
299}
300
301/// The bump allocator used as the default rust heap when running programs.
302pub struct BumpAllocator {
303 start: usize,
304 len: usize,
305}
306
307impl BumpAllocator {
308 /// Creates the allocator tied to a provided slice.
309 /// This will not initialize the provided memory, except for the first
310 /// bytes where the pointer is stored.
311 ///
312 /// # Safety
313 /// As long as BumpAllocator or any of its allocations are alive,
314 /// writing into or deallocating the arena will cause UB.
315 ///
316 /// Integer arithmetic in this global allocator implementation is safe when
317 /// operating on the prescribed `HEAP_START_ADDRESS` and `HEAP_LENGTH`. Any
318 /// other use may overflow and is thus unsupported and at one's own risk.
319 #[inline]
320 #[allow(clippy::arithmetic_side_effects)]
321 pub unsafe fn new(arena: &mut [u8]) -> Self {
322 debug_assert!(
323 arena.len() > size_of::<usize>(),
324 "Arena should be larger than usize"
325 );
326
327 // create a pointer to the start of the arena
328 // that will hold an address of the byte following free space
329 let pos_ptr = arena.as_mut_ptr() as *mut usize;
330 // initialize the data there
331 *pos_ptr = pos_ptr as usize + arena.len();
332
333 Self {
334 start: pos_ptr as usize,
335 len: arena.len(),
336 }
337 }
338
339 /// Creates the allocator tied to specific range of addresses.
340 ///
341 /// # Safety
342 /// This is unsafe in most situations, unless you are totally sure that the
343 /// provided start address and length can be written to by the allocator,
344 /// and that the memory will be usable for the lifespan of the allocator.
345 ///
346 /// For Atlas on-chain programs, a certain address range is reserved, so
347 /// the allocator can be given those addresses.
348 pub const unsafe fn with_fixed_address_range(start: usize, len: usize) -> Self {
349 Self { start, len }
350 }
351}
352
353/// Integer arithmetic in this global allocator implementation is safe when
354/// operating on the prescribed `HEAP_START_ADDRESS` and `HEAP_LENGTH`. Any
355/// other use may overflow and is thus unsupported and at one's own risk.
356#[allow(clippy::arithmetic_side_effects)]
357unsafe impl std::alloc::GlobalAlloc for BumpAllocator {
358 #[inline]
359 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
360 let pos_ptr = self.start as *mut usize;
361 let mut pos = *pos_ptr;
362 if pos == 0 {
363 // First time, set starting position
364 pos = self.start + self.len;
365 }
366 pos = pos.saturating_sub(layout.size());
367 pos &= !(layout.align().wrapping_sub(1));
368 if pos < self.start + size_of::<*mut u8>() {
369 return null_mut();
370 }
371 *pos_ptr = pos;
372 pos as *mut u8
373 }
374 #[inline]
375 unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
376 // I'm a bump allocator, I don't free
377 }
378}
379
380/// `assert_eq(std::mem::align_of::<u128>(), 8)` is true for BPF but not for some host machines
381pub const BPF_ALIGN_OF_U128: usize = 8;
382
383#[allow(clippy::arithmetic_side_effects)]
384#[inline(always)] // this reduces CU usage
385unsafe fn deserialize_instruction_data<'a>(input: *mut u8, mut offset: usize) -> (&'a [u8], usize) {
386 #[allow(clippy::cast_ptr_alignment)]
387 let instruction_data_len = *(input.add(offset) as *const u64) as usize;
388 offset += size_of::<u64>();
389
390 let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
391 offset += instruction_data_len;
392
393 (instruction_data, offset)
394}
395
396#[allow(clippy::arithmetic_side_effects)]
397#[inline(always)] // this reduces CU usage by half!
398unsafe fn deserialize_account_info<'a>(
399 input: *mut u8,
400 mut offset: usize,
401) -> (AccountInfo<'a>, usize) {
402 #[allow(clippy::cast_ptr_alignment)]
403 let is_signer = *(input.add(offset) as *const u8) != 0;
404 offset += size_of::<u8>();
405
406 #[allow(clippy::cast_ptr_alignment)]
407 let is_writable = *(input.add(offset) as *const u8) != 0;
408 offset += size_of::<u8>();
409
410 #[allow(clippy::cast_ptr_alignment)]
411 let executable = *(input.add(offset) as *const u8) != 0;
412 offset += size_of::<u8>();
413
414 // The original data length is stored here because these 4 bytes were
415 // originally only used for padding and served as a good location to
416 // track the original size of the account data in a compatible way.
417 let original_data_len_offset = offset;
418 offset += size_of::<u32>();
419
420 let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
421 offset += size_of::<Pubkey>();
422
423 let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
424 offset += size_of::<Pubkey>();
425
426 #[allow(clippy::cast_ptr_alignment)]
427 let lamports = &mut *(input.add(offset) as *mut u64);
428 offset += size_of::<u64>();
429
430 #[allow(clippy::cast_ptr_alignment)]
431 let data_len = *(input.add(offset) as *const u64) as usize;
432 offset += size_of::<u64>();
433
434 // Store the original data length for detecting invalid reallocations and
435 // requires that MAX_PERMITTED_DATA_LENGTH fits in a u32
436 *(input.add(original_data_len_offset) as *mut u32) = data_len as u32;
437
438 let data = from_raw_parts_mut(input.add(offset), data_len);
439 // rent epoch is not deserialized, so skip it
440 offset += data_len + MAX_PERMITTED_DATA_INCREASE + size_of::<u64>();
441 offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); // padding
442
443 (
444 AccountInfo::new(
445 key,
446 is_signer,
447 is_writable,
448 lamports,
449 data,
450 owner,
451 executable,
452 ),
453 offset,
454 )
455}
456
457/// Deserialize the input arguments
458///
459/// The integer arithmetic in this method is safe when called on a buffer that was
460/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
461/// done at one's own risk.
462///
463/// # Safety
464#[allow(clippy::arithmetic_side_effects)]
465pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
466 let mut offset: usize = 0;
467
468 // Number of accounts present
469
470 #[allow(clippy::cast_ptr_alignment)]
471 let num_accounts = *(input.add(offset) as *const u64) as usize;
472 offset += size_of::<u64>();
473
474 // Account Infos
475
476 let mut accounts = Vec::with_capacity(num_accounts);
477 for _ in 0..num_accounts {
478 let dup_info = *(input.add(offset) as *const u8);
479 offset += size_of::<u8>();
480 if dup_info == NON_DUP_MARKER {
481 let (account_info, new_offset) = deserialize_account_info(input, offset);
482 offset = new_offset;
483 accounts.push(account_info);
484 } else {
485 offset += 7; // padding
486
487 // Duplicate account, clone the original
488 accounts.push(accounts[dup_info as usize].clone());
489 }
490 }
491
492 // Instruction data
493
494 let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
495 offset = new_offset;
496
497 // Program Id
498
499 let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
500
501 (program_id, accounts, instruction_data)
502}
503
504/// Deserialize the input arguments
505///
506/// Differs from `deserialize` by writing the account infos into an uninitialized
507/// slice, which provides better performance, roughly 30 CUs per unique account
508/// provided to the instruction.
509///
510/// Panics if the input slice is not large enough.
511///
512/// The integer arithmetic in this method is safe when called on a buffer that was
513/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
514/// done at one's own risk.
515///
516/// # Safety
517#[allow(clippy::arithmetic_side_effects)]
518pub unsafe fn deserialize_into<'a>(
519 input: *mut u8,
520 accounts: &mut [MaybeUninit<AccountInfo<'a>>],
521) -> (&'a Pubkey, usize, &'a [u8]) {
522 let mut offset: usize = 0;
523
524 // Number of accounts present
525
526 #[allow(clippy::cast_ptr_alignment)]
527 let num_accounts = *(input.add(offset) as *const u64) as usize;
528 offset += size_of::<u64>();
529
530 if num_accounts > accounts.len() {
531 panic!(
532 "{} accounts provided, but only {} are supported",
533 num_accounts,
534 accounts.len()
535 );
536 }
537
538 // Account Infos
539
540 for i in 0..num_accounts {
541 let dup_info = *(input.add(offset) as *const u8);
542 offset += size_of::<u8>();
543 if dup_info == NON_DUP_MARKER {
544 let (account_info, new_offset) = deserialize_account_info(input, offset);
545 offset = new_offset;
546 accounts[i].write(account_info);
547 } else {
548 offset += 7; // padding
549
550 // Duplicate account, clone the original
551 accounts[i].write(accounts[dup_info as usize].assume_init_ref().clone());
552 }
553 }
554
555 // Instruction data
556
557 let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
558 offset = new_offset;
559
560 // Program Id
561
562 let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
563
564 (program_id, num_accounts, instruction_data)
565}
566
567#[cfg(test)]
568mod test {
569 use {super::*, std::alloc::GlobalAlloc};
570
571 #[test]
572 fn test_bump_allocator() {
573 // alloc the entire
574 {
575 let mut heap = [0u8; 128];
576 let allocator = unsafe { BumpAllocator::new(&mut heap) };
577 for i in 0..128 - size_of::<*mut u8>() {
578 let ptr = unsafe {
579 allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
580 };
581 assert_eq!(ptr as usize, heap.as_ptr() as usize + heap.len() - 1 - i);
582 }
583 assert_eq!(null_mut(), unsafe {
584 allocator.alloc(Layout::from_size_align(1, 1).unwrap())
585 });
586 }
587 // check alignment
588 {
589 let mut heap = [0u8; 128];
590 let allocator = unsafe { BumpAllocator::new(&mut heap) };
591 let ptr =
592 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap()) };
593 assert_eq!(0, ptr.align_offset(size_of::<u8>()));
594 let ptr =
595 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u16>()).unwrap()) };
596 assert_eq!(0, ptr.align_offset(size_of::<u16>()));
597 let ptr =
598 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u32>()).unwrap()) };
599 assert_eq!(0, ptr.align_offset(size_of::<u32>()));
600 let ptr =
601 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u64>()).unwrap()) };
602 assert_eq!(0, ptr.align_offset(size_of::<u64>()));
603 let ptr =
604 unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u128>()).unwrap()) };
605 assert_eq!(0, ptr.align_offset(size_of::<u128>()));
606 let ptr = unsafe { allocator.alloc(Layout::from_size_align(1, 64).unwrap()) };
607 assert_eq!(0, ptr.align_offset(64));
608 }
609 // alloc entire block (minus the pos ptr)
610 {
611 let mut heap = [0u8; 128];
612 let allocator = unsafe { BumpAllocator::new(&mut heap) };
613 let ptr = unsafe {
614 allocator.alloc(
615 Layout::from_size_align(heap.len() - size_of::<usize>(), size_of::<u8>())
616 .unwrap(),
617 )
618 };
619 assert_ne!(ptr, null_mut());
620 assert_eq!(0, ptr.align_offset(size_of::<u64>()));
621 }
622 }
623}