1#[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)]
34pub enum RuntimeError {
36 PathNotFound,
38 StoreListIndexOutOfBounds,
40 HostErr(Error),
42 DecodingError,
44}
45
46#[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#[derive(Debug, Copy, Clone, Eq, PartialEq)]
70pub enum ValueType {
71 Value,
73 Subtree,
75 ValueWithSubtree,
77}
78
79pub trait Runtime {
86 fn write_output(&mut self, from: &[u8]) -> Result<(), RuntimeError>;
88
89 fn write_debug(&self, msg: &str);
91
92 #[cfg(feature = "alloc")]
99 fn read_input(&mut self) -> Result<Option<Message>, RuntimeError>;
100
101 fn store_has<T: Path>(&self, path: &T) -> Result<Option<ValueType>, RuntimeError>;
103
104 #[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 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 #[cfg(feature = "alloc")]
129 fn store_read_all(&self, path: &impl Path) -> Result<Vec<u8>, RuntimeError>;
130
131 fn store_write<T: Path>(
137 &mut self,
138 path: &T,
139 src: &[u8],
140 at_offset: usize,
141 ) -> Result<(), RuntimeError>;
142
143 fn store_write_all<T: Path>(
148 &mut self,
149 path: &T,
150 src: &[u8],
151 ) -> Result<(), RuntimeError>;
152
153 fn store_delete<T: Path>(&mut self, path: &T) -> Result<(), RuntimeError>;
155
156 fn store_delete_value<T: Path>(&mut self, path: &T) -> Result<(), RuntimeError>;
158
159 fn store_count_subkeys<T: Path>(&self, prefix: &T) -> Result<u64, RuntimeError>;
163
164 fn store_move(
168 &mut self,
169 from_path: &impl Path,
170 to_path: &impl Path,
171 ) -> Result<(), RuntimeError>;
172
173 fn store_copy(
177 &mut self,
178 from_path: &impl Path,
179 to_path: &impl Path,
180 ) -> Result<(), RuntimeError>;
181
182 fn reveal_preimage(
188 &self,
189 hash: &[u8; PREIMAGE_HASH_SIZE],
190 destination: &mut [u8],
191 ) -> Result<usize, RuntimeError>;
192
193 #[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 #[cfg(feature = "proto-alpha")]
205 fn reveal_dal_parameters(&self) -> RollupDalParameters;
206
207 fn store_value_size(&self, path: &impl Path) -> Result<usize, RuntimeError>;
209
210 fn mark_for_reboot(&mut self) -> Result<(), RuntimeError>;
227
228 fn reveal_metadata(&self) -> RollupMetadata;
230
231 fn last_run_aborted(&self) -> Result<bool, RuntimeError>;
233
234 fn upgrade_failed(&self) -> Result<bool, RuntimeError>;
236
237 fn restart_forced(&self) -> Result<bool, RuntimeError>;
239
240 fn reboot_left(&self) -> Result<u32, RuntimeError>;
242
243 #[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 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 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 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 while buffer.len() < length {
391 let offset = buffer.len();
392 let max_length = usize::min(MAX_FILE_CHUNK_SIZE, length - offset);
393 let slice = &mut buffer[offset..];
399 unsafe {
400 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 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 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 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 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 let payload: &[u8] = &[
611 &[2u8], 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 let payload: &[u8] = &[3u8]; 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 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 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 let result = mock.write_output(output.as_bytes());
790
791 assert_eq!(Ok(()), result);
793 }
794
795 #[test]
796 fn given_output_too_large_then_err() {
797 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 let result = mock.write_output(output.as_slice());
813
814 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 let mut mock = MockSmartRollupCore::new();
825 mock.expect_read_input().return_const(0_i32);
826
827 let outcome = mock.read_input();
829
830 assert_eq!(Ok(None), outcome);
832 }
833
834 #[test]
835 fn read_message_input_with_size_max_bytes() {
836 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 let outcome = mock.read_input();
846
847 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 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 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 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 let result = mock.store_has(&non_existent_path);
899
900 assert!(matches!(result, Ok(None)));
902 }
903
904 #[test]
905 fn store_read_max_bytes() {
906 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 let result = mock.store_read(&PATH, OFFSET, READ_SIZE);
929
930 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 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 let result = mock.store_read(&PATH, OFFSET, READ_SIZE);
961
962 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 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 let result = mock.store_read(&path, offset, READ_SIZE);
979
980 assert_eq!(Err(RuntimeError::PathNotFound), result);
982 }
983
984 #[test]
985 fn store_read_all_above_max_file_chunk_size() {
986 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 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 .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 let result = Runtime::store_read_all(&mock, &PATH);
1034
1035 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 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 let result = mock.store_write(&PATH, OUTPUT, OFFSET);
1065
1066 assert_eq!(Ok(()), result);
1068 }
1069
1070 #[test]
1071 fn store_write_too_large() {
1072 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 let result = mock.store_write(&PATH, OUTPUT, OFFSET);
1091
1092 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 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 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_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 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 let result = mock.store_delete(&PATH);
1191
1192 assert_eq!(Ok(()), result);
1194 }
1195
1196 #[test]
1197 fn store_delete_path_not_found() {
1198 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 let result = mock.store_delete(&path);
1206
1207 assert_eq!(Err(RuntimeError::PathNotFound), result);
1209 }
1210
1211 #[test]
1212 fn store_delete_value() {
1213 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 let result = mock.store_delete(&PATH);
1237 let result_remaining = mock.store_has(&REMAINING_PATH);
1238
1239 assert_eq!(Ok(()), result);
1241 assert!(matches!(result_remaining, Ok(Some(_))));
1242 }
1243
1244 #[test]
1245 fn store_count_subkeys() {
1246 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 let result = mock.store_count_subkeys(&PATH);
1264
1265 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 let result =
1289 mock.reveal_preimage(&[5; PREIMAGE_HASH_SIZE], buffer.as_mut_slice());
1290
1291 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 b'M', 165, 28, b']', 231, 161, 205, 212, 148, 193, b'[', b'S', 129, b'^', 31,
1327 170, b'L', 26, 150, 202, 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 let result = mock.reveal_metadata();
1342
1343 assert_eq!(expected_metadata, result);
1345 }
1346
1347 #[test]
1348 #[cfg(feature = "proto-alpha")]
1349 fn reveal_dal_parameters_ok() {
1350 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 let result = mock.reveal_dal_parameters();
1373
1374 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}