Selaa lähdekoodia

Setup project structure

febo 1 vuosi sitten
vanhempi
sitoutus
a0f6c73489
13 muutettua tiedostoa jossa 1303 lisäystä ja 25 poistoa
  1. 75 0
      .github/workflows/main.yml
  2. 1 0
      .gitignore
  3. 7 0
      Cargo.lock
  4. 5 0
      Cargo.toml
  5. 1 25
      LICENSE
  6. 419 0
      src/account_info.rs
  7. 261 0
      src/entrypoint.rs
  8. 34 0
      src/instruction.rs
  9. 28 0
      src/lib.rs
  10. 121 0
      src/log.rs
  11. 212 0
      src/program_error.rs
  12. 15 0
      src/pubkey.rs
  13. 124 0
      src/syscalls.rs

+ 75 - 0
.github/workflows/main.yml

@@ -0,0 +1,75 @@
+name: Main
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+
+env:
+  RUST_VERSION: 1.78.0
+  SOLANA_VERSION: 1.18.20
+  CARGO_CACHE: |
+    ~/.cargo/bin/
+    ~/.cargo/registry/index/
+    ~/.cargo/registry/cache/
+    ~/.cargo/git/db/
+    target/
+
+jobs:
+  lint:
+    name: Lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: Git checkout
+        uses: actions/checkout@v4
+      - name: Setup environment
+        uses: ./.github/actions/setup
+      - name: Install components
+        uses: dtolnay/rust-toolchain@stable
+        with:
+          components: clippy, rustfmt
+      - name: Formatting
+        run: cargo fmt --check
+      - name: Clippy
+        run: cargo clippy
+
+  build:
+    name: Build
+    needs: lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: Git checkout
+        uses: actions/checkout@v4
+      - name: Install Solana
+        uses: nifty-oss/actions/install-solana@v1
+        with:
+          version: ${{ env.SOLANA_VERSION }}
+          cache: true
+      - name: Cache cargo dependencies
+        uses: actions/cache@v4
+        with:
+          path: ${{ env.CARGO_CACHE }}
+          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+          restore-keys: ${{ runner.os }}-cargo
+      - name: Build
+        run: cargo build-bpf
+
+  test:
+    name: Test
+    needs: lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: Git checkout
+        uses: actions/checkout@v4
+      - name: Setup environment
+        uses: ./.github/actions/setup
+        with:
+          solana: ${{ env.SOLANA_VERSION }}
+      - name: Cache cargo dependencies
+        uses: actions/cache@v4
+        with:
+          path: ${{ env.CARGO_CACHE }}
+          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+          restore-keys: ${{ runner.os }}-cargo
+      - name: Build
+        run: cargo test

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/target

+ 7 - 0
Cargo.lock

@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "pinocchio"
+version = "0.0.1"

+ 5 - 0
Cargo.toml

@@ -0,0 +1,5 @@
+[package]
+name = "pinocchio"
+description = "Create Solana programs with no strings attached"
+version = "0.0.1"
+edition = "2021"

+ 1 - 25
LICENSE

@@ -174,28 +174,4 @@
       of your accepting any such warranty or additional liability.
 
    END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
+   

+ 419 - 0
src/account_info.rs

