tezos_smart_rollup_host/
runtime.rs

1// SPDX-FileCopyrightText: 2022-2023 TriliTech <contact@trili.tech>
2// SPDX-FileCopyrightText: 2023 Marigold <contact@marigold.dev>
3// SPDX-FileCopyrightText: 2022-2023 Nomadic Labs <contact@nomadic-labs.com>
4//
5// SPDX-License-Identifier: MIT
6
7//! Definition of **Runtime** api that is callable from *safe* rust.
8//!
9//! Includes blanket implementation for all types implementing [SmartRollupCore].
10
11#[cfg(feature = "alloc")]
12use alloc::vec::Vec;
13use tezos_smart_rollup_core::{SmartRollupCore, PREIMAGE_HASH_SIZE};
14
15#[cfg(feature = "proto-alpha")]
16use crate::dal_parameters::RollupDalParameters;
17#[cfg(feature = "alloc")]
18use crate::input::Message;
19use crate::metadata::RollupMetadata;
20#[cfg(feature = "alloc")]
21use crate::path::{Path, RefPath};
22#[cfg(not(feature = "alloc"))]
23use crate::path::{Path, RefPath};
24#[cfg(feature = "proto-alpha")]
25use crate::DAL_PARAMETERS_SIZE;
26use crate::{Error, METADATA_SIZE};
27#[cfg(feature = "alloc")]
28use tezos_smart_rollup_core::smart_rollup_core::ReadInputMessageInfo;
29
30#[cfg(feature = "alloc")]
31use alloc::string::String;
32
33#[derive(Copy, Eq, PartialEq, Clone, Debug)]
34/// Errors that may be returned when called [Runtime] methods.
35pub enum RuntimeError {
36    /// Attempted to read from/delete a key that does not exist.
37    PathNotFound,
38    /// Attempted to get a subkey at an out-of-bounds index.
39    StoreListIndexOutOfBounds,
40    /// Errors returned by the host functions
41    HostErr(Error),
42    /// Failed parsing
43    DecodingError,
44}
45
46// TODO: use `core:error::Error` once `error_in_core` stabilised.
47//       <https://github.com/rust-lang/rust/issues/103765>
48#[cfg(feature = "std")]
49impl std::error::Error for RuntimeError {
50    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
51        None
52    }
53}
54
55impl core::fmt::Display for RuntimeError {
56    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57        match self {
58            Self::PathNotFound => write!(f, "RuntimeError::PathNotFound"),
59            Self::HostErr(e) => e.fmt(f),
60            Self::DecodingError => write!(f, "RuntimeError::DecodingError"),
61            Self::StoreListIndexOutOfBounds => {
62                write!(f, "RuntimeError::StoreListIndexOutOfBounds")
63            }
64        }
65    }
66}
67
68/// Returned by [`Runtime::store_has`] - specifies whether a path has a value or is a prefix.
69#[derive(Debug, Copy, Clone, Eq, PartialEq)]
70pub enum ValueType {
71    /// The path has a value, but is not a prefix to further values.
72    Value,
73    /// The path is a prefix to further values, but has no value.
74    Subtree,
75    /// The path has a value, and is a prefix to further values.
76    ValueWithSubtree,
77}
78
79/// Safe wrappers for host capabilities.
80///
81/// **NB**:
82/// - methods that take `&self` will not cause changes to the runtime state.
83/// - methods taking `&mut self` are expected to cause changes - either to *input*,
84///   *output* or *durable storage*.
85pub trait Runtime {
86    /// Write contents of the given slice to output.
87    fn write_output(&mut self, from: &[u8]) -> Result<(), RuntimeError>;
88
89    /// Write message to debug log.
90    fn write_debug(&self, msg: &str);
91
92    /// Read the next input from the global inbox.
93    ///
94    /// Returns `None` if no message was available. This happens when the kernel has
95    /// finished reading the inbox at the current level.
96    ///
97    /// The kernel will need to yield to the next level to recieve more input.
98    #[cfg(feature = "alloc")]
99    fn read_input(&mut self) -> Result<Option<Message>, RuntimeError>;
100
101    /// Returns whether a given path exists in storage.
102    fn store_has<T: Path>(&self, path: &T) -> Result<Option<ValueType>, RuntimeError>;
103
104    /// Read up to `max_bytes` from the given path in storage, starting `from_offset`.
105    #[cfg(feature = "alloc")]
106    fn store_read<T: Path>(
107        &self,
108        path: &T,
109        from_offset: usize,
110        max_bytes: usize,
111    ) -> Result<Vec<u8>, RuntimeError>;
112
113    /// Read up to `buffer.len()` from the given path in storage.
114    ///
115    /// Value is read starting `from_offset`.
116    ///
117    /// The total bytes read is returned.
118    /// If the returned value `n` is `n < buffer.len()`, then only the first `n`
119    /// bytes of the buffer will have been written too.
120    fn store_read_slice<T: Path>(
121        &self,
122        path: &T,
123        from_offset: usize,
124        buffer: &mut [u8],
125    ) -> Result<usize, RuntimeError>;
126
127    /// Read an entire value from the given path in storage.
128    #[cfg(feature = "alloc")]
129    fn store_read_all(&self, path: &impl Path) -> Result<Vec<u8>, RuntimeError>;
130
131    /// Write the bytes given by `src` to storage at `path`, starting `at_offset`.
132    ///
133    /// Contrary to `store_write_all`, this does not replace the value (if any)
134    /// previously stored under `path`. This allows for splicing/patching values
135    /// directly in storage, without having to read the entire value from disk.
136    fn store_write<T: Path>(
137        &mut self,
138        path: &T,
139        src: &[u8],
140        at_offset: usize,
141    ) -> Result<(), RuntimeError>;
142
143    /// Write the bytes given by `src` to storage at `path`.
144    ///
145    /// Contrary to `store_write`, this replaces the value (if any) that
146    /// was previously stored at `path`.
147    fn store_write_all<T: Path>(
148        &mut self,
149        path: &T,
150        src: &[u8],
151    ) -> Result<(), RuntimeError>;
152
153    /// Delete `path` from storage.
154    fn store_delete<T: Path>(&mut self, path: &T) -> Result<(), RuntimeError>;
155
156    /// Delete value under `path` from storage.
157    fn store_delete_value<T: Path>(&mut self, path: &T) -> Result<(), RuntimeError>;
158
159    /// Count the number of subkeys under `prefix`.
160    ///
161    /// See [SmartRollupCore::store_list_size].
162    fn store_count_subkeys<T: Path>(&self, prefix: &T) -> Result<u64, RuntimeError>;
163
164    /// Move one part of durable storage to a different location
165    ///
166    /// See [SmartRollupCore::store_move].
167    fn store_move(
168        &mut self,
169        from_path: &impl Path,
170        to_path: &impl Path,
171    ) -> Result<(), RuntimeError>;
172
173    /// Copy one part of durable storage to a different location
174    ///
175    /// See [SmartRollupCore::store_copy].
176    fn store_copy(
177        &mut self,
178        from_path: &impl Path,
179        to_path: &impl Path,
180    ) -> Result<(), RuntimeError>;
181
182    /// Reveal pre-image from a hash of size `PREIMAGE_HASH_SIZE` in bytes.
183    ///
184    /// N.B. in future, multiple hashing schemes will be supported, but for
185    /// now the kernels only support hashes of type `Reveal_hash`, which is
186    /// a 32-byte Blake2b hash with a prefix-byte of `0`.
187    fn reveal_preimage(
188        &self,
189        hash: &[u8; PREIMAGE_HASH_SIZE],
190        destination: &mut [u8],
191    ) -> Result<usize, RuntimeError>;
192
193    /// Reveal a DAL page.
194    #[cfg(all(feature = "alloc", feature = "proto-alpha"))]
195    fn reveal_dal_page(
196        &self,
197        published_level: i32,
198        slot_index: u8,
199        page_index: i16,
200        destination: &mut [u8],
201    ) -> Result<usize, RuntimeError>;
202
203    /// Reveal the DAL parameters.
204    #[cfg(feature = "proto-alpha")]
205    fn reveal_dal_parameters(&self) -> RollupDalParameters;
206
207    /// Return the size of value stored at `path`
208    fn store_value_size(&self, path: &impl Path) -> Result<usize, RuntimeError>;
209
210    /// Mark the kernel for reboot.
211    ///
212    /// If the kernel is marked for reboot, it will continue
213    /// reading inbox messages for the current level next time `kernel_run` runs.
214    /// If the inbox contains no more messages, the kernel will still continue at
215    /// the current inbox level until it is no longer marked for reboot.
216    ///
217    /// If the kernel is _not_ marked for reboot, it will skip the rest of the inbox
218    /// for the current level and _yield_. It will then continue at the next inbox
219    /// level.
220    ///
221    /// The kernel is given a maximum number of reboots per level. The number of reboots remaining
222    /// is written to `/readonly/kernel/env/reboot_counter` (little endian i32).
223    ///
224    /// If the kernel exceeds this, it is forced to yield to the next level (and a flag is set at
225    /// `/readonly/kernel/env/too_many_reboot` to indicate this happened.
226    fn mark_for_reboot(&mut self) -> Result<(), RuntimeError>;
227
228    /// Returns [RollupMetadata]
229    fn reveal_metadata(&self) -> RollupMetadata;
230
231    /// True if the last kernel run was aborted.
232    fn last_run_aborted(&self) -> Result<bool, RuntimeError>;
233
234    /// True if the kernel failed to upgrade.
235    fn upgrade_failed(&self) -> Result<bool, RuntimeError>;
236
237    /// True if the kernel rebooted too many times.
238    fn restart_forced(&self) -> Result<bool, RuntimeError>;
239
240    /// The number of reboot left for the kernel.
241    fn reboot_left(&self) -> Result<u32, RuntimeError>;
242
243    /// The runtime_version the kernel is using.
244    #[cfg(feature = "alloc")]
245    fn runtime_version(&self) -> Result<String, RuntimeError>;
246}
247
248const REBOOT_PATH: RefPath = RefPath::assert_from(b"/kernel/env/reboot");
249
250impl<Host> Runtime for Host
251where
252    Host: SmartRollupCore,
253{
254    fn write_output(&mut self, output: &[u8]) -> Result<(), RuntimeError> {
255        let result_code =
256            unsafe { SmartRollupCore::write_output(self, output.as_ptr(), output.len()) };
257
258        match Error::wrap(result_code) {
259            Ok(_) => Ok(()),
260            Err(e) => Err(RuntimeError::HostErr(e)),
261        }
262    }
263
264    fn write_debug(&self, msg: &str) {
265        unsafe { SmartRollupCore::write_debug(self, msg.as_ptr(), msg.len()) };
266    }
267
268    #[cfg(feature = "alloc")]
269    fn read_input(&mut self) -> Result<Option<Message>, RuntimeError> {
270        use core::mem::MaybeUninit;
271        use tezos_smart_rollup_core::MAX_INPUT_MESSAGE_SIZE;
272
273        let mut buffer = Vec::with_capacity(MAX_INPUT_MESSAGE_SIZE);
274
275        let mut message_info = MaybeUninit::<ReadInputMessageInfo>::uninit();
276
277        let bytes_read = unsafe {
278            SmartRollupCore::read_input(
279                self,
280                message_info.as_mut_ptr(),
281                buffer.as_mut_ptr(),
282                MAX_INPUT_MESSAGE_SIZE,
283            )
284        };
285
286        let bytes_read = match Error::wrap(bytes_read) {
287            Ok(0) => return Ok(None),
288            Ok(size) => size,
289            Err(e) => return Err(RuntimeError::HostErr(e)),
290        };
291
292        let ReadInputMessageInfo { level, id } = unsafe {
293            buffer.set_len(bytes_read);
294            message_info.assume_init()
295        };
296
297        // level & id are guaranteed to be positive
298        let input = Message::new(level as u32, id as u32, buffer);
299
300        Ok(Some(input))
301    }
302
303    fn store_has<T: Path>(&self, path: &T) -> Result<Option<ValueType>, RuntimeError> {
304        let result =
305            unsafe { SmartRollupCore::store_has(self, path.as_ptr(), path.size()) };
306
307        let value_type = Error::wrap(result).map_err(RuntimeError::HostErr)? as i32;
308
309        match value_type {
310            tezos_smart_rollup_core::VALUE_TYPE_NONE => Ok(None),
311            tezos_smart_rollup_core::VALUE_TYPE_VALUE => Ok(Some(ValueType::Value)),
312            tezos_smart_rollup_core::VALUE_TYPE_SUBTREE => Ok(Some(ValueType::Subtree)),
313            tezos_smart_rollup_core::VALUE_TYPE_VALUE_WITH_SUBTREE => {
314                Ok(Some(ValueType::ValueWithSubtree))
315            }
316            _ => Err(RuntimeError::HostErr(Error::GenericInvalidAccess)),
317        }
318    }
319
320    #[cfg(feature = "alloc")]
321    fn store_read<T: Path>(
322        &self,
323        path: &T,
324        from_offset: usize,
325        max_bytes: usize,
326    ) -> Result<Vec<u8>, RuntimeError> {
327        use tezos_smart_rollup_core::MAX_FILE_CHUNK_SIZE;
328
329        check_path_has_value(self, path)?;
330
331        let mut buffer = Vec::with_capacity(max_bytes);
332
333        unsafe {
334            #![allow(clippy::uninit_vec)]
335            // SAFETY:
336            // Setting length here gives access, from safe rust, to
337            // uninitialised bytes.
338            //
339            // This is safe as these bytes will not be read by `store_read_slice`.
340            // Rather, store_read_slice writes to the (part) of the slice, and
341            // returns the total bytes written.
342            buffer.set_len(usize::min(MAX_FILE_CHUNK_SIZE, max_bytes));
343
344            let size = self.store_read_slice(path, from_offset, &mut buffer)?;
345
346            // SAFETY:
347            // We ensure that we set the length of the vector to the
348            // total bytes written - ie so that only the bytes that are now
349            // initialised, are accessible.
350            buffer.set_len(size);
351        }
352
353        Ok(buffer)
354    }
355
356    fn store_read_slice<T: Path>(
357        &self,
358        path: &T,
359        from_offset: usize,
360        buffer: &mut [u8],
361    ) -> Result<usize, RuntimeError> {
362        let result = unsafe {
363            self.store_read(
364                path.as_ptr(),
365                path.size(),
366                from_offset,
367                buffer.as_mut_ptr(),
368                buffer.len(),
369            )
370        };
371
372        match Error::wrap(result) {
373            Ok(i) => Ok(i),
374            Err(e) => Err(RuntimeError::HostErr(e)),
375        }
376    }
377
378    #[cfg(feature = "alloc")]
379    fn store_read_all(&self, path: &impl Path) -> Result<Vec<u8>, RuntimeError> {
380        use tezos_smart_rollup_core::MAX_FILE_CHUNK_SIZE;
381
382        let length = Runtime::store_value_size(self, path)?;
383        let mut buffer: Vec<u8> = Vec::with_capacity(length);
384
385        // SAFETY: the algorithm goes as follows:
386        //
387        // A vector of capacity [length] has been previously allocated, of the exact
388        // size of the value in the storage. Only `MAX_FILE_CHUNK_SIZE` bytes
389        // can be read at once, as such the values must be read as chunks.
390        while buffer.len() < length {
391            let offset = buffer.len();
392            let max_length = usize::min(MAX_FILE_CHUNK_SIZE, length - offset);
393            // At each loop, `store_read` takes a slice starting at the next
394            // offset in the value, which is the current length of the buffer.
395            // The slicing is always valid since it starts at the buffer's
396            // length, and always below the vector capacity by construction of
397            // the looping condition.
398            let slice = &mut buffer[offset..];
399            unsafe {
400                // SAFETY: `store_read` expects a pointer to write the value,
401                // which is given by the slicing in the buffer.
402                let chunk_size = self.store_read(
403                    path.as_ptr(),
404                    path.size(),
405                    offset,
406                    slice.as_mut_ptr(),
407                    max_length,
408                );
409                let chunk_size =
410                    Error::wrap(chunk_size).map_err(RuntimeError::HostErr)?;
411                // SAFETY: at the end of the loop, the buffer's length is
412                // incremented with the size of the value read, since
413                // `store_read` won't update it (as it only deals with
414                // pointers). This makes the slicing at the next loop valid,
415                // since it starts from the buffer length, and is less than the capacity.
416                buffer.set_len(offset + chunk_size)
417            };
418        }
419        Ok(buffer)
420    }
421
422    fn store_write<T: Path>(
423        &mut self,
424        path: &T,
425        mut src: &[u8],
426        mut at_offset: usize,
427    ) -> Result<(), RuntimeError> {
428        use tezos_smart_rollup_core::MAX_FILE_CHUNK_SIZE;
429
430        let write = |bytes: &[u8], offset| {
431            let result_code = unsafe {
432                SmartRollupCore::store_write(
433                    self,
434                    path.as_ptr(),
435                    path.size(),
436                    offset,
437                    bytes.as_ptr(),
438                    bytes.len(),
439                )
440            };
441            match Error::wrap(result_code) {
442                Ok(_) => Ok(()),
443                Err(e) => Err(RuntimeError::HostErr(e)),
444            }
445        };
446
447        if src.len() <= MAX_FILE_CHUNK_SIZE {
448            return write(src, at_offset);
449        }
450
451        while src.len() > MAX_FILE_CHUNK_SIZE {
452            write(&src[..MAX_FILE_CHUNK_SIZE], at_offset)?;
453            at_offset += MAX_FILE_CHUNK_SIZE;
454            src = &src[MAX_FILE_CHUNK_SIZE..];
455        }
456
457        // Don't do final extra write of zero bytes
458        if !src.is_empty() {
459            write(src, at_offset)
460        } else {
461            Ok(())
462        }
463    }
464
465    fn store_write_all<T: Path>(
466        &mut self,
467        path: &T,
468        value: &[u8],
469    ) -> Result<(), RuntimeError> {
470        // Removing the value first prevents some cases where a value already
471        // exists in the storage at the given path and is bigger than the one
472        // being written. Keeping the old value would lead to a situation where
473        // ```
474        // let () = host.store_write_all(path, value)?;
475        // let value_read = host.store_read_all(path)?;
476        // value != value_read
477        // ```
478        // due to remaining bytes from the previous value.
479        //
480        // `store_delete_value` always succeeds even if the path does not
481        // exists, hence there is no need to check the value exists beforehand.
482        Runtime::store_delete_value(self, path)?;
483
484        Runtime::store_write(self, path, value, 0)
485    }
486
487    fn store_delete<T: Path>(&mut self, path: &T) -> Result<(), RuntimeError> {
488        check_path_exists(self, path)?;
489
490        let res =
491            unsafe { SmartRollupCore::store_delete(self, path.as_ptr(), path.size()) };
492        match Error::wrap(res) {
493            Ok(_) => Ok(()),
494            Err(e) => Err(RuntimeError::HostErr(e)),
495        }
496    }
497
498    fn store_delete_value<T: Path>(&mut self, path: &T) -> Result<(), RuntimeError> {
499        let res = unsafe {
500            SmartRollupCore::store_delete_value(self, path.as_ptr(), path.size())
501        };
502        match Error::wrap(res) {
503            Ok(_) => Ok(()),
504            Err(e) => Err(RuntimeError::HostErr(e)),
505        }
506    }
507
508    fn store_count_subkeys<T: Path>(&self, path: &T) -> Result<u64, RuntimeError> {
509        let count =
510            unsafe { SmartRollupCore::store_list_size(self, path.as_ptr(), path.size()) };
511
512        if count >= 0 {
513            Ok(count as u64)
514        } else {
515            Err(RuntimeError::HostErr(count.into()))
516        }
517    }
518
519    fn store_move(
520        &mut self,
521        from_path: &impl Path,
522        to_path: &impl Path,
523    ) -> Result<(), RuntimeError> {
524        check_path_exists(self, from_path)?;
525
526        let res = unsafe {
527            SmartRollupCore::store_move(
528                self,
529                from_path.as_ptr(),
530                from_path.size(),
531                to_path.as_ptr(),
532                to_path.size(),
533            )
534        };
535        match Error::wrap(res) {
536            Ok(_) => Ok(()),
537            Err(e) => Err(RuntimeError::HostErr(e)),
538        }
539    }
540
541    fn store_copy(
542        &mut self,
543        from_path: &impl Path,
544        to_path: &impl Path,
545    ) -> Result<(), RuntimeError> {
546        check_path_exists(self, from_path)?;
547
548        let res = unsafe {
549            SmartRollupCore::store_copy(
550                self,
551                from_path.as_ptr(),
552                from_path.size(),
553                to_path.as_ptr(),
554                to_path.size(),
555            )
556        };
557        match Error::wrap(res) {
558            Ok(_) => Ok(()),
559            Err(e) => Err(RuntimeError::HostErr(e)),
560        }
561    }
562
563    fn reveal_preimage(
564        &self,
565        hash: &[u8; PREIMAGE_HASH_SIZE],
566        buffer: &mut [u8],
567    ) -> Result<usize, RuntimeError> {
568        let res = unsafe {
569            SmartRollupCore::reveal_preimage(
570                self,
571                hash.as_ptr(),
572                PREIMAGE_HASH_SIZE,
573                buffer.as_mut_ptr(),
574                buffer.len(),
575            )
576        };
577        match Error::wrap(res) {
578            Ok(size) => Ok(size),
579            Err(e) => Err(RuntimeError::HostErr(e)),
580        }
581    }
582
583    fn reveal_metadata(&self) -> RollupMetadata {
584        let mut destination = [0u8; METADATA_SIZE];
585        let res = unsafe {
586            SmartRollupCore::reveal_metadata(
587                self,
588                destination.as_mut_ptr(),
589                destination.len(),
590            )
591        };
592
593        // Revealing metadata should always succeed
594        debug_assert!(res == METADATA_SIZE as i32, "SDK_ERROR: Revealing metadata always succeeds. \
595                                             If you see this message, please report it to the \
596                                             SDK developers at https://gitlab.com/tezos/tezos");
597
598        RollupMetadata::from(destination)
599    }
600
601    #[cfg(all(feature = "alloc", feature = "proto-alpha"))]
602    fn reveal_dal_page(
603        &self,
604        published_level: i32,
605        slot_index: u8,
606        page_index: i16,
607        destination: &mut [u8],
608    ) -> Result<usize, RuntimeError> {
609        // This will match the encoding declared for a DAL page in the Tezos protocol.
610        let payload: &[u8] = &[
611            &[2u8], // tag
612            published_level.to_be_bytes().as_ref(),
613            &[slot_index],
614            page_index.to_be_bytes().as_ref(),
615        ]
616        .concat();
617
618        let res = unsafe {
619            SmartRollupCore::reveal(
620                self,
621                payload.as_ptr(),
622                payload.len(),
623                destination.as_mut_ptr(),
624                destination.len(),
625            )
626        };
627
628        match Error::wrap(res) {
629            Ok(size) => Ok(size),
630            Err(e) => Err(RuntimeError::HostErr(e)),
631        }
632    }
633
634    #[cfg(feature = "proto-alpha")]
635    fn reveal_dal_parameters(&self) -> RollupDalParameters {
636        let mut destination = [0u8; DAL_PARAMETERS_SIZE];
637        // This will match the encoding declared for revealing DAL parameters in the Tezos protocol.
638        let payload: &[u8] = &[3u8]; // tag
639
640        let bytes_read = unsafe {
641            SmartRollupCore::reveal(
642                self,
643                payload.as_ptr(),
644                payload.len(),
645                destination.as_mut_ptr(),
646                destination.len(),
647            )
648        };
649
650        debug_assert!(bytes_read == DAL_PARAMETERS_SIZE as i32, "SDK_ERROR: Revealing DAL parameters should always succeed. \
651                                             If you see this message, please report it to the \
652                                             SDK developers at https://gitlab.com/tezos/tezos");
653
654        match RollupDalParameters::try_from(destination) {
655            Ok(dal_parameters) => dal_parameters,
656            Err(_) => {
657                debug_assert!(
658                    false,
659                    "SDK_ERROR: Decoding DAL parameters should always succeed. \
660                     If you see this message, please report it to the \
661                     SDK developers at https://gitlab.com/tezos/tezos"
662                );
663                unreachable!()
664            }
665        }
666    }
667
668    fn store_value_size(&self, path: &impl Path) -> Result<usize, RuntimeError> {
669        check_path_exists(self, path)?;
670        let res = unsafe {
671            SmartRollupCore::store_value_size(self, path.as_ptr(), path.size())
672        };
673        match Error::wrap(res) {
674            Ok(size) => Ok(size),
675            Err(e) => Err(RuntimeError::HostErr(e)),
676        }
677    }
678
679    fn mark_for_reboot(&mut self) -> Result<(), RuntimeError> {
680        self.store_write(&REBOOT_PATH, &[0_u8], 0)
681    }
682
683    fn last_run_aborted(&self) -> Result<bool, RuntimeError> {
684        const PATH_STUCK_FLAG: RefPath =
685            RefPath::assert_from_readonly(b"/readonly/kernel/env/stuck");
686        let last_run_aborted = Runtime::store_has(self, &PATH_STUCK_FLAG)?.is_some();
687        Ok(last_run_aborted)
688    }
689
690    fn upgrade_failed(&self) -> Result<bool, RuntimeError> {
691        const PATH_UPGRADE_ERROR_FLAG: RefPath =
692            RefPath::assert_from_readonly(b"/readonly/kernel/env/upgrade_error");
693        let upgrade_failed =
694            Runtime::store_has(self, &PATH_UPGRADE_ERROR_FLAG)?.is_some();
695        Ok(upgrade_failed)
696    }
697
698    fn restart_forced(&self) -> Result<bool, RuntimeError> {
699        const PATH_TOO_MANY_REBOOT_FLAG: RefPath =
700            RefPath::assert_from_readonly(b"/readonly/kernel/env/too_many_reboot");
701        let restart_forced =
702            Runtime::store_has(self, &PATH_TOO_MANY_REBOOT_FLAG)?.is_some();
703        Ok(restart_forced)
704    }
705
706    fn reboot_left(&self) -> Result<u32, RuntimeError> {
707        const PATH_REBOOT_COUNTER: RefPath =
708            RefPath::assert_from_readonly(b"/readonly/kernel/env/reboot_counter");
709        const SIZE: usize = core::mem::size_of::<i32>();
710
711        let mut bytes: [u8; SIZE] = [0; SIZE];
712        self.store_read_slice(&PATH_REBOOT_COUNTER, 0, &mut bytes)?;
713
714        let counter = u32::from_le_bytes(bytes);
715        Ok(counter)
716    }
717
718    #[cfg(feature = "alloc")]
719    fn runtime_version(&self) -> Result<String, RuntimeError> {
720        const PATH_VERSION: RefPath =
721            RefPath::assert_from_readonly(b"/readonly/wasm_version");
722        let bytes = Runtime::store_read(self, &PATH_VERSION, 0, 9)?;
723        // SAFETY: This storage can only contains valid version string which are utf8 safe.
724        let version = unsafe { alloc::string::String::from_utf8_unchecked(bytes) };
725        Ok(version)
726    }
727}
728
729#[cfg(feature = "alloc")]
730fn check_path_has_value<T: Path>(
731    runtime: &impl Runtime,
732    path: &T,
733) -> Result<(), RuntimeError> {
734    if let Ok(Some(ValueType::Value | ValueType::ValueWithSubtree)) =
735        runtime.store_has(path)
736    {
737        Ok(())
738    } else {
739        Err(RuntimeError::PathNotFound)
740    }
741}
742
743fn check_path_exists<T: Path>(
744    runtime: &impl Runtime,
745    path: &T,
746) -> Result<(), RuntimeError> {
747    if let Ok(Some(_)) = runtime.store_has(path) {
748        Ok(())
749    } else {
750        Err(RuntimeError::PathNotFound)
751    }
752}
753
754#[cfg(test)]
755mod tests {
756    use super::{Runtime, RuntimeError, PREIMAGE_HASH_SIZE};
757    #[cfg(feature = "proto-alpha")]
758    use crate::{dal_parameters::RollupDalParameters, DAL_PARAMETERS_SIZE};
759    use crate::{
760        input::Message,
761        metadata::RollupMetadata,
762        path::{OwnedPath, Path, RefPath},
763        Error, METADATA_SIZE,
764    };
765    use std::slice::{from_raw_parts, from_raw_parts_mut};
766    use test_helpers::*;
767    use tezos_smart_rollup_core::{
768        smart_rollup_core::MockSmartRollupCore, MAX_FILE_CHUNK_SIZE,
769        MAX_INPUT_MESSAGE_SIZE, MAX_OUTPUT_SIZE,
770    };
771
772    const READ_SIZE: usize = 80;
773
774    #[test]
775    fn given_output_written_then_ok() {
776        // Arrange
777        let mut mock = MockSmartRollupCore::new();
778        let output = "just a bit of output we want to write";
779
780        mock.expect_write_output()
781            .withf(|ptr, len| {
782                let slice = unsafe { from_raw_parts(*ptr, *len) };
783
784                output.as_bytes() == slice
785            })
786            .return_const(0);
787
788        // Act
789        let result = mock.write_output(output.as_bytes());
790
791        // Assert
792        assert_eq!(Ok(()), result);
793    }
794
795    #[test]
796    fn given_output_too_large_then_err() {
797        // Arrange
798        let mut mock = MockSmartRollupCore::new();
799
800        let output = [b'a'; MAX_OUTPUT_SIZE + 1];
801
802        mock.expect_write_output().return_once(|ptr, len| {
803            let slice = unsafe { from_raw_parts(ptr, len) };
804
805            assert!(slice.iter().all(|b| b == &b'a'));
806            assert_eq!(MAX_OUTPUT_SIZE + 1, slice.len());
807
808            Error::InputOutputTooLarge.code()
809        });
810
811        // Act
812        let result = mock.write_output(output.as_slice());
813
814        // Assert
815        assert_eq!(
816            Err(RuntimeError::HostErr(Error::InputOutputTooLarge)),
817            result
818        );
819    }
820
821    #[test]
822    fn read_input_returns_none_when_nothing_read() {
823        // Arrange
824        let mut mock = MockSmartRollupCore::new();
825        mock.expect_read_input().return_const(0_i32);
826
827        // Act
828        let outcome = mock.read_input();
829
830        // Assert
831        assert_eq!(Ok(None), outcome);
832    }
833
834    #[test]
835    fn read_message_input_with_size_max_bytes() {
836        // Arrange
837        let level = 5;
838        let id = 12908;
839        let byte = b'?';
840        const FRACTION: usize = 1;
841
842        let mut mock = read_input_with(level, id, byte, FRACTION);
843
844        // Act
845        let outcome = mock.read_input();
846
847        // Assert
848        let expected = Message::new(
849            level,
850            id,
851            [byte; MAX_INPUT_MESSAGE_SIZE / FRACTION].to_vec(),
852        );
853
854        assert_eq!(Ok(Some(expected)), outcome);
855    }
856
857    #[test]
858    fn store_has_existing_return_true() {
859        // Arrange
860        let mut mock = MockSmartRollupCore::new();
861        let existing_path = RefPath::assert_from("/an/Existing/path".as_bytes());
862
863        mock.expect_store_has()
864            .withf(move |ptr, size| {
865                let bytes = unsafe { from_raw_parts(*ptr, *size) };
866                existing_path.as_bytes() == bytes
867            })
868            .return_const(tezos_smart_rollup_core::VALUE_TYPE_VALUE);
869
870        // Act
871        let result = mock.store_has(&existing_path);
872
873        assert!(matches!(result, Ok(Some(_))));
874    }
875
876    fn mock_path_not_existing(path_bytes: Vec<u8>) -> MockSmartRollupCore {
877        let mut mock = MockSmartRollupCore::new();
878
879        mock.expect_store_has()
880            .withf(move |ptr, size| {
881                let bytes = unsafe { from_raw_parts(*ptr, *size) };
882                path_bytes == bytes
883            })
884            .return_const(tezos_smart_rollup_core::VALUE_TYPE_NONE);
885
886        mock
887    }
888
889    #[test]
890    fn store_has_not_existing_returns_false() {
891        // Arrange
892        let path_bytes = String::from("/does/not.exist").into_bytes();
893        let non_existent_path: OwnedPath = RefPath::assert_from(&path_bytes).into();
894
895        let mock = mock_path_not_existing(path_bytes);
896
897        // Act
898        let result = mock.store_has(&non_existent_path);
899
900        // Assert
901        assert!(matches!(result, Ok(None)));
902    }
903
904    #[test]
905    fn store_read_max_bytes() {
906        // Arrange
907        const FRACTION: usize = 1;
908        const PATH: RefPath<'static> = RefPath::assert_from("/a/simple/path".as_bytes());
909        const OFFSET: usize = 5;
910
911        let mut mock = mock_path_exists(PATH.as_bytes());
912        mock.expect_store_read()
913            .withf(|path_ptr, path_size, from_offset, _, max_bytes| {
914                let slice = unsafe { from_raw_parts(*path_ptr, *path_size) };
915
916                READ_SIZE == *max_bytes
917                    && PATH.as_bytes() == slice
918                    && OFFSET == *from_offset
919            })
920            .return_once(|_, _, _, buf_ptr, _| {
921                let stored_bytes = [b'2'; READ_SIZE / FRACTION];
922                let buffer = unsafe { from_raw_parts_mut(buf_ptr, READ_SIZE / FRACTION) };
923                buffer.copy_from_slice(&stored_bytes);
924                (READ_SIZE / FRACTION).try_into().unwrap()
925            });
926
927        // Act
928        let result = mock.store_read(&PATH, OFFSET, READ_SIZE);
929
930        // Assert
931        let expected = std::iter::repeat(b'2').take(READ_SIZE / FRACTION).collect();
932
933        assert_eq!(Ok(expected), result);
934    }
935
936    #[test]
937    fn store_read_lt_max_bytes() {
938        // Arrange
939        const FRACTION: usize = 5;
940        const PATH: RefPath<'static> = RefPath::assert_from("/a/simple/path".as_bytes());
941        const OFFSET: usize = 10;
942
943        let mut mock = mock_path_exists(PATH.as_bytes());
944        mock.expect_store_read()
945            .withf(|path_ptr, path_size, from_offset, _, max_bytes| {
946                let slice = unsafe { from_raw_parts(*path_ptr, *path_size) };
947
948                READ_SIZE == *max_bytes
949                    && PATH.as_bytes() == slice
950                    && OFFSET == *from_offset
951            })
952            .return_once(|_, _, _, buf_ptr, _| {
953                let stored_bytes = [b'Z'; READ_SIZE / FRACTION];
954                let buffer = unsafe { from_raw_parts_mut(buf_ptr, READ_SIZE / FRACTION) };
955                buffer.copy_from_slice(&stored_bytes);
956                (READ_SIZE / FRACTION).try_into().unwrap()
957            });
958
959        // Act
960        let result = mock.store_read(&PATH, OFFSET, READ_SIZE);
961
962        // Assert
963        let expected = std::iter::repeat(b'Z').take(READ_SIZE / FRACTION).collect();
964
965        assert_eq!(Ok(expected), result);
966    }
967
968    #[test]
969    fn store_read_path_not_found() {
970        // Arrange
971        let bytes = "/a/2nd/PATH.which/doesnt/exist".as_bytes().to_vec();
972        let path: OwnedPath = RefPath::assert_from(&bytes).into();
973        let offset = 25;
974
975        let mock = mock_path_not_existing(bytes);
976
977        // Act
978        let result = mock.store_read(&path, offset, READ_SIZE);
979
980        // Assert
981        assert_eq!(Err(RuntimeError::PathNotFound), result);
982    }
983
984    #[test]
985    fn store_read_all_above_max_file_chunk_size() {
986        // Arrange
987
988        // The value read is formed of 3 chunks, two of the max chunk value and
989        // the last one being less than the max size.
990        const PATH: RefPath<'static> = RefPath::assert_from("/a/simple/path".as_bytes());
991        const VALUE_FIRST_CHUNK: [u8; MAX_FILE_CHUNK_SIZE] = [b'a'; MAX_FILE_CHUNK_SIZE];
992        const VALUE_SECOND_CHUNK: [u8; MAX_FILE_CHUNK_SIZE] = [b'b'; MAX_FILE_CHUNK_SIZE];
993        const VALUE_LAST_CHUNK: [u8; MAX_FILE_CHUNK_SIZE / 2] =
994            [b'c'; MAX_FILE_CHUNK_SIZE / 2];
995        const VALUE_SIZE: usize =
996            VALUE_FIRST_CHUNK.len() + VALUE_SECOND_CHUNK.len() + VALUE_LAST_CHUNK.len();
997
998        let mut mock = mock_path_exists(PATH.as_bytes());
999        // Check that the path is the one given to `store_read_all` and the
1000        // offset is always a multiple of `MAX_FILE_CHUNK_SIZE`.
1001        mock.expect_store_read()
1002            .withf(|path_ptr, path_size, offset, _, max_bytes| {
1003                let slice = unsafe { from_raw_parts(*path_ptr, *path_size) };
1004                let last_offset = MAX_FILE_CHUNK_SIZE * 2;
1005                let expected_max_bytes = if offset == &last_offset {
1006                    VALUE_LAST_CHUNK.len()
1007                } else {
1008                    MAX_FILE_CHUNK_SIZE
1009                };
1010
1011                (offset % MAX_FILE_CHUNK_SIZE) == 0
1012                    && &expected_max_bytes == max_bytes
1013                    && PATH.as_bytes() == slice
1014            })
1015            // Returns the expected chunks, as we know the offsets are consistent.
1016            .returning(|_, _, offset, buf_ptr, _| {
1017                let chunk = if offset == 0 {
1018                    VALUE_FIRST_CHUNK.to_vec()
1019                } else if offset == MAX_FILE_CHUNK_SIZE {
1020                    VALUE_SECOND_CHUNK.to_vec()
1021                } else {
1022                    VALUE_LAST_CHUNK.to_vec()
1023                };
1024                let buffer = unsafe { from_raw_parts_mut(buf_ptr, chunk.len()) };
1025                buffer.copy_from_slice(&chunk);
1026                (chunk.len()).try_into().unwrap()
1027            });
1028
1029        mock.expect_store_value_size()
1030            .return_const(i32::try_from(VALUE_SIZE).unwrap());
1031
1032        // Act
1033        let result = Runtime::store_read_all(&mock, &PATH);
1034
1035        // Assert
1036        let mut expected: Vec<u8> = Vec::new();
1037        expected.extend_from_slice(&VALUE_FIRST_CHUNK);
1038        expected.extend_from_slice(&VALUE_SECOND_CHUNK);
1039        expected.extend_from_slice(&VALUE_LAST_CHUNK);
1040
1041        assert_eq!(Ok(expected), result);
1042    }
1043
1044    #[test]
1045    fn store_write_ok() {
1046        // Arrange
1047        const PATH: RefPath<'static> = RefPath::assert_from("/a/simple/path".as_bytes());
1048        const OUTPUT: &[u8] = "One two three four five".as_bytes();
1049        const OFFSET: usize = 12398;
1050
1051        let mut mock = MockSmartRollupCore::new();
1052        mock.expect_store_write()
1053            .withf(|path_ptr, path_size, at_offset, src_ptr, src_size| {
1054                let path_slice = unsafe { from_raw_parts(*path_ptr, *path_size) };
1055                let output_slice = unsafe { from_raw_parts(*src_ptr, *src_size) };
1056
1057                OUTPUT == output_slice
1058                    && PATH.as_bytes() == path_slice
1059                    && OFFSET == *at_offset
1060            })
1061            .return_const(0);
1062
1063        // Act
1064        let result = mock.store_write(&PATH, OUTPUT, OFFSET);
1065
1066        // Assert
1067        assert_eq!(Ok(()), result);
1068    }
1069
1070    #[test]
1071    fn store_write_too_large() {
1072        // Arrange
1073        const PATH: RefPath<'static> = RefPath::assert_from("/a/simple/path".as_bytes());
1074        const OUTPUT: &[u8] = "once I saw a fish alive".as_bytes();
1075        const OFFSET: usize = 0;
1076
1077        let mut mock = MockSmartRollupCore::new();
1078        mock.expect_store_write()
1079            .withf(|path_ptr, path_size, at_offset, src_ptr, src_size| {
1080                let path_slice = unsafe { from_raw_parts(*path_ptr, *path_size) };
1081                let output_slice = unsafe { from_raw_parts(*src_ptr, *src_size) };
1082
1083                OUTPUT == output_slice
1084                    && PATH.as_bytes() == path_slice
1085                    && OFFSET == *at_offset
1086            })
1087            .return_const(Error::InputOutputTooLarge.code());
1088
1089        // Act
1090        let result = mock.store_write(&PATH, OUTPUT, OFFSET);
1091
1092        // Assert
1093        assert_eq!(
1094            Err(RuntimeError::HostErr(Error::InputOutputTooLarge)),
1095            result
1096        );
1097    }
1098
1099    #[test]
1100    fn store_write_all() {
1101        const PATH: RefPath<'static> = RefPath::assert_from("/a/simple/path".as_bytes());
1102        const VALUE_FIRST_CHUNK: [u8; MAX_FILE_CHUNK_SIZE] = [b'a'; MAX_FILE_CHUNK_SIZE];
1103        const VALUE_SECOND_CHUNK: [u8; MAX_FILE_CHUNK_SIZE] = [b'b'; MAX_FILE_CHUNK_SIZE];
1104        const VALUE_LAST_CHUNK: [u8; MAX_FILE_CHUNK_SIZE / 2] =
1105            [b'c'; MAX_FILE_CHUNK_SIZE / 2];
1106
1107        let mut mock = MockSmartRollupCore::new();
1108        mock.expect_store_delete_value()
1109            .withf(|path_ptr, path_size| {
1110                let path = unsafe { from_raw_parts(*path_ptr, *path_size) };
1111                path == PATH.as_bytes()
1112            })
1113            .return_const(0);
1114
1115        mock.expect_store_write()
1116            .withf(|path_ptr, path_size, at_offset, src_ptr, src_size| {
1117                let path_slice = unsafe { from_raw_parts(*path_ptr, *path_size) };
1118                let output_slice = unsafe { from_raw_parts(*src_ptr, *src_size) };
1119                // Store_write_all should always write maximum size of chunk per
1120                // maximum size of chunk
1121                let correct_value = if *at_offset == 0 {
1122                    output_slice == VALUE_FIRST_CHUNK
1123                } else if *at_offset == MAX_FILE_CHUNK_SIZE {
1124                    output_slice == VALUE_SECOND_CHUNK
1125                } else if *at_offset == MAX_FILE_CHUNK_SIZE * 2 {
1126                    output_slice == VALUE_LAST_CHUNK
1127                } else {
1128                    false
1129                };
1130
1131                correct_value && PATH.as_bytes() == path_slice
1132            })
1133            .return_const(0);
1134
1135        mock.expect_store_read()
1136            .withf(|path_ptr, path_size, _, _, _| {
1137                let slice = unsafe { from_raw_parts(*path_ptr, *path_size) };
1138                PATH.as_bytes() == slice
1139            })
1140            .returning(|_, _, offset, buf_ptr, _| {
1141                let chunk = if offset < MAX_FILE_CHUNK_SIZE {
1142                    VALUE_FIRST_CHUNK.to_vec()
1143                } else if offset < MAX_FILE_CHUNK_SIZE * 2 {
1144                    VALUE_SECOND_CHUNK.to_vec()
1145                } else {
1146                    VALUE_LAST_CHUNK.to_vec()
1147                };
1148                let buffer = unsafe { from_raw_parts_mut(buf_ptr, chunk.len()) };
1149                buffer.copy_from_slice(&chunk);
1150                (chunk.len()).try_into().unwrap()
1151            });
1152
1153        mock.expect_store_value_size().return_const(
1154            i32::try_from(MAX_FILE_CHUNK_SIZE * 2 + MAX_FILE_CHUNK_SIZE / 2).unwrap(),
1155        );
1156
1157        mock.expect_store_has().return_const(1_i32);
1158
1159        // Act
1160
1161        let mut value: Vec<u8> = Vec::new();
1162        value.extend_from_slice(&VALUE_FIRST_CHUNK);
1163        value.extend_from_slice(&VALUE_SECOND_CHUNK);
1164        value.extend_from_slice(&VALUE_LAST_CHUNK);
1165        let result = mock.store_write_all(&PATH, &value);
1166        let result_read = Runtime::store_read_all(&mock, &PATH).unwrap();
1167
1168        // Assert
1169        assert_eq!(Ok(()), result);
1170        assert_eq!(value.len(), result_read.len());
1171        assert_eq!(value, result_read);
1172    }
1173
1174    #[test]
1175    fn store_delete() {
1176        // Arrange
1177        const PATH: RefPath<'static> =
1178            RefPath::assert_from("/a/2nd/PATH.which/does/exist".as_bytes());
1179
1180        let mut mock = mock_path_exists(PATH.as_bytes());
1181        mock.expect_store_delete()
1182            .withf(|ptr, size| {
1183                let slice = unsafe { from_raw_parts(*ptr, *size) };
1184
1185                PATH.as_bytes() == slice
1186            })
1187            .return_const(0);
1188
1189        // Act
1190        let result = mock.store_delete(&PATH);
1191
1192        // Assert
1193        assert_eq!(Ok(()), result);
1194    }
1195
1196    #[test]
1197    fn store_delete_path_not_found() {
1198        // Arrange
1199        let bytes = String::from("/a/2nd/PATH.which/doesnt/exist").into_bytes();
1200        let path: OwnedPath = RefPath::assert_from(&bytes).into();
1201
1202        let mut mock = mock_path_not_existing(bytes);
1203
1204        // Act
1205        let result = mock.store_delete(&path);
1206
1207        // Assert
1208        assert_eq!(Err(RuntimeError::PathNotFound), result);
1209    }
1210
1211    #[test]
1212    fn store_delete_value() {
1213        // Arrange
1214        const PATH: RefPath<'static> =
1215            RefPath::assert_from("/a/PATH.which/does/exist".as_bytes());
1216
1217        const REMAINING_PATH: RefPath<'static> =
1218            RefPath::assert_from("/a/PATH.which/does/exist/and/survived".as_bytes());
1219
1220        let mut mock = mock_path_exists(PATH.as_bytes());
1221        mock.expect_store_delete()
1222            .withf(|ptr, size| {
1223                let slice = unsafe { from_raw_parts(*ptr, *size) };
1224
1225                PATH.as_bytes() == slice
1226            })
1227            .return_const(0);
1228        mock.expect_store_has()
1229            .withf(move |ptr, size| {
1230                let bytes = unsafe { from_raw_parts(*ptr, *size) };
1231                REMAINING_PATH.as_bytes() == bytes
1232            })
1233            .return_const(tezos_smart_rollup_core::VALUE_TYPE_VALUE);
1234
1235        // Act
1236        let result = mock.store_delete(&PATH);
1237        let result_remaining = mock.store_has(&REMAINING_PATH);
1238
1239        // Assert
1240        assert_eq!(Ok(()), result);
1241        assert!(matches!(result_remaining, Ok(Some(_))));
1242    }
1243
1244    #[test]
1245    fn store_count_subkeys() {
1246        // Arrange
1247        const PATH: RefPath<'static> =
1248            RefPath::assert_from("/prefix/of/other/keys".as_bytes());
1249
1250        let subkey_count = 14;
1251
1252        let mut mock = MockSmartRollupCore::new();
1253
1254        mock.expect_store_list_size()
1255            .withf(|ptr, size| {
1256                let slice = unsafe { from_raw_parts(*ptr, *size) };
1257
1258                PATH.as_bytes() == slice
1259            })
1260            .return_const(subkey_count);
1261
1262        // Act
1263        let result = mock.store_count_subkeys(&PATH);
1264
1265        // Assert
1266        assert_eq!(Ok(subkey_count.try_into().unwrap()), result);
1267    }
1268
1269    #[test]
1270    fn reveal_preimage_ok() {
1271        let mut mock = MockSmartRollupCore::new();
1272
1273        mock.expect_reveal_preimage()
1274            .withf(|hash_addr, hash_len, _dest_addr, max_bytes| {
1275                let hash = unsafe { from_raw_parts(*hash_addr, *hash_len) };
1276                hash_len == &PREIMAGE_HASH_SIZE
1277                    && hash == [5; PREIMAGE_HASH_SIZE]
1278                    && *max_bytes == 55
1279            })
1280            .return_once(|_, _, destination_address, _| {
1281                let revealed_bytes = [b'!'; 50];
1282                let buffer = unsafe { from_raw_parts_mut(destination_address, 50) };
1283                buffer.copy_from_slice(&revealed_bytes);
1284                50
1285            });
1286        let mut buffer = [0; 55];
1287        // Act
1288        let result =
1289            mock.reveal_preimage(&[5; PREIMAGE_HASH_SIZE], buffer.as_mut_slice());
1290
1291        // Assert
1292        assert_eq!(Ok(50), result);
1293    }
1294
1295    #[test]
1296    fn store_value_size() {
1297        let mut mock = MockSmartRollupCore::new();
1298        const PATH: RefPath<'static> = RefPath::assert_from(b"/prefix/of/other/paths");
1299        let size = 256_usize;
1300        mock.expect_store_has()
1301            .return_const(tezos_smart_rollup_core::VALUE_TYPE_VALUE);
1302        mock.expect_store_value_size()
1303            .return_const(i32::try_from(size).unwrap());
1304        let value_size = mock.store_value_size(&PATH);
1305        assert_eq!(size, value_size.unwrap());
1306    }
1307
1308    #[test]
1309    fn store_value_size_path_not_found() {
1310        let mut mock = MockSmartRollupCore::new();
1311        const PATH: RefPath<'static> = RefPath::assert_from(b"/prefix/of/other/paths");
1312        mock.expect_store_has()
1313            .return_const(tezos_smart_rollup_core::VALUE_TYPE_NONE);
1314
1315        assert_eq!(
1316            Err(RuntimeError::PathNotFound),
1317            mock.store_value_size(&PATH)
1318        );
1319    }
1320
1321    #[test]
1322    fn reveal_metadata_ok() {
1323        let mut mock = MockSmartRollupCore::new();
1324        let metadata_bytes = [
1325            // sr1 as 20 bytes
1326            b'M', 165, 28, b']', 231, 161, 205, 212, 148, 193, b'[', b'S', 129, b'^', 31,
1327            170, b'L', 26, 150, 202, // origination level as 4 bytes
1328            0, 0, 0, 42,
1329        ];
1330        let expected_metadata = RollupMetadata::from(metadata_bytes);
1331
1332        mock.expect_reveal_metadata()
1333            .return_once(move |destination_address, _| {
1334                let buffer =
1335                    unsafe { from_raw_parts_mut(destination_address, METADATA_SIZE) };
1336                buffer.copy_from_slice(&metadata_bytes.clone());
1337                METADATA_SIZE as i32
1338            });
1339
1340        // Act
1341        let result = mock.reveal_metadata();
1342
1343        // Assert
1344        assert_eq!(expected_metadata, result);
1345    }
1346
1347    #[test]
1348    #[cfg(feature = "proto-alpha")]
1349    fn reveal_dal_parameters_ok() {
1350        // Arrange
1351        let expected_dal_parameters = RollupDalParameters {
1352            number_of_slots: 1122,
1353            attestation_lag: 3344,
1354            slot_size: 5566,
1355            page_size: 7788,
1356        };
1357        let mut mock = MockSmartRollupCore::new();
1358        let dal_parameters_bytes = [
1359            0, 0, 0, 0, 0, 0, 4, 98, 0, 0, 0, 0, 0, 0, 13, 16, 0, 0, 0, 0, 0, 0, 21, 190,
1360            0, 0, 0, 0, 0, 0, 30, 108,
1361        ];
1362        mock.expect_reveal()
1363            .return_once(move |_, _, destination_address, _| {
1364                let buffer = unsafe {
1365                    from_raw_parts_mut(destination_address, DAL_PARAMETERS_SIZE)
1366                };
1367                buffer.copy_from_slice(&dal_parameters_bytes.clone());
1368                DAL_PARAMETERS_SIZE as i32
1369            });
1370
1371        // Act
1372        let result = mock.reveal_dal_parameters();
1373
1374        // Assert
1375        assert_eq!(expected_dal_parameters, result);
1376    }
1377
1378    mod test_helpers {
1379        use tezos_smart_rollup_core::smart_rollup_core::ReadInputMessageInfo;
1380        use tezos_smart_rollup_core::MAX_INPUT_MESSAGE_SIZE;
1381
1382        use super::MockSmartRollupCore;
1383        use std::slice::{from_raw_parts, from_raw_parts_mut};
1384
1385        pub fn mock_path_exists(path_bytes: &'static [u8]) -> MockSmartRollupCore {
1386            let mut mock = MockSmartRollupCore::new();
1387
1388            mock.expect_store_has()
1389                .withf(move |ptr, size| {
1390                    let bytes = unsafe { from_raw_parts(*ptr, *size) };
1391                    path_bytes == bytes
1392                })
1393                .return_const(tezos_smart_rollup_core::VALUE_TYPE_VALUE);
1394
1395            mock
1396        }
1397
1398        pub fn read_input_with(
1399            level: u32,
1400            id: u32,
1401            fill_with: u8,
1402            fill_fraction: usize,
1403        ) -> MockSmartRollupCore {
1404            let mut mock = MockSmartRollupCore::new();
1405
1406            let write_bytes = MAX_INPUT_MESSAGE_SIZE / fill_fraction;
1407
1408            let input_bytes = std::iter::repeat(fill_with)
1409                .take(write_bytes)
1410                .collect::<Box<_>>();
1411
1412            mock.expect_read_input().return_once(
1413                move |message_info_arg, buffer_arg, max_bytes_arg| {
1414                    assert_eq!(max_bytes_arg, MAX_INPUT_MESSAGE_SIZE);
1415
1416                    unsafe {
1417                        std::ptr::write(
1418                            message_info_arg,
1419                            ReadInputMessageInfo {
1420                                level: level as i32,
1421                                id: id as i32,
1422                            },
1423                        );
1424                        let buffer = from_raw_parts_mut(buffer_arg, write_bytes);
1425                        buffer.copy_from_slice(input_bytes.as_ref());
1426                    }
1427                    write_bytes.try_into().unwrap()
1428                },
1429            );
1430
1431            mock
1432        }
1433    }
1434}