uip_solana_sdk/
extension.rs

1//! Module for use by the Solana protocol extensions.
2
3use solana_program::{clock::Epoch, instruction::AccountMeta, pubkey::Pubkey};
4
5use super::{deser::DeserializeRef, utils::split_at_checked, MessageDataRef};
6
7impl MessageDataRef<'_> {
8    /// Parse `MessageDataRef` out of the raw pointer and length.
9    ///
10    /// # Safety
11    ///
12    /// - `msg_data_ptr` must point to at least `msg_data_len` bytes of valid,
13    ///   initialized memory that must remain alive and immutable for the duration
14    ///   of its use.
15    pub unsafe fn load(msg_data_ptr: *const u8, msg_data_len: usize) -> Self {
16        let mut slice = core::slice::from_raw_parts(msg_data_ptr, msg_data_len);
17        MessageDataRef::deserialize_ref(&mut slice).unwrap()
18    }
19}
20
21/// Information about the compute budget, optional heap frame size, and the list
22/// of account metas required by the instruction.
23#[repr(C)]
24pub struct InstructionInfo {
25    pub compute_units: u32,
26    pub heap_frame: u32,
27    pub accounts_len: u32,
28    pub accounts: [AccountMeta; 57],
29}
30
31impl InstructionInfo {
32    /// Add `account_meta` to the array of requested accounts.
33    ///
34    /// Returns back the `account_meta` if the array is full.
35    pub fn push_account(&mut self, account_meta: AccountMeta) -> Result<(), AccountMeta> {
36        if self.accounts_len as usize >= self.accounts.len() {
37            return Err(account_meta);
38        }
39
40        self.accounts[self.accounts_len as usize] = account_meta;
41        self.accounts_len += 1;
42
43        Ok(())
44    }
45}
46
47#[link(wasm_import_module = "solana_transmitter")]
48extern "C" {
49    fn get_multiple_accounts(keys_ptr: *const Pubkey, keys_len: u32, out_ptr: *mut u8) -> i32;
50}
51
52/// Helper for managing the shared memory region used for host-calling from
53/// within a WASM extension.
54pub struct HostCallContext {
55    params_ptr: *mut u8,
56    result_ptr: *mut u8,
57}
58
59impl HostCallContext {
60    /// Constructs a new `HostCallContext` given the base pointer and length
61    /// of the serialized message data.
62    #[allow(clippy::not_unsafe_ptr_arg_deref)]
63    pub fn new(msg_data_ptr: *const u8, msg_data_len: usize) -> Self {
64        const MAX_RESULT_LEN: usize = core::mem::size_of::<InstructionInfo>();
65        const MAX_PARAMS_LEN: usize = 4 + 32 * 100;
66
67        let offset = msg_data_len + MAX_RESULT_LEN;
68        // cast to guarantee safety
69        let offset = offset as isize as usize;
70
71        // SAFETY: `offset` and `MAX_PARAMS_LEN` fit in `isize`.
72        unsafe {
73            let params_ptr = msg_data_ptr.add(offset).cast_mut();
74            Self {
75                params_ptr,
76                result_ptr: params_ptr.add(MAX_PARAMS_LEN),
77            }
78        }
79    }
80
81    /// Request multiple accounts from the executor.
82    ///
83    /// On return, this function advances `self.result_ptr` past the last
84    /// deserialized account, so that a subsequent call will not overwrite
85    /// previous data.
86    ///
87    /// # Safety
88    ///
89    /// - At least `4 + 32 * keys.len()` bytes starting at `self.params_ptr` and
90    ///   enough bytes to store the result of `get_multiple_accounts` invocation
91    ///   starting at `self.result_ptr` must be valid to read from and write to.
92    ///
93    /// - The host's implementation of `get_multiple_accounts` must read
94    ///   `4 + 32 * keys.len()` bytes from `self.params_ptr`, then write out a
95    ///   byte‐stream of (`Option` tag byte, `AccountRef` bytes) for each key
96    ///   consecutively into `self.result_ptr`.
97    pub unsafe fn get_multiple_accounts<'a>(
98        &mut self,
99        keys: &[Pubkey],
100    ) -> heapless::Vec<Option<AccountRef<'a>>, 100>
101    where
102        Self: 'a,
103    {
104        let params_slice = core::slice::from_raw_parts_mut(self.params_ptr.cast(), keys.len());
105        params_slice.copy_from_slice(keys);
106
107        assert!(
108            get_multiple_accounts(self.params_ptr.cast(), keys.len() as u32, self.result_ptr) == 0
109        );
110
111        let mut result_slice = core::slice::from_raw_parts(self.result_ptr, 1 << (isize::BITS - 1));
112        let mut res = heapless::Vec::new();
113        for _ in 0..keys.len() {
114            let option_tag = result_slice[0];
115            result_slice = &result_slice[1..];
116            if option_tag == 0 {
117                res.push(None).unwrap();
118            } else {
119                res.push(Some(AccountRef::deserialize_ref(&mut result_slice).unwrap())).unwrap();
120            }
121        }
122        self.result_ptr = result_slice.as_ptr().cast_mut();
123        res
124    }
125}
126
127/// A lightweight, non-owning form of `solana_sdk::account::Account`.
128#[derive(Debug)]
129pub struct AccountRef<'a> {
130    pub lamports: u64,
131    pub data: &'a [u8],
132    pub owner: Pubkey,
133    pub executable: bool,
134    pub rent_epoch: Epoch,
135}
136
137impl<'a> DeserializeRef<'a> for AccountRef<'a> {
138    fn deserialize_ref(slice: &mut &'a [u8]) -> Option<Self> {
139        let mut read_data = |count| {
140            let (src, rest) = split_at_checked(slice, count)?;
141            *slice = rest;
142            Some(src)
143        };
144
145        let lamports = u64::from_le_bytes(read_data(8)?.try_into().unwrap());
146
147        let data_len = u32::from_le_bytes(read_data(4)?.try_into().unwrap()) as usize;
148        let data = read_data(data_len)?;
149
150        let owner = Pubkey::new_from_array(read_data(32)?.try_into().unwrap());
151
152        let executable = read_data(1)?[0] != 0;
153
154        let rent_epoch = u64::from_le_bytes(read_data(8)?.try_into().unwrap());
155
156        Some(Self {
157            lamports,
158            data,
159            owner,
160            executable,
161            rent_epoch,
162        })
163    }
164}