@@ -0,0 +1,419 @@
+//! Data structures to represent account information.
+
+#![allow(clippy::missing_safety_doc)]
+
+use std::{ptr::NonNull, slice::from_raw_parts_mut};
+
+use crate::{
+    program_error::ProgramError, pubkey::Pubkey, syscalls::sol_memset_, MAX_PERMITTED_DATA_INCREASE,
+};
+
+/// Raw account data.
+///
+/// This data is wrapped in an `AccountInfo` struct, which provides safe access
+/// to the data.
+#[repr(C)]
+#[derive(Clone, Copy, Default)]
+pub(crate) struct Account {
+    /// Borrow state of the account data.
+    ///
+    /// 0) We reuse the duplicate flag for this. We set it to 0b0000_0000.
+    /// 1) We use the first four bits to track state of lamport borrow
+    /// 2) We use the second four bits to track state of data borrow
+    ///
+    /// 4 bit state: [1 bit mutable borrow flag | u3 immmutable borrow flag]
+    /// This gives us up to 7 immutable borrows. Note that does not mean 7
+    /// duplicate account infos, but rather 7 calls to borrow lamports or
+    /// borrow data across all duplicate account infos.
+    pub(crate) borrow_state: u8,
+
+    /// Indicates whether the transaction was signed by this account.
+    is_signer: u8,
+
+    /// Indicates whether the account is writable.
+    is_writable: u8,
+
+    /// Indicates whether this account represents a program.
+    executable: u8,
+
+    /// Account's original data length when it was serialized for the
+    /// current program invocation.
+    ///
+    /// The value of this field is lazily initialized to the current data length.
+    /// On first access, the original data length will be 0. The caller should
+    /// ensure that the original data length is set to the current data length for
+    /// subsequence access.
+    ///
+    /// The value of this field is currently only used for `realloc`.
+    original_data_len: [u8; 4],
+
+    /// Public key of the account
+    key: Pubkey,
+
+    /// Program that owns this account
+    owner: Pubkey,
+
+    /// The lamports in the account.  Modifiable by programs.
+    lamports: u64,
+
+    /// Length of the data.
+    pub(crate) data_len: u64,
+}
+
+// Convenience macro to get the original data length from the account – the value will
+// be zero on first access.
+macro_rules! get_original_data_len {
+    ( $self:expr ) => {
+        unsafe { *(&(*$self).original_data_len as *const _ as *const u32) as usize }
+    };
+}
+
+// Convenience macro to set the original data length in the account.
+macro_rules! set_original_data_len {
+    ( $self:expr, $len:expr ) => {
+        unsafe {
+            *(&mut (*$self).original_data_len) = u32::to_le_bytes($len as u32);
+        }
+    };
+}
+
+/// Wrapper struct for an `Account`.
+///
+/// This struct provides safe access to the data in an `Account`. It is also
+/// used to track borrows of the account data and lamports, given that an
+/// account can be "shared" across multiple `AccountInfo` instances.
+#[repr(C)]
+#[derive(Clone, PartialEq, Eq)]
+pub struct AccountInfo {
+    /// Raw (pointer to) account data.
+    ///
+    /// Note that this is a pointer can be shared across multiple `AccountInfo`.
+    pub(crate) raw: *mut Account,
+}
+
+impl AccountInfo {
+    /// Public key of the account.
+    #[inline(always)]
+    pub fn key(&self) -> &Pubkey {
+        unsafe { &(*self.raw).key }
+    }
+
+    /// Program that owns this account.
+    #[inline(always)]
+    pub fn owner(&self) -> &Pubkey {
+        unsafe { &(*self.raw).owner }
+    }
+
+    /// Indicates whether the transaction was signed by this account.
+    #[inline(always)]
+    pub fn is_signer(&self) -> bool {
+        unsafe { (*self.raw).is_signer != 0 }
+    }
+
+    /// Indicates whether the account is writable.
+    #[inline(always)]
+    pub fn is_writable(&self) -> bool {
+        unsafe { (*self.raw).is_writable != 0 }
+    }
+
+    /// Indicates whether this account represents a program.
+    ///
+    /// Program accounts are always read-only.
+    #[inline(always)]
+    pub fn executable(&self) -> bool {
+        unsafe { (*self.raw).executable != 0 }
+    }
+
+    /// Returns the size of the data in the account.
+    #[inline(always)]
+    pub fn data_len(&self) -> usize {
+        unsafe { (*self.raw).data_len as usize }
+    }
+
+    /// Indicates whether the account data is empty.
+    ///
+    /// An account is considered empty if the data length is zero.
+    #[inline(always)]
+    pub fn data_is_empty(&self) -> bool {
+        self.data_len() == 0
+    }
+
+    /// Changes the owner of the account.
+    #[allow(invalid_reference_casting)]
+    pub fn assign(&self, new_owner: &Pubkey) {
+        unsafe {
+            std::ptr::write_volatile(&(*self.raw).owner as *const _ as *mut Pubkey, *new_owner);
+        }
+    }
+
+    /// Returns a read-only reference to the lamports in the account.
+    ///
+    /// # SAFETY
+    ///
+    /// This does not check or modify the 4-bit refcell. Useful when instruction
+    /// has verified non-duplicate accounts.
+    pub unsafe fn unchecked_borrow_lamports(&self) -> &u64 {
+        &(*self.raw).lamports
+    }
+
+    /// Returns a mutable reference to the lamports in the account.
+    ///
+    /// # SAFETY
+    ///
+    /// This does not check or modify the 4-bit refcell. Useful when instruction
+    /// has verified non-duplicate accounts.
+    #[allow(clippy::mut_from_ref)]
+    pub unsafe fn unchecked_borrow_mut_lamports(&self) -> &mut u64 {
+        &mut (*self.raw).lamports
+    }
+
+    /// Returns a read-only reference to the data in the account.
+    ///
+    /// # SAFETY
+    ///
+    /// This does not check or modify the 4-bit refcell. Useful when instruction
+    /// has verified non-duplicate accounts.
+    pub unsafe fn unchecked_borrow_data(&self) -> &[u8] {
+        core::slice::from_raw_parts(self.data_ptr(), self.data_len())
+    }
+
+    /// Returns a mutable reference to the data in the account.
+    ///
+    /// # SAFETY
+    ///
+    /// This does not check or modify the 4-bit refcell. Useful when instruction
+    /// has verified non-duplicate accounts.
+    #[allow(clippy::mut_from_ref)]
+    pub unsafe fn unchecked_borrow_mut_data(&self) -> &mut [u8] {
+        core::slice::from_raw_parts_mut(self.data_ptr(), self.data_len())
+    }
+
+    /// Tries to get a read-only reference to the lamport field, failing if the
+    /// field is already mutable borrowed or if 7 borrows already exist.
+    pub fn try_borrow_lamports(&self) -> Result<Ref<u64>, ProgramError> {
+        let borrow_state = unsafe { &mut (*self.raw).borrow_state };
+
+        // check if mutable borrow is already taken
+        if *borrow_state & 0b_1000_0000 != 0 {
+            return Err(ProgramError::AccountBorrowFailed);
+        }
+
+        // check if we have reached the max immutable borrow count
+        if *borrow_state & 0b_0111_0000 == 0b_0111_0000 {
+            return Err(ProgramError::AccountBorrowFailed);
+        }
+
+        // increment the immutable borrow count
+        *borrow_state += 1 << LAMPORTS_SHIFT;
+
+        // return the reference to lamports
+        Ok(Ref {
+            value: unsafe { &(*self.raw).lamports },
+            state: unsafe { NonNull::new_unchecked(&mut (*self.raw).borrow_state) },
+            borrow_shift: LAMPORTS_SHIFT,
+        })
+    }
+
+    /// Tries to get a read only reference to the lamport field, failing if the field
+    /// is already borrowed in any form.
+    pub fn try_borrow_mut_lamports(&self) -> Result<RefMut<u64>, ProgramError> {
+        let borrow_state = unsafe { &mut (*self.raw).borrow_state };
+
+        // check if any borrow (mutable or immutable) is already taken for lamports
+        if *borrow_state & 0b_1111_0000 != 0 {
+            return Err(ProgramError::AccountBorrowFailed);
+        }
+
+        // set the mutable lamport borrow flag
+        *borrow_state |= 0b_1000_0000;
+
+        // return the mutable reference to lamports
+        Ok(RefMut {
+            value: unsafe { &mut (*self.raw).lamports },
+            state: unsafe { NonNull::new_unchecked(&mut (*self.raw).borrow_state) },
+            borrow_mask: LAMPORTS_MASK,
+        })
+    }
+
+    /// Tries to get a read only reference to the data field, failing if the field
+    /// is already mutable borrowed or if 7 borrows already exist.
+    pub fn try_borrow_data(&self) -> Result<Ref<[u8]>, ProgramError> {
+        let borrow_state = unsafe { &mut (*self.raw).borrow_state };
+
+        // check if mutable data borrow is already taken (most significant bit
+        // of the data_borrow_state)
+        if *borrow_state & 0b_0000_1000 != 0 {
+            return Err(ProgramError::AccountBorrowFailed);
+        }
+
+        // check if we have reached the max immutable data borrow count (7)
+        if *borrow_state & 0b0111 == 0b0111 {
+            return Err(ProgramError::AccountBorrowFailed);
+        }
+
+        // increment the immutable data borrow count
+        *borrow_state += 1;
+
+        // return the reference to data
+        Ok(Ref {
+            value: unsafe { core::slice::from_raw_parts(self.data_ptr(), self.data_len()) },
+            state: unsafe { NonNull::new_unchecked(&mut (*self.raw).borrow_state) },
+            borrow_shift: DATA_SHIFT,
+        })
+    }
+
+    /// Tries to get a read only reference to the data field, failing if the field
+    /// is already borrowed in any form.
+    pub fn try_borrow_mut_data(&self) -> Result<RefMut<[u8]>, ProgramError> {
+        let borrow_state = unsafe { &mut (*self.raw).borrow_state };
+
+        // check if any borrow (mutable or immutable) is already taken for data
+        if *borrow_state & 0b_0000_1111 != 0 {
+            return Err(ProgramError::AccountBorrowFailed);
+        }
+
+        // set the mutable data borrow flag
+        *borrow_state |= 0b0000_1000;
+
+        // return the mutable reference to data
+        Ok(RefMut {
+            value: unsafe { from_raw_parts_mut(self.data_ptr(), self.data_len()) },
+            state: unsafe { NonNull::new_unchecked(&mut (*self.raw).borrow_state) },
+            borrow_mask: DATA_MASK,
+        })
+    }
+
+    /// Realloc the account's data and optionally zero-initialize the new
+    /// memory.
+    ///
+    /// Note:  Account data can be increased within a single call by up to
+    /// `solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE` bytes.
+    ///
+    /// Note: Memory used to grow is already zero-initialized upon program
+    /// entrypoint and re-zeroing it wastes compute units.  If within the same
+    /// call a program reallocs from larger to smaller and back to larger again
+    /// the new space could contain stale data.  Pass `true` for `zero_init` in
+    /// this case, otherwise compute units will be wasted re-zero-initializing.
+    ///
+    /// # Safety
+    ///
+    /// This method makes assumptions about the layout and location of memory
+    /// referenced by `AccountInfo` fields. It should only be called for
+    /// instances of `AccountInfo` that were created by the runtime and received
+    /// in the `process_instruction` entrypoint of a program.
+    pub fn realloc(&self, new_len: usize, zero_init: bool) -> Result<(), ProgramError> {
+        let mut data = self.try_borrow_mut_data()?;
+        let current_len = data.len();
+
+        // return early if length hasn't changed
+        if new_len == current_len {
+            return Ok(());
+        }
+
+        let original_len = match get_original_data_len!(self.raw) {
+            len if len > 0 => len,
+            _ => {
+                set_original_data_len!(self.raw, current_len);
+                current_len
+            }
+        };
+
+        // return early if the length increase from the original serialized data
+        // length is too large and would result in an out of bounds allocation
+        if new_len.saturating_sub(original_len) > MAX_PERMITTED_DATA_INCREASE {
+            return Err(ProgramError::InvalidRealloc);
+        }
+
+        // realloc
+        unsafe {
+            let data_ptr = data.as_mut_ptr();
+            // set new length in the serialized data
+            *(data_ptr.offset(-8) as *mut u64) = new_len as u64;
+            // recreate the local slice with the new length
+            data.value = from_raw_parts_mut(data_ptr, new_len);
+        }
+
+        if zero_init {
+            let len_increase = new_len.saturating_sub(current_len);
+            if len_increase > 0 {
+                unsafe {
+                    sol_memset_(
+                        &mut data[original_len..] as *mut _ as *mut u8,
+                        0,
+                        len_increase as u64,
+                    );
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Returns the memory address of the account data.
+    fn data_ptr(&self) -> *mut u8 {
+        unsafe { (self.raw as *const _ as *mut u8).add(std::mem::size_of::<Account>()) }
+    }
+}
+
+/// Bytes to shift to get to the borrow state of lamports.
+const LAMPORTS_SHIFT: u8 = 4;
+
+/// Bytes to shift to get to the borrow state of data.
+const DATA_SHIFT: u8 = 0;
+
+/// Reference to account data or lamports with checked borrow rules.
+pub struct Ref<'a, T: ?Sized> {
+    value: &'a T,
+    state: NonNull<u8>,
+    /// Indicates the type of borrow (lamports or data) by representing the
+    /// shift amount.
+    borrow_shift: u8,
+}
+
+impl<'a, T: ?Sized> core::ops::Deref for Ref<'a, T> {
+    type Target = T;
+    fn deref(&self) -> &Self::Target {
+        self.value
+    }
+}
+
+impl<'a, T: ?Sized> Drop for Ref<'a, T> {
+    // decrement the immutable borrow count
+    fn drop(&mut self) {
+        unsafe { *self.state.as_mut() -= 1 << self.borrow_shift };
+    }
+}
+
+/// Mask representing the mutable borrow flag for lamports.
+const LAMPORTS_MASK: u8 = 0b_0111_1111;
+
+/// Mask representing the mutable borrow flag for data.
+const DATA_MASK: u8 = 0b_1111_0111;
+
+/// Mutable reference to account data or lamports with checked borrow rules.
+pub struct RefMut<'a, T: ?Sized> {
+    value: &'a mut T,
+    state: NonNull<u8>,
+    /// Indicates the type of borrow (lamports or data) by representing the
+    /// mutable borrow mask.
+    borrow_mask: u8,
+}
+
+impl<'a, T: ?Sized> core::ops::Deref for RefMut<'a, T> {
+    type Target = T;
+    fn deref(&self) -> &Self::Target {
+        self.value
+    }
+}
+impl<'a, T: ?Sized> core::ops::DerefMut for RefMut<'a, T> {
+    fn deref_mut(&mut self) -> &mut <Self as core::ops::Deref>::Target {
+        self.value
+    }
+}
+
+impl<'a, T: ?Sized> Drop for RefMut<'a, T> {
+    // unset the mutable borrow flag
+    fn drop(&mut self) {
+        unsafe { *self.state.as_mut() &= self.borrow_mask };
+    }
+}

+ 261 - 0
src/entrypoint.rs

@@ -0,0 +1,261 @@
+use std::{alloc::Layout, mem::size_of, ptr::null_mut, slice::from_raw_parts};
+
+use crate::{
+    account_info::{Account, AccountInfo},
+    pubkey::Pubkey,
+    BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, NON_DUP_MARKER,
+};
+
+/// Start address of the memory region used for program heap.
+pub const HEAP_START_ADDRESS: u64 = 0x300000000;
+
+/// Length of the heap memory region used for program heap.
+pub const HEAP_LENGTH: usize = 32 * 1024;
+
+/// Maximum number of accounts that a transaction may lock.
+///
+/// This value is used to set the maximum number of accounts that a program
+/// is expecting and statically initialize the array of `AccountInfo`.
+pub const MAX_TX_ACCOUNT_LOCKS: usize = 64;
+
+/// Declare the program entrypoint and set up global handlers.
+///
+/// The main difference from the standard `entrypoint!` macro is that this macro represents an
+/// entrypoint that does not perform allocattions or copies when reading the input buffer.
+///
+/// This macro emits the common boilerplate necessary to begin program execution, calling a
+/// provided function to process the program instruction supplied by the runtime, and reporting
+/// its result to the runtime.
+///
+/// It also sets up a [global allocator] and [panic handler], using the [`custom_heap_default`]
+/// and [`custom_panic_default`] macros.
+///
+/// The first argument is the name of a function with this type signature:
+///
+/// ```ignore
+/// fn process_instruction(
+///     program_id: &Pubkey,      // Public key of the account the program was loaded into
+///     accounts: &[AccountInfo], // All accounts required to process the instruction
+///     instruction_data: &[u8],  // Serialized instruction-specific data
+/// ) -> ProgramResult;
+/// ```
+///
+/// The second (optional) argument is the maximum number of accounts that the program is expecting.
+/// A program can receive more than the specified maximum, but any account exceeding the maximum will
+/// be ignored. When the maximum is not specified, the default is 64. This is currently the [maximum
+/// number of accounts] that a transaction may lock in a block.
+///
+/// [maximum number of accounts]: https://github.com/anza-xyz/agave/blob/ccabfcf84921977202fd06d3197cbcea83742133/runtime/src/bank.rs#L3207-L3219
+///
+/// # Examples
+///
+/// Defining an entrypoint which reads up to 10 accounts and making it conditional on the
+/// `bpf-entrypoint` feature. Although the `entrypoint` module is written inline in this example,
+/// it is common to put it into its own file.
+///
+/// ```no_run
+/// #[cfg(feature = "bpf-entrypoint")]
+/// pub mod entrypoint {
+///
+///     use pinocchio::{entrypoint, account_info::AccountInfo};
+///     use solana_program::{
+///         entrypoint::ProgramResult,
+///         msg,
+///         pubkey::Pubkey,
+///     };
+///
+///     entrypoint!(process_instruction, 10);
+///
+///     pub fn process_instruction(
+///         program_id: &Pubkey,
+///         accounts: &[AccountInfo],
+///         instruction_data: &[u8],
+///     ) -> ProgramResult {
+///         msg!("Hello from my program!");
+///
+///         Ok(())
+///     }
+///
+/// }
+/// ```
+#[macro_export]
+macro_rules! entrypoint {
+    ( $process_instruction:ident ) => {
+        $crate::entrypoint::entrypoint!(
+            $process_instruction,
+            $crate::entrypoint::MAX_TX_ACCOUNT_LOCKS
+        );
+    };
+    ( $process_instruction:ident, $maximum:literal ) => {
+        #[no_mangle]
+        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
+            // create an array of uninitialized account infos; it is safe to `assume_init` since
+            // we are claiming that the array of `MaybeUninit` is initialized and `MaybeUninit` do
+            // not require initialization
+            let mut accounts: [std::mem::MaybeUninit<$crate::account_info::AccountInfo>; $maximum] =
+                std::mem::MaybeUninit::uninit().assume_init();
+
+            let (program_id, count, instruction_data) =
+                $crate::entrypoint::deserialize::<$maximum>(input, accounts.as_mut_ptr());
+
+            // call the program's entrypoint passing `count` account infos; we know that
+            // they are initialized so we cast the pointer to a slice of `[AccountInfo]`
+            match $process_instruction(
+                &program_id,
+                std::slice::from_raw_parts(accounts.as_ptr() as _, count),
+                &instruction_data,
+            ) {
+                Ok(()) => $crate::entrypoint::SUCCESS,
+                Err(error) => error.into(),
+            }
+        }
+
+        $crate::entrypoint::custom_heap_default!();
+        $crate::entrypoint::custom_panic_default!();
+    };
+}
+
+/// Deserialize the input arguments.
+///
+/// This can only be called from the entrypoint function of a Solana program and with
+/// a buffer that was serialized by the runtime.
+#[allow(clippy::cast_ptr_alignment, clippy::missing_safety_doc)]
+#[inline(always)]
+pub unsafe fn deserialize<'a, const MAX_ACCOUNTS: usize>(
+    input: *mut u8,
+    accounts: *mut std::mem::MaybeUninit<AccountInfo>,
+) -> (&'a Pubkey, usize, &'a [u8]) {
+    let mut offset: usize = 0;
+
+    // total number of accounts present; it only process up to MAX_ACCOUNTS
+    let total_accounts = *(input.add(offset) as *const u64) as usize;
+
+    // number of processed accounts
+    let count = if total_accounts <= MAX_ACCOUNTS {
+        total_accounts
+    } else {
+        MAX_ACCOUNTS
+    };
+
+    offset += std::mem::size_of::<u64>();
+
+    for i in 0..count {
+        let duplicate_info = *(input.add(offset) as *const u8);
+        if duplicate_info == NON_DUP_MARKER {
+            let account_info: *mut Account = input.add(offset) as *mut _;
+
+            offset += std::mem::size_of::<Account>();
+            offset += (*account_info).data_len as usize;
+            offset += MAX_PERMITTED_DATA_INCREASE;
+            offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128);
+            offset += std::mem::size_of::<u64>(); // MAGNETAR FIELDS: ignore rent epoch
+
+            // MAGNETAR FIELDS: reset borrow state right before pushing
+            (*account_info).borrow_state = 0b_0000_0000;
+
+            std::ptr::write(
+                accounts.add(i),
+                std::mem::MaybeUninit::new(AccountInfo {
+                    raw: account_info as *const _ as *mut _,
+                }),
+            );
+        } else {
+            offset += 8;
+            // duplicate account, clone the original
+            std::ptr::copy_nonoverlapping(
+                accounts.add(duplicate_info as usize),
+                accounts.add(i),
+                1,
+            );
+        }
+    }
+
+    // process any remaining accounts to move the offset to the instruction
+    // data (there is a duplication of logic but we avoid testing whether we
+    // have space for the account or not)
+    for _ in count..total_accounts {
+        let duplicate_info = *(input.add(offset) as *const u8);
+
+        if duplicate_info == NON_DUP_MARKER {
+            let account_info: *mut Account = input.add(offset) as *mut _;
+            offset += std::mem::size_of::<Account>();
+            offset += (*account_info).data_len as usize;
+            offset += MAX_PERMITTED_DATA_INCREASE;
+            offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128);
+            offset += std::mem::size_of::<u64>(); // MAGNETAR FIELDS: ignore rent epoch
+        } else {
+            offset += 8;
+        }
+    }
+
+    // instruction data
+    #[allow(clippy::cast_ptr_alignment)]
+    let instruction_data_len = *(input.add(offset) as *const u64) as usize;
+    offset += std::mem::size_of::<u64>();
+
+    let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
+    offset += instruction_data_len;
+
+    // program id
+    let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
+
+    (program_id, count, instruction_data)
+}
+
+#[macro_export]
+macro_rules! custom_panic_default {
+    () => {
+        #[cfg(all(not(feature = "custom-panic"), target_os = "solana"))]
+        #[no_mangle]
+        fn custom_panic(info: &core::panic::PanicInfo<'_>) {
+            // Full panic reporting
+            $crate::msg!("{}", info);
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! custom_heap_default {
+    () => {
+        #[cfg(all(not(feature = "custom-heap"), target_os = "solana"))]
+        #[global_allocator]
+        static A: $crate::BumpAllocator = $crate::BumpAllocator {
+            start: $crate::HEAP_START_ADDRESS as usize,
+            len: $crate::HEAP_LENGTH,
+        };
+    };
+}
+
+/// The bump allocator used as the default rust heap when running programs.
+pub struct BumpAllocator {
+    pub start: usize,
+    pub len: usize,
+}
+
+/// Integer arithmetic in this global allocator implementation is safe when
+/// operating on the prescribed `HEAP_START_ADDRESS` and `HEAP_LENGTH`. Any
+/// other use may overflow and is thus unsupported and at one's own risk.
+#[allow(clippy::arithmetic_side_effects)]
+unsafe impl std::alloc::GlobalAlloc for BumpAllocator {
+    #[inline]
+    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+        let pos_ptr = self.start as *mut usize;
+
+        let mut pos = *pos_ptr;
+        if pos == 0 {
+            // First time, set starting position
+            pos = self.start + self.len;
+        }
+        pos = pos.saturating_sub(layout.size());
+        pos &= !(layout.align().wrapping_sub(1));
+        if pos < self.start + size_of::<*mut u8>() {
+            return null_mut();
+        }
+        *pos_ptr = pos;
+        pos as *mut u8
+    }
+    #[inline]
+    unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
+        // I'm a bump allocator, I don't free
+    }
+}

+ 34 - 0
src/instruction.rs

@@ -0,0 +1,34 @@
+use crate::pubkey::Pubkey;
+
+/// An `AccountMeta`` as expected by `sol_invoke_signed_c`.
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub struct AccountMeta {
+    // Public key of the account.
+    pubkey: *const Pubkey,
+
+    // Is the account writable?
+    pub is_writable: bool,
+
+    // Transaction was signed by this account's key?
+    pub is_signer: bool,
+}
+
+impl AccountMeta {
+    #[inline(always)]
+    pub fn pubkey(&self) -> &Pubkey {
+        unsafe { &*self.pubkey }
+    }
+}
+
+/// Use to query and convey information about the sibling instruction components
+/// when calling the `sol_get_processed_sibling_instruction` syscall.
+#[repr(C)]
+#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
+pub struct ProcessedSiblingInstruction {
+    /// Length of the instruction data
+    pub data_len: u64,
+
+    /// Number of AccountMeta structures
+    pub accounts_len: u64,
+}

+ 28 - 0
src/lib.rs

@@ -0,0 +1,28 @@
+//! A library to build a Solana Program in Rust.
+//!
+//! This library is intended to be used by on-chain programs only. It provides
+//! a zero-dependency library to minimise dependencies conflits. For off-chain
+//! programs, use instead the [`solana-sdk`] crate, which reexports all modules
+//! from [`solana-program`].
+//!
+//! [`solana-sdk`]: https://docs.rs/solana-sdk/latest/solana_sdk/
+//! [`solana-program`]: https://docs.rs/solana-program/latest/solana_program/
+
+pub mod account_info;
+pub mod entrypoint;
+pub mod instruction;
+pub mod log;
+pub mod program_error;
+pub mod pubkey;
+pub mod syscalls;
+
+/// Maximum number of bytes a program may add to an account during a
+/// single realloc.
+pub const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10;
+
+/// `assert_eq(std::mem::align_of::<u128>(), 8)` is true for BPF but not
+/// for some host machines./
+pub const BPF_ALIGN_OF_U128: usize = 8;
+
+/// Value used to indicate that a serialized account is not a duplicate.
+pub const NON_DUP_MARKER: u8 = u8::MAX;

+ 121 - 0
src/log.rs

@@ -0,0 +1,121 @@
+//! Logging utilities for Rust-based Solana programs.
+//!
+//! Logging is the main mechanism for getting debugging information out of
+//! running Solana programs, and there are several functions available for doing
+//! so efficiently, depending on the type of data being logged.
+//!
+//! The most common way to emit logs is through the [`msg!`] macro, which logs
+//! simple strings, as well as [formatted strings][fs].
+//!
+//! [`msg!`]: crate::msg!
+//! [fs]: https://doc.rust-lang.org/std/fmt/
+//!
+//! Logs can be viewed in multiple ways:
+//!
+//! - The `solana logs` command displays logs for all transactions executed on a
+//!   network. Note though that transactions that fail during pre-flight
+//!   simulation are not displayed here.
+//! - When submitting transactions via [`RpcClient`], if Rust's own logging is
+//!   active then the `solana_rpc_client` crate logs at the "debug" level any logs
+//!   for transactions that failed during simulation. If using [`env_logger`]
+//!   these logs can be activated by setting `RUST_LOG=solana_rpc_client=debug`.
+//! - Logs can be retrieved from a finalized transaction by calling
+//!   [`RpcClient::get_transaction`].
+//! - Block explorers may display logs.
+//!
+//! [`RpcClient`]: https://docs.rs/solana-rpc-client/latest/solana_rpc_client/rpc_client/struct.RpcClient.html
+//! [`env_logger`]: https://docs.rs/env_logger
+//! [`RpcClient::get_transaction`]: https://docs.rs/solana-rpc-client/latest/solana_rpc_client/rpc_client/struct.RpcClient.html#method.get_transaction
+//!
+//! While most logging functions are defined in this module, [`Pubkey`]s can
+//! also be efficiently logged with the [`Pubkey::log`] function.
+//!
+//! [`Pubkey`]: crate::pubkey::Pubkey
+//! [`Pubkey::log`]: crate::pubkey::Pubkey::log
+
+#[macro_export]
+macro_rules! msg {
+    ($msg:expr) => {
+        $crate::log::sol_log($msg)
+    };
+    ($($arg:tt)*) => ($crate::log::sol_log(&format!($($arg)*)));
+}
+
+/// Print a string to the log.
+#[inline(always)]
+pub fn sol_log(message: &str) {
+    #[cfg(target_os = "solana")]
+    unsafe {
+        crate::syscalls::sol_log_(message.as_ptr(), message.len() as u64);
+    }
+
+    #[cfg(not(target_os = "solana"))]
+    core::hint::black_box(message);
+}
+
+/// Print 64-bit values represented as hexadecimal to the log.
+#[inline]
+pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) {
+    #[cfg(target_os = "solana")]
+    unsafe {
+        crate::syscalls::sol_log_64_(arg1, arg2, arg3, arg4, arg5);
+    }
+
+    #[cfg(not(target_os = "solana"))]
+    core::hint::black_box((arg1, arg2, arg3, arg4, arg5));
+}
+
+/// Print some slices as base64.
+pub fn sol_log_data(data: &[&[u8]]) {
+    #[cfg(target_os = "solana")]
+    unsafe {
+        crate::syscalls::sol_log_data(data as *const _ as *const u8, data.len() as u64)
+    };
+
+    #[cfg(not(target_os = "solana"))]
+    core::hint::black_box(data);
+}
+
+/// Print the hexadecimal representation of a slice.
+pub fn sol_log_slice(slice: &[u8]) {
+    for (i, s) in slice.iter().enumerate() {
+        sol_log_64(0, 0, 0, i as u64, *s as u64);
+    }
+}
+
+/// Print the hexadecimal representation of the program's input parameters.
+///
+/// - `accounts` - A slice of [`AccountInfo`].
+/// - `data` - The instruction data.
+/// TODO: This function is not yet implemented.
+/*
+pub fn sol_log_params(accounts: &[AccountInfo], data: &[u8]) {
+    for (i, account) in accounts.iter().enumerate() {
+        msg!("AccountInfo");
+        sol_log_64(0, 0, 0, 0, i as u64);
+        msg!("- Is signer");
+        sol_log_64(0, 0, 0, 0, account.is_signer() as u64);
+        msg!("- Key");
+        account.key().log();
+        msg!("- Lamports");
+        sol_log_64(0, 0, 0, 0, account.lamports());
+        msg!("- Account data length");
+        sol_log_64(0, 0, 0, 0, account.data_len() as u64);
+        msg!("- Owner");
+        account.owner().log();
+    }
+    msg!("Instruction data");
+    sol_log_slice(data);
+}
+*/
+
+/// Print the remaining compute units available to the program.
+#[inline]
+pub fn sol_log_compute_units() {
+    #[cfg(target_os = "solana")]
+    unsafe {
+        crate::syscalls::sol_log_compute_units_();
+    }
+    #[cfg(not(target_os = "solana"))]
+    core::hint::black_box(());
+}

