calimero_runtime/
logic.rs

1#![allow(single_use_lifetimes, unused_lifetimes, reason = "False positive")]
2#![allow(clippy::mem_forget, reason = "Safe for now")]
3
4use core::mem::MaybeUninit;
5use core::num::NonZeroU64;
6use core::{fmt, slice};
7use std::borrow::Cow;
8use std::collections::{BTreeMap, HashMap};
9use std::vec;
10
11use calimero_node_primitives::client::NodeClient;
12use calimero_sys as sys;
13use ouroboros::self_referencing;
14use serde::Serialize;
15
16use crate::constants::{DIGEST_SIZE, ONE_GIB, ONE_KIB, ONE_MIB};
17use crate::constraint::{Constrained, MaxU64};
18use crate::errors::{FunctionCallError, HostError, Location, PanicContext};
19use crate::store::Storage;
20use crate::Constraint;
21
22mod errors;
23mod host_functions;
24mod imports;
25mod registers;
26
27pub use errors::VMLogicError;
28pub use host_functions::*;
29use registers::Registers;
30
31/// A specialized `Result` type for VMLogic operations.
32pub type VMLogicResult<T, E = VMLogicError> = Result<T, E>;
33
34/// Encapsulates the context for a single VM execution.
35///
36/// This struct holds all the necessary information about the current execution environment,
37/// such as the input data, the context ID, and the executor's public key.
38#[derive(Debug)]
39#[non_exhaustive]
40pub struct VMContext<'a> {
41    /// The input data for the context.
42    pub input: Cow<'a, [u8]>,
43    /// The unique ID for the current execution context.
44    pub context_id: [u8; DIGEST_SIZE],
45    /// The public key of the entity executing the function call/transaction.
46    pub executor_public_key: [u8; DIGEST_SIZE],
47}
48
49impl<'a> VMContext<'a> {
50    /// Creates a new `VMContext`.
51    ///
52    /// # Arguments
53    ///
54    /// * `input` - The input data for the context.
55    /// * `context_id` - The unique ID for the execution context.
56    /// * `executor_public_key` - The public key of the executor.
57    #[must_use]
58    pub const fn new(
59        input: Cow<'a, [u8]>,
60        context_id: [u8; DIGEST_SIZE],
61        executor_public_key: [u8; DIGEST_SIZE],
62    ) -> Self {
63        Self {
64            input,
65            context_id,
66            executor_public_key,
67        }
68    }
69}
70
71/// Defines the resource limits for a VM instance.
72///
73/// This struct is used to configure constraints on various VM operations to prevent
74/// excessive resource consumption.
75#[derive(Debug, Clone, Copy)]
76pub struct VMLimits {
77    /// The maximum number of memory pages allowed.
78    pub max_memory_pages: u32,
79    /// The maximum stack size in bytes.
80    pub max_stack_size: usize,
81    /// The maximum number of registers that can be used.
82    pub max_registers: u64,
83    /// The maximum size of a single register's data in bytes.
84    /// constrained to be less than u64::MAX
85    /// because register_len returns u64::MAX if the register is not found
86    pub max_register_size: Constrained<u64, MaxU64<{ u64::MAX - 1 }>>,
87    /// The total capacity across all registers in bytes.
88    pub max_registers_capacity: u64, // todo! must not be less than max_register_size
89    /// The maximum number of log entries that can be created.
90    pub max_logs: u64,
91    /// The maximum size of a single log message in bytes.
92    pub max_log_size: u64,
93    /// The maximum number of events that can be emitted.
94    pub max_events: u64,
95    /// The maximum size of an event's "kind" string in bytes.
96    pub max_event_kind_size: u64,
97    /// The maximum size of an event's data payload in bytes.
98    pub max_event_data_size: u64,
99    /// The maximum size of a storage key in bytes.
100    pub max_storage_key_size: NonZeroU64,
101    /// The maximum size of a storage value in bytes.
102    pub max_storage_value_size: NonZeroU64,
103    /// The maximum number of blob handles that can exist.
104    pub max_blob_handles: u64,
105    /// The maximum size of a single chunk when writing to or reading from a blob.
106    pub max_blob_chunk_size: u64,
107}
108
109impl Default for VMLimits {
110    fn default() -> Self {
111        #[inline(always)]
112        fn is_valid<T, E: fmt::Debug>(t: Result<T, E>) -> T {
113            t.expect("is valid")
114        }
115
116        Self {
117            max_memory_pages: ONE_KIB,                                          // 1 KiB
118            max_stack_size: 200 * ONE_KIB as usize,                             // 200 KiB
119            max_registers: 100,                                                 //
120            max_register_size: is_valid((100 * ONE_MIB as u64).validate()),     // 100 MiB
121            max_registers_capacity: ONE_GIB as u64,                             // 1 GiB
122            max_logs: 100,                                                      //
123            max_log_size: 16 * ONE_KIB as u64,                                  // 16 KiB
124            max_events: 100,                                                    //
125            max_event_kind_size: 100,                                           //
126            max_event_data_size: 16 * ONE_KIB as u64,                           // 16 KiB
127            max_storage_key_size: is_valid((ONE_MIB as u64).try_into()),        // 1 MiB
128            max_storage_value_size: is_valid((10 * ONE_MIB as u64).try_into()), // 10 MiB
129            max_blob_handles: 100,                                              //
130            max_blob_chunk_size: 10 * ONE_MIB as u64,                           // 10 MiB
131        }
132    }
133}
134
135/// The core logic controller for the VM.
136///
137/// This struct manages the state of an execution, including storage,
138/// memory, registers, and the results of the execution (logs, events, return values).
139#[expect(
140    missing_debug_implementations,
141    reason = "storage and node_client can't impl Debug"
142)]
143pub struct VMLogic<'a> {
144    /// A mutable reference to the storage.
145    storage: &'a mut dyn Storage,
146    /// The Wasmer memory instance associated with the guest module.
147    memory: Option<wasmer::Memory>,
148    /// The execution context for the current call.
149    context: VMContext<'a>,
150    /// The VM resource limits applied to this execution.
151    limits: &'a VMLimits,
152    /// A collection of registers for temporary data exchange between host and guest.
153    registers: Registers,
154    /// The optional final result of the execution, which can be a success (`Ok`) or an error (`Err`).
155    returns: Option<VMLogicResult<Vec<u8>, Vec<u8>>>,
156    /// A list of log messages generated during execution.
157    logs: Vec<String>,
158    /// A list of events emitted during execution.
159    events: Vec<Event>,
160    /// The root hash of the state after a successful commit.
161    root_hash: Option<[u8; DIGEST_SIZE]>,
162    /// A binary artifact produced by the execution.
163    artifact: Vec<u8>,
164    /// A map of proposals created during execution, having proposal ID as a key.
165    proposals: BTreeMap<[u8; DIGEST_SIZE], Vec<u8>>,
166    /// A list of approvals submitted during execution.
167    approvals: Vec<[u8; DIGEST_SIZE]>,
168
169    // Blob functionality
170    /// An optional client for interacting with the node's blob storage.
171    node_client: Option<NodeClient>,
172    /// A map of active blob handles, having blob's file descriptor as a key.
173    blob_handles: HashMap<u64, BlobHandle>,
174    /// The next available file descriptor for a new blob handle.
175    next_blob_fd: u64,
176}
177
178impl<'a> VMLogic<'a> {
179    /// Creates a new `VMLogic` instance.
180    ///
181    /// # Arguments
182    ///
183    /// * `storage` - A mutable reference to the storage implementation.
184    /// * `context` - The execution context for the VM.
185    /// * `limits` - The VM resource limits to enforce.
186    /// * `node_client` - An optional client for blob storage operations.
187    pub fn new(
188        storage: &'a mut dyn Storage,
189        context: VMContext<'a>,
190        limits: &'a VMLimits,
191        node_client: Option<NodeClient>,
192    ) -> Self {
193        VMLogic {
194            storage,
195            memory: None,
196            context,
197            limits,
198            registers: Registers::default(),
199            returns: None,
200            logs: vec![],
201            events: vec![],
202            root_hash: None,
203            artifact: vec![],
204            proposals: BTreeMap::new(),
205            approvals: vec![],
206
207            // Blob functionality
208            node_client,
209            blob_handles: HashMap::new(),
210            next_blob_fd: 1,
211        }
212    }
213
214    /// Associates a Wasmer memory instance with this `VMLogic`.
215    ///
216    /// This method should be called after the guest module is instantiated but before
217    /// any host functions are called.
218    ///
219    /// # Arguments
220    ///
221    /// * `memory` - The `wasmer::Memory` instance from the instantiated guest module.
222    pub fn with_memory(&mut self, memory: wasmer::Memory) -> &mut Self {
223        self.memory = Some(memory);
224        self
225    }
226
227    /// Creates a `VMHostFunctions` instance to be imported by the guest module.
228    ///
229    /// This method builds the self-referential struct that provides guest code
230    /// with access to host capabilities.
231    ///
232    /// # Panics
233    ///
234    /// Panics if `with_memory` has not been called first.
235    ///
236    /// # Arguments
237    ///
238    /// * `store` - A mutable view of the Wasmer store.
239    pub fn host_functions(&'a mut self, store: wasmer::StoreMut<'a>) -> VMHostFunctions<'a> {
240        // TODO: review the `clone()` and figure out if the function should be a one-time call only.
241        let memory = self.memory.clone().expect("VM Memory not initialized");
242
243        VMHostFunctionsBuilder {
244            logic: self,
245            store,
246
247            memory_builder: |store| memory.view(store),
248        }
249        .build()
250    }
251}
252
253/// Represents the final outcome of a VM execution.
254///
255/// This struct aggregates all the results and side effects of function calls,
256/// such as the return value, logs, events, and state changes.
257#[derive(Debug, Serialize)]
258#[non_exhaustive]
259pub struct Outcome {
260    /// The result of the execution. `Ok(Some(value))` for a successful return,
261    /// `Ok(None)` for no return, and `Err` for a trap or execution error.
262    pub returns: VMLogicResult<Option<Vec<u8>>, FunctionCallError>,
263    /// All log messages generated during the execution.
264    pub logs: Vec<String>,
265    /// All events emitted during the execution.
266    pub events: Vec<Event>,
267    /// The new state root hash if there were commits during the execution.
268    pub root_hash: Option<[u8; DIGEST_SIZE]>,
269    /// The binary artifact produced if there were commits during the execution.
270    //TODO: why the artifact is not an Option?
271    pub artifact: Vec<u8>,
272    /// A map of proposals created during execution, having proposal ID as a key.
273    pub proposals: BTreeMap<[u8; DIGEST_SIZE], Vec<u8>>,
274    /// A list of approvals submitted during execution.
275    pub approvals: Vec<[u8; DIGEST_SIZE]>,
276    //TODO: execution runtime (???).
277    //TODO: current storage usage of the app (???).
278}
279
280impl VMLogic<'_> {
281    /// Consumes the `VMLogic` instance and produces the final `Outcome`.
282    ///
283    /// This method should be called after the guest function has finished executing
284    /// or has trapped. It packages the final state of the `VMLogic` into an
285    /// `Outcome` struct.
286    ///
287    /// # Arguments
288    ///
289    /// * `err` - An optional `FunctionCallError` that occurred during execution (e.g., a trap).
290    ///           If `None`, the outcome is determined by the `returns` field.
291    #[must_use]
292    pub fn finish(self, err: Option<FunctionCallError>) -> Outcome {
293        let returns = match err {
294            Some(err) => Err(err),
295            None => self
296                .returns
297                .map(|t| t.map_err(FunctionCallError::ExecutionError))
298                .transpose(),
299        };
300
301        Outcome {
302            returns,
303            logs: self.logs,
304            events: self.events,
305            root_hash: self.root_hash,
306            artifact: self.artifact,
307            proposals: self.proposals,
308            approvals: self.approvals,
309        }
310    }
311}
312
313/// A self-referential struct that holds the `VMLogic`, the Wasmer `StoreMut`,
314/// and a `MemoryView` derived from them.
315///
316/// This structure is necessary to safely provide guest access to host functions,
317/// hold the reference to guest memory (`MemoryView`) simultaneously. The `ouroboros`
318/// crate ensures that the lifetimes are managed correctly.
319#[self_referencing]
320pub struct VMHostFunctions<'a> {
321    logic: &'a mut VMLogic<'a>,
322    store: wasmer::StoreMut<'a>,
323
324    #[covariant]
325    #[borrows(store)]
326    memory: wasmer::MemoryView<'this>,
327}
328
329// Private helper functions for memory access.
330impl VMHostFunctions<'_> {
331    /// Reads an IMMUTABLE slice of guest memory.
332    /// This should be used when the host needs to READ from a buffer
333    /// provided by the guest.
334    ///
335    /// # Arguments
336    ///
337    /// * `slice` - A `sys::Buffer` descriptor pointing to the buffer location and length in guest memory.
338    ///
339    /// # Returns
340    ///
341    /// * Immutable slice of the guest memory contents.
342    fn read_guest_memory_slice(&self, slice: &sys::Buffer<'_>) -> &[u8] {
343        let ptr = slice.ptr().value().as_usize();
344        let len = slice.len() as usize;
345
346        unsafe { &self.borrow_memory().data_unchecked()[ptr..ptr + len] }
347    }
348
349    /// Reads a MUTABLE slice of guest memory.
350    /// This should only be used when the host needs to WRITE into a buffer
351    /// provided by the guest.
352    ///
353    /// # Arguments
354    ///
355    /// * `slice` - A `sys::Buffer` descriptor pointing to the buffer location and length in guest memory.
356    ///
357    /// # Returns
358    ///
359    /// * Mutable slice of the guest memory contents.
360    #[allow(
361        clippy::mut_from_ref,
362        reason = "We are not modifying the self explicitly, only the underlying slice of the guest memory.\
363        Meantime we are required to have an immutable reference to self, hence the exception"
364    )]
365    fn read_guest_memory_slice_mut(&self, slice: &sys::BufferMut<'_>) -> &mut [u8] {
366        let ptr = slice.ptr().value().as_usize();
367        let len = slice.len() as usize;
368
369        unsafe { &mut self.borrow_memory().data_unchecked_mut()[ptr..ptr + len] }
370    }
371
372    /// Reads an immutable UTF-8 string slice from guest memory.
373    ///
374    /// # Arguments
375    ///
376    /// * `slice` - A `sys::Buffer` descriptor pointing to the buffer location and length in guest memory.
377    ///
378    /// # Returns
379    ///
380    /// * `VMLogicResult(Ok(utf8_slice))`
381    ///
382    /// # Errors
383    ///
384    /// Returns `VMLogicError::HostError(HostError::BadUTF8)` if the memory slice
385    /// does not contain valid UTF-8 data.
386    fn read_guest_memory_str(&self, slice: &sys::Buffer<'_>) -> VMLogicResult<&str> {
387        let buf = self.read_guest_memory_slice(slice);
388
389        std::str::from_utf8(buf).map_err(|_| HostError::BadUTF8.into())
390    }
391
392    /// Reads a fixed-size IMMUTABLE array slice from guest memory.
393    ///
394    /// # Arguments
395    ///
396    /// * `slice` - A `sys::Buffer` descriptor pointing to the buffer location and length in guest memory.
397    ///
398    /// # Errors
399    ///
400    /// Returns `VMLogicError::HostError(HostError::InvalidMemoryAccess)` if the buffer
401    /// length in guest memory does not exactly match the requested array size `N`.
402    fn read_guest_memory_sized<const N: usize>(
403        &self,
404        slice: &sys::Buffer<'_>,
405    ) -> VMLogicResult<&[u8; N]> {
406        let buf = self.read_guest_memory_slice(slice);
407
408        buf.try_into()
409            .map_err(|_| HostError::InvalidMemoryAccess.into())
410    }
411
412    /// Reads a sized type from guest memory.
413    /// Reads a `Sized` type `T` from a specified location in guest memory.
414    ///
415    /// # Arguments
416    ///
417    /// * `ptr` - The memory address in the guest's linear memory where the type `T` is located.
418    ///
419    /// # Errors
420    ///
421    /// Can return a `Host::InvalidMemoryAccesssError` if the read operation goes out of bounds.
422    ///
423    /// # Safety
424    ///
425    /// This function is unsafe because:
426    /// 1. It reads raw bytes from guest memory and interprets them as type `T`. The caller
427    ///    must ensure that the bytes at `ptr` represent a valid instance of `T`.
428    /// 2. It relies on `ptr` being a valid, aligned pointer within the guest memory.
429    //TODO: refactor to use `sys::Buffer` instead of `ptr`.
430    unsafe fn read_guest_memory_typed<T>(&self, ptr: u64) -> VMLogicResult<T> {
431        let mut value = MaybeUninit::<T>::uninit();
432
433        let raw = slice::from_raw_parts_mut(value.as_mut_ptr().cast::<u8>(), size_of::<T>());
434
435        self.borrow_memory().read(ptr, raw)?;
436
437        Ok(value.assume_init())
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    use super::*;
444    use crate::store::{Key, Value};
445    use core::ops::Deref;
446    use wasmer::{AsStoreMut, Store};
447
448    // The descriptor has the size of 16-bytes with the layout `{ ptr: u64, len: u64 }`
449    // See below: [`prepare_guest_buf_descriptor`]
450    pub const DESCRIPTOR_SIZE: usize = u64::BITS as usize / 8 * 2;
451
452    // This implementation is more suitable for testing host-side components
453    // in comparison to `store::MockedStorage` which is a better for guest-side
454    // tests - e.g. testing Calimero application contracts.
455    // This version minimally satisfies the `Storage` trait without introducing
456    // the global state, guaranteed having a proper test isolation and not having a risk
457    // to collide with other tests due to developer error, too (i.e. developer accidentally
458    // used the same scope for the global state of `store::MockedStorage` in two different
459    // tests).
460    pub struct SimpleMockStorage {
461        data: HashMap<Vec<u8>, Vec<u8>>,
462    }
463
464    impl SimpleMockStorage {
465        pub fn new() -> Self {
466            Self {
467                data: HashMap::new(),
468            }
469        }
470    }
471
472    impl Storage for SimpleMockStorage {
473        fn get(&self, key: &Key) -> Option<Value> {
474            self.data.get(key).cloned()
475        }
476
477        fn set(&mut self, key: Key, value: Value) -> Option<Value> {
478            self.data.insert(key, value)
479        }
480
481        fn remove(&mut self, key: &Key) -> Option<Value> {
482            self.data.remove(key)
483        }
484
485        fn has(&self, key: &Key) -> bool {
486            self.data.contains_key(key)
487        }
488    }
489
490    /// A macro to set up the VM environment within a test.
491    /// It takes references to storage and limits, which are owned by the test function,
492    /// ensuring that all lifetimes are valid.
493    macro_rules! setup_vm {
494        ($storage:expr, $limits:expr, $input:expr) => {{
495            let context =
496                VMContext::new(Cow::Owned($input), [0u8; DIGEST_SIZE], [0u8; DIGEST_SIZE]);
497            let mut store = Store::default();
498            let memory =
499                wasmer::Memory::new(&mut store, wasmer::MemoryType::new(1, None, false)).unwrap();
500            let mut logic = VMLogic::new($storage, context, $limits, None);
501            let _ = logic.with_memory(memory);
502            (logic, store)
503        }};
504    }
505
506    // Export macros to the module level, so it can be reused in other host functions' tests.
507    pub(super) use setup_vm;
508
509    /// Helper to write a similar to `sys::Buffer` struct representation to memory.
510    /// Simulates a WASM guest preparing a memory descriptor for a host call.
511    ///
512    /// # Why this is necessary
513    /// When a WASM guest needs the host to read/write a slice of its memory, it cannot
514    /// pass a slice directly. Instead, it must pass a pointer to a "descriptor" structure
515    /// that exists within guest's memory. This descriptor tells the host where the
516    /// actual data is (`ptr`) and how long it is (`len`). This function simulates the guest
517    /// writing that descriptor into the mock memory.
518    ///
519    /// # Parameters
520    /// - `host`: A reference to the `VMHostFunctions` to get access to the guest memory view.
521    /// - `offset`: The address of the descriptor struct itself in the guest memory.
522    ///   This is the pointer that the guest would pass to the host function.
523    /// - `ptr`: The address of the actual data payload (e.g., a string or byte array) in the
524    ///   guest memory. This value is written inside the descriptor structure.
525    /// - `len`: The length of the data payload. This value is also written inside the
526    ///   descriptor structure.
527    ///
528    /// # ABI and Memory Layout
529    /// Although the guest is `wasm32` and uses `u32` pointers internally, the host-guest
530    /// ABI often standardizes on `u64` for all pointers and lengths for consistency and
531    /// forward-compatibility with `wasm64`. Therefore, this function writes both `ptr` and `len`
532    /// as `u64`, creating a 16-byte descriptor in memory with the layout `{ ptr: u64, len: u64 }`.
533    /// All values are little-endian, as required by the WebAssembly specification.
534    pub fn prepare_guest_buf_descriptor(
535        host: &VMHostFunctions<'_>,
536        offset: u64,
537        ptr: u64,
538        len: u64,
539    ) {
540        let data: Vec<u8> = [ptr.to_le_bytes(), len.to_le_bytes()].concat();
541
542        host.borrow_memory()
543            .write(offset, &data)
544            .expect("Failed to write buffer");
545    }
546
547    /// A test helper to write a string slice directly into the guest's mock memory.
548    ///
549    /// This simulates the guest having string data (e.g., a log message, a storage key)
550    /// in its linear memory, making it available for the host to read.
551    ///
552    /// # Parameters
553    /// - `host`: A reference to the `VMHostFunctions` to get access to the guest memory view.
554    /// - `offset`: The memory address where the string's byte data will be written.
555    /// - `s`: The string slice to write into the guest's memory.
556    pub fn write_str(host: &VMHostFunctions<'_>, offset: u64, s: &str) {
557        host.borrow_memory()
558            .write(offset, s.as_bytes())
559            .expect("Failed to write string");
560    }
561
562    /// A simple sanity check to ensure the default `VMLimits` are configured as expected.
563    /// This test helps prevent accidental changes to the default limits.
564    #[test]
565    fn test_default_limits() {
566        let limits = VMLimits::default();
567        assert_eq!(limits.max_memory_pages, 1 << 10);
568        assert_eq!(limits.max_stack_size, 200 << 10);
569        assert_eq!(limits.max_registers, 100);
570        assert_eq!(*limits.max_register_size.deref(), 100 << 20);
571        assert_eq!(limits.max_registers_capacity, 1 << 30); // 1 GiB
572        assert_eq!(limits.max_logs, 100);
573        assert_eq!(limits.max_log_size, 16 << 10); // 16 KiB
574        assert_eq!(limits.max_events, 100);
575        assert_eq!(limits.max_event_kind_size, 100);
576        assert_eq!(limits.max_event_data_size, 16 << 10); // 16 KiB
577        assert_eq!(limits.max_storage_key_size.get(), 1 << 20); // 1 MiB
578        assert_eq!(limits.max_storage_value_size.get(), 10 << 20); // 10 MiB
579        assert_eq!(limits.max_blob_handles, 100);
580        assert_eq!(limits.max_blob_chunk_size, 10 << 20); // 10 MiB
581    }
582
583    /// A smoke test for the successful path of the `finish` method.
584    ///
585    /// This test simulates a VM execution that successfully finished by
586    /// calling `finish(None)` and asserts that the `returns` field in
587    /// the final `Outcome` is an `Ok`, ensuring the error is propagated correctly.
588    #[test]
589    fn test_smoke_finish() {
590        let mut storage = SimpleMockStorage::new();
591        let limits = VMLimits::default();
592        let (logic, _) = setup_vm!(&mut storage, &limits, vec![1, 2, 3]);
593        let outcome = logic.finish(None);
594
595        assert!(outcome.returns.is_ok());
596    }
597
598    /// A smoke test for the error-handling path of the `finish` method.
599    ///
600    /// This test simulates a VM execution that failed by calling `finish(Some(Error))`
601    /// and asserts that the `returns` field in the final `Outcome` is an `Err`,
602    /// ensuring the error is propagated correctly.
603    #[test]
604    fn test_smoke_finish_with_error() {
605        let mut storage = SimpleMockStorage::new();
606        let limits = VMLimits::default();
607        let (logic, _) = setup_vm!(&mut storage, &limits, vec![]);
608        let outcome = logic.finish(Some(FunctionCallError::ExecutionError(vec![])));
609        assert!(outcome.returns.is_err());
610    }
611
612    // ===========================================================================
613    // Tests for private functions
614    // ===========================================================================
615
616    /// Verifies the success path of the private `read_guest_memory_slice` and `read_guest_memory_str` functions.
617    #[test]
618    fn test_private_read_guest_memory_slice_and_str_success() {
619        let mut storage = SimpleMockStorage::new();
620        let limits = VMLimits::default();
621        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
622        let host = logic.host_functions(store.as_store_mut());
623
624        let expected_str = "hello world";
625        let data_ptr = 100u64;
626        // Write msg to guest memory.
627        write_str(&host, data_ptr, expected_str);
628        let buf_ptr = 16u64;
629        // Guest: prepare the descriptor for the destination buffer so host can access it.
630        prepare_guest_buf_descriptor(&host, buf_ptr, data_ptr, expected_str.len() as u64);
631
632        // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
633        // just like public host functions do internally.
634        let buffer = unsafe {
635            host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr)
636                .unwrap()
637        };
638
639        // Guest: ask host to read str from the `buffer` located in guest memory.
640        let result_str = host.read_guest_memory_str(&buffer).unwrap();
641        assert_eq!(result_str, expected_str);
642
643        // Guest: ask host to read slice from the `buffer` located in guest memory.
644        let result_slice = host.read_guest_memory_slice(&buffer);
645        assert_eq!(result_slice, expected_str.as_bytes());
646    }
647
648    /// Verifies the `read_guest_memory_slice` function can't modify the guest buffer.
649    #[test]
650    fn test_private_read_guest_memory_slice_unmutable() {
651        let mut storage = SimpleMockStorage::new();
652        let limits = VMLimits::default();
653        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
654        let host = logic.host_functions(store.as_store_mut());
655
656        let expected_str = "hello world";
657        let data_ptr = 100u64;
658        // Write msg to guest memory.
659        write_str(&host, data_ptr, expected_str);
660        let buf_ptr = 16u64;
661        // Guest: prepare the descriptor for the destination buffer so host can access it.
662        prepare_guest_buf_descriptor(&host, buf_ptr, data_ptr, expected_str.len() as u64);
663
664        // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
665        // just like public host functions do internally.
666        let buffer = unsafe {
667            host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr)
668                .unwrap()
669        };
670
671        // Guest: ask host to read slice from the `buffer` located in guest memory.
672        let result_slice = host.read_guest_memory_slice(&buffer);
673        assert_eq!(result_slice, expected_str.as_bytes());
674
675        // Now, this code won't be compilable as we get an immutable ref.
676        // Host: modify the memory (this could happen accidentally and not intended).
677        // ```compile_fail
678        // for value in result_slice.iter() {
679        // }
680        // ```
681
682        // Guest: ask host to read str from the `buffer` located in guest memory.
683        let result_str = host.read_guest_memory_str(&buffer).unwrap();
684        assert_eq!(result_str, expected_str);
685    }
686
687    /// Verifies the error handling of the private `read_guest_memory_str` function for invalid UTF-8.
688    #[test]
689    fn test_private_read_guest_memory_str_invalid_utf8() {
690        let mut storage = SimpleMockStorage::new();
691        let limits = VMLimits::default();
692        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
693        let host = logic.host_functions(store.as_store_mut());
694
695        let invalid_utf8: &[u8] = &[0, 159, 146, 150];
696        let data_ptr = 100u64;
697        // Write invalid utf8 buffer to the guest memory
698        host.borrow_memory().write(data_ptr, invalid_utf8).unwrap();
699        let buf_ptr = 16u64;
700        // Guest: prepare the descriptor for the destination buffer so host can access it.
701        prepare_guest_buf_descriptor(&host, buf_ptr, data_ptr, invalid_utf8.len() as u64);
702
703        // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
704        // just like public host functions do internally.
705        let buffer = unsafe {
706            host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr)
707                .unwrap()
708        };
709
710        // Test that `read_guest_memory_str` fails as expected.
711        let err = host.read_guest_memory_str(&buffer).unwrap_err();
712        assert!(matches!(err, VMLogicError::HostError(HostError::BadUTF8)));
713    }
714
715    /// Verifies the success and failure paths of the private `read_guest_memory_sized` function.
716    #[test]
717    fn test_private_read_guest_memory_sized() {
718        let mut storage = SimpleMockStorage::new();
719        let limits = VMLimits::default();
720        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
721        let host = logic.host_functions(store.as_store_mut());
722
723        // Test success case.
724        let correct_data = [42u8; DIGEST_SIZE];
725        let data_ptr_ok = 100u64;
726        // Write correct data to guest memory.
727        host.borrow_memory()
728            .write(data_ptr_ok, &correct_data)
729            .unwrap();
730        let buf_ptr_ok = 16u64;
731        // Guest: prepare the descriptor for the destination buffer so host can access it.
732        prepare_guest_buf_descriptor(&host, buf_ptr_ok, data_ptr_ok, correct_data.len() as u64);
733
734        // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
735        // just like public host functions do internally.
736        let buffer_ok = unsafe {
737            host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr_ok)
738                .unwrap()
739        };
740        let result_sized_ok = host
741            .read_guest_memory_sized::<DIGEST_SIZE>(&buffer_ok)
742            .unwrap();
743        assert_eq!(result_sized_ok, &correct_data);
744
745        // Test failure case (incorrect length).
746        let incorrect_data = [1u8; 31];
747        let data_ptr_err = 300u64;
748        // Write incorrect data to guest memory.
749        host.borrow_memory()
750            .write(data_ptr_err, &incorrect_data)
751            .unwrap();
752        let buf_ptr_err = 32u64;
753        // Guest: prepare the descriptor for the destination buffer so host can access it.
754        prepare_guest_buf_descriptor(
755            &host,
756            buf_ptr_err,
757            data_ptr_err,
758            incorrect_data.len() as u64,
759        );
760
761        // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
762        // just like public host functions do internally.
763        let buffer_err = unsafe {
764            host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr_err)
765                .unwrap()
766        };
767        // Guest: ask host to read the guest memory sized.
768        let err = host
769            .read_guest_memory_sized::<DIGEST_SIZE>(&buffer_err)
770            .unwrap_err();
771        assert!(matches!(
772            err,
773            VMLogicError::HostError(HostError::InvalidMemoryAccess)
774        ));
775    }
776}