uip-solana-sdk 0.16.0

Universal Interoperability Protocol Solana SDK
Documentation
//! Module for use by the Solana protocol extensions.

use solana_program::{clock::Epoch, instruction::AccountMeta, pubkey::Pubkey};

use super::{deser::DeserializeRef, utils::split_at_checked, MessageDataRef};

impl MessageDataRef<'_> {
    /// Parse `MessageDataRef` out of the raw pointer and length.
    ///
    /// # Safety
    ///
    /// - `msg_data_ptr` must point to at least `msg_data_len` bytes of valid,
    ///   initialized memory that must remain alive and immutable for the duration
    ///   of its use.
    pub unsafe fn load(msg_data_ptr: *const u8, msg_data_len: usize) -> Self {
        let mut slice = core::slice::from_raw_parts(msg_data_ptr, msg_data_len);
        MessageDataRef::deserialize_ref(&mut slice).unwrap()
    }
}

/// Information about the compute budget, optional heap frame size, and the list
/// of account metas required by the instruction.
#[repr(C)]
pub struct InstructionInfo {
    pub compute_units: u32,
    pub heap_frame: u32,
    pub accounts_len: u32,
    pub accounts: [AccountMeta; 57],
}

impl InstructionInfo {
    /// Add `account_meta` to the array of requested accounts.
    ///
    /// Returns back the `account_meta` if the array is full.
    pub fn push_account(&mut self, account_meta: AccountMeta) -> Result<(), AccountMeta> {
        if self.accounts_len as usize >= self.accounts.len() {
            return Err(account_meta);
        }

        self.accounts[self.accounts_len as usize] = account_meta;
        self.accounts_len += 1;

        Ok(())
    }
}

#[link(wasm_import_module = "solana_transmitter")]
extern "C" {
    fn get_multiple_accounts(keys_ptr: *const Pubkey, keys_len: u32, out_ptr: *mut u8) -> i32;
}

/// Helper for managing the shared memory region used for host-calling from
/// within a WASM extension.
pub struct HostCallContext {
    params_ptr: *mut u8,
    result_ptr: *mut u8,
}

impl HostCallContext {
    /// Constructs a new `HostCallContext` given the base pointer and length
    /// of the serialized message data.
    #[allow(clippy::not_unsafe_ptr_arg_deref)]
    pub fn new(msg_data_ptr: *const u8, msg_data_len: usize) -> Self {
        const MAX_RESULT_LEN: usize = core::mem::size_of::<InstructionInfo>();
        const MAX_PARAMS_LEN: usize = 4 + 32 * 100;

        let offset = msg_data_len + MAX_RESULT_LEN;
        // cast to guarantee safety
        let offset = offset as isize as usize;

        // SAFETY: `offset` and `MAX_PARAMS_LEN` fit in `isize`.
        unsafe {
            let params_ptr = msg_data_ptr.add(offset).cast_mut();
            Self {
                params_ptr,
                result_ptr: params_ptr.add(MAX_PARAMS_LEN),
            }
        }
    }

    /// Request multiple accounts from the executor.
    ///
    /// On return, this function advances `self.result_ptr` past the last
    /// deserialized account, so that a subsequent call will not overwrite
    /// previous data.
    ///
    /// # Safety
    ///
    /// - At least `4 + 32 * keys.len()` bytes starting at `self.params_ptr` and
    ///   enough bytes to store the result of `get_multiple_accounts` invocation
    ///   starting at `self.result_ptr` must be valid to read from and write to.
    ///
    /// - The host's implementation of `get_multiple_accounts` must read
    ///   `4 + 32 * keys.len()` bytes from `self.params_ptr`, then write out a
    ///   byte‐stream of (`Option` tag byte, `AccountRef` bytes) for each key
    ///   consecutively into `self.result_ptr`.
    pub unsafe fn get_multiple_accounts<'a>(
        &mut self,
        keys: &[Pubkey],
    ) -> heapless::Vec<Option<AccountRef<'a>>, 100>
    where
        Self: 'a,
    {
        let params_slice = core::slice::from_raw_parts_mut(self.params_ptr.cast(), keys.len());
        params_slice.copy_from_slice(keys);

        assert!(
            get_multiple_accounts(self.params_ptr.cast(), keys.len() as u32, self.result_ptr) == 0
        );

        let mut result_slice = core::slice::from_raw_parts(self.result_ptr, 1 << (isize::BITS - 1));
        let mut res = heapless::Vec::new();
        for _ in 0..keys.len() {
            let option_tag = result_slice[0];
            result_slice = &result_slice[1..];
            if option_tag == 0 {
                res.push(None).unwrap();
            } else {
                res.push(Some(AccountRef::deserialize_ref(&mut result_slice).unwrap())).unwrap();
            }
        }
        self.result_ptr = result_slice.as_ptr().cast_mut();
        res
    }
}

/// A lightweight, non-owning form of `solana_sdk::account::Account`.
#[derive(Debug)]
pub struct AccountRef<'a> {
    pub lamports: u64,
    pub data: &'a [u8],
    pub owner: Pubkey,
    pub executable: bool,
    pub rent_epoch: Epoch,
}

impl<'a> DeserializeRef<'a> for AccountRef<'a> {
    fn deserialize_ref(slice: &mut &'a [u8]) -> Option<Self> {
        let mut read_data = |count| {
            let (src, rest) = split_at_checked(slice, count)?;
            *slice = rest;
            Some(src)
        };

        let lamports = u64::from_le_bytes(read_data(8)?.try_into().unwrap());

        let data_len = u32::from_le_bytes(read_data(4)?.try_into().unwrap()) as usize;
        let data = read_data(data_len)?;

        let owner = Pubkey::new_from_array(read_data(32)?.try_into().unwrap());

        let executable = read_data(1)?[0] != 0;

        let rent_epoch = u64::from_le_bytes(read_data(8)?.try_into().unwrap());

        Some(Self {
            lamports,
            data,
            owner,
            executable,
            rent_epoch,
        })
    }
}