soroban_env_host/vm/
module_cache.rs1use super::{
2 func_info::HOST_FUNCTIONS,
3 parsed_module::{ParsedModule, VersionedContractCodeCostInputs},
4};
5use crate::{
6 budget::{get_wasmi_config, AsBudget},
7 host::metered_clone::{MeteredClone, MeteredContainer},
8 xdr::{Hash, ScErrorCode, ScErrorType},
9 Host, HostError, MeteredOrdMap,
10};
11use std::{collections::BTreeSet, rc::Rc};
12use wasmi::Engine;
13
14#[derive(Clone, Default)]
20pub struct ModuleCache {
21 pub(crate) engine: Engine,
22 modules: MeteredOrdMap<Hash, Rc<ParsedModule>, Host>,
23}
24
25impl ModuleCache {
26 pub fn new(host: &Host) -> Result<Self, HostError> {
27 let config = get_wasmi_config(host.as_budget())?;
28 let engine = Engine::new(&config);
29 let modules = MeteredOrdMap::new();
30 let mut cache = Self { engine, modules };
31 cache.add_stored_contracts(host)?;
32 Ok(cache)
33 }
34
35 pub fn add_stored_contracts(&mut self, host: &Host) -> Result<(), HostError> {
36 use crate::xdr::{ContractCodeEntry, ContractCodeEntryExt, LedgerEntryData, LedgerKey};
37 let storage = host.try_borrow_storage()?;
38 for (k, v) in storage.map.iter(host.as_budget())? {
39 #[cfg(any(test, feature = "recording_mode"))]
45 let init_value = if host.in_storage_recording_mode()? {
46 storage.get_snapshot_value(host, k)?
47 } else {
48 v.clone()
49 };
50 #[cfg(any(test, feature = "recording_mode"))]
51 let v = &init_value;
52
53 if let LedgerKey::ContractCode(_) = &**k {
54 if let Some((e, _)) = v {
55 if let LedgerEntryData::ContractCode(ContractCodeEntry { code, hash, ext }) =
56 &e.data
57 {
58 if cfg!(any(test, feature = "testutils")) && code.as_slice().is_empty() {
65 continue;
66 }
67
68 let code_cost_inputs = match ext {
69 ContractCodeEntryExt::V0 => VersionedContractCodeCostInputs::V0 {
70 wasm_bytes: code.len(),
71 },
72 ContractCodeEntryExt::V1(v1) => VersionedContractCodeCostInputs::V1(
73 v1.cost_inputs.metered_clone(host.as_budget())?,
74 ),
75 };
76 self.parse_and_cache_module(host, hash, code, code_cost_inputs)?;
77 }
78 }
79 }
80 }
81 Ok(())
82 }
83
84 pub fn parse_and_cache_module(
85 &mut self,
86 host: &Host,
87 contract_id: &Hash,
88 wasm: &[u8],
89 cost_inputs: VersionedContractCodeCostInputs,
90 ) -> Result<(), HostError> {
91 if self.modules.contains_key(contract_id, host)? {
92 return Err(host.err(
93 ScErrorType::Context,
94 ScErrorCode::InternalError,
95 "module cache already contains contract",
96 &[],
97 ));
98 }
99 let parsed_module = ParsedModule::new(host, &self.engine, &wasm, cost_inputs)?;
100 self.modules =
101 self.modules
102 .insert(contract_id.metered_clone(host)?, parsed_module, host)?;
103 Ok(())
104 }
105
106 pub fn with_import_symbols<T>(
107 &self,
108 host: &Host,
109 callback: impl FnOnce(&BTreeSet<(&str, &str)>) -> Result<T, HostError>,
110 ) -> Result<T, HostError> {
111 let mut import_symbols = BTreeSet::new();
112 for module in self.modules.values(host)? {
113 module.with_import_symbols(host, |module_symbols| {
114 for hf in HOST_FUNCTIONS {
115 let sym = (hf.mod_str, hf.fn_str);
116 if module_symbols.contains(&sym) {
117 import_symbols.insert(sym);
118 }
119 }
120 Ok(())
121 })?;
122 }
123 Vec::<(&str, &str)>::charge_bulk_init_cpy(import_symbols.len() as u64, host)?;
131 callback(&import_symbols)
132 }
133
134 pub fn make_linker(&self, host: &Host) -> Result<wasmi::Linker<Host>, HostError> {
135 self.with_import_symbols(host, |symbols| Host::make_linker(&self.engine, symbols))
136 }
137
138 pub fn get_module(
139 &self,
140 host: &Host,
141 wasm_hash: &Hash,
142 ) -> Result<Option<Rc<ParsedModule>>, HostError> {
143 if let Some(m) = self.modules.get(wasm_hash, host)? {
144 Ok(Some(m.clone()))
145 } else {
146 Ok(None)
147 }
148 }
149}