1use crate::{
2 budget::{AsBudget, Budget},
3 err,
4 host::metered_clone::MeteredContainer,
5 meta,
6 xdr::{
7 ContractCodeEntry, ContractCodeEntryExt, ContractCostType, Limited, ReadXdr,
8 ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion, ScErrorCode, ScErrorType,
9 },
10 ErrorHandler, Host, HostError, Val, DEFAULT_XDR_RW_LIMITS,
11};
12
13use super::{Vm, HOST_FUNCTIONS};
14use std::{collections::BTreeSet, io::Cursor, sync::Arc};
15
16#[derive(Debug, Clone)]
17pub enum VersionedContractCodeCostInputs {
18 V0 { wasm_bytes: usize },
19 V1(crate::xdr::ContractCodeCostInputs),
20}
21
22impl VersionedContractCodeCostInputs {
23 pub fn is_v0(&self) -> bool {
24 match self {
25 Self::V0 { .. } => true,
26 Self::V1(_) => false,
27 }
28 }
29
30 pub fn charge_for_parsing(&self, budget: &impl AsBudget) -> Result<(), HostError> {
31 let budget = budget.as_budget();
32 match self {
33 Self::V0 { wasm_bytes } => {
34 budget.charge(ContractCostType::VmInstantiation, Some(*wasm_bytes as u64))?;
35 }
36 Self::V1(inputs) => {
37 budget.charge(
38 ContractCostType::ParseWasmInstructions,
39 Some(inputs.n_instructions as u64),
40 )?;
41 budget.charge(
42 ContractCostType::ParseWasmFunctions,
43 Some(inputs.n_functions as u64),
44 )?;
45 budget.charge(
46 ContractCostType::ParseWasmGlobals,
47 Some(inputs.n_globals as u64),
48 )?;
49 budget.charge(
50 ContractCostType::ParseWasmTableEntries,
51 Some(inputs.n_table_entries as u64),
52 )?;
53 budget.charge(
54 ContractCostType::ParseWasmTypes,
55 Some(inputs.n_types as u64),
56 )?;
57 budget.charge(
58 ContractCostType::ParseWasmDataSegments,
59 Some(inputs.n_data_segments as u64),
60 )?;
61 budget.charge(
62 ContractCostType::ParseWasmElemSegments,
63 Some(inputs.n_elem_segments as u64),
64 )?;
65 budget.charge(
66 ContractCostType::ParseWasmImports,
67 Some(inputs.n_imports as u64),
68 )?;
69 budget.charge(
70 ContractCostType::ParseWasmExports,
71 Some(inputs.n_exports as u64),
72 )?;
73 budget.charge(
74 ContractCostType::ParseWasmDataSegmentBytes,
75 Some(inputs.n_data_segment_bytes as u64),
76 )?;
77 }
78 }
79 Ok(())
80 }
81
82 pub fn charge_for_instantiation(&self, _host: &Host) -> Result<(), HostError> {
83 match self {
84 Self::V0 { wasm_bytes } => {
85 _host.charge_budget(
94 ContractCostType::VmCachedInstantiation,
95 Some(*wasm_bytes as u64),
96 )?;
97 }
98 Self::V1(inputs) => {
99 _host.charge_budget(ContractCostType::InstantiateWasmInstructions, None)?;
100 _host.charge_budget(
101 ContractCostType::InstantiateWasmFunctions,
102 Some(inputs.n_functions as u64),
103 )?;
104 _host.charge_budget(
105 ContractCostType::InstantiateWasmGlobals,
106 Some(inputs.n_globals as u64),
107 )?;
108 _host.charge_budget(
109 ContractCostType::InstantiateWasmTableEntries,
110 Some(inputs.n_table_entries as u64),
111 )?;
112 _host.charge_budget(ContractCostType::InstantiateWasmTypes, None)?;
113 _host.charge_budget(
114 ContractCostType::InstantiateWasmDataSegments,
115 Some(inputs.n_data_segments as u64),
116 )?;
117 _host.charge_budget(
118 ContractCostType::InstantiateWasmElemSegments,
119 Some(inputs.n_elem_segments as u64),
120 )?;
121 _host.charge_budget(
122 ContractCostType::InstantiateWasmImports,
123 Some(inputs.n_imports as u64),
124 )?;
125 _host.charge_budget(
126 ContractCostType::InstantiateWasmExports,
127 Some(inputs.n_exports as u64),
128 )?;
129 _host.charge_budget(
130 ContractCostType::InstantiateWasmDataSegmentBytes,
131 Some(inputs.n_data_segment_bytes as u64),
132 )?;
133 }
134 }
135 Ok(())
136 }
137}
138
139pub trait CompilationContext: AsBudget + ErrorHandler {}
145impl CompilationContext for Host {}
146
147pub struct ParsedModule {
151 pub wasmi_module: wasmi::Module,
152 pub proto_version: u32,
153 pub cost_inputs: VersionedContractCodeCostInputs,
154}
155
156pub fn wasm_module_memory_cost(
157 budget: &Budget,
158 contract_code_entry: &ContractCodeEntry,
159) -> Result<u64, HostError> {
160 match &contract_code_entry.ext {
161 ContractCodeEntryExt::V0 => budget.get_memory_cost(
162 ContractCostType::VmInstantiation,
163 Some(contract_code_entry.code.len() as u64),
164 ),
165 ContractCodeEntryExt::V1(contract_code_entry_v1) => {
166 let cost_inputs = &contract_code_entry_v1.cost_inputs;
167 let mut res = 0_u64;
168 res = res.saturating_add(budget.get_memory_cost(
169 ContractCostType::ParseWasmInstructions,
170 Some(cost_inputs.n_instructions as u64),
171 )?);
172 res = res.saturating_add(budget.get_memory_cost(
173 ContractCostType::ParseWasmFunctions,
174 Some(cost_inputs.n_functions as u64),
175 )?);
176 res = res.saturating_add(budget.get_memory_cost(
177 ContractCostType::ParseWasmGlobals,
178 Some(cost_inputs.n_globals as u64),
179 )?);
180 res = res.saturating_add(budget.get_memory_cost(
181 ContractCostType::ParseWasmTableEntries,
182 Some(cost_inputs.n_table_entries as u64),
183 )?);
184 res = res.saturating_add(budget.get_memory_cost(
185 ContractCostType::ParseWasmTypes,
186 Some(cost_inputs.n_types as u64),
187 )?);
188 res = res.saturating_add(budget.get_memory_cost(
189 ContractCostType::ParseWasmDataSegments,
190 Some(cost_inputs.n_data_segments as u64),
191 )?);
192 res = res.saturating_add(budget.get_memory_cost(
193 ContractCostType::ParseWasmElemSegments,
194 Some(cost_inputs.n_elem_segments as u64),
195 )?);
196 res = res.saturating_add(budget.get_memory_cost(
197 ContractCostType::ParseWasmImports,
198 Some(cost_inputs.n_imports as u64),
199 )?);
200 res = res.saturating_add(budget.get_memory_cost(
201 ContractCostType::ParseWasmExports,
202 Some(cost_inputs.n_exports as u64),
203 )?);
204 res = res.saturating_add(budget.get_memory_cost(
205 ContractCostType::ParseWasmDataSegmentBytes,
206 Some(cost_inputs.n_data_segment_bytes as u64),
207 )?);
208 Ok(res)
209 }
210 }
211}
212
213impl ParsedModule {
214 pub fn new<Ctx: CompilationContext>(
215 context: &Ctx,
216 curr_ledger_protocol: u32,
217 wasmi_engine: &wasmi::Engine,
218 wasm: &[u8],
219 cost_inputs: VersionedContractCodeCostInputs,
220 ) -> Result<Arc<Self>, HostError> {
221 cost_inputs.charge_for_parsing(context.as_budget())?;
222 let (wasmi_module, proto_version) =
223 Self::parse_wasm(context, curr_ledger_protocol, wasmi_engine, wasm)?;
224 Ok(Arc::new(Self {
225 wasmi_module,
226 proto_version,
227 cost_inputs,
228 }))
229 }
230
231 pub fn with_import_symbols<T>(
232 &self,
233 host: &Host,
234 callback: impl FnOnce(&BTreeSet<(&str, &str)>) -> Result<T, HostError>,
235 ) -> Result<T, HostError> {
236 const SYM_LEN_LIMIT: usize = 10;
241 let symbols: BTreeSet<(&str, &str)> = self
242 .wasmi_module
243 .imports()
244 .filter_map(|i| {
245 if i.ty().func().is_some() {
246 let mod_str = i.module();
247 let fn_str = i.name();
248 if mod_str.len() < SYM_LEN_LIMIT && fn_str.len() < SYM_LEN_LIMIT {
249 return Some((mod_str, fn_str));
250 }
251 }
252 None
253 })
254 .collect();
255
256 Vec::<(&str, &str)>::charge_bulk_init_cpy(symbols.len() as u64, host)?;
263 callback(&symbols)
264 }
265
266 pub fn make_wasmi_linker(&self, host: &Host) -> Result<wasmi::Linker<Host>, HostError> {
267 self.with_import_symbols(host, |symbols| {
268 Host::make_minimal_wasmi_linker_for_symbols(host, self.wasmi_module.engine(), symbols)
269 })
270 }
271
272 pub fn new_with_isolated_engine(
273 host: &Host,
274 wasm: &[u8],
275 cost_inputs: VersionedContractCodeCostInputs,
276 ) -> Result<Arc<Self>, HostError> {
277 use crate::budget::AsBudget;
278 let wasmi_config = crate::vm::get_wasmi_config(host.as_budget())?;
279 let wasmi_engine = wasmi::Engine::new(&wasmi_config);
280
281 Self::new(
282 host,
283 host.get_ledger_protocol_version()?,
284 &wasmi_engine,
285 wasm,
286 cost_inputs,
287 )
288 }
289
290 fn parse_wasm<Ctx: CompilationContext>(
292 context: &Ctx,
293 curr_ledger_protocol: u32,
294 wasmi_engine: &wasmi::Engine,
295 wasm: &[u8],
296 ) -> Result<(wasmi::Module, u32), HostError> {
297 let module = {
298 let _span = tracy_span!("wasmi::Module::new");
299 context.map_err(wasmi::Module::new(&wasmi_engine, wasm))?
300 };
301 Self::check_max_args(context, &module)?;
302 let interface_version = Self::check_meta_section(context, curr_ledger_protocol, &module)?;
303 let contract_proto = interface_version.protocol;
304
305 Ok((module, contract_proto))
306 }
307
308 fn check_contract_interface_version<Ctx: CompilationContext>(
309 context: &Ctx,
310 curr_ledger_protocol: u32,
311 interface_version: &ScEnvMetaEntryInterfaceVersion,
312 ) -> Result<(), HostError> {
313 let want_proto = {
314 let env_proto = meta::INTERFACE_VERSION.protocol;
315 if curr_ledger_protocol <= env_proto {
316 curr_ledger_protocol
318 } else {
319 return Err(context.error(
320 (ScErrorType::Context, ScErrorCode::InternalError).into(),
321 "ledger protocol number is ahead of supported env protocol number",
322 &[
323 Val::from_u32(curr_ledger_protocol).to_val(),
324 Val::from_u32(env_proto).to_val(),
325 ],
326 ));
327 }
328 };
329
330 #[cfg(not(feature = "next"))]
332 let got_pre = interface_version.pre_release;
333
334 let got_proto = interface_version.protocol;
335
336 if got_proto < want_proto {
337 #[cfg(not(feature = "next"))]
346 if got_pre != 0 {
347 return Err(context.error(
348 (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
349 "contract pre-release number for old protocol is nonzero",
350 &[Val::from_u32(got_pre).to_val()],
351 ));
352 }
353 } else if got_proto == want_proto {
354 #[cfg(not(feature = "next"))]
358 {
359 let want_pre = meta::INTERFACE_VERSION.pre_release;
362 if want_pre != got_pre {
363 return Err(context.error(
364 (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
365 "contract pre-release number for current protocol does not match host",
366 &[
367 Val::from_u32(got_pre).to_val(),
368 Val::from_u32(want_pre).to_val(),
369 ],
370 ));
371 }
372 }
373 } else {
374 return Err(context.error(
381 (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
382 "contract protocol number is newer than host",
383 &[Val::from_u32(got_proto).to_val()],
384 ));
385 }
386 Ok(())
387 }
388
389 pub(crate) fn check_contract_imports_match_host_protocol(
390 &self,
391 host: &Host,
392 ) -> Result<(), HostError> {
393 let _span = tracy_span!("ParsedModule::check_contract_imports_match_host_protocol");
410 let ledger_proto = host.with_ledger_info(|li| Ok(li.protocol_version))?;
411 self.with_import_symbols(host, |module_symbols| {
412 for hf in HOST_FUNCTIONS {
413 if !module_symbols.contains(&(hf.mod_str, hf.fn_str)) {
414 continue;
415 }
416 if let Some(min_proto) = hf.min_proto {
417 if self.proto_version < min_proto || ledger_proto < min_proto {
418 return Err(host.err(
419 ScErrorType::WasmVm,
420 ScErrorCode::InvalidAction,
421 "contract calls a host function not yet supported by current protocol",
422 &[],
423 ));
424 }
425 }
426 if let Some(max_proto) = hf.max_proto {
427 if self.proto_version > max_proto || ledger_proto > max_proto {
428 return Err(host.err(
429 ScErrorType::WasmVm,
430 ScErrorCode::InvalidAction,
431 "contract calls a host function no longer supported in the current protocol",
432 &[],
433 ));
434 }
435 }
436 }
437 Ok(())
438 })?;
439 Ok(())
440 }
441
442 fn module_custom_section(m: &wasmi::Module, name: impl AsRef<str>) -> Option<&[u8]> {
443 m.custom_sections().iter().find_map(|s| {
444 if &*s.name == name.as_ref() {
445 Some(&*s.data)
446 } else {
447 None
448 }
449 })
450 }
451
452 pub fn custom_section(&self, name: impl AsRef<str>) -> Option<&[u8]> {
455 Self::module_custom_section(&self.wasmi_module, name)
456 }
457
458 fn check_meta_section<Ctx: CompilationContext>(
459 context: &Ctx,
460 curr_ledger_protocol: u32,
461 m: &wasmi::Module,
462 ) -> Result<ScEnvMetaEntryInterfaceVersion, HostError> {
463 if let Some(env_meta) = Self::module_custom_section(m, meta::ENV_META_V0_SECTION_NAME) {
464 let mut limits = DEFAULT_XDR_RW_LIMITS;
465 limits.len = env_meta.len();
466 let mut cursor = Limited::new(Cursor::new(env_meta), limits);
467 if let Some(env_meta_entry) = ScEnvMetaEntry::read_xdr_iter(&mut cursor).next() {
468 let ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) =
469 context.map_err(env_meta_entry)?;
470 Self::check_contract_interface_version(context, curr_ledger_protocol, &v)?;
471 Ok(v)
472 } else {
473 Err(context.error(
474 (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
475 "contract missing environment interface version",
476 &[],
477 ))
478 }
479 } else {
480 Err(context.error(
481 (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
482 "contract missing metadata section",
483 &[],
484 ))
485 }
486 }
487
488 fn check_max_args<E: ErrorHandler>(handler: &E, m: &wasmi::Module) -> Result<(), HostError> {
489 for e in m.exports() {
490 match e.ty() {
491 wasmi::ExternType::Func(f) => {
492 if f.results().len() > Vm::MAX_VM_ARGS {
493 return Err(handler.error(
494 (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
495 "Too many return values in Wasm export",
496 &[Val::from_u32(f.results().len() as u32).to_val()],
497 ));
498 }
499 if f.params().len() > Vm::MAX_VM_ARGS {
500 return Err(handler.error(
501 (ScErrorType::WasmVm, ScErrorCode::InvalidInput).into(),
502 "Too many arguments Wasm export",
503 &[Val::from_u32(f.params().len() as u32).to_val()],
504 ));
505 }
506 }
507 _ => (),
508 }
509 }
510 Ok(())
511 }
512
513 pub fn extract_refined_contract_cost_inputs(
516 host: &Host,
517 wasm: &[u8],
518 ) -> Result<crate::xdr::ContractCodeCostInputs, HostError> {
519 use wasmparser::{ElementItems, ElementKind, Parser, Payload::*, TableInit};
520
521 if !Parser::is_core_wasm(wasm) {
522 return Err(host.err(
523 ScErrorType::WasmVm,
524 ScErrorCode::InvalidInput,
525 "unsupported non-core wasm module",
526 &[],
527 ));
528 }
529
530 let mut costs = crate::xdr::ContractCodeCostInputs {
531 ext: crate::xdr::ExtensionPoint::V0,
532 n_instructions: 0,
533 n_functions: 0,
534 n_globals: 0,
535 n_table_entries: 0,
536 n_types: 0,
537 n_data_segments: 0,
538 n_elem_segments: 0,
539 n_imports: 0,
540 n_exports: 0,
541 n_data_segment_bytes: 0,
542 };
543
544 let parser = Parser::new(0);
545 let mut elements: u32 = 0;
546 let mut available_memory: u32 = 0;
547 for section in parser.parse_all(wasm) {
548 let section = host.map_err(section)?;
549 match section {
550 Version { .. }
552 | DataCountSection { .. }
553 | CustomSection(_)
554 | CodeSectionStart { .. }
555 | End(_) => (),
556
557 StartSection { .. }
559 | ModuleSection { .. }
560 | InstanceSection(_)
561 | CoreTypeSection(_)
562 | ComponentSection { .. }
563 | ComponentInstanceSection(_)
564 | ComponentAliasSection(_)
565 | ComponentTypeSection(_)
566 | ComponentCanonicalSection(_)
567 | ComponentStartSection { .. }
568 | ComponentImportSection(_)
569 | ComponentExportSection(_)
570 | TagSection(_)
571 | UnknownSection { .. } => {
572 return Err(host.err(
573 ScErrorType::WasmVm,
574 ScErrorCode::InvalidInput,
575 "unsupported wasm section type",
576 &[],
577 ))
578 }
579
580 MemorySection(s) => {
581 for mem in s {
582 let mem = host.map_err(mem)?;
583 if mem.memory64 {
584 return Err(host.err(
585 ScErrorType::WasmVm,
586 ScErrorCode::InvalidInput,
587 "unsupported 64-bit memory",
588 &[],
589 ));
590 }
591 if mem.shared {
592 return Err(host.err(
593 ScErrorType::WasmVm,
594 ScErrorCode::InvalidInput,
595 "unsupported shared memory",
596 &[],
597 ));
598 }
599 if mem
600 .initial
601 .saturating_mul(crate::vm::WASM_STD_MEM_PAGE_SIZE_IN_BYTES as u64)
602 > u32::MAX as u64
603 {
604 return Err(host.err(
605 ScErrorType::WasmVm,
606 ScErrorCode::InvalidInput,
607 "unsupported memory size",
608 &[],
609 ));
610 }
611 available_memory = available_memory.saturating_add(
612 (mem.initial as u32)
613 .saturating_mul(crate::vm::WASM_STD_MEM_PAGE_SIZE_IN_BYTES),
614 );
615 }
616 }
617
618 TypeSection(s) => costs.n_types = costs.n_types.saturating_add(s.count()),
619 ImportSection(s) => costs.n_imports = costs.n_imports.saturating_add(s.count()),
620 FunctionSection(s) => {
621 costs.n_functions = costs.n_functions.saturating_add(s.count())
622 }
623 TableSection(s) => {
624 for table in s {
625 let table = host.map_err(table)?;
626 costs.n_table_entries =
627 costs.n_table_entries.saturating_add(table.ty.initial);
628 match table.init {
629 TableInit::RefNull => (),
630 TableInit::Expr(ref expr) => {
631 Self::check_const_expr_simple(&host, &expr)?;
632 }
633 }
634 }
635 }
636 GlobalSection(s) => {
637 costs.n_globals = costs.n_globals.saturating_add(s.count());
638 for global in s {
639 let global = host.map_err(global)?;
640 Self::check_const_expr_simple(&host, &global.init_expr)?;
641 }
642 }
643 ExportSection(s) => costs.n_exports = costs.n_exports.saturating_add(s.count()),
644 ElementSection(s) => {
645 costs.n_elem_segments = costs.n_elem_segments.saturating_add(s.count());
646 for elem in s {
647 let elem = host.map_err(elem)?;
648 match elem.kind {
649 ElementKind::Declared | ElementKind::Passive => (),
650 ElementKind::Active { offset_expr, .. } => {
651 Self::check_const_expr_simple(&host, &offset_expr)?
652 }
653 }
654 match elem.items {
655 ElementItems::Functions(fs) => {
656 elements = elements.saturating_add(fs.count());
657 }
658 ElementItems::Expressions(_, exprs) => {
659 elements = elements.saturating_add(exprs.count());
660 for expr in exprs {
661 let expr = host.map_err(expr)?;
662 Self::check_const_expr_simple(&host, &expr)?;
663 }
664 }
665 }
666 }
667 }
668 DataSection(s) => {
669 costs.n_data_segments = costs.n_data_segments.saturating_add(s.count());
670 for d in s {
671 let d = host.map_err(d)?;
672 if d.data.len() > u32::MAX as usize {
673 return Err(host.err(
674 ScErrorType::WasmVm,
675 ScErrorCode::InvalidInput,
676 "data segment exceeds u32::MAX",
677 &[],
678 ));
679 }
680 costs.n_data_segment_bytes = costs
681 .n_data_segment_bytes
682 .saturating_add(d.data.len() as u32);
683 match d.kind {
684 wasmparser::DataKind::Active { offset_expr, .. } => {
685 Self::check_const_expr_simple(&host, &offset_expr)?
686 }
687 wasmparser::DataKind::Passive => (),
688 }
689 }
690 }
691 CodeSectionEntry(s) => {
692 let ops = host.map_err(s.get_operators_reader())?;
693 for _op in ops {
694 costs.n_instructions = costs.n_instructions.saturating_add(1);
695 }
696 }
697 }
698 }
699 if costs.n_data_segment_bytes > available_memory {
700 return Err(err!(
701 host,
702 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
703 "data segment(s) content exceeds memory size",
704 costs.n_data_segment_bytes,
705 available_memory
706 ));
707 }
708 if elements > costs.n_table_entries {
709 return Err(err!(
710 host,
711 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
712 "elem segments(s) content exceeds table size",
713 elements,
714 costs.n_table_entries
715 ));
716 }
717 Ok(costs)
718 }
719
720 fn check_const_expr_simple(host: &Host, expr: &wasmparser::ConstExpr) -> Result<(), HostError> {
721 use wasmparser::Operator::*;
722 let mut op = expr.get_operators_reader();
723 while !op.eof() {
724 match host.map_err(op.read())? {
725 I32Const { .. } | I64Const { .. } | RefFunc { .. } | RefNull { .. } | End => (),
726 _ => {
727 return Err(host.err(
728 ScErrorType::WasmVm,
729 ScErrorCode::InvalidInput,
730 "unsupported complex Wasm constant expression",
731 &[],
732 ))
733 }
734 }
735 }
736 Ok(())
737 }
738}