1use crate::{
2 err,
3 host::metered_clone::MeteredContainer,
4 meta,
5 xdr::{
6 ContractCostType, Limited, ReadXdr, ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion,
7 ScErrorCode, ScErrorType,
8 },
9 Host, HostError, DEFAULT_XDR_RW_LIMITS,
10};
11
12use wasmi::{Engine, Module};
13
14use super::Vm;
15use std::{collections::BTreeSet, io::Cursor, rc::Rc};
16
17#[derive(Debug, Clone)]
18pub enum VersionedContractCodeCostInputs {
19 V0 { wasm_bytes: usize },
20 V1(crate::xdr::ContractCodeCostInputs),
21}
22
23impl VersionedContractCodeCostInputs {
24 pub fn is_v0(&self) -> bool {
25 match self {
26 Self::V0 { .. } => true,
27 Self::V1(_) => false,
28 }
29 }
30 pub fn charge_for_parsing(&self, host: &Host) -> Result<(), HostError> {
31 match self {
32 Self::V0 { wasm_bytes } => {
33 host.charge_budget(ContractCostType::VmInstantiation, Some(*wasm_bytes as u64))?;
34 }
35 Self::V1(inputs) => {
36 host.charge_budget(
37 ContractCostType::ParseWasmInstructions,
38 Some(inputs.n_instructions as u64),
39 )?;
40 host.charge_budget(
41 ContractCostType::ParseWasmFunctions,
42 Some(inputs.n_functions as u64),
43 )?;
44 host.charge_budget(
45 ContractCostType::ParseWasmGlobals,
46 Some(inputs.n_globals as u64),
47 )?;
48 host.charge_budget(
49 ContractCostType::ParseWasmTableEntries,
50 Some(inputs.n_table_entries as u64),
51 )?;
52 host.charge_budget(
53 ContractCostType::ParseWasmTypes,
54 Some(inputs.n_types as u64),
55 )?;
56 host.charge_budget(
57 ContractCostType::ParseWasmDataSegments,
58 Some(inputs.n_data_segments as u64),
59 )?;
60 host.charge_budget(
61 ContractCostType::ParseWasmElemSegments,
62 Some(inputs.n_elem_segments as u64),
63 )?;
64 host.charge_budget(
65 ContractCostType::ParseWasmImports,
66 Some(inputs.n_imports as u64),
67 )?;
68 host.charge_budget(
69 ContractCostType::ParseWasmExports,
70 Some(inputs.n_exports as u64),
71 )?;
72 host.charge_budget(
73 ContractCostType::ParseWasmDataSegmentBytes,
74 Some(inputs.n_data_segment_bytes as u64),
75 )?;
76 }
77 }
78 Ok(())
79 }
80 pub fn charge_for_instantiation(&self, _host: &Host) -> Result<(), HostError> {
81 match self {
82 Self::V0 { wasm_bytes } => {
83 _host.charge_budget(
92 ContractCostType::VmCachedInstantiation,
93 Some(*wasm_bytes as u64),
94 )?;
95 }
96 Self::V1(inputs) => {
97 _host.charge_budget(ContractCostType::InstantiateWasmInstructions, None)?;
98 _host.charge_budget(
99 ContractCostType::InstantiateWasmFunctions,
100 Some(inputs.n_functions as u64),
101 )?;
102 _host.charge_budget(
103 ContractCostType::InstantiateWasmGlobals,
104 Some(inputs.n_globals as u64),
105 )?;
106 _host.charge_budget(
107 ContractCostType::InstantiateWasmTableEntries,
108 Some(inputs.n_table_entries as u64),
109 )?;
110 _host.charge_budget(ContractCostType::InstantiateWasmTypes, None)?;
111 _host.charge_budget(
112 ContractCostType::InstantiateWasmDataSegments,
113 Some(inputs.n_data_segments as u64),
114 )?;
115 _host.charge_budget(
116 ContractCostType::InstantiateWasmElemSegments,
117 Some(inputs.n_elem_segments as u64),
118 )?;
119 _host.charge_budget(
120 ContractCostType::InstantiateWasmImports,
121 Some(inputs.n_imports as u64),
122 )?;
123 _host.charge_budget(
124 ContractCostType::InstantiateWasmExports,
125 Some(inputs.n_exports as u64),
126 )?;
127 _host.charge_budget(
128 ContractCostType::InstantiateWasmDataSegmentBytes,
129 Some(inputs.n_data_segment_bytes as u64),
130 )?;
131 }
132 }
133 Ok(())
134 }
135}
136
137pub struct ParsedModule {
141 pub module: Module,
142 pub proto_version: u32,
143 pub cost_inputs: VersionedContractCodeCostInputs,
144}
145
146impl ParsedModule {
147 pub fn new(
148 host: &Host,
149 engine: &Engine,
150 wasm: &[u8],
151 cost_inputs: VersionedContractCodeCostInputs,
152 ) -> Result<Rc<Self>, HostError> {
153 cost_inputs.charge_for_parsing(host)?;
154 let (module, proto_version) = Self::parse_wasm(host, engine, wasm)?;
155 Ok(Rc::new(Self {
156 module,
157 proto_version,
158 cost_inputs,
159 }))
160 }
161
162 pub fn with_import_symbols<T>(
163 &self,
164 host: &Host,
165 callback: impl FnOnce(&BTreeSet<(&str, &str)>) -> Result<T, HostError>,
166 ) -> Result<T, HostError> {
167 const SYM_LEN_LIMIT: usize = 10;
172 let symbols: BTreeSet<(&str, &str)> = self
173 .module
174 .imports()
175 .filter_map(|i| {
176 if i.ty().func().is_some() {
177 let mod_str = i.module();
178 let fn_str = i.name();
179 if mod_str.len() < SYM_LEN_LIMIT && fn_str.len() < SYM_LEN_LIMIT {
180 return Some((mod_str, fn_str));
181 }
182 }
183 None
184 })
185 .collect();
186 Vec::<(&str, &str)>::charge_bulk_init_cpy(symbols.len() as u64, host)?;
193 callback(&symbols)
194 }
195
196 pub fn make_linker(&self, host: &Host) -> Result<wasmi::Linker<Host>, HostError> {
197 self.with_import_symbols(host, |symbols| {
198 Host::make_linker(self.module.engine(), symbols)
199 })
200 }
201
202 pub fn new_with_isolated_engine(
203 host: &Host,
204 wasm: &[u8],
205 cost_inputs: VersionedContractCodeCostInputs,
206 ) -> Result<Rc<Self>, HostError> {
207 use crate::budget::AsBudget;
208 let config = crate::vm::get_wasmi_config(host.as_budget())?;
209 let engine = Engine::new(&config);
210 Self::new(host, &engine, wasm, cost_inputs)
211 }
212
213 fn parse_wasm(host: &Host, engine: &Engine, wasm: &[u8]) -> Result<(Module, u32), HostError> {
215 let module = {
216 let _span0 = tracy_span!("parse module");
217 host.map_err(Module::new(&engine, wasm))?
218 };
219
220 Self::check_max_args(host, &module)?;
221 let interface_version = Self::check_meta_section(host, &module)?;
222 let contract_proto = interface_version.protocol;
223
224 Ok((module, contract_proto))
225 }
226
227 fn check_contract_interface_version(
228 host: &Host,
229 interface_version: &ScEnvMetaEntryInterfaceVersion,
230 ) -> Result<(), HostError> {
231 let want_proto = {
232 let ledger_proto = host.get_ledger_protocol_version()?;
233 let env_proto = meta::INTERFACE_VERSION.protocol;
234 if ledger_proto <= env_proto {
235 ledger_proto
237 } else {
238 return Err(err!(
239 host,
240 (ScErrorType::Context, ScErrorCode::InternalError),
241 "ledger protocol number is ahead of supported env protocol number",
242 ledger_proto,
243 env_proto
244 ));
245 }
246 };
247
248 #[cfg(not(feature = "next"))]
250 let got_pre = interface_version.pre_release;
251
252 let got_proto = interface_version.protocol;
253
254 if got_proto < want_proto {
255 #[cfg(not(feature = "next"))]
264 if got_pre != 0 {
265 return Err(err!(
266 host,
267 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
268 "contract pre-release number for old protocol is nonzero",
269 got_pre
270 ));
271 }
272 } else if got_proto == want_proto {
273 #[cfg(not(feature = "next"))]
277 {
278 let want_pre = meta::INTERFACE_VERSION.pre_release;
281 if want_pre != got_pre {
282 return Err(err!(
283 host,
284 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
285 "contract pre-release number for current protocol does not match host",
286 got_pre,
287 want_pre
288 ));
289 }
290 }
291 } else {
292 return Err(err!(
299 host,
300 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
301 "contract protocol number is newer than host",
302 got_proto
303 ));
304 }
305 Ok(())
306 }
307
308 fn module_custom_section(m: &Module, name: impl AsRef<str>) -> Option<&[u8]> {
309 m.custom_sections().iter().find_map(|s| {
310 if &*s.name == name.as_ref() {
311 Some(&*s.data)
312 } else {
313 None
314 }
315 })
316 }
317
318 pub fn custom_section(&self, name: impl AsRef<str>) -> Option<&[u8]> {
321 Self::module_custom_section(&self.module, name)
322 }
323
324 fn check_meta_section(
325 host: &Host,
326 m: &Module,
327 ) -> Result<ScEnvMetaEntryInterfaceVersion, HostError> {
328 if let Some(env_meta) = Self::module_custom_section(m, meta::ENV_META_V0_SECTION_NAME) {
329 let mut limits = DEFAULT_XDR_RW_LIMITS;
330 limits.len = env_meta.len();
331 let mut cursor = Limited::new(Cursor::new(env_meta), limits);
332 if let Some(env_meta_entry) = ScEnvMetaEntry::read_xdr_iter(&mut cursor).next() {
333 let ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) =
334 host.map_err(env_meta_entry)?;
335 Self::check_contract_interface_version(host, &v)?;
336 Ok(v)
337 } else {
338 Err(host.err(
339 ScErrorType::WasmVm,
340 ScErrorCode::InvalidInput,
341 "contract missing environment interface version",
342 &[],
343 ))
344 }
345 } else {
346 Err(host.err(
347 ScErrorType::WasmVm,
348 ScErrorCode::InvalidInput,
349 "contract missing metadata section",
350 &[],
351 ))
352 }
353 }
354
355 fn check_max_args(host: &Host, m: &Module) -> Result<(), HostError> {
356 for e in m.exports() {
357 match e.ty() {
358 wasmi::ExternType::Func(f) => {
359 if f.results().len() > Vm::MAX_VM_ARGS {
360 return Err(err!(
361 host,
362 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
363 "Too many return values in Wasm export",
364 f.results().len()
365 ));
366 }
367 if f.params().len() > Vm::MAX_VM_ARGS {
368 return Err(err!(
369 host,
370 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
371 "Too many arguments Wasm export",
372 f.params().len()
373 ));
374 }
375 }
376 _ => (),
377 }
378 }
379 Ok(())
380 }
381
382 pub fn extract_refined_contract_cost_inputs(
385 host: &Host,
386 wasm: &[u8],
387 ) -> Result<crate::xdr::ContractCodeCostInputs, HostError> {
388 use wasmparser::{ElementItems, ElementKind, Parser, Payload::*, TableInit};
389
390 if !Parser::is_core_wasm(wasm) {
391 return Err(host.err(
392 ScErrorType::WasmVm,
393 ScErrorCode::InvalidInput,
394 "unsupported non-core wasm module",
395 &[],
396 ));
397 }
398
399 let mut costs = crate::xdr::ContractCodeCostInputs {
400 ext: crate::xdr::ExtensionPoint::V0,
401 n_instructions: 0,
402 n_functions: 0,
403 n_globals: 0,
404 n_table_entries: 0,
405 n_types: 0,
406 n_data_segments: 0,
407 n_elem_segments: 0,
408 n_imports: 0,
409 n_exports: 0,
410 n_data_segment_bytes: 0,
411 };
412
413 let parser = Parser::new(0);
414 let mut elements: u32 = 0;
415 let mut available_memory: u32 = 0;
416 for section in parser.parse_all(wasm) {
417 let section = host.map_err(section)?;
418 match section {
419 Version { .. }
421 | DataCountSection { .. }
422 | CustomSection(_)
423 | CodeSectionStart { .. }
424 | End(_) => (),
425
426 StartSection { .. }
428 | ModuleSection { .. }
429 | InstanceSection(_)
430 | CoreTypeSection(_)
431 | ComponentSection { .. }
432 | ComponentInstanceSection(_)
433 | ComponentAliasSection(_)
434 | ComponentTypeSection(_)
435 | ComponentCanonicalSection(_)
436 | ComponentStartSection { .. }
437 | ComponentImportSection(_)
438 | ComponentExportSection(_)
439 | TagSection(_)
440 | UnknownSection { .. } => {
441 return Err(host.err(
442 ScErrorType::WasmVm,
443 ScErrorCode::InvalidInput,
444 "unsupported wasm section type",
445 &[],
446 ))
447 }
448
449 MemorySection(s) => {
450 for mem in s {
451 let mem = host.map_err(mem)?;
452 if mem.memory64 {
453 return Err(host.err(
454 ScErrorType::WasmVm,
455 ScErrorCode::InvalidInput,
456 "unsupported 64-bit memory",
457 &[],
458 ));
459 }
460 if mem.shared {
461 return Err(host.err(
462 ScErrorType::WasmVm,
463 ScErrorCode::InvalidInput,
464 "unsupported shared memory",
465 &[],
466 ));
467 }
468 if mem
469 .initial
470 .saturating_mul(crate::vm::WASM_STD_MEM_PAGE_SIZE_IN_BYTES as u64)
471 > u32::MAX as u64
472 {
473 return Err(host.err(
474 ScErrorType::WasmVm,
475 ScErrorCode::InvalidInput,
476 "unsupported memory size",
477 &[],
478 ));
479 }
480 available_memory = available_memory.saturating_add(
481 (mem.initial as u32)
482 .saturating_mul(crate::vm::WASM_STD_MEM_PAGE_SIZE_IN_BYTES),
483 );
484 }
485 }
486
487 TypeSection(s) => costs.n_types = costs.n_types.saturating_add(s.count()),
488 ImportSection(s) => costs.n_imports = costs.n_imports.saturating_add(s.count()),
489 FunctionSection(s) => {
490 costs.n_functions = costs.n_functions.saturating_add(s.count())
491 }
492 TableSection(s) => {
493 for table in s {
494 let table = host.map_err(table)?;
495 costs.n_table_entries =
496 costs.n_table_entries.saturating_add(table.ty.initial);
497 match table.init {
498 TableInit::RefNull => (),
499 TableInit::Expr(ref expr) => {
500 Self::check_const_expr_simple(&host, &expr)?;
501 }
502 }
503 }
504 }
505 GlobalSection(s) => {
506 costs.n_globals = costs.n_globals.saturating_add(s.count());
507 for global in s {
508 let global = host.map_err(global)?;
509 Self::check_const_expr_simple(&host, &global.init_expr)?;
510 }
511 }
512 ExportSection(s) => costs.n_exports = costs.n_exports.saturating_add(s.count()),
513 ElementSection(s) => {
514 costs.n_elem_segments = costs.n_elem_segments.saturating_add(s.count());
515 for elem in s {
516 let elem = host.map_err(elem)?;
517 match elem.kind {
518 ElementKind::Declared | ElementKind::Passive => (),
519 ElementKind::Active { offset_expr, .. } => {
520 Self::check_const_expr_simple(&host, &offset_expr)?
521 }
522 }
523 match elem.items {
524 ElementItems::Functions(fs) => {
525 elements = elements.saturating_add(fs.count());
526 }
527 ElementItems::Expressions(_, exprs) => {
528 elements = elements.saturating_add(exprs.count());
529 for expr in exprs {
530 let expr = host.map_err(expr)?;
531 Self::check_const_expr_simple(&host, &expr)?;
532 }
533 }
534 }
535 }
536 }
537 DataSection(s) => {
538 costs.n_data_segments = costs.n_data_segments.saturating_add(s.count());
539 for d in s {
540 let d = host.map_err(d)?;
541 if d.data.len() > u32::MAX as usize {
542 return Err(host.err(
543 ScErrorType::WasmVm,
544 ScErrorCode::InvalidInput,
545 "data segment exceeds u32::MAX",
546 &[],
547 ));
548 }
549 costs.n_data_segment_bytes = costs
550 .n_data_segment_bytes
551 .saturating_add(d.data.len() as u32);
552 match d.kind {
553 wasmparser::DataKind::Active { offset_expr, .. } => {
554 Self::check_const_expr_simple(&host, &offset_expr)?
555 }
556 wasmparser::DataKind::Passive => (),
557 }
558 }
559 }
560 CodeSectionEntry(s) => {
561 let ops = host.map_err(s.get_operators_reader())?;
562 for _op in ops {
563 costs.n_instructions = costs.n_instructions.saturating_add(1);
564 }
565 }
566 }
567 }
568 if costs.n_data_segment_bytes > available_memory {
569 return Err(err!(
570 host,
571 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
572 "data segment(s) content exceeds memory size",
573 costs.n_data_segment_bytes,
574 available_memory
575 ));
576 }
577 if elements > costs.n_table_entries {
578 return Err(err!(
579 host,
580 (ScErrorType::WasmVm, ScErrorCode::InvalidInput),
581 "elem segments(s) content exceeds table size",
582 elements,
583 costs.n_table_entries
584 ));
585 }
586 Ok(costs)
587 }
588
589 fn check_const_expr_simple(host: &Host, expr: &wasmparser::ConstExpr) -> Result<(), HostError> {
590 use wasmparser::Operator::*;
591 let mut op = expr.get_operators_reader();
592 while !op.eof() {
593 match host.map_err(op.read())? {
594 I32Const { .. } | I64Const { .. } | RefFunc { .. } | RefNull { .. } | End => (),
595 _ => {
596 return Err(host.err(
597 ScErrorType::WasmVm,
598 ScErrorCode::InvalidInput,
599 "unsupported complex Wasm constant expression",
600 &[],
601 ))
602 }
603 }
604 }
605 Ok(())
606 }
607}