1use std::collections::BTreeSet;
2use std::collections::HashSet;
3
4use wasmer::wasmparser::Import;
5use wasmer::wasmparser::TypeRef;
6
7use crate::capabilities::required_capabilities_from_module;
8use crate::config::WasmLimits;
9use crate::errors::{VmError, VmResult};
10use crate::limited::LimitedDisplay;
11use crate::parsed_wasm::ParsedWasm;
12use crate::static_analysis::ExportInfo;
13
14const SUPPORTED_IMPORTS: &[&str] = &[
17 "env.abort",
18 "env.db_read",
19 "env.db_write",
20 "env.db_remove",
21 "env.addr_validate",
22 "env.addr_canonicalize",
23 "env.addr_humanize",
24 "env.bls12_381_aggregate_g1",
25 "env.bls12_381_aggregate_g2",
26 "env.bls12_381_pairing_equality",
27 "env.bls12_381_hash_to_g1",
28 "env.bls12_381_hash_to_g2",
29 "env.secp256k1_verify",
30 "env.secp256k1_recover_pubkey",
31 "env.secp256r1_verify",
32 "env.secp256r1_recover_pubkey",
33 "env.ed25519_verify",
34 "env.ed25519_batch_verify",
35 "env.debug",
36 "env.query_chain",
37 #[cfg(feature = "iterator")]
38 "env.db_scan",
39 #[cfg(feature = "iterator")]
40 "env.db_next",
41 #[cfg(feature = "iterator")]
42 "env.db_next_key",
43 #[cfg(feature = "iterator")]
44 "env.db_next_value",
45];
46
47const REQUIRED_EXPORTS: &[&str] = &[
52 "allocate",
54 "deallocate",
55];
56
57const INTERFACE_VERSION_PREFIX: &str = "interface_version_";
58const SUPPORTED_INTERFACE_VERSIONS: &[&str] = &["interface_version_8"];
59
60#[derive(Clone, Copy)]
61pub enum LogOutput {
62 StdOut,
63 StdErr,
64}
65#[derive(Clone, Copy, Default)]
66pub enum Logger<'a> {
67 On {
68 prefix: &'a str,
69 output: LogOutput,
70 },
71 #[default]
72 Off,
73}
74
75impl<'a> Logger<'a> {
76 pub fn with_config(output: LogOutput, prefix: &'a str) -> Self {
77 On { output, prefix }
78 }
79
80 pub fn add(&self, msg_fn: impl FnOnce() -> String) {
85 if let On { prefix, output } = &self {
86 let msg = msg_fn();
87 match output {
88 LogOutput::StdOut => println!("{prefix}{msg}"),
89 LogOutput::StdErr => eprintln!("{prefix}{msg}"),
90 }
91 }
92 }
93}
94
95use Logger::*;
96
97pub fn check_wasm(
99 wasm_code: &[u8],
100 available_capabilities: &HashSet<String>,
101 limits: &WasmLimits,
102 logs: Logger<'_>,
103) -> VmResult<()> {
104 logs.add(|| format!("Size of Wasm blob: {}", wasm_code.len()));
105
106 let mut module = ParsedWasm::parse(wasm_code)?;
107
108 check_wasm_tables(&module, limits)?;
109 check_wasm_memories(&module, limits)?;
110 check_interface_version(&module)?;
111 check_wasm_exports(&module, logs)?;
112 check_wasm_imports(&module, SUPPORTED_IMPORTS, limits, logs)?;
113 check_wasm_capabilities(&module, available_capabilities, logs)?;
114 check_wasm_functions(&module, limits, logs)?;
115
116 module.validate_funcs()
117}
118
119fn check_wasm_tables(module: &ParsedWasm, wasm_limits: &WasmLimits) -> VmResult<()> {
120 match module.tables.len() {
121 0 => Ok(()),
122 1 => {
123 let limits = &module.tables[0];
124 if let Some(maximum) = limits.maximum {
125 if maximum > wasm_limits.table_size_limit_elements() as u64 {
126 return Err(VmError::static_validation_err(
127 "Wasm contract's first table section has a too large max limit",
128 ));
129 }
130 Ok(())
131 } else {
132 Err(VmError::static_validation_err(
133 "Wasm contract must not have unbound table section",
134 ))
135 }
136 }
137 _ => Err(VmError::static_validation_err(
138 "Wasm contract must not have more than 1 table section",
139 )),
140 }
141}
142
143fn check_wasm_memories(module: &ParsedWasm, limits: &WasmLimits) -> VmResult<()> {
144 if module.memories.len() != 1 {
145 return Err(VmError::static_validation_err(
146 "Wasm contract must contain exactly one memory",
147 ));
148 }
149 let memory = &module.memories[0];
150
151 if memory.initial > limits.initial_memory_limit_pages() as u64 {
152 return Err(VmError::static_validation_err(format!(
153 "Wasm contract memory's minimum must not exceed {} pages.",
154 limits.initial_memory_limit_pages()
155 )));
156 }
157
158 if memory.maximum.is_some() {
159 return Err(VmError::static_validation_err(
160 "Wasm contract memory's maximum must be unset. The host will set it for you.",
161 ));
162 }
163 Ok(())
164}
165
166fn check_interface_version(module: &ParsedWasm) -> VmResult<()> {
167 let mut interface_version_exports = module
168 .exported_function_names(Some(INTERFACE_VERSION_PREFIX))
169 .into_iter();
170 if let Some(first_interface_version_export) = interface_version_exports.next() {
171 if interface_version_exports.next().is_some() {
172 Err(VmError::static_validation_err(
173 "Wasm contract contains more than one marker export: interface_version_*",
174 ))
175 } else {
176 let version_str = first_interface_version_export.as_str();
178 if SUPPORTED_INTERFACE_VERSIONS.contains(&version_str) {
179 Ok(())
180 } else {
181 Err(VmError::static_validation_err(
182 "Wasm contract has unknown interface_version_* marker export (see https://github.com/CosmWasm/cosmwasm/blob/main/packages/vm/README.md)",
183 ))
184 }
185 }
186 } else {
187 Err(VmError::static_validation_err(
188 "Wasm contract missing a required marker export: interface_version_*",
189 ))
190 }
191}
192
193fn check_wasm_exports(module: &ParsedWasm, logs: Logger) -> VmResult<()> {
194 let available_exports: HashSet<String> = module.exported_function_names(None);
195
196 logs.add(|| format!("Exports: {}", available_exports.to_string_limited(20_000)));
197
198 for required_export in REQUIRED_EXPORTS {
199 if !available_exports.contains(*required_export) {
200 return Err(VmError::static_validation_err(format!(
201 "Wasm contract doesn't have required export: \"{required_export}\". Exports required by VM: {REQUIRED_EXPORTS:?}."
202 )));
203 }
204 }
205 Ok(())
206}
207
208fn check_wasm_imports(
212 module: &ParsedWasm,
213 supported_imports: &[&str],
214 limits: &WasmLimits,
215 logs: Logger,
216) -> VmResult<()> {
217 logs.add(|| {
218 format!(
219 "Imports ({}): {}",
220 module.imports.len(),
221 module
222 .imports
223 .iter()
224 .map(|import| full_import_name(import))
225 .collect::<Vec<_>>()
226 .join(", ")
227 )
228 });
229
230 if module.imports.len() > limits.max_imports() {
231 return Err(VmError::static_validation_err(format!(
232 "Import count exceeds limit. Imports: {}. Limit: {}.",
233 module.imports.len(),
234 limits.max_imports()
235 )));
236 }
237
238 for required_import in &module.imports {
239 let full_name = full_import_name(required_import);
240 if !supported_imports.contains(&full_name.as_str()) {
241 let required_import_names: BTreeSet<_> =
242 module.imports.iter().map(full_import_name).collect();
243 return Err(VmError::static_validation_err(format!(
244 "Wasm contract requires unsupported import: \"{}\". Required imports: {}. Available imports: {:?}.",
245 full_name, required_import_names.to_string_limited(200), supported_imports
246 )));
247 }
248
249 match required_import.ty {
250 TypeRef::Func(_) => {} _ => return Err(VmError::static_validation_err(format!(
252 "Wasm contract requires non-function import: \"{full_name}\". Right now, all supported imports are functions."
253 )))
254 }
255 }
256 Ok(())
257}
258
259fn full_import_name(ie: &Import) -> String {
260 format!("{}.{}", ie.module, ie.name)
261}
262
263fn check_wasm_capabilities(
264 module: &ParsedWasm,
265 available_capabilities: &HashSet<String>,
266 logs: Logger,
267) -> VmResult<()> {
268 let required_capabilities = required_capabilities_from_module(module);
269 logs.add(|| {
270 format!(
271 "Required capabilities: {}",
272 required_capabilities.to_string_limited(20_000)
273 )
274 });
275 if !required_capabilities.is_subset(available_capabilities) {
276 let unavailable: BTreeSet<_> = required_capabilities
278 .difference(available_capabilities)
279 .collect();
280 return Err(VmError::static_validation_err(format!(
281 "Wasm contract requires unavailable capabilities: {}",
282 unavailable.to_string_limited(200)
283 )));
284 }
285 Ok(())
286}
287
288fn check_wasm_functions(module: &ParsedWasm, limits: &WasmLimits, logs: Logger) -> VmResult<()> {
289 logs.add(|| format!("Function count: {}", module.function_count));
290 logs.add(|| format!("Max function parameters: {}", module.max_func_params));
291 logs.add(|| format!("Max function results: {}", module.max_func_results));
292 logs.add(|| {
293 format!(
294 "Total function parameter count: {}",
295 module.total_func_params
296 )
297 });
298
299 if module.function_count > limits.max_functions() {
300 return Err(VmError::static_validation_err(format!(
301 "Wasm contract contains more than {} functions",
302 limits.max_functions()
303 )));
304 }
305 if module.max_func_params > limits.max_function_params() {
306 return Err(VmError::static_validation_err(format!(
307 "Wasm contract contains function with more than {} parameters",
308 limits.max_function_params()
309 )));
310 }
311 if module.max_func_results > limits.max_function_results() {
312 return Err(VmError::static_validation_err(format!(
313 "Wasm contract contains function with more than {} results",
314 limits.max_function_results()
315 )));
316 }
317
318 if module.total_func_params > limits.max_total_function_params() {
319 return Err(VmError::static_validation_err(format!(
320 "Wasm contract contains more than {} function parameters in total",
321 limits.max_total_function_params()
322 )));
323 }
324
325 Ok(())
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 static CONTRACT_0_7: &[u8] = include_bytes!("../testdata/hackatom_0.7.wasm");
333 static CONTRACT_0_12: &[u8] = include_bytes!("../testdata/hackatom_0.12.wasm");
334 static CONTRACT_0_14: &[u8] = include_bytes!("../testdata/hackatom_0.14.wasm");
335 static CONTRACT_0_15: &[u8] = include_bytes!("../testdata/hackatom_0.15.wasm");
336 static HACKATOM: &[u8] = include_bytes!("../testdata/hackatom.wasm");
337 static CYBERPUNK: &[u8] = include_bytes!("../testdata/cyberpunk.wasm");
338 static CYBERPUNK_RUST_170: &[u8] = include_bytes!("../testdata/cyberpunk_rust170.wasm");
339
340 fn default_capabilities() -> HashSet<String> {
341 HashSet::from([
342 "cosmwasm_1_1".to_string(),
343 "cosmwasm_1_2".to_string(),
344 "cosmwasm_1_3".to_string(),
345 "cosmwasm_1_4".to_string(),
346 "cosmwasm_1_4".to_string(),
347 "cosmwasm_2_0".to_string(),
348 "cosmwasm_2_1".to_string(),
349 "cosmwasm_2_2".to_string(),
350 "iterator".to_string(),
351 "staking".to_string(),
352 "stargate".to_string(),
353 ])
354 }
355
356 #[test]
357 fn check_wasm_passes_for_latest_contract() {
358 check_wasm(
360 HACKATOM,
361 &default_capabilities(),
362 &WasmLimits::default(),
363 Off,
364 )
365 .unwrap();
366 check_wasm(
367 CYBERPUNK,
368 &default_capabilities(),
369 &WasmLimits::default(),
370 Off,
371 )
372 .unwrap();
373 }
374
375 #[test]
376 fn check_wasm_allows_sign_ext() {
377 check_wasm(
379 CYBERPUNK_RUST_170,
380 &default_capabilities(),
381 &WasmLimits::default(),
382 Off,
383 )
384 .unwrap();
385 }
386
387 #[test]
388 fn check_wasm_old_contract() {
389 match check_wasm(CONTRACT_0_15, &default_capabilities(),&WasmLimits::default(),
390 Off) {
391 Err(VmError::StaticValidationErr { msg, .. }) => assert_eq!(
392 msg,
393 "Wasm contract has unknown interface_version_* marker export (see https://github.com/CosmWasm/cosmwasm/blob/main/packages/vm/README.md)"
394 ),
395 Err(e) => panic!("Unexpected error {e:?}"),
396 Ok(_) => panic!("This must not succeed"),
397 };
398
399 match check_wasm(CONTRACT_0_14, &default_capabilities(),&WasmLimits::default(),
400 Off,) {
401 Err(VmError::StaticValidationErr { msg, .. }) => assert_eq!(
402 msg,
403 "Wasm contract has unknown interface_version_* marker export (see https://github.com/CosmWasm/cosmwasm/blob/main/packages/vm/README.md)"
404 ),
405 Err(e) => panic!("Unexpected error {e:?}"),
406 Ok(_) => panic!("This must not succeed"),
407 };
408
409 match check_wasm(
410 CONTRACT_0_12,
411 &default_capabilities(),
412 &WasmLimits::default(),
413 Off,
414 ) {
415 Err(VmError::StaticValidationErr { msg, .. }) => {
416 assert!(msg.contains(
417 "Wasm contract missing a required marker export: interface_version_*"
418 ))
419 }
420 Err(e) => panic!("Unexpected error {e:?}"),
421 Ok(_) => panic!("This must not succeed"),
422 };
423
424 match check_wasm(
425 CONTRACT_0_7,
426 &default_capabilities(),
427 &WasmLimits::default(),
428 Off,
429 ) {
430 Err(VmError::StaticValidationErr { msg, .. }) => {
431 assert!(msg.contains(
432 "Wasm contract missing a required marker export: interface_version_*"
433 ))
434 }
435 Err(e) => panic!("Unexpected error {e:?}"),
436 Ok(_) => panic!("This must not succeed"),
437 };
438 }
439
440 #[test]
441 fn check_wasm_tables_works() {
442 let limits = WasmLimits::default();
443 let wasm = wat::parse_str("(module)").unwrap();
445 assert!(ParsedWasm::parse(&wasm).unwrap().tables.is_empty());
446
447 let wasm = wat::parse_str("(module (table $name 123 123 funcref))").unwrap();
449 check_wasm_tables(&ParsedWasm::parse(&wasm).unwrap(), &limits).unwrap();
450
451 let wasm = wat::parse_str("(module (table $name 124 123 funcref))").unwrap();
453 let err = &ParsedWasm::parse(&wasm).unwrap_err();
455 assert!(err
456 .to_string()
457 .contains("size minimum must not be greater than maximum"));
458
459 let wasm = wat::parse_str("(module (table $name 100 9999 funcref))").unwrap();
461 let err = check_wasm_tables(&ParsedWasm::parse(&wasm).unwrap(), &limits).unwrap_err();
462 assert!(err
463 .to_string()
464 .contains("Wasm contract's first table section has a too large max limit"));
465
466 let wasm = wat::parse_str("(module (table $name 100 funcref))").unwrap();
468 let err = check_wasm_tables(&ParsedWasm::parse(&wasm).unwrap(), &limits).unwrap_err();
469 assert!(err
470 .to_string()
471 .contains("Wasm contract must not have unbound table section"));
472 }
473
474 #[test]
475 fn check_wasm_memories_ok() {
476 let wasm = wat::parse_str("(module (memory 1))").unwrap();
477 check_wasm_memories(&ParsedWasm::parse(&wasm).unwrap(), &WasmLimits::default()).unwrap()
478 }
479
480 #[test]
481 fn check_wasm_memories_no_memory() {
482 let limits = WasmLimits::default();
483 let wasm = wat::parse_str("(module)").unwrap();
484 match check_wasm_memories(&ParsedWasm::parse(&wasm).unwrap(), &limits) {
485 Err(VmError::StaticValidationErr { msg, .. }) => {
486 assert!(msg.starts_with("Wasm contract must contain exactly one memory"));
487 }
488 Err(e) => panic!("Unexpected error {e:?}"),
489 Ok(_) => panic!("Didn't reject wasm with invalid api"),
490 }
491 }
492
493 #[test]
494 fn check_wasm_memories_two_memories() {
495 let wasm = hex::decode(concat!(
498 "0061736d", "01000000", "05", "05", "02", "0009", "0009", ))
506 .unwrap();
507
508 match ParsedWasm::parse(&wasm) {
510 Err(VmError::StaticValidationErr { msg, .. }) => {
511 assert!(msg.contains("multiple memories"));
512 }
513 Err(e) => panic!("Unexpected error {e:?}"),
514 Ok(_) => panic!("Didn't reject wasm with invalid api"),
515 }
516 }
517
518 #[test]
519 fn check_wasm_memories_zero_memories() {
520 let wasm = hex::decode(concat!(
522 "0061736d", "01000000", "05", "01", "00", ))
528 .unwrap();
529
530 match check_wasm_memories(&ParsedWasm::parse(&wasm).unwrap(), &WasmLimits::default()) {
531 Err(VmError::StaticValidationErr { msg, .. }) => {
532 assert!(msg.starts_with("Wasm contract must contain exactly one memory"));
533 }
534 Err(e) => panic!("Unexpected error {e:?}"),
535 Ok(_) => panic!("Didn't reject wasm with invalid api"),
536 }
537 }
538
539 #[test]
540 fn check_wasm_memories_initial_size() {
541 let limits = WasmLimits::default();
542 let wasm_ok = wat::parse_str("(module (memory 512))").unwrap();
543 check_wasm_memories(&ParsedWasm::parse(&wasm_ok).unwrap(), &limits).unwrap();
544
545 let wasm_too_big = wat::parse_str("(module (memory 513))").unwrap();
546 match check_wasm_memories(&ParsedWasm::parse(&wasm_too_big).unwrap(), &limits) {
547 Err(VmError::StaticValidationErr { msg, .. }) => {
548 assert!(msg.starts_with("Wasm contract memory's minimum must not exceed 512 pages"));
549 }
550 Err(e) => panic!("Unexpected error {e:?}"),
551 Ok(_) => panic!("Didn't reject wasm with invalid api"),
552 }
553 }
554
555 #[test]
556 fn check_wasm_memories_maximum_size() {
557 let wasm_max = wat::parse_str("(module (memory 1 5))").unwrap();
558 match check_wasm_memories(
559 &ParsedWasm::parse(&wasm_max).unwrap(),
560 &WasmLimits::default(),
561 ) {
562 Err(VmError::StaticValidationErr { msg, .. }) => {
563 assert!(msg.starts_with("Wasm contract memory's maximum must be unset"));
564 }
565 Err(e) => panic!("Unexpected error {e:?}"),
566 Ok(_) => panic!("Didn't reject wasm with invalid api"),
567 }
568 }
569
570 #[test]
571 fn check_interface_version_works() {
572 let wasm = wat::parse_str(
574 r#"(module
575 (type (func))
576 (func (type 0) nop)
577 (export "add_one" (func 0))
578 (export "allocate" (func 0))
579 (export "interface_version_8" (func 0))
580 (export "deallocate" (func 0))
581 (export "instantiate" (func 0))
582 )"#,
583 )
584 .unwrap();
585 let module = ParsedWasm::parse(&wasm).unwrap();
586 check_interface_version(&module).unwrap();
587
588 let wasm = wat::parse_str(
590 r#"(module
591 (type (func))
592 (func (type 0) nop)
593 (export "add_one" (func 0))
594 (export "allocate" (func 0))
595 (export "deallocate" (func 0))
596 (export "instantiate" (func 0))
597 )"#,
598 )
599 .unwrap();
600 let module = ParsedWasm::parse(&wasm).unwrap();
601 match check_interface_version(&module).unwrap_err() {
602 VmError::StaticValidationErr { msg, .. } => {
603 assert_eq!(
604 msg,
605 "Wasm contract missing a required marker export: interface_version_*"
606 );
607 }
608 err => panic!("Unexpected error {err:?}"),
609 }
610
611 let wasm = wat::parse_str(
613 r#"(module
614 (type (func))
615 (func (type 0) nop)
616 (export "add_one" (func 0))
617 (export "allocate" (func 0))
618 (export "interface_version_8" (func 0))
619 (export "interface_version_9" (func 0))
620 (export "deallocate" (func 0))
621 (export "instantiate" (func 0))
622 )"#,
623 )
624 .unwrap();
625 let module = ParsedWasm::parse(&wasm).unwrap();
626 match check_interface_version(&module).unwrap_err() {
627 VmError::StaticValidationErr { msg, .. } => {
628 assert_eq!(
629 msg,
630 "Wasm contract contains more than one marker export: interface_version_*"
631 );
632 }
633 err => panic!("Unexpected error {err:?}"),
634 }
635
636 let wasm = wat::parse_str(
638 r#"(module
639 (type (func))
640 (func (type 0) nop)
641 (export "add_one" (func 0))
642 (export "allocate" (func 0))
643 (export "interface_version_6" (func 0))
644 (export "deallocate" (func 0))
645 (export "instantiate" (func 0))
646 )"#,
647 )
648 .unwrap();
649 let module = ParsedWasm::parse(&wasm).unwrap();
650 match check_interface_version(&module).unwrap_err() {
651 VmError::StaticValidationErr { msg, .. } => {
652 assert_eq!(msg, "Wasm contract has unknown interface_version_* marker export (see https://github.com/CosmWasm/cosmwasm/blob/main/packages/vm/README.md)");
653 }
654 err => panic!("Unexpected error {err:?}"),
655 }
656
657 let wasm = wat::parse_str(
659 r#"(module
660 (type (func))
661 (func (type 0) nop)
662 (export "add_one" (func 0))
663 (export "allocate" (func 0))
664 (export "interface_version_broken" (func 0))
665 (export "deallocate" (func 0))
666 (export "instantiate" (func 0))
667 )"#,
668 )
669 .unwrap();
670 let module = ParsedWasm::parse(&wasm).unwrap();
671 match check_interface_version(&module).unwrap_err() {
672 VmError::StaticValidationErr { msg, .. } => {
673 assert_eq!(msg, "Wasm contract has unknown interface_version_* marker export (see https://github.com/CosmWasm/cosmwasm/blob/main/packages/vm/README.md)");
674 }
675 err => panic!("Unexpected error {err:?}"),
676 }
677 }
678
679 #[test]
680 fn check_wasm_exports_works() {
681 let wasm = wat::parse_str(
683 r#"(module
684 (type (func))
685 (func (type 0) nop)
686 (export "add_one" (func 0))
687 (export "allocate" (func 0))
688 (export "deallocate" (func 0))
689 )"#,
690 )
691 .unwrap();
692 let module = ParsedWasm::parse(&wasm).unwrap();
693 check_wasm_exports(&module, Off).unwrap();
694
695 let wasm = wat::parse_str(
697 r#"(module
698 (type (func))
699 (func (type 0) nop)
700 (export "add_one" (func 0))
701 )"#,
702 )
703 .unwrap();
704 let module = ParsedWasm::parse(&wasm).unwrap();
705 match check_wasm_exports(&module, Off) {
706 Err(VmError::StaticValidationErr { msg, .. }) => {
707 assert!(msg.starts_with("Wasm contract doesn't have required export: \"allocate\""));
708 }
709 Err(e) => panic!("Unexpected error {e:?}"),
710 Ok(_) => panic!("Didn't reject wasm with invalid api"),
711 }
712
713 let wasm = wat::parse_str(
715 r#"(module
716 (type (func))
717 (func (type 0) nop)
718 (export "add_one" (func 0))
719 (export "allocate" (func 0))
720 )"#,
721 )
722 .unwrap();
723 let module = ParsedWasm::parse(&wasm).unwrap();
724 match check_wasm_exports(&module, Off) {
725 Err(VmError::StaticValidationErr { msg, .. }) => {
726 assert!(
727 msg.starts_with("Wasm contract doesn't have required export: \"deallocate\"")
728 );
729 }
730 Err(e) => panic!("Unexpected error {e:?}"),
731 Ok(_) => panic!("Didn't reject wasm with invalid api"),
732 }
733 }
734
735 #[test]
736 fn check_wasm_imports_ok() {
737 let wasm = wat::parse_str(
738 r#"(module
739 (import "env" "db_read" (func (param i32 i32) (result i32)))
740 (import "env" "db_write" (func (param i32 i32) (result i32)))
741 (import "env" "db_remove" (func (param i32) (result i32)))
742 (import "env" "addr_validate" (func (param i32) (result i32)))
743 (import "env" "addr_canonicalize" (func (param i32 i32) (result i32)))
744 (import "env" "addr_humanize" (func (param i32 i32) (result i32)))
745 (import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32)))
746 (import "env" "secp256k1_recover_pubkey" (func (param i32 i32 i32) (result i64)))
747 (import "env" "secp256r1_verify" (func (param i32 i32 i32) (result i32)))
748 (import "env" "secp256r1_recover_pubkey" (func (param i32 i32 i32) (result i64)))
749 (import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32)))
750 (import "env" "ed25519_batch_verify" (func (param i32 i32 i32) (result i32)))
751 )"#,
752 )
753 .unwrap();
754 check_wasm_imports(
755 &ParsedWasm::parse(&wasm).unwrap(),
756 SUPPORTED_IMPORTS,
757 &WasmLimits::default(),
758 Off,
759 )
760 .unwrap();
761 }
762
763 #[test]
764 fn check_wasm_imports_exceeds_limit() {
765 let wasm = wat::parse_str(
766 r#"(module
767 (import "env" "db_write" (func (param i32 i32) (result i32)))
768 (import "env" "db_remove" (func (param i32) (result i32)))
769 (import "env" "addr_validate" (func (param i32) (result i32)))
770 (import "env" "addr_canonicalize" (func (param i32 i32) (result i32)))
771 (import "env" "addr_humanize" (func (param i32 i32) (result i32)))
772 (import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32)))
773 (import "env" "secp256k1_recover_pubkey" (func (param i32 i32 i32) (result i64)))
774 (import "env" "secp256r1_verify" (func (param i32 i32 i32) (result i32)))
775 (import "env" "secp256r1_recover_pubkey" (func (param i32 i32 i32) (result i64)))
776 (import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32)))
777 (import "env" "ed25519_batch_verify" (func (param i32 i32 i32) (result i32)))
778 (import "env" "spam01" (func (param i32 i32) (result i32)))
779 (import "env" "spam02" (func (param i32 i32) (result i32)))
780 (import "env" "spam03" (func (param i32 i32) (result i32)))
781 (import "env" "spam04" (func (param i32 i32) (result i32)))
782 (import "env" "spam05" (func (param i32 i32) (result i32)))
783 (import "env" "spam06" (func (param i32 i32) (result i32)))
784 (import "env" "spam07" (func (param i32 i32) (result i32)))
785 (import "env" "spam08" (func (param i32 i32) (result i32)))
786 (import "env" "spam09" (func (param i32 i32) (result i32)))
787 (import "env" "spam10" (func (param i32 i32) (result i32)))
788 (import "env" "spam11" (func (param i32 i32) (result i32)))
789 (import "env" "spam12" (func (param i32 i32) (result i32)))
790 (import "env" "spam13" (func (param i32 i32) (result i32)))
791 (import "env" "spam14" (func (param i32 i32) (result i32)))
792 (import "env" "spam15" (func (param i32 i32) (result i32)))
793 (import "env" "spam16" (func (param i32 i32) (result i32)))
794 (import "env" "spam17" (func (param i32 i32) (result i32)))
795 (import "env" "spam18" (func (param i32 i32) (result i32)))
796 (import "env" "spam19" (func (param i32 i32) (result i32)))
797 (import "env" "spam20" (func (param i32 i32) (result i32)))
798 (import "env" "spam21" (func (param i32 i32) (result i32)))
799 (import "env" "spam22" (func (param i32 i32) (result i32)))
800 (import "env" "spam23" (func (param i32 i32) (result i32)))
801 (import "env" "spam24" (func (param i32 i32) (result i32)))
802 (import "env" "spam25" (func (param i32 i32) (result i32)))
803 (import "env" "spam26" (func (param i32 i32) (result i32)))
804 (import "env" "spam27" (func (param i32 i32) (result i32)))
805 (import "env" "spam28" (func (param i32 i32) (result i32)))
806 (import "env" "spam29" (func (param i32 i32) (result i32)))
807 (import "env" "spam30" (func (param i32 i32) (result i32)))
808 (import "env" "spam31" (func (param i32 i32) (result i32)))
809 (import "env" "spam32" (func (param i32 i32) (result i32)))
810 (import "env" "spam33" (func (param i32 i32) (result i32)))
811 (import "env" "spam34" (func (param i32 i32) (result i32)))
812 (import "env" "spam35" (func (param i32 i32) (result i32)))
813 (import "env" "spam36" (func (param i32 i32) (result i32)))
814 (import "env" "spam37" (func (param i32 i32) (result i32)))
815 (import "env" "spam38" (func (param i32 i32) (result i32)))
816 (import "env" "spam39" (func (param i32 i32) (result i32)))
817 (import "env" "spam40" (func (param i32 i32) (result i32)))
818 (import "env" "spam41" (func (param i32 i32) (result i32)))
819 (import "env" "spam42" (func (param i32 i32) (result i32)))
820 (import "env" "spam43" (func (param i32 i32) (result i32)))
821 (import "env" "spam44" (func (param i32 i32) (result i32)))
822 (import "env" "spam45" (func (param i32 i32) (result i32)))
823 (import "env" "spam46" (func (param i32 i32) (result i32)))
824 (import "env" "spam47" (func (param i32 i32) (result i32)))
825 (import "env" "spam48" (func (param i32 i32) (result i32)))
826 (import "env" "spam49" (func (param i32 i32) (result i32)))
827 (import "env" "spam50" (func (param i32 i32) (result i32)))
828 (import "env" "spam51" (func (param i32 i32) (result i32)))
829 (import "env" "spam52" (func (param i32 i32) (result i32)))
830 (import "env" "spam53" (func (param i32 i32) (result i32)))
831 (import "env" "spam54" (func (param i32 i32) (result i32)))
832 (import "env" "spam55" (func (param i32 i32) (result i32)))
833 (import "env" "spam56" (func (param i32 i32) (result i32)))
834 (import "env" "spam57" (func (param i32 i32) (result i32)))
835 (import "env" "spam58" (func (param i32 i32) (result i32)))
836 (import "env" "spam59" (func (param i32 i32) (result i32)))
837 (import "env" "spam60" (func (param i32 i32) (result i32)))
838 (import "env" "spam61" (func (param i32 i32) (result i32)))
839 (import "env" "spam62" (func (param i32 i32) (result i32)))
840 (import "env" "spam63" (func (param i32 i32) (result i32)))
841 (import "env" "spam64" (func (param i32 i32) (result i32)))
842 (import "env" "spam65" (func (param i32 i32) (result i32)))
843 (import "env" "spam66" (func (param i32 i32) (result i32)))
844 (import "env" "spam67" (func (param i32 i32) (result i32)))
845 (import "env" "spam68" (func (param i32 i32) (result i32)))
846 (import "env" "spam69" (func (param i32 i32) (result i32)))
847 (import "env" "spam70" (func (param i32 i32) (result i32)))
848 (import "env" "spam71" (func (param i32 i32) (result i32)))
849 (import "env" "spam72" (func (param i32 i32) (result i32)))
850 (import "env" "spam73" (func (param i32 i32) (result i32)))
851 (import "env" "spam74" (func (param i32 i32) (result i32)))
852 (import "env" "spam75" (func (param i32 i32) (result i32)))
853 (import "env" "spam76" (func (param i32 i32) (result i32)))
854 (import "env" "spam77" (func (param i32 i32) (result i32)))
855 (import "env" "spam78" (func (param i32 i32) (result i32)))
856 (import "env" "spam79" (func (param i32 i32) (result i32)))
857 (import "env" "spam80" (func (param i32 i32) (result i32)))
858 (import "env" "spam81" (func (param i32 i32) (result i32)))
859 (import "env" "spam82" (func (param i32 i32) (result i32)))
860 (import "env" "spam83" (func (param i32 i32) (result i32)))
861 (import "env" "spam84" (func (param i32 i32) (result i32)))
862 (import "env" "spam85" (func (param i32 i32) (result i32)))
863 (import "env" "spam86" (func (param i32 i32) (result i32)))
864 (import "env" "spam87" (func (param i32 i32) (result i32)))
865 (import "env" "spam88" (func (param i32 i32) (result i32)))
866 (import "env" "spam89" (func (param i32 i32) (result i32)))
867 (import "env" "spam90" (func (param i32 i32) (result i32)))
868 )"#,
869 )
870 .unwrap();
871 let err = check_wasm_imports(
872 &ParsedWasm::parse(&wasm).unwrap(),
873 SUPPORTED_IMPORTS,
874 &WasmLimits::default(),
875 Off,
876 )
877 .unwrap_err();
878 match err {
879 VmError::StaticValidationErr { msg, .. } => {
880 assert_eq!(msg, "Import count exceeds limit. Imports: 101. Limit: 100.");
881 }
882 err => panic!("Unexpected error: {err:?}"),
883 }
884 }
885
886 #[test]
887 fn check_wasm_imports_missing() {
888 let wasm = wat::parse_str(
889 r#"(module
890 (import "env" "foo" (func (param i32 i32) (result i32)))
891 (import "env" "bar" (func (param i32 i32) (result i32)))
892 (import "env" "spammyspam01" (func (param i32 i32) (result i32)))
893 (import "env" "spammyspam02" (func (param i32 i32) (result i32)))
894 (import "env" "spammyspam03" (func (param i32 i32) (result i32)))
895 (import "env" "spammyspam04" (func (param i32 i32) (result i32)))
896 (import "env" "spammyspam05" (func (param i32 i32) (result i32)))
897 (import "env" "spammyspam06" (func (param i32 i32) (result i32)))
898 (import "env" "spammyspam07" (func (param i32 i32) (result i32)))
899 (import "env" "spammyspam08" (func (param i32 i32) (result i32)))
900 (import "env" "spammyspam09" (func (param i32 i32) (result i32)))
901 (import "env" "spammyspam10" (func (param i32 i32) (result i32)))
902 )"#,
903 )
904 .unwrap();
905 let supported_imports: &[&str] = &[
906 "env.db_read",
907 "env.db_write",
908 "env.db_remove",
909 "env.addr_canonicalize",
910 "env.addr_humanize",
911 "env.debug",
912 "env.query_chain",
913 ];
914 let result = check_wasm_imports(
915 &ParsedWasm::parse(&wasm).unwrap(),
916 supported_imports,
917 &WasmLimits::default(),
918 Off,
919 );
920 match result.unwrap_err() {
921 VmError::StaticValidationErr { msg, .. } => {
922 println!("{msg}");
923 assert_eq!(
924 msg,
925 r#"Wasm contract requires unsupported import: "env.foo". Required imports: {"env.bar", "env.foo", "env.spammyspam01", "env.spammyspam02", "env.spammyspam03", "env.spammyspam04", "env.spammyspam05", "env.spammyspam06", "env.spammyspam07", "env.spammyspam08", ... 2 more}. Available imports: ["env.db_read", "env.db_write", "env.db_remove", "env.addr_canonicalize", "env.addr_humanize", "env.debug", "env.query_chain"]."#
926 );
927 }
928 err => panic!("Unexpected error: {err:?}"),
929 }
930 }
931
932 #[test]
933 fn check_wasm_imports_of_old_contract() {
934 let module = &ParsedWasm::parse(CONTRACT_0_7).unwrap();
935 let result = check_wasm_imports(module, SUPPORTED_IMPORTS, &WasmLimits::default(), Off);
936 match result.unwrap_err() {
937 VmError::StaticValidationErr { msg, .. } => {
938 assert!(
939 msg.starts_with("Wasm contract requires unsupported import: \"env.read_db\"")
940 );
941 }
942 err => panic!("Unexpected error: {err:?}"),
943 }
944 }
945
946 #[test]
947 fn check_wasm_imports_wrong_type() {
948 let wasm = wat::parse_str(r#"(module (import "env" "db_read" (memory 1 1)))"#).unwrap();
949 let result = check_wasm_imports(
950 &ParsedWasm::parse(&wasm).unwrap(),
951 SUPPORTED_IMPORTS,
952 &WasmLimits::default(),
953 Off,
954 );
955 match result.unwrap_err() {
956 VmError::StaticValidationErr { msg, .. } => {
957 assert!(
958 msg.starts_with("Wasm contract requires non-function import: \"env.db_read\"")
959 );
960 }
961 err => panic!("Unexpected error: {err:?}"),
962 }
963 }
964
965 #[test]
966 fn check_wasm_capabilities_ok() {
967 let wasm = wat::parse_str(
968 r#"(module
969 (type (func))
970 (func (type 0) nop)
971 (export "requires_water" (func 0))
972 (export "requires_" (func 0))
973 (export "requires_nutrients" (func 0))
974 (export "require_milk" (func 0))
975 (export "REQUIRES_air" (func 0))
976 (export "requires_sun" (func 0))
977 )"#,
978 )
979 .unwrap();
980 let module = ParsedWasm::parse(&wasm).unwrap();
981 let available = [
982 "water".to_string(),
983 "nutrients".to_string(),
984 "sun".to_string(),
985 "freedom".to_string(),
986 ]
987 .into_iter()
988 .collect();
989 check_wasm_capabilities(&module, &available, Off).unwrap();
990 }
991
992 #[test]
993 fn check_wasm_capabilities_fails_for_missing() {
994 let wasm = wat::parse_str(
995 r#"(module
996 (type (func))
997 (func (type 0) nop)
998 (export "requires_water" (func 0))
999 (export "requires_" (func 0))
1000 (export "requires_nutrients" (func 0))
1001 (export "require_milk" (func 0))
1002 (export "REQUIRES_air" (func 0))
1003 (export "requires_sun" (func 0))
1004 )"#,
1005 )
1006 .unwrap();
1007 let module = ParsedWasm::parse(&wasm).unwrap();
1008
1009 let available = [
1011 "water".to_string(),
1012 "nutrients".to_string(),
1013 "freedom".to_string(),
1014 ]
1015 .into_iter()
1016 .collect();
1017 match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
1018 VmError::StaticValidationErr { msg, .. } => assert_eq!(
1019 msg,
1020 "Wasm contract requires unavailable capabilities: {\"sun\"}"
1021 ),
1022 _ => panic!("Got unexpected error"),
1023 }
1024
1025 let available = [
1027 "nutrients".to_string(),
1028 "freedom".to_string(),
1029 "Water".to_string(), ]
1031 .into_iter()
1032 .collect();
1033 match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
1034 VmError::StaticValidationErr { msg, .. } => assert_eq!(
1035 msg,
1036 "Wasm contract requires unavailable capabilities: {\"sun\", \"water\"}"
1037 ),
1038 _ => panic!("Got unexpected error"),
1039 }
1040
1041 let available = ["freedom".to_string()].into_iter().collect();
1043 match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
1044 VmError::StaticValidationErr { msg, .. } => assert_eq!(
1045 msg,
1046 "Wasm contract requires unavailable capabilities: {\"nutrients\", \"sun\", \"water\"}"
1047 ),
1048 _ => panic!("Got unexpected error"),
1049 }
1050
1051 let available = [].into_iter().collect();
1053 match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
1054 VmError::StaticValidationErr { msg, .. } => assert_eq!(
1055 msg,
1056 "Wasm contract requires unavailable capabilities: {\"nutrients\", \"sun\", \"water\"}"
1057 ),
1058 _ => panic!("Got unexpected error"),
1059 }
1060 }
1061
1062 #[test]
1063 fn check_wasm_fails_for_big_functions() {
1064 let limits = WasmLimits::default();
1065 let args = " i32".repeat(limits.max_function_params() + 1);
1067 let wasm = wat::parse_str(format!(
1068 r#"(module
1069 (type (func (param {args})))
1070 (func (type 0) nop)
1071 )"#
1072 ))
1073 .unwrap();
1074 let module = ParsedWasm::parse(&wasm).unwrap();
1075
1076 match check_wasm_functions(&module, &limits, Off).unwrap_err() {
1077 VmError::StaticValidationErr { msg, .. } => assert_eq!(
1078 msg,
1079 "Wasm contract contains function with more than 100 parameters"
1080 ),
1081 _ => panic!("Got unexpected error"),
1082 }
1083
1084 let return_types = " i32".repeat(limits.max_function_results() + 1);
1086 let returns = " i32.const 42".repeat(limits.max_function_results() + 1);
1087 let wasm = wat::parse_str(format!(
1088 r#"(module
1089 (type (func (result {return_types})))
1090 (func (type 0) {returns})
1091 )"#
1092 ))
1093 .unwrap();
1094 let module = ParsedWasm::parse(&wasm).unwrap();
1095 match check_wasm_functions(&module, &limits, Off).unwrap_err() {
1096 VmError::StaticValidationErr { msg, .. } => assert_eq!(
1097 msg,
1098 "Wasm contract contains function with more than 1 results"
1099 ),
1100 _ => panic!("Got unexpected error"),
1101 }
1102
1103 let functions = "(func (type 0) nop)".repeat(limits.max_functions() + 1);
1105 let wasm = wat::parse_str(format!(
1106 r#"(module
1107 (type (func))
1108 {functions}
1109 )"#
1110 ))
1111 .unwrap();
1112 let module = ParsedWasm::parse(&wasm).unwrap();
1113 match check_wasm_functions(&module, &limits, Off).unwrap_err() {
1114 VmError::StaticValidationErr { msg, .. } => {
1115 assert_eq!(msg, "Wasm contract contains more than 20000 functions")
1116 }
1117 _ => panic!("Got unexpected error"),
1118 }
1119 }
1120}