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