+ 212 - 0
src/program_error.rs

@@ -0,0 +1,212 @@
+//! Errors generated by programs.
+//!
+//! Current implementation is based on the `ProgramError` enum from
+//! the Solana SDK:
+//!
+//! https://github.com/anza-xyz/agave/blob/master/sdk/program/src/program_error.rs
+//!
+//! Considerations:
+//!
+//! - Not deriving `thiserror::Error` for now, as it's not clear if it's needed.
+
+/// Reasons the program may fail.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum ProgramError {
+    /// Allows on-chain programs to implement program-specific error types and see them returned
+    /// by the Solana runtime. A program-specific error may be any type that is represented as
+    /// or serialized to a u32 integer.
+    ///
+    /// Custom program error: `{0:#x}`
+    Custom(u32),
+
+    /// The arguments provided to a program instruction were invalid
+    InvalidArgument,
+
+    /// An instruction's data contents was invalid
+    InvalidInstructionData,
+
+    /// An account's data contents was invalid
+    InvalidAccountData,
+
+    /// An account's data was too small
+    AccountDataTooSmall,
+
+    /// An account's balance was too small to complete the instruction
+    InsufficientFunds,
+
+    /// The account did not have the expected program id
+    IncorrectProgramId,
+
+    /// A signature was required but not found
+    MissingRequiredSignature,
+
+    /// An initialize instruction was sent to an account that has already been initialized
+    AccountAlreadyInitialized,
+
+    /// An attempt to operate on an account that hasn't been initialized
+    UninitializedAccount,
+
+    /// The instruction expected additional account keys
+    NotEnoughAccountKeys,
+
+    /// Failed to borrow a reference to account data, already borrowed
+    AccountBorrowFailed,
+
+    /// Length of the seed is too long for address generation
+    MaxSeedLengthExceeded,
+
+    /// Provided seeds do not result in a valid address
+    InvalidSeeds,
+
+    /// IO Error: `{0}`
+    BorshIoError(String),
+
+    /// An account does not have enough lamports to be rent-exempt
+    AccountNotRentExempt,
+
+    /// Unsupported sysvar
+    UnsupportedSysvar,
+
+    /// Provided owner is not allowed
+    IllegalOwner,
+
+    /// Accounts data allocations exceeded the maximum allowed per transaction
+    MaxAccountsDataAllocationsExceeded,
+
+    /// Account data reallocation was invalid
+    InvalidRealloc,
+
+    /// Instruction trace length exceeded the maximum allowed per transaction
+    MaxInstructionTraceLengthExceeded,
+
+    /// Builtin programs must consume compute units
+    BuiltinProgramsMustConsumeComputeUnits,
+
+    /// Invalid account owner
+    InvalidAccountOwner,
+
+    /// Program arithmetic overflowed
+    ArithmeticOverflow,
+
+    /// Account is immutable
+    Immutable,
+
+    /// Incorrect authority provided
+    IncorrectAuthority,
+}
+
+/// Builtin return values occupy the upper 32 bits
+const BUILTIN_BIT_SHIFT: usize = 32;
+macro_rules! to_builtin {
+    ($error:expr) => {
+        ($error as u64) << BUILTIN_BIT_SHIFT
+    };
+}
+
+pub const CUSTOM_ZERO: u64 = to_builtin!(1);
+pub const INVALID_ARGUMENT: u64 = to_builtin!(2);
+pub const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3);
+pub const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4);
+pub const ACCOUNT_DATA_TOO_SMALL: u64 = to_builtin!(5);
+pub const INSUFFICIENT_FUNDS: u64 = to_builtin!(6);
+pub const INCORRECT_PROGRAM_ID: u64 = to_builtin!(7);
+pub const MISSING_REQUIRED_SIGNATURES: u64 = to_builtin!(8);
+pub const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9);
+pub const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10);
+pub const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11);
+pub const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12);
+pub const MAX_SEED_LENGTH_EXCEEDED: u64 = to_builtin!(13);
+pub const INVALID_SEEDS: u64 = to_builtin!(14);
+pub const BORSH_IO_ERROR: u64 = to_builtin!(15);
+pub const ACCOUNT_NOT_RENT_EXEMPT: u64 = to_builtin!(16);
+pub const UNSUPPORTED_SYSVAR: u64 = to_builtin!(17);
+pub const ILLEGAL_OWNER: u64 = to_builtin!(18);
+pub const MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED: u64 = to_builtin!(19);
+pub const INVALID_ACCOUNT_DATA_REALLOC: u64 = to_builtin!(20);
+pub const MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED: u64 = to_builtin!(21);
+pub const BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS: u64 = to_builtin!(22);
+pub const INVALID_ACCOUNT_OWNER: u64 = to_builtin!(23);
+pub const ARITHMETIC_OVERFLOW: u64 = to_builtin!(24);
+pub const IMMUTABLE: u64 = to_builtin!(25);
+pub const INCORRECT_AUTHORITY: u64 = to_builtin!(26);
+
+impl From<u64> for ProgramError {
+    fn from(error: u64) -> Self {
+        match error {
+            CUSTOM_ZERO => Self::Custom(0),
+            INVALID_ARGUMENT => Self::InvalidArgument,
+            INVALID_INSTRUCTION_DATA => Self::InvalidInstructionData,
+            INVALID_ACCOUNT_DATA => Self::InvalidAccountData,
+            ACCOUNT_DATA_TOO_SMALL => Self::AccountDataTooSmall,
+            INSUFFICIENT_FUNDS => Self::InsufficientFunds,
+            INCORRECT_PROGRAM_ID => Self::IncorrectProgramId,
+            MISSING_REQUIRED_SIGNATURES => Self::MissingRequiredSignature,
+            ACCOUNT_ALREADY_INITIALIZED => Self::AccountAlreadyInitialized,
+            UNINITIALIZED_ACCOUNT => Self::UninitializedAccount,
+            NOT_ENOUGH_ACCOUNT_KEYS => Self::NotEnoughAccountKeys,
+            ACCOUNT_BORROW_FAILED => Self::AccountBorrowFailed,
+            MAX_SEED_LENGTH_EXCEEDED => Self::MaxSeedLengthExceeded,
+            INVALID_SEEDS => Self::InvalidSeeds,
+            BORSH_IO_ERROR => Self::BorshIoError("Unknown".to_string()),
+            ACCOUNT_NOT_RENT_EXEMPT => Self::AccountNotRentExempt,
+            UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar,
+            ILLEGAL_OWNER => Self::IllegalOwner,
+            MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED => Self::MaxAccountsDataAllocationsExceeded,
+            INVALID_ACCOUNT_DATA_REALLOC => Self::InvalidRealloc,
+            MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED => Self::MaxInstructionTraceLengthExceeded,
+            BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS => {
+                Self::BuiltinProgramsMustConsumeComputeUnits
+            }
+            INVALID_ACCOUNT_OWNER => Self::InvalidAccountOwner,
+            ARITHMETIC_OVERFLOW => Self::ArithmeticOverflow,
+            IMMUTABLE => Self::Immutable,
+            INCORRECT_AUTHORITY => Self::IncorrectAuthority,
+            _ => Self::Custom(error as u32),
+        }
+    }
+}
+
+impl From<ProgramError> for u64 {
+    fn from(error: ProgramError) -> Self {
+        match error {
+            ProgramError::InvalidArgument => INVALID_ARGUMENT,
+            ProgramError::InvalidInstructionData => INVALID_INSTRUCTION_DATA,
+            ProgramError::InvalidAccountData => INVALID_ACCOUNT_DATA,
+            ProgramError::AccountDataTooSmall => ACCOUNT_DATA_TOO_SMALL,
+            ProgramError::InsufficientFunds => INSUFFICIENT_FUNDS,
+            ProgramError::IncorrectProgramId => INCORRECT_PROGRAM_ID,
+            ProgramError::MissingRequiredSignature => MISSING_REQUIRED_SIGNATURES,
+            ProgramError::AccountAlreadyInitialized => ACCOUNT_ALREADY_INITIALIZED,
+            ProgramError::UninitializedAccount => UNINITIALIZED_ACCOUNT,
+            ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS,
+            ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED,
+            ProgramError::MaxSeedLengthExceeded => MAX_SEED_LENGTH_EXCEEDED,
+            ProgramError::InvalidSeeds => INVALID_SEEDS,
+            ProgramError::BorshIoError(_) => BORSH_IO_ERROR,
+            ProgramError::AccountNotRentExempt => ACCOUNT_NOT_RENT_EXEMPT,
+            ProgramError::UnsupportedSysvar => UNSUPPORTED_SYSVAR,
+            ProgramError::IllegalOwner => ILLEGAL_OWNER,
+            ProgramError::MaxAccountsDataAllocationsExceeded => {
+                MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED
+            }
+            ProgramError::InvalidRealloc => INVALID_ACCOUNT_DATA_REALLOC,
+            ProgramError::MaxInstructionTraceLengthExceeded => {
+                MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED
+            }
+            ProgramError::BuiltinProgramsMustConsumeComputeUnits => {
+                BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS
+            }
+            ProgramError::InvalidAccountOwner => INVALID_ACCOUNT_OWNER,
+            ProgramError::ArithmeticOverflow => ARITHMETIC_OVERFLOW,
+            ProgramError::Immutable => IMMUTABLE,
+            ProgramError::IncorrectAuthority => INCORRECT_AUTHORITY,
+            ProgramError::Custom(error) => {
+                if error == 0 {
+                    CUSTOM_ZERO
+                } else {
+                    error as u64
+                }
+            }
+        }
+    }
+}

