calimero_runtime/logic/host_functions/
system.rs

1use serde::Serialize;
2use std::cell::RefCell;
3
4use crate::{
5    errors::{HostError, Location, PanicContext},
6    logic::{sys, VMHostFunctions, VMLogicResult, DIGEST_SIZE},
7};
8
9thread_local! {
10    /// The name of the callback handler method to call when emitting events with handlers.
11    /// This is set temporarily by the SDK's `emit_with_handler` function and read by the runtime.
12    static CURRENT_CALLBACK_HANDLER: RefCell<Option<String>> = RefCell::new(None);
13}
14
15/// Represents a structured event emitted during the execution.
16#[derive(Debug, Serialize)]
17#[non_exhaustive]
18pub struct Event {
19    /// A string identifying the type or category of the event.
20    pub kind: String,
21    /// The binary data payload associated with the event.
22    pub data: Vec<u8>,
23    /// Optional handler name for the event.
24    pub handler: Option<String>,
25}
26
27impl VMHostFunctions<'_> {
28    /// Host function to handle a simple panic from the guest.
29    ///
30    /// This function is called when the guest code panics without a message. It captures
31    /// the source location (file, line, column) of the panic and terminates the execution.
32    ///
33    /// # Arguments
34    ///
35    /// * `src_location_ptr` - A pointer in guest memory to a `sys::Location` struct,
36    ///   containing file, line, and column information about the panic's origin.
37    ///
38    /// # Returns/Errors
39    ///
40    /// * `HostError::Panic` if the panic action was successfully executed.
41    /// * `HostError::InvalidMemoryAccess` if memory access fails for a descriptor buffer.
42    pub fn panic(&mut self, src_location_ptr: u64) -> VMLogicResult<()> {
43        let location =
44            unsafe { self.read_guest_memory_typed::<sys::Location<'_>>(src_location_ptr)? };
45
46        let file = self.read_guest_memory_str(&location.file())?.to_owned();
47        let line = location.line();
48        let column = location.column();
49
50        Err(HostError::Panic {
51            context: PanicContext::Guest,
52            message: "explicit panic".to_owned(),
53            location: Location::At { file, line, column },
54        }
55        .into())
56    }
57
58    /// Host function to handle a panic with a UTF-8 message from the guest.
59    ///
60    /// This function is called when guest code panics with a message. It captures the
61    /// message and source location, then terminates the execution.
62    ///
63    /// # Arguments
64    ///
65    /// * `src_panic_msg_ptr` - A pointer in guest memory to a source-buffer `sys::Buffer` containing
66    /// the UTF-8 panic message.
67    /// * `src_location_ptr` - A pointer in guest memory to a `sys::Location` struct for the panic's origin.
68    ///
69    /// # Returns/Errors
70    ///
71    /// * `HostError::Panic` if the panic action was successfully executed.
72    /// * `HostError::BadUTF8` if reading UTF8 string from guest memory fails.
73    /// * `HostError::InvalidMemoryAccess` if memory access fails for descriptor buffers.
74    pub fn panic_utf8(
75        &mut self,
76        src_panic_msg_ptr: u64,
77        src_location_ptr: u64,
78    ) -> VMLogicResult<()> {
79        let panic_message_buf =
80            unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_panic_msg_ptr)? };
81        let location =
82            unsafe { self.read_guest_memory_typed::<sys::Location<'_>>(src_location_ptr)? };
83
84        let panic_message = self.read_guest_memory_str(&panic_message_buf)?.to_owned();
85        let file = self.read_guest_memory_str(&location.file())?.to_owned();
86        let line = location.line();
87        let column = location.column();
88
89        Err(HostError::Panic {
90            context: PanicContext::Guest,
91            message: panic_message,
92            location: Location::At { file, line, column },
93        }
94        .into())
95    }
96
97    /// Returns the length of the data in a given register.
98    ///
99    /// # Arguments
100    ///
101    /// * `register_id` - The ID of the register to query.
102    ///
103    /// # Returns
104    ///
105    /// The length of the data in the specified register. If the register is not found,
106    /// it returns `u64::MAX`.
107    pub fn register_len(&self, register_id: u64) -> VMLogicResult<u64> {
108        Ok(self
109            .borrow_logic()
110            .registers
111            .get_len(register_id)
112            .unwrap_or(u64::MAX))
113    }
114
115    /// Reads the data from a register into a guest memory buffer.
116    ///
117    /// # Arguments
118    ///
119    /// * `register_id` - The ID of the register to read from.
120    /// * `dest_data_ptr` - A pointer in guest memory to a destination buffer `sys::BufferMut`
121    /// where the data should be copied.
122    ///
123    /// # Returns
124    ///
125    /// * Returns `1` if the data was successfully read and copied.
126    /// * Returns `0` if the provided guest buffer has a different length than the register's data.
127    ///
128    /// # Errors
129    ///
130    /// * `HostError::InvalidRegisterId` if the register does not exist.
131    /// * `HostError::InvalidMemoryAccess` if memory access fails for a descriptor buffer.
132    pub fn read_register(&self, register_id: u64, dest_data_ptr: u64) -> VMLogicResult<u32> {
133        let dest_data =
134            unsafe { self.read_guest_memory_typed::<sys::BufferMut<'_>>(dest_data_ptr)? };
135
136        let data = self.borrow_logic().registers.get(register_id)?;
137
138        if data.len() != usize::try_from(dest_data.len()).map_err(|_| HostError::IntegerOverflow)? {
139            return Ok(0);
140        }
141
142        self.read_guest_memory_slice_mut(&dest_data)
143            .copy_from_slice(data);
144
145        Ok(1)
146    }
147
148    /// Copies the current context ID into a register.
149    ///
150    /// # Arguments
151    ///
152    /// * `dest_register_id` - The ID of the destination register.
153    ///
154    /// # Errors
155    ///
156    /// * `HostError::InvalidMemoryAccess` if the register operation fails (e.g., exceeds limits).
157    pub fn context_id(&mut self, dest_register_id: u64) -> VMLogicResult<()> {
158        self.with_logic_mut(|logic| {
159            logic
160                .registers
161                .set(logic.limits, dest_register_id, logic.context.context_id)
162        })
163    }
164
165    /// Copies the executor's public key into a register.
166    ///
167    /// # Arguments
168    ///
169    /// * `dest_register_id` - The ID of the destination register.
170    ///
171    /// # Errors
172    ///
173    /// * `HostError::InvalidMemoryAccess` if the register operation fails (e.g., exceeds limits).
174    pub fn executor_id(&mut self, dest_register_id: u64) -> VMLogicResult<()> {
175        self.with_logic_mut(|logic| {
176            logic.registers.set(
177                logic.limits,
178                dest_register_id,
179                logic.context.executor_public_key,
180            )
181        })
182    }
183
184    /// Copies the input data for the current execution (from context ID) into a register.
185    ///
186    /// # Arguments
187    ///
188    /// * `dest_register_id` - The ID of the destination register.
189    ///
190    /// # Errors
191    ///
192    /// * `HostError::InvalidMemoryAccess` if the register operation fails (e.g., exceeds limits).
193    pub fn input(&mut self, dest_register_id: u64) -> VMLogicResult<()> {
194        self.with_logic_mut(|logic| {
195            logic
196                .registers
197                .set(logic.limits, dest_register_id, &*logic.context.input)
198        })?;
199
200        Ok(())
201    }
202
203    /// Sets the final return value of the execution.
204    ///
205    /// This function can be called by the guest to specify a successful result (`Ok`)
206    /// or a custom execution error (`Err`). This value will be part of the final `Outcome`.
207    ///
208    /// # Arguments
209    ///
210    /// * `src_value_ptr` - A pointer in guest memory to a source-`sys::ValueReturn`,
211    /// which is an enum indicating success or error, along with the data buffer.
212    ///
213    /// # Errors
214    ///
215    /// * `HostError::InvalidMemoryAccess` if memory access fails for descriptor buffers.
216    pub fn value_return(&mut self, src_value_ptr: u64) -> VMLogicResult<()> {
217        let result =
218            unsafe { self.read_guest_memory_typed::<sys::ValueReturn<'_>>(src_value_ptr)? };
219
220        let result = match result {
221            sys::ValueReturn::Ok(value) => Ok(self.read_guest_memory_slice(&value).to_vec()),
222            sys::ValueReturn::Err(value) => Err(self.read_guest_memory_slice(&value).to_vec()),
223        };
224
225        self.with_logic_mut(|logic| logic.returns = Some(result));
226
227        Ok(())
228    }
229
230    /// Adds a new log message (UTF-8 encoded string) to the execution log. The message is being
231    /// obtained from the guest memory.
232    ///
233    /// # Arguments
234    ///
235    /// * `src_log_ptr` - A pointer in guest memory to a source-`sys::Buffer` containing the log message.
236    ///
237    /// # Errors
238    ///
239    /// * `HostError::LogsOverflow` if the maximum number of logs has been reached.
240    /// * `HostError::BadUTF8` if the message is not a valid UTF-8 string.
241    /// * `HostError::InvalidMemoryAccess` if memory access fails for descriptor buffers.
242    pub fn log_utf8(&mut self, src_log_ptr: u64) -> VMLogicResult<()> {
243        let src_log_buf = unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_log_ptr)? };
244
245        let logic = self.borrow_logic();
246
247        if logic.logs.len()
248            >= usize::try_from(logic.limits.max_logs).map_err(|_| HostError::IntegerOverflow)?
249        {
250            return Err(HostError::LogsOverflow.into());
251        }
252
253        let message = self.read_guest_memory_str(&src_log_buf)?.to_owned();
254
255        self.with_logic_mut(|logic| logic.logs.push(message));
256
257        Ok(())
258    }
259
260    /// Emits a structured event that is added to the events log.
261    ///
262    /// Events are recorded and included in the final execution `Outcome`.
263    ///
264    /// # Arguments
265    ///
266    /// * `src_event_ptr` - A pointer in guest memory to a `sys::Event` struct, which
267    /// contains source-buffers for the event `kind` and `data`.
268    ///
269    /// # Errors
270    ///
271    /// * `HostError::EventKindSizeOverflow` if the event kind is too long.
272    /// * `HostError::EventDataSizeOverflow` if the event data is too large.
273    /// * `HostError::EventsOverflow` if the maximum number of events has been reached.
274    /// * `HostError::InvalidMemoryAccess` if memory access fails for descriptor buffers.
275    pub fn emit(&mut self, src_event_ptr: u64) -> VMLogicResult<()> {
276        let event = unsafe { self.read_guest_memory_typed::<sys::Event<'_>>(src_event_ptr)? };
277
278        let kind_len = event.kind().len();
279        let data_len = event.data().len();
280
281        let logic = self.borrow_logic();
282
283        if kind_len > logic.limits.max_event_kind_size {
284            return Err(HostError::EventKindSizeOverflow.into());
285        }
286
287        if data_len > logic.limits.max_event_data_size {
288            return Err(HostError::EventDataSizeOverflow.into());
289        }
290
291        if logic.events.len()
292            >= usize::try_from(logic.limits.max_events).map_err(|_| HostError::IntegerOverflow)?
293        {
294            return Err(HostError::EventsOverflow.into());
295        }
296
297        let kind = self.read_guest_memory_str(event.kind())?.to_owned();
298        let data = self.read_guest_memory_slice(event.data()).to_vec();
299
300        // Read callback handler name from thread-local storage
301        let handler = CURRENT_CALLBACK_HANDLER.with(|name| name.borrow().clone());
302
303        self.with_logic_mut(|logic| {
304            logic.events.push(Event {
305                kind,
306                data,
307                handler,
308            })
309        });
310
311        Ok(())
312    }
313
314    /// Emits an event with an optional handler name.
315    ///
316    /// This function is similar to `emit` but includes handler information.
317    /// The handler name is read from the provided memory pointer.
318    ///
319    /// # Arguments
320    ///
321    /// * `src_event_ptr` - Pointer to the event data in guest memory.
322    /// * `src_handler_ptr` - Pointer to the handler name in guest memory (can be 0 for no handler).
323    ///
324    /// # Returns
325    ///
326    /// * `Ok(())` if the event was successfully emitted.
327    ///
328    /// # Errors
329    ///
330    /// * `HostError::EventKindSizeOverflow` if the event kind is too long.
331    /// * `HostError::EventDataSizeOverflow` if the event data is too large.
332    /// * `HostError::EventsOverflow` if the maximum number of events has been reached.
333    /// * `HostError::InvalidMemoryAccess` if memory access fails for descriptor buffers.
334    pub fn emit_with_handler(
335        &mut self,
336        src_event_ptr: u64,
337        src_handler_ptr: u64,
338    ) -> VMLogicResult<()> {
339        let event = unsafe { self.read_guest_memory_typed::<sys::Event<'_>>(src_event_ptr)? };
340
341        let kind_len = event.kind().len();
342        let data_len = event.data().len();
343
344        let logic = self.borrow_logic();
345
346        if kind_len > logic.limits.max_event_kind_size {
347            return Err(HostError::EventKindSizeOverflow.into());
348        }
349
350        if data_len > logic.limits.max_event_data_size {
351            return Err(HostError::EventDataSizeOverflow.into());
352        }
353
354        if logic.events.len()
355            >= usize::try_from(logic.limits.max_events).map_err(|_| HostError::IntegerOverflow)?
356        {
357            return Err(HostError::EventsOverflow.into());
358        }
359
360        let kind = self.read_guest_memory_str(event.kind())?.to_owned();
361        let data = self.read_guest_memory_slice(event.data()).to_vec();
362
363        // Parse handler name if provided (src_handler_ptr != 0)
364        let handler = if src_handler_ptr == 0 {
365            None
366        } else {
367            // Read the handler buffer from guest memory
368            let handler_buffer =
369                unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_handler_ptr)? };
370            match self.read_guest_memory_str(&handler_buffer) {
371                Ok(handler_str) => Some(handler_str.to_owned()),
372                Err(_) => None, // If we can't read the handler, just set to None
373            }
374        };
375
376        self.with_logic_mut(|logic| {
377            logic.events.push(Event {
378                kind,
379                data,
380                handler,
381            })
382        });
383
384        Ok(())
385    }
386
387    /// Commits the execution state, providing a state root and an artifact.
388    ///
389    /// This function can only be called once per execution.
390    ///
391    /// # Arguments
392    ///
393    /// * `src_root_hash_ptr` - A pointer to a source-buffer in guest memory containing the 32-byte state root hash.
394    /// * `src_artifact_ptr` - A pointer to a source-buffer in guest memory containing a binary artifact.
395    ///
396    /// # Errors
397    ///
398    /// * `HostError::InvalidMemoryAccess` if this function is called more than once or if memory
399    /// access fails for descriptor buffers.
400    pub fn commit(&mut self, src_root_hash_ptr: u64, src_artifact_ptr: u64) -> VMLogicResult<()> {
401        let root_hash =
402            unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_root_hash_ptr)? };
403        let artifact =
404            unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_artifact_ptr)? };
405
406        let root_hash = *self.read_guest_memory_sized::<DIGEST_SIZE>(&root_hash)?;
407        let artifact = self.read_guest_memory_slice(&artifact).to_vec();
408
409        self.with_logic_mut(|logic| {
410            if logic.root_hash.is_some() {
411                return Err(HostError::InvalidMemoryAccess);
412            }
413
414            logic.root_hash = Some(root_hash);
415            logic.artifact = artifact;
416
417            Ok(())
418        })?;
419
420        Ok(())
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use wasmer::{AsStoreMut, Store};
427
428    use crate::errors::{HostError, Location};
429    use crate::logic::{
430        tests::{
431            prepare_guest_buf_descriptor, setup_vm, write_str, SimpleMockStorage, DESCRIPTOR_SIZE,
432        },
433        Cow, VMContext, VMLimits, VMLogic, VMLogicError, DIGEST_SIZE,
434    };
435
436    /// Tests the `input()`, `register_len()`, `read_register()` host functions.
437    #[test]
438    fn test_input_and_basic_registers_api() {
439        let input = vec![1u8, 2, 3];
440        let input_len = input.len() as u64;
441        let mut storage = SimpleMockStorage::new();
442        let limits = VMLimits::default();
443        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, input.clone());
444
445        {
446            let mut host = logic.host_functions(store.as_store_mut());
447            let register_id = 1u64;
448
449            // Guest: load the context data into a host-side register.
450            host.input(register_id).expect("Input call failed");
451            // Guest: verify the byte length of the host-side register's data matches the input length.
452            assert_eq!(host.register_len(register_id).unwrap(), input_len);
453
454            let buf_ptr = 100u64;
455            let data_output_ptr = 200u64;
456            // Guest: prepare the descriptor for the destination buffer so host can write there.
457            prepare_guest_buf_descriptor(&host, buf_ptr, data_output_ptr, input_len);
458
459            // Guest: read the register from the host into `buf_ptr`.
460            let res = host.read_register(register_id, buf_ptr).unwrap();
461            // Guest: assert the host successfully wrote the data from its register to our `buf_ptr`.
462            assert_eq!(res, 1);
463
464            let mut mem_buffer = vec![0u8; input_len as usize];
465            // Host: perform a priveleged read of the contents of guest's memory to verify it
466            // matches the `input`.
467            host.borrow_memory()
468                .read(data_output_ptr, &mut mem_buffer)
469                .unwrap();
470            assert_eq!(mem_buffer, input);
471        }
472    }
473
474    /// Tests the `context_id()` and `executor_id()` host functions.
475    ///
476    /// This test verifies that the guest can request and receive context and executor IDs.
477    #[test]
478    fn test_context_and_executor_id() {
479        let context_id = [3u8; DIGEST_SIZE];
480        let executor_id = [5u8; DIGEST_SIZE];
481        let mut storage = SimpleMockStorage::new();
482        let limits = VMLimits::default();
483        let context = VMContext::new(Cow::Owned(vec![]), context_id, executor_id);
484        let mut logic = VMLogic::new(&mut storage, context, &limits, None);
485
486        let mut store = Store::default();
487        let memory =
488            wasmer::Memory::new(&mut store, wasmer::MemoryType::new(1, None, false)).unwrap();
489        let _ = logic.with_memory(memory);
490        let mut host = logic.host_functions(store.as_store_mut());
491
492        let context_id_register = 1;
493        // Guest: ask the host to put the context ID into host register
494        // that has a value `context_id_register`.
495        host.context_id(context_id_register).unwrap();
496        // Very the `context_id` is correctly written into its host-side register.
497        let requested_context_id = host
498            .borrow_logic()
499            .registers
500            .get(context_id_register)
501            .unwrap();
502        assert_eq!(requested_context_id, context_id);
503
504        let executor_id_register = 2;
505        // Guest: ask the host to put the executor ID into host register
506        // that has a value `executor_id_register`.
507        host.executor_id(executor_id_register).unwrap();
508        // Verify the `executor_id` is correctly written into its host-side register.
509        let requested_executor_id = host
510            .borrow_logic()
511            .registers
512            .get(executor_id_register)
513            .unwrap();
514        assert_eq!(requested_executor_id, executor_id);
515    }
516
517    /// Tests the `value_return()` host function for both `Ok` and `Err` variants.
518    ///
519    /// This test verifies the primary mechanism for a guest to finish its execution
520    /// and return a final value to the host. It checks that both successful (`Ok`) and
521    /// unsuccessful (`Err`) return values are correctly stored in the `VMLogic` state.
522    #[test]
523    fn test_value_return() {
524        let mut storage = SimpleMockStorage::new();
525        let limits = VMLimits::default();
526        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
527        let mut host = logic.host_functions(store.as_store_mut());
528
529        // Test returning an Ok value
530        let ok_value = "this is Ok value";
531        let ok_value_ptr = 200u64;
532        // Guest: write ok
533        write_str(&host, ok_value_ptr, ok_value);
534
535        // Write a `sys::ValueReturn::Ok` enum representation (0) to memory.
536        // The value then is followed by the buffer.
537        let ok_discriminant = 0u8;
538        let ok_return_ptr = 32u64;
539        host.borrow_memory()
540            .write(ok_return_ptr, &[ok_discriminant])
541            .unwrap();
542        // Guest: prepare the descriptor for the buffer so host can access it.
543        prepare_guest_buf_descriptor(
544            &host,
545            ok_return_ptr + 8,
546            ok_value_ptr,
547            ok_value.len() as u64,
548        );
549
550        // Guest: ask host to read the return value.
551        host.value_return(ok_return_ptr).unwrap();
552        let returned_ok_value = host.borrow_logic().returns.clone().unwrap().unwrap();
553        let returned_ok_value_str = std::str::from_utf8(&returned_ok_value).unwrap();
554        // Verify the returned value matches the one from the guest.
555        assert_eq!(returned_ok_value_str, ok_value);
556
557        // Test returning an Err value
558        let err_value = "this is Err value";
559        let err_value_ptr = 400u64;
560        write_str(&host, err_value_ptr, err_value);
561
562        // Write a `sys::ValueReturn::Ok` enum representation (1) to memory.
563        // The value then is followed by the buffer.
564        let err_discriminant = 1u8;
565        let err_return_ptr = 64u64;
566        host.borrow_memory()
567            .write(err_return_ptr, &[err_discriminant])
568            .unwrap();
569        // Guest: prepare the descriptor for the buffer so host can access it.
570        prepare_guest_buf_descriptor(
571            &host,
572            err_return_ptr + 8,
573            err_value_ptr,
574            err_value.len() as u64,
575        );
576
577        // Guest: ask host to read the return value.
578        host.value_return(err_return_ptr).unwrap();
579        let returned_err_value = host.borrow_logic().returns.clone().unwrap().unwrap_err();
580        let returned_err_value_str = std::str::from_utf8(&returned_err_value).unwrap();
581        // Verify the returned value matches the one from the guest.
582        assert_eq!(returned_err_value_str, err_value);
583    }
584
585    /// Tests the `log_utf8()` host function for a successful log operation.
586    #[test]
587    fn test_log_utf8() {
588        let mut storage = SimpleMockStorage::new();
589        let limits = VMLimits::default();
590        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
591        let mut host = logic.host_functions(store.as_store_mut());
592
593        let msg = "test log";
594        let msg_ptr = 200u64;
595        // Guest: write msg to its memory.
596        write_str(&host, msg_ptr, msg);
597
598        let buf_ptr = 10u64;
599        // Guest: prepare the descriptor for the destination buffer so host can write there.
600        prepare_guest_buf_descriptor(&host, buf_ptr, msg_ptr, msg.len() as u64);
601        // Guest: ask the host to log the contents of `buf_ptr`'s descriptor.
602        host.log_utf8(buf_ptr).expect("Log failed");
603
604        // Guest: verify the host successfully logged the message
605        assert_eq!(host.borrow_logic().logs.len(), 1);
606        assert_eq!(host.borrow_logic().logs[0], "test log");
607    }
608
609    /// Tests that the `log_utf8()` host function correctly handles the log limit and properly returns
610    /// an error `HostError::LogOverflow` when the logs limit is exceeded.
611    #[test]
612    fn test_log_utf8_overflow() {
613        let mut storage = SimpleMockStorage::new();
614        let mut limits = VMLimits::default();
615        limits.max_logs = 5;
616        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
617        let mut host = logic.host_functions(store.as_store_mut());
618
619        let msg = "log";
620        let msg_ptr = 200u64;
621        // Guest: write msg to its memory.
622        write_str(&host, msg_ptr, msg);
623        let buf_ptr = 10u64;
624        // Guest: prepare the descriptor for the destination buffer so host can write there.
625        prepare_guest_buf_descriptor(&host, buf_ptr, msg_ptr, msg.len() as u64);
626
627        // Guest: ask the host to log for a max limit of logs
628        for _ in 0..limits.max_logs {
629            host.log_utf8(buf_ptr).expect("Log failed");
630        }
631
632        // Guest: verify the host successfully logged `limits.max_logs` msgs.
633        assert_eq!(host.borrow_logic().logs.len(), limits.max_logs as usize);
634        // Guest: do over-the limit log
635        let err = host.log_utf8(buf_ptr).unwrap_err();
636        // Guest: verify the host didn't log over the limit and returned an error.
637        assert_eq!(host.borrow_logic().logs.len(), limits.max_logs as usize);
638        assert!(matches!(
639            err,
640            VMLogicError::HostError(HostError::LogsOverflow)
641        ));
642    }
643
644    /// Tests that the `log_utf8()` host function correctly handles the bad UTF8 and properly returns
645    /// an error `HostError::BadUTF8` when the incorrect string is provided (the failure occurs
646    /// because of the verification happening inside the private `read_guest_memory_str` function).
647    #[test]
648    fn test_log_utf8_with_bad_utf8() {
649        let mut storage = SimpleMockStorage::new();
650        let limits = VMLimits::default();
651        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
652        let mut host = logic.host_functions(store.as_store_mut());
653
654        // Prepare invalid UTF-8 bytes in guest memory.
655        let invalid_utf8: &[u8] = &[0, 159, 146, 150];
656        let data_ptr = 200u64;
657        host.borrow_memory().write(data_ptr, invalid_utf8).unwrap();
658
659        let buf_ptr = 16u64;
660        prepare_guest_buf_descriptor(&host, buf_ptr, data_ptr, invalid_utf8.len() as u64);
661
662        // `log_utf8` calls `read_guest_memory_str` internally. We expect it to fail.
663        let err = host.log_utf8(buf_ptr).unwrap_err();
664        assert!(matches!(err, VMLogicError::HostError(HostError::BadUTF8)));
665    }
666
667    /// Tests the `panic()` host function (without a custom message).
668    #[test]
669    fn test_panic() {
670        let mut storage = SimpleMockStorage::new();
671        let limits = VMLimits::default();
672        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
673        let mut host = logic.host_functions(store.as_store_mut());
674
675        let expected_file_name = "simple_panic.rs";
676        let file_ptr = 400u64;
677        // Guest: write file name to its memory.
678        write_str(&host, file_ptr, expected_file_name);
679
680        let loc_data_ptr = 300u64;
681        // Guest: prepare the descriptor for the destination buffer so host can write there.
682        prepare_guest_buf_descriptor(
683            &host,
684            loc_data_ptr,
685            file_ptr,
686            expected_file_name.len() as u64,
687        );
688
689        let expected_line: u32 = 10;
690        let expected_column: u32 = 5;
691        let u32_size: u64 = (u32::BITS / 8).into();
692        // Host: perform a priveleged write to the contents of guest's memory with a line and column
693        // of the expected panic message. We write the `line` after the descriptor, and the `column` -
694        // after the `line`.
695        host.borrow_memory()
696            .write(
697                loc_data_ptr + DESCRIPTOR_SIZE as u64,
698                &expected_line.to_le_bytes(),
699            )
700            .unwrap();
701        host.borrow_memory()
702            .write(
703                loc_data_ptr + DESCRIPTOR_SIZE as u64 + u32_size,
704                &expected_column.to_le_bytes(),
705            )
706            .unwrap();
707
708        // Guest: ask the host to panic with the given location data.
709        let err = host.panic(loc_data_ptr).unwrap_err();
710        // Guest: assert the host panics with a "explicit panic" message, and `Location` (consisting
711        // of file name, line, and column).
712        match err {
713            VMLogicError::HostError(HostError::Panic {
714                message, location, ..
715            }) => {
716                assert_eq!(message, "explicit panic");
717                match location {
718                    Location::At { file, line, column } => {
719                        assert_eq!(file, expected_file_name);
720                        assert_eq!(line, expected_line);
721                        assert_eq!(column, expected_column);
722                    }
723                    _ => panic!("Unexpected location variant"),
724                }
725            }
726            _ => panic!("Unexpected error variant"),
727        }
728    }
729
730    /// Tests the `panic_utf8()` host function.
731    #[test]
732    fn test_panic_utf8() {
733        let mut storage = SimpleMockStorage::new();
734        let limits = VMLimits::default();
735        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
736        let mut host = logic.host_functions(store.as_store_mut());
737
738        let expected_msg = "panic message";
739        let msg_ptr = 200u64;
740        // Guest: write msg to its memory.
741        write_str(&host, msg_ptr, expected_msg);
742        let msg_buf_ptr = 16u64;
743        // Guest: prepare the descriptor for the destination buffer so host can write there.
744        prepare_guest_buf_descriptor(&host, msg_buf_ptr, msg_ptr, expected_msg.len() as u64);
745
746        let expected_file_name = "file.rs";
747        let file_ptr = 400u64;
748        // Guest: write file name to its memory.
749        write_str(&host, file_ptr, expected_file_name);
750
751        let loc_data_ptr = 300u64;
752        // Guest: prepare the descriptor for the destination buffer so host can write there.
753        prepare_guest_buf_descriptor(
754            &host,
755            loc_data_ptr,
756            file_ptr,
757            expected_file_name.len() as u64,
758        );
759
760        let expected_line: u32 = 10;
761        let expected_column: u32 = 5;
762        let u32_size: u64 = (u32::BITS / 8).into();
763        // Host: perform a priveleged write to the contents of guest's memory with a line and column
764        // of the expected panic message. We write the `line` after the descriptor, and the `column` -
765        // after the `line`.
766        host.borrow_memory()
767            .write(
768                loc_data_ptr + DESCRIPTOR_SIZE as u64,
769                &expected_line.to_le_bytes(),
770            )
771            .unwrap();
772        host.borrow_memory()
773            .write(
774                loc_data_ptr + DESCRIPTOR_SIZE as u64 + u32_size,
775                &expected_column.to_le_bytes(),
776            )
777            .unwrap();
778
779        // Guest: ask the host to panic with the given msg and location.
780        let err = host.panic_utf8(msg_buf_ptr, loc_data_ptr).unwrap_err();
781        // Guest: assert the host panics with a specified panic message, and `Location` (consisting
782        // of file name, line, and column).
783        match err {
784            VMLogicError::HostError(HostError::Panic {
785                message, location, ..
786            }) => {
787                assert_eq!(message, expected_msg);
788                match location {
789                    Location::At { file, line, column } => {
790                        assert_eq!(file, expected_file_name);
791                        assert_eq!(line, expected_line);
792                        assert_eq!(column, expected_column);
793                    }
794                    _ => panic!("Unexpected location variant"),
795                }
796            }
797            _ => panic!("Unexpected error variant"),
798        }
799    }
800
801    /// Tests the `emit()` host function for event creation and events overflow.
802    #[test]
803    fn test_emit_and_events_overflow() {
804        let mut storage = SimpleMockStorage::new();
805        let limits = VMLimits::default();
806        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
807        let mut host = logic.host_functions(store.as_store_mut());
808
809        // Prepare a valid event
810        let kind = "my-event";
811        let data = vec![1, 2, 3];
812        let kind_ptr = 200u64;
813        let data_ptr = 300u64;
814        // Guest: write msg to its memory.
815        write_str(&host, kind_ptr, kind);
816        host.borrow_memory().write(data_ptr, &data).unwrap();
817
818        // Prepare the sys::Event struct in memory.
819        let event_struct_ptr = 48u64;
820        let kind_buf_ptr = event_struct_ptr;
821        let data_buf_ptr = event_struct_ptr + DESCRIPTOR_SIZE as u64;
822        prepare_guest_buf_descriptor(&host, kind_buf_ptr, kind_ptr, kind.len() as u64);
823        prepare_guest_buf_descriptor(&host, data_buf_ptr, data_ptr, data.len() as u64);
824
825        // Guest: ask host to emit the event located at `event_struct_ptr`.
826        host.emit(event_struct_ptr).unwrap();
827        // Test successful event emission
828        assert_eq!(host.borrow_logic().events.len(), 1);
829        assert_eq!(host.borrow_logic().events[0].kind, kind);
830        assert_eq!(host.borrow_logic().events[0].data, data);
831
832        // Test events overflow
833        for _ in 1..limits.max_events {
834            host.emit(event_struct_ptr).unwrap();
835        }
836        assert_eq!(host.borrow_logic().events.len() as u64, limits.max_events);
837        // Guest: ask the host to do over the limit event emission.
838        let err = host.emit(event_struct_ptr).unwrap_err();
839        // Guest: verify the host didn't emit over the limit and returned an error.
840        assert!(matches!(
841            err,
842            VMLogicError::HostError(HostError::EventsOverflow)
843        ));
844    }
845
846    /// Tests the `commit()` host function.
847    #[test]
848    fn test_commit() {
849        let mut storage = SimpleMockStorage::new();
850        let limits = VMLimits::default();
851        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
852        let mut host = logic.host_functions(store.as_store_mut());
853
854        let root_hash = [1u8; DIGEST_SIZE];
855        let artifact = vec![1, 2, 3];
856        let root_hash_ptr = 200u64;
857        let artifact_ptr = 300u64;
858        host.borrow_memory()
859            .write(root_hash_ptr, &root_hash)
860            .unwrap();
861        host.borrow_memory().write(artifact_ptr, &artifact).unwrap();
862
863        let root_hash_buf_ptr = 16u64;
864        let artifact_buf_ptr = 32u64;
865        // Guest: prepare the descriptor for the root_hash and artifact buffers so host can access them.
866        prepare_guest_buf_descriptor(
867            &host,
868            root_hash_buf_ptr,
869            root_hash_ptr,
870            root_hash.len() as u64,
871        );
872        prepare_guest_buf_descriptor(&host, artifact_buf_ptr, artifact_ptr, artifact.len() as u64);
873
874        // Guest: ask host to commit with the given root hash and artifact.
875        host.commit(root_hash_buf_ptr, artifact_buf_ptr).unwrap();
876        // Verify the host successfully stored the root hash and artifact in the `VMLogic` state.
877        assert_eq!(host.borrow_logic().root_hash, Some(root_hash));
878        assert_eq!(host.borrow_logic().artifact, artifact);
879    }
880}