1use crate::{
23 chain_extension::ChainExtension,
24 storage::meter::Diff,
25 wasm::{
26 runtime::AllowDeprecatedInterface, CodeInfo, Determinism, Environment, WasmBlob,
27 BYTES_PER_PAGE,
28 },
29 AccountIdOf, CodeVec, Config, Error, Schedule, LOG_TARGET,
30};
31#[cfg(any(test, feature = "runtime-benchmarks"))]
32use alloc::vec::Vec;
33use codec::MaxEncodedLen;
34use sp_runtime::{traits::Hash, DispatchError};
35use wasmi::{
36 core::ValType as WasmiValueType, CompilationMode, Config as WasmiConfig, Engine, ExternType,
37 Module, StackLimits,
38};
39
40pub const IMPORT_MODULE_MEMORY: &str = "env";
43
44pub struct LoadedModule {
47 pub module: Module,
48 pub engine: Engine,
49}
50
51#[derive(PartialEq, Debug, Clone)]
52pub enum LoadingMode {
53 Checked,
54 Unchecked,
55}
56
57#[cfg(test)]
58pub mod tracker {
59 use core::cell::RefCell;
60 thread_local! {
61 pub static LOADED_MODULE: RefCell<Vec<super::LoadingMode>> = RefCell::new(Vec::new());
62 }
63}
64
65impl LoadedModule {
66 pub fn new<T>(
71 code: &[u8],
72 determinism: Determinism,
73 stack_limits: Option<StackLimits>,
74 loading_mode: LoadingMode,
75 compilation_mode: CompilationMode,
76 ) -> Result<Self, &'static str> {
77 let mut config = WasmiConfig::default();
80 config
81 .wasm_multi_value(false)
82 .wasm_mutable_global(false)
83 .wasm_sign_extension(true)
84 .wasm_bulk_memory(false)
85 .wasm_reference_types(false)
86 .wasm_tail_call(false)
87 .wasm_extended_const(false)
88 .wasm_saturating_float_to_int(false)
89 .floats(matches!(determinism, Determinism::Relaxed))
90 .compilation_mode(compilation_mode)
91 .consume_fuel(true);
92
93 if let Some(stack_limits) = stack_limits {
94 config.set_stack_limits(stack_limits);
95 }
96
97 let engine = Engine::new(&config);
98
99 let module = match loading_mode {
100 LoadingMode::Checked => Module::new(&engine, code),
101 LoadingMode::Unchecked => unsafe { Module::new_unchecked(&engine, code) },
103 }
104 .map_err(|err| {
105 log::debug!(target: LOG_TARGET, "Module creation failed: {:?}", err);
106 "Can't load the module into wasmi!"
107 })?;
108
109 #[cfg(test)]
110 tracker::LOADED_MODULE.with(|t| t.borrow_mut().push(loading_mode));
111
112 Ok(LoadedModule { module, engine })
115 }
116
117 fn scan_exports(&self) -> Result<(), &'static str> {
125 let mut deploy_found = false;
126 let mut call_found = false;
127 let module = &self.module;
128 let exports = module.exports();
129
130 for export in exports {
131 match export.ty() {
132 ExternType::Func(ft) => {
133 match export.name() {
134 "call" => call_found = true,
135 "deploy" => deploy_found = true,
136 _ =>
137 return Err(
138 "unknown function export: expecting only deploy and call functions",
139 ),
140 }
141 if !(ft.params().is_empty() &&
145 (ft.results().is_empty() || ft.results() == [WasmiValueType::I32]))
146 {
147 return Err("entry point has wrong signature")
148 }
149 },
150 ExternType::Memory(_) => return Err("memory export is forbidden"),
151 ExternType::Global(_) => return Err("global export is forbidden"),
152 ExternType::Table(_) => return Err("table export is forbidden"),
153 }
154 }
155
156 if !deploy_found {
157 return Err("deploy function isn't exported")
158 }
159 if !call_found {
160 return Err("call function isn't exported")
161 }
162
163 Ok(())
164 }
165
166 pub fn scan_imports<T: Config>(
183 &self,
184 schedule: &Schedule<T>,
185 ) -> Result<(u32, u32), &'static str> {
186 let module = &self.module;
187 let imports = module.imports();
188 let mut memory_limits = None;
189
190 for import in imports {
191 match *import.ty() {
192 ExternType::Table(_) => return Err("Cannot import tables"),
193 ExternType::Global(_) => return Err("Cannot import globals"),
194 ExternType::Func(_) => {
195 import.ty().func().ok_or("expected a function")?;
196
197 if !<T as Config>::ChainExtension::enabled() &&
198 (import.name().as_bytes() == b"seal_call_chain_extension" ||
199 import.name().as_bytes() == b"call_chain_extension")
200 {
201 return Err("Module uses chain extensions but chain extensions are disabled")
202 }
203 },
204 ExternType::Memory(mt) => {
205 if import.module().as_bytes() != IMPORT_MODULE_MEMORY.as_bytes() {
206 return Err("Invalid module for imported memory")
207 }
208 if import.name().as_bytes() != b"memory" {
209 return Err("Memory import must have the field name 'memory'")
210 }
211 if memory_limits.is_some() {
212 return Err("Multiple memory imports defined")
213 }
214 let (initial, maximum) = (
217 mt.initial_pages().to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE)
218 as u32,
219 mt.maximum_pages().map_or(schedule.limits.memory_pages, |p| {
220 p.to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32
221 }),
222 );
223 if initial > maximum {
224 return Err(
225 "Requested initial number of memory pages should not exceed the requested maximum",
226 )
227 }
228 if maximum > schedule.limits.memory_pages {
229 return Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule")
230 }
231
232 memory_limits = Some((initial, maximum));
233 continue
234 },
235 }
236 }
237
238 memory_limits.ok_or("No memory import found in the module")
239 }
240}
241
242fn validate<E, T>(
249 code: &[u8],
250 schedule: &Schedule<T>,
251 determinism: &mut Determinism,
252) -> Result<(), (DispatchError, &'static str)>
253where
254 E: Environment<()>,
255 T: Config,
256{
257 let module = (|| {
258 let stack_limits = Some(StackLimits::new(1, 1, 0).expect("initial <= max; qed"));
261
262 let contract_module = match *determinism {
265 Determinism::Relaxed =>
266 if let Ok(module) = LoadedModule::new::<T>(
267 code,
268 Determinism::Enforced,
269 stack_limits,
270 LoadingMode::Checked,
271 CompilationMode::Eager,
272 ) {
273 *determinism = Determinism::Enforced;
274 module
275 } else {
276 LoadedModule::new::<T>(
277 code,
278 Determinism::Relaxed,
279 None,
280 LoadingMode::Checked,
281 CompilationMode::Eager,
282 )?
283 },
284 Determinism::Enforced => LoadedModule::new::<T>(
285 code,
286 Determinism::Enforced,
287 stack_limits,
288 LoadingMode::Checked,
289 CompilationMode::Eager,
290 )?,
291 };
292
293 contract_module.scan_exports()?;
295 contract_module.scan_imports::<T>(schedule)?;
296 Ok(contract_module)
297 })()
298 .map_err(|msg: &str| {
299 log::debug!(target: LOG_TARGET, "New code rejected on validation: {}", msg);
300 (Error::<T>::CodeRejected.into(), msg)
301 })?;
302
303 WasmBlob::<T>::instantiate::<E, _>(module, (), schedule, AllowDeprecatedInterface::No)
308 .map_err(|err| {
309 log::debug!(target: LOG_TARGET, "{err}");
310 (Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
311 })?;
312
313 Ok(())
314}
315
316pub fn prepare<E, T>(
325 code: CodeVec<T>,
326 schedule: &Schedule<T>,
327 owner: AccountIdOf<T>,
328 mut determinism: Determinism,
329) -> Result<WasmBlob<T>, (DispatchError, &'static str)>
330where
331 E: Environment<()>,
332 T: Config,
333{
334 validate::<E, T>(code.as_ref(), schedule, &mut determinism)?;
335
336 let code_len = code.len() as u32;
338 let bytes_added = code_len.saturating_add(<CodeInfo<T>>::max_encoded_len() as u32);
339 let deposit = Diff { bytes_added, items_added: 2, ..Default::default() }
340 .update_contract::<T>(None)
341 .charge_or_zero();
342 let code_info = CodeInfo { owner, deposit, determinism, refcount: 0, code_len };
343 let code_hash = T::Hashing::hash(&code);
344
345 Ok(WasmBlob { code, code_info, code_hash })
346}
347
348#[cfg(any(test, feature = "runtime-benchmarks"))]
354pub mod benchmarking {
355 use super::*;
356
357 pub fn prepare<T: Config>(
359 code: Vec<u8>,
360 schedule: &Schedule<T>,
361 owner: AccountIdOf<T>,
362 ) -> Result<WasmBlob<T>, DispatchError> {
363 let determinism = Determinism::Enforced;
364 let contract_module = LoadedModule::new::<T>(
365 &code,
366 determinism,
367 None,
368 LoadingMode::Checked,
369 CompilationMode::Eager,
370 )?;
371 contract_module.scan_imports::<T>(schedule)?;
372 let code: CodeVec<T> = code.try_into().map_err(|_| <Error<T>>::CodeTooLarge)?;
373 let code_info = CodeInfo {
374 owner,
375 deposit: Default::default(),
377 refcount: 0,
378 code_len: code.len() as u32,
379 determinism,
380 };
381 let code_hash = T::Hashing::hash(&code);
382
383 Ok(WasmBlob { code, code_info, code_hash })
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390 use crate::{
391 exec::Ext,
392 schedule::Limits,
393 tests::{Test, ALICE},
394 };
395 use pallet_contracts_proc_macro::define_env;
396 use std::fmt;
397
398 impl fmt::Debug for WasmBlob<Test> {
399 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
400 write!(f, "ContractCode {{ .. }}")
401 }
402 }
403
404 #[allow(unreachable_code)]
406 mod env {
407 use super::*;
408 use crate::wasm::runtime::{AllowDeprecatedInterface, AllowUnstableInterface, TrapReason};
409
410 #[define_env]
413 pub mod test_env {
414 fn panic(_ctx: _, _memory: _) -> Result<(), TrapReason> {
415 Ok(())
416 }
417
418 fn gas(_ctx: _, _memory: _, _amount: u64) -> Result<(), TrapReason> {
420 Ok(())
421 }
422
423 fn nop(_ctx: _, _memory: _, _unused: u64) -> Result<(), TrapReason> {
424 Ok(())
425 }
426
427 #[version(1)]
429 fn nop(_ctx: _, _memory: _, _unused: i32) -> Result<(), TrapReason> {
430 Ok(())
431 }
432 }
433 }
434
435 macro_rules! prepare_test {
436 ($name:ident, $wat:expr, $($expected:tt)*) => {
437 #[test]
438 fn $name() {
439 let wasm = wat::parse_str($wat).unwrap().try_into().unwrap();
440 let schedule = Schedule {
441 limits: Limits {
442 memory_pages: 16,
443 .. Default::default()
444 },
445 .. Default::default()
446 };
447 let r = prepare::<env::Env, Test>(
448 wasm,
449 &schedule,
450 ALICE,
451 Determinism::Enforced,
452 );
453 assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
454 }
455 };
456 }
457
458 prepare_test!(
459 no_floats,
460 r#"
461 (module
462 (func (export "call")
463 (drop
464 (f32.add
465 (f32.const 0)
466 (f32.const 1)
467 )
468 )
469 )
470 (func (export "deploy"))
471 )"#,
472 Err("Can't load the module into wasmi!")
473 );
474
475 mod memories {
476 use super::*;
477
478 prepare_test!(
479 memory_with_one_page,
480 r#"
481 (module
482 (import "env" "memory" (memory 1 1))
483
484 (func (export "call"))
485 (func (export "deploy"))
486 )
487 "#,
488 Ok(_)
489 );
490
491 prepare_test!(
492 internal_memory_declaration,
493 r#"
494 (module
495 (memory 1 1)
496
497 (func (export "call"))
498 (func (export "deploy"))
499 )
500 "#,
501 Err("No memory import found in the module")
502 );
503
504 prepare_test!(
505 no_memory_import,
506 r#"
507 (module
508 ;; no memory imported
509
510 (func (export "call"))
511 (func (export "deploy"))
512 )"#,
513 Err("No memory import found in the module")
514 );
515
516 prepare_test!(
517 initial_exceeds_maximum,
518 r#"
519 (module
520 (import "env" "memory" (memory 16 1))
521
522 (func (export "call"))
523 (func (export "deploy"))
524 )
525 "#,
526 Err("Can't load the module into wasmi!")
527 );
528
529 prepare_test!(
530 requested_maximum_valid,
531 r#"
532 (module
533 (import "env" "memory" (memory 1 16))
534
535 (func (export "call"))
536 (func (export "deploy"))
537 )
538 "#,
539 Ok(_)
540 );
541
542 prepare_test!(
543 requested_maximum_exceeds_configured_maximum,
544 r#"
545 (module
546 (import "env" "memory" (memory 1 17))
547
548 (func (export "call"))
549 (func (export "deploy"))
550 )
551 "#,
552 Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule")
553 );
554
555 prepare_test!(
556 field_name_not_memory,
557 r#"
558 (module
559 (import "env" "forgetit" (memory 1 1))
560
561 (func (export "call"))
562 (func (export "deploy"))
563 )
564 "#,
565 Err("Memory import must have the field name 'memory'")
566 );
567
568 prepare_test!(
569 multiple_memory_imports,
570 r#"
571 (module
572 (import "env" "memory" (memory 1 1))
573 (import "env" "memory" (memory 1 1))
574
575 (func (export "call"))
576 (func (export "deploy"))
577 )
578 "#,
579 Err("Can't load the module into wasmi!")
580 );
581
582 prepare_test!(
583 table_import,
584 r#"
585 (module
586 (import "seal0" "table" (table 1 anyfunc))
587
588 (func (export "call"))
589 (func (export "deploy"))
590 )
591 "#,
592 Err("Cannot import tables")
593 );
594
595 prepare_test!(
596 global_import,
597 r#"
598 (module
599 (global $g (import "seal0" "global") i32)
600 (func (export "call"))
601 (func (export "deploy"))
602 )
603 "#,
604 Err("Cannot import globals")
605 );
606 }
607
608 mod imports {
609 use super::*;
610
611 prepare_test!(
612 can_import_legit_function,
613 r#"
614 (module
615 (import "seal0" "nop" (func (param i64)))
616 (import "env" "memory" (memory 1 1))
617
618 (func (export "call"))
619 (func (export "deploy"))
620 )
621 "#,
622 Ok(_)
623 );
624
625 prepare_test!(
627 memory_not_in_seal0,
628 r#"
629 (module
630 (import "seal0" "memory" (memory 1 1))
631
632 (func (export "call"))
633 (func (export "deploy"))
634 )
635 "#,
636 Err("Invalid module for imported memory")
637 );
638
639 prepare_test!(
641 memory_not_in_arbitrary_module,
642 r#"
643 (module
644 (import "any_module" "memory" (memory 1 1))
645
646 (func (export "call"))
647 (func (export "deploy"))
648 )
649 "#,
650 Err("Invalid module for imported memory")
651 );
652
653 prepare_test!(
654 function_in_other_module_works,
655 r#"
656 (module
657 (import "seal1" "nop" (func (param i32)))
658 (import "env" "memory" (memory 1 1))
659
660
661 (func (export "call"))
662 (func (export "deploy"))
663 )
664 "#,
665 Ok(_)
666 );
667
668 prepare_test!(
669 wrong_signature,
670 r#"
671 (module
672 (import "seal0" "input" (func (param i64)))
673 (import "env" "memory" (memory 1 1))
674
675 (func (export "call"))
676 (func (export "deploy"))
677 )
678 "#,
679 Err("New code rejected on wasmi instantiation!")
680 );
681
682 prepare_test!(
683 unknown_func_name,
684 r#"
685 (module
686 (import "seal0" "unknown_func" (func))
687
688 (func (export "call"))
689 (func (export "deploy"))
690 )
691 "#,
692 Err("No memory import found in the module")
693 );
694
695 prepare_test!(
697 try_import_from_wrong_module,
698 r#"
699 (module
700 (import "env" "panic" (func))
701 (import "env" "memory" (memory 1 1))
702
703 (func (export "call"))
704 (func (export "deploy"))
705 )
706 "#,
707 Err("New code rejected on wasmi instantiation!")
708 );
709 }
710
711 mod entrypoints {
712 use super::*;
713
714 prepare_test!(
715 it_works,
716 r#"
717 (module
718 (import "env" "memory" (memory 1 1))
719 (func (export "call"))
720 (func (export "deploy"))
721 )
722 "#,
723 Ok(_)
724 );
725
726 prepare_test!(
727 signed_extension_works,
728 r#"
729 (module
730 (import "env" "memory" (memory 1 1))
731 (func (export "deploy"))
732 (func (export "call"))
733 (func (param i32) (result i32)
734 local.get 0
735 i32.extend8_s
736 )
737 )
738 "#,
739 Ok(_)
740 );
741
742 prepare_test!(
743 omit_memory,
744 r#"
745 (module
746 (func (export "call"))
747 (func (export "deploy"))
748 )
749 "#,
750 Err("No memory import found in the module")
751 );
752
753 prepare_test!(
754 omit_deploy,
755 r#"
756 (module
757 (func (export "call"))
758 )
759 "#,
760 Err("deploy function isn't exported")
761 );
762
763 prepare_test!(
764 omit_call,
765 r#"
766 (module
767 (func (export "deploy"))
768 )
769 "#,
770 Err("call function isn't exported")
771 );
772
773 prepare_test!(
776 try_sneak_export_as_entrypoint,
777 r#"
778 (module
779 (import "seal0" "panic" (func))
780 (import "env" "memory" (memory 1 1))
781
782 (func (export "deploy"))
783
784 (export "call" (func 0))
785 )
786 "#,
787 Ok(_)
788 );
789
790 prepare_test!(
792 try_sneak_export_as_global,
793 r#"
794 (module
795 (func (export "deploy"))
796 (global (export "call") i32 (i32.const 0))
797 )
798 "#,
799 Err("global export is forbidden")
800 );
801
802 prepare_test!(
803 wrong_signature,
804 r#"
805 (module
806 (func (export "deploy"))
807 (func (export "call") (param i32))
808 )
809 "#,
810 Err("entry point has wrong signature")
811 );
812
813 prepare_test!(
814 unknown_exports,
815 r#"
816 (module
817 (func (export "call"))
818 (func (export "deploy"))
819 (func (export "whatevs"))
820 )
821 "#,
822 Err("unknown function export: expecting only deploy and call functions")
823 );
824
825 prepare_test!(
826 global_float,
827 r#"
828 (module
829 (global $x f32 (f32.const 0))
830 (func (export "call"))
831 (func (export "deploy"))
832 )
833 "#,
834 Err("Can't load the module into wasmi!")
835 );
836
837 prepare_test!(
838 local_float,
839 r#"
840 (module
841 (func $foo (local f32))
842 (func (export "call"))
843 (func (export "deploy"))
844 )
845 "#,
846 Err("Can't load the module into wasmi!")
847 );
848
849 prepare_test!(
850 param_float,
851 r#"
852 (module
853 (func $foo (param f32))
854 (func (export "call"))
855 (func (export "deploy"))
856 )
857 "#,
858 Err("Can't load the module into wasmi!")
859 );
860
861 prepare_test!(
862 result_float,
863 r#"
864 (module
865 (func $foo (result f32) (f32.const 0))
866 (func (export "call"))
867 (func (export "deploy"))
868 )
869 "#,
870 Err("Can't load the module into wasmi!")
871 );
872 }
873}