+ 15 - 0
src/pubkey.rs

@@ -0,0 +1,15 @@
+/// The address of a [Solana account][account].
+///
+/// [account]: https://solana.com/docs/core/accounts
+pub type Pubkey = [u8; 32];
+
+/// Log a `Pubkey` from a program
+pub fn log_pubkey(pubkey: &Pubkey) {
+    #[cfg(target_os = "solana")]
+    unsafe {
+        crate::syscalls::sol_log_pubkey(pubkey as *const _ as *const u8)
+    };
+
+    #[cfg(not(target_os = "solana"))]
+    core::hint::black_box(pubkey);
+}

+ 124 - 0
src/syscalls.rs

@@ -0,0 +1,124 @@
+use crate::{
+    instruction::{AccountMeta, ProcessedSiblingInstruction},
+    pubkey::Pubkey,
+};
+
+#[cfg(target_feature = "static-syscalls")]
+macro_rules! define_syscall {
+    (fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
+		#[inline]
+        pub unsafe fn $name($($arg: $typ),*) -> $ret {
+			// this enum is used to force the hash to be computed in a const context
+			#[repr(usize)]
+			enum Syscall {
+				Code = sys_hash(stringify!($name)),
+			}
+
+            let syscall: extern "C" fn($($arg: $typ),*) -> $ret = core::mem::transmute(Syscall::Code);
+            syscall($($arg),*)
+        }
+
+    };
+    (fn $name:ident($($arg:ident: $typ:ty),*)) => {
+        define_syscall!(fn $name($($arg: $typ),*) -> ());
+    }
+}
+
+#[cfg(not(target_feature = "static-syscalls"))]
+macro_rules! define_syscall {
+	(fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
+		extern "C" {
+			pub fn $name($($arg: $typ),*) -> $ret;
+		}
+	};
+	(fn $name:ident($($arg:ident: $typ:ty),*)) => {
+		define_syscall!(fn $name($($arg: $typ),*) -> ());
+	}
+}
+
+define_syscall!(fn sol_log_(message: *const u8, len: u64));
+define_syscall!(fn sol_log_64_(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64));
+define_syscall!(fn sol_log_compute_units_());
+define_syscall!(fn sol_log_pubkey(pubkey_addr: *const u8));
+define_syscall!(fn sol_create_program_address(seeds_addr: *const u8, seeds_len: u64, program_id_addr: *const u8, address_bytes_addr: *const u8) -> u64);
+define_syscall!(fn sol_try_find_program_address(seeds_addr: *const u8, seeds_len: u64, program_id_addr: *const u8, address_bytes_addr: *const u8, bump_seed_addr: *const u8) -> u64);
+define_syscall!(fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64);
+define_syscall!(fn sol_keccak256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64);
+define_syscall!(fn sol_secp256k1_recover(hash: *const u8, recovery_id: u64, signature: *const u8, result: *mut u8) -> u64);
+define_syscall!(fn sol_blake3(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64);
+define_syscall!(fn sol_get_clock_sysvar(addr: *mut u8) -> u64);
+define_syscall!(fn sol_get_epoch_schedule_sysvar(addr: *mut u8) -> u64);
+define_syscall!(fn sol_get_fees_sysvar(addr: *mut u8) -> u64);
+define_syscall!(fn sol_get_rent_sysvar(addr: *mut u8) -> u64);
+define_syscall!(fn sol_get_last_restart_slot(addr: *mut u8) -> u64);
+define_syscall!(fn sol_memcpy_(dst: *mut u8, src: *const u8, n: u64));
+define_syscall!(fn sol_memmove_(dst: *mut u8, src: *const u8, n: u64));
+define_syscall!(fn sol_memcmp_(s1: *const u8, s2: *const u8, n: u64, result: *mut i32));
+define_syscall!(fn sol_memset_(s: *mut u8, c: u8, n: u64));
+define_syscall!(fn sol_invoke_signed_c(instruction_addr: *const u8, account_infos_addr: *const u8, account_infos_len: u64, signers_seeds_addr: *const u8, signers_seeds_len: u64) -> u64);
+define_syscall!(fn sol_invoke_signed_rust(instruction_addr: *const u8, account_infos_addr: *const u8, account_infos_len: u64, signers_seeds_addr: *const u8, signers_seeds_len: u64) -> u64);
+define_syscall!(fn sol_set_return_data(data: *const u8, length: u64));
+define_syscall!(fn sol_get_return_data(data: *mut u8, length: u64, program_id: *mut Pubkey) -> u64);
+define_syscall!(fn sol_log_data(data: *const u8, data_len: u64));
+define_syscall!(fn sol_get_processed_sibling_instruction(index: u64, meta: *mut ProcessedSiblingInstruction, program_id: *mut Pubkey, data: *mut u8, accounts: *mut AccountMeta) -> u64);
+define_syscall!(fn sol_get_stack_height() -> u64);
+define_syscall!(fn sol_curve_validate_point(curve_id: u64, point_addr: *const u8, result: *mut u8) -> u64);
+define_syscall!(fn sol_curve_group_op(curve_id: u64, group_op: u64, left_input_addr: *const u8, right_input_addr: *const u8, result_point_addr: *mut u8) -> u64);
+define_syscall!(fn sol_curve_multiscalar_mul(curve_id: u64, scalars_addr: *const u8, points_addr: *const u8, points_len: u64, result_point_addr: *mut u8) -> u64);
+define_syscall!(fn sol_curve_pairing_map(curve_id: u64, point: *const u8, result: *mut u8) -> u64);
+define_syscall!(fn sol_alt_bn128_group_op(group_op: u64, input: *const u8, input_size: u64, result: *mut u8) -> u64);
+define_syscall!(fn sol_big_mod_exp(params: *const u8, result: *mut u8) -> u64);
+define_syscall!(fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64);
+define_syscall!(fn sol_poseidon(parameters: u64, endianness: u64, vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64);
+define_syscall!(fn sol_remaining_compute_units() -> u64);
+define_syscall!(fn sol_alt_bn128_compression(op: u64, input: *const u8, input_size: u64, result: *mut u8) -> u64);
+
+#[cfg(target_feature = "static-syscalls")]
+pub const fn sys_hash(name: &str) -> usize {
+    murmur3_32(name.as_bytes(), 0) as usize
+}
+
+#[cfg(target_feature = "static-syscalls")]
+const fn murmur3_32(buf: &[u8], seed: u32) -> u32 {
+    const fn pre_mix(buf: [u8; 4]) -> u32 {
+        u32::from_le_bytes(buf)
+            .wrapping_mul(0xcc9e2d51)
+            .rotate_left(15)
+            .wrapping_mul(0x1b873593)
+    }
+
+    let mut hash = seed;
+
+    let mut i = 0;
+    while i < buf.len() / 4 {
+        let buf = [buf[i * 4], buf[i * 4 + 1], buf[i * 4 + 2], buf[i * 4 + 3]];
+        hash ^= pre_mix(buf);
+        hash = hash.rotate_left(13);
+        hash = hash.wrapping_mul(5).wrapping_add(0xe6546b64);
+
+        i += 1;
+    }
+
+    match buf.len() % 4 {
+        0 => {}
+        1 => {
+            hash = hash ^ pre_mix([buf[i * 4], 0, 0, 0]);
+        }
+        2 => {
+            hash = hash ^ pre_mix([buf[i * 4], buf[i * 4 + 1], 0, 0]);
+        }
+        3 => {
+            hash = hash ^ pre_mix([buf[i * 4], buf[i * 4 + 1], buf[i * 4 + 2], 0]);
+        }
+        _ => { /* unreachable!() */ }
+    }
+
+    hash = hash ^ buf.len() as u32;
+    hash = hash ^ (hash.wrapping_shr(16));
+    hash = hash.wrapping_mul(0x85ebca6b);
+    hash = hash ^ (hash.wrapping_shr(13));
+    hash = hash.wrapping_mul(0xc2b2ae35);
+    hash = hash ^ (hash.wrapping_shr(16));
+
+    hash
+}