gemachain_program/
entrypoint.rs

1//! @brief Gemachain Rust-based BPF program entry point supported by the latest
2//! BPFLoader.  For more information see './bpf_loader.rs'
3
4extern crate alloc;
5use crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
6use alloc::vec::Vec;
7use std::{
8    alloc::Layout,
9    cell::RefCell,
10    mem::{align_of, size_of},
11    ptr::null_mut,
12    rc::Rc,
13    // Hide Result from bindgen gets confused about generics in non-generic type declarations
14    result::Result as ResultGeneric,
15    slice::{from_raw_parts, from_raw_parts_mut},
16};
17
18pub type ProgramResult = ResultGeneric<(), ProgramError>;
19
20/// User implemented function to process an instruction
21///
22/// program_id: Program ID of the currently executing program accounts: Accounts
23/// passed as part of the instruction instruction_data: Instruction data
24pub type ProcessInstruction =
25    fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult;
26
27/// Programs indicate success with a return value of 0
28pub const SUCCESS: u64 = 0;
29
30/// Start address of the memory region used for program heap.
31pub const HEAP_START_ADDRESS: usize = 0x300000000;
32/// Length of the heap memory region used for program heap.
33pub const HEAP_LENGTH: usize = 32 * 1024;
34
35/// Declare the entry point of the program and use the default local heap
36/// implementation
37///
38/// Deserialize the program input arguments and call the user defined
39/// `process_instruction` function. Users must call this macro otherwise an
40/// entry point for their program will not be created.
41#[macro_export]
42macro_rules! entrypoint {
43    ($process_instruction:ident) => {
44        /// # Safety
45        #[no_mangle]
46        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
47            let (program_id, accounts, instruction_data) =
48                unsafe { $crate::entrypoint::deserialize(input) };
49            match $process_instruction(&program_id, &accounts, &instruction_data) {
50                Ok(()) => $crate::entrypoint::SUCCESS,
51                Err(error) => error.into(),
52            }
53        }
54        $crate::custom_heap_default!();
55        $crate::custom_panic_default!();
56    };
57}
58
59/// Fallback to default for unused custom heap feature.
60#[macro_export]
61macro_rules! custom_heap_default {
62    () => {
63        /// A program can provide their own custom heap implementation by adding
64        /// a `custom-heap` feature to `Cargo.toml` and implementing their own
65        /// `global_allocator`.
66        ///
67        /// If the program defines the feature `custom-heap` then the default heap
68        /// implementation will not be included and the program is free to implement
69        /// their own `#[global_allocator]`
70        #[cfg(all(not(feature = "custom-heap"), target_arch = "bpf"))]
71        #[global_allocator]
72        static A: $crate::entrypoint::BumpAllocator = $crate::entrypoint::BumpAllocator {
73            start: $crate::entrypoint::HEAP_START_ADDRESS,
74            len: $crate::entrypoint::HEAP_LENGTH,
75        };
76    };
77}
78
79/// Fallback to default for unused custom panic feature.
80/// This must be used if the entrypoint! macro is not used.
81#[macro_export]
82macro_rules! custom_panic_default {
83    () => {
84        /// A program can provide their own custom panic implementation by
85        /// adding a `custom-panic` feature to `Cargo.toml` and implementing
86        /// their own `custom_panic`.
87        ///
88        /// A good way to reduce the final size of the program is to provide a
89        /// `custom_panic` implementation that does nothing.  Doing so will cut
90        /// ~25kb from a noop program.  That number goes down the more the
91        /// programs pulls in Rust's libstd for other purposes.
92        #[cfg(all(not(feature = "custom-panic"), target_arch = "bpf"))]
93        #[no_mangle]
94        fn custom_panic(info: &core::panic::PanicInfo<'_>) {
95            // Full panic reporting
96            $crate::msg!("{}", info);
97        }
98    };
99}
100
101/// The bump allocator used as the default rust heap when running programs.
102pub struct BumpAllocator {
103    pub start: usize,
104    pub len: usize,
105}
106/// Integer arithmetic in this global allocator implementation is safe when
107/// operating on the prescribed `HEAP_START_ADDRESS` and `HEAP_LENGTH`. Any
108/// other use may overflow and is thus unsupported and at one's own risk.
109#[allow(clippy::integer_arithmetic)]
110unsafe impl std::alloc::GlobalAlloc for BumpAllocator {
111    #[inline]
112    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
113        let pos_ptr = self.start as *mut usize;
114
115        let mut pos = *pos_ptr;
116        if pos == 0 {
117            // First time, set starting position
118            pos = self.start + self.len;
119        }
120        pos = pos.saturating_sub(layout.size());
121        pos &= !(layout.align().wrapping_sub(1));
122        if pos < self.start + size_of::<*mut u8>() {
123            return null_mut();
124        }
125        *pos_ptr = pos;
126        pos as *mut u8
127    }
128    #[inline]
129    unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
130        // I'm a bump allocator, I don't free
131    }
132}
133
134/// Maximum number of bytes a program may add to an account during a single realloc
135pub const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10;
136
137/// Deserialize the input arguments
138///
139/// The integer arithmetic in this method is safe when called on a buffer that was
140/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
141/// done at one's own risk.
142#[allow(clippy::integer_arithmetic)]
143///
144/// # Safety
145#[allow(clippy::type_complexity)]
146pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
147    let mut offset: usize = 0;
148
149    // Number of accounts present
150
151    #[allow(clippy::cast_ptr_alignment)]
152    let num_accounts = *(input.add(offset) as *const u64) as usize;
153    offset += size_of::<u64>();
154
155    // Account Infos
156
157    let mut accounts = Vec::with_capacity(num_accounts);
158    for _ in 0..num_accounts {
159        let dup_info = *(input.add(offset) as *const u8);
160        offset += size_of::<u8>();
161        if dup_info == std::u8::MAX {
162            #[allow(clippy::cast_ptr_alignment)]
163            let is_signer = *(input.add(offset) as *const u8) != 0;
164            offset += size_of::<u8>();
165
166            #[allow(clippy::cast_ptr_alignment)]
167            let is_writable = *(input.add(offset) as *const u8) != 0;
168            offset += size_of::<u8>();
169
170            #[allow(clippy::cast_ptr_alignment)]
171            let executable = *(input.add(offset) as *const u8) != 0;
172            offset += size_of::<u8>();
173
174            offset += size_of::<u32>(); // padding to u64
175
176            let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
177            offset += size_of::<Pubkey>();
178
179            let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
180            offset += size_of::<Pubkey>();
181
182            #[allow(clippy::cast_ptr_alignment)]
183            let carats = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
184            offset += size_of::<u64>();
185
186            #[allow(clippy::cast_ptr_alignment)]
187            let data_len = *(input.add(offset) as *const u64) as usize;
188            offset += size_of::<u64>();
189
190            let data = Rc::new(RefCell::new({
191                from_raw_parts_mut(input.add(offset), data_len)
192            }));
193            offset += data_len + MAX_PERMITTED_DATA_INCREASE;
194            offset += (offset as *const u8).align_offset(align_of::<u128>()); // padding
195
196            #[allow(clippy::cast_ptr_alignment)]
197            let rent_epoch = *(input.add(offset) as *const u64);
198            offset += size_of::<u64>();
199
200            accounts.push(AccountInfo {
201                key,
202                is_signer,
203                is_writable,
204                carats,
205                data,
206                owner,
207                executable,
208                rent_epoch,
209            });
210        } else {
211            offset += 7; // padding
212
213            // Duplicate account, clone the original
214            accounts.push(accounts[dup_info as usize].clone());
215        }
216    }
217
218    // Instruction data
219
220    #[allow(clippy::cast_ptr_alignment)]
221    let instruction_data_len = *(input.add(offset) as *const u64) as usize;
222    offset += size_of::<u64>();
223
224    let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
225    offset += instruction_data_len;
226
227    // Program Id
228
229    let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
230
231    (program_id, accounts, instruction_data)
232}
233
234#[cfg(test)]
235mod test {
236    use super::*;
237    use std::alloc::GlobalAlloc;
238
239    #[test]
240    fn test_bump_allocator() {
241        // alloc the entire
242        {
243            let heap = vec![0u8; 128];
244            let allocator = BumpAllocator {
245                start: heap.as_ptr() as *const _ as usize,
246                len: heap.len(),
247            };
248            for i in 0..128 - size_of::<*mut u8>() {
249                let ptr = unsafe {
250                    allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
251                };
252                assert_eq!(
253                    ptr as *const _ as usize,
254                    heap.as_ptr() as *const _ as usize + heap.len() - 1 - i
255                );
256            }
257            assert_eq!(null_mut(), unsafe {
258                allocator.alloc(Layout::from_size_align(1, 1).unwrap())
259            });
260        }
261        // check alignment
262        {
263            let heap = vec![0u8; 128];
264            let allocator = BumpAllocator {
265                start: heap.as_ptr() as *const _ as usize,
266                len: heap.len(),
267            };
268            let ptr =
269                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap()) };
270            assert_eq!(0, ptr.align_offset(size_of::<u8>()));
271            let ptr =
272                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u16>()).unwrap()) };
273            assert_eq!(0, ptr.align_offset(size_of::<u16>()));
274            let ptr =
275                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u32>()).unwrap()) };
276            assert_eq!(0, ptr.align_offset(size_of::<u32>()));
277            let ptr =
278                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u64>()).unwrap()) };
279            assert_eq!(0, ptr.align_offset(size_of::<u64>()));
280            let ptr =
281                unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u128>()).unwrap()) };
282            assert_eq!(0, ptr.align_offset(size_of::<u128>()));
283            let ptr = unsafe { allocator.alloc(Layout::from_size_align(1, 64).unwrap()) };
284            assert_eq!(0, ptr.align_offset(64));
285        }
286        // alloc entire block (minus the pos ptr)
287        {
288            let heap = vec![0u8; 128];
289            let allocator = BumpAllocator {
290                start: heap.as_ptr() as *const _ as usize,
291                len: heap.len(),
292            };
293            let ptr =
294                unsafe { allocator.alloc(Layout::from_size_align(120, size_of::<u8>()).unwrap()) };
295            assert_ne!(ptr, null_mut());
296            assert_eq!(0, ptr.align_offset(size_of::<u64>()));
297        }
298    }
299}