soroban_env_host_zephyr/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 const MIN_LEDGER_VERSION: u32 = 21;
28
29 pub fn new(host: &Host) -> Result<Self, HostError> {
30 let config = get_wasmi_config(host.as_budget())?;
31 let engine = Engine::new(&config);
32 let modules = MeteredOrdMap::new();
33 let mut cache = Self { engine, modules };
34 cache.add_stored_contracts(host)?;
35 Ok(cache)
36 }
37
38 pub fn add_stored_contracts(&mut self, host: &Host) -> Result<(), HostError> {
39 use crate::xdr::{ContractCodeEntry, ContractCodeEntryExt, LedgerEntryData, LedgerKey};
40 let storage = host.try_borrow_storage()?;
41 for (k, v) in storage.map.iter(host.as_budget())? {
42 #[cfg(any(test, feature = "recording_mode"))]
48 let init_value = if host.in_storage_recording_mode()? {
49 storage.get_snapshot_value(host, k)?
50 } else {
51 v.clone()
52 };
53 #[cfg(any(test, feature = "recording_mode"))]
54 let v = &init_value;
55
56 if let LedgerKey::ContractCode(_) = &**k {
57 if let Some((e, _)) = v {
58 if let LedgerEntryData::ContractCode(ContractCodeEntry { code, hash, ext }) =
59 &e.data
60 {
61 if cfg!(any(test, feature = "testutils")) && code.as_slice().is_empty() {
68 continue;
69 }
70
71 let code_cost_inputs = match ext {
72 ContractCodeEntryExt::V0 => VersionedContractCodeCostInputs::V0 {
73 wasm_bytes: code.len(),
74 },
75 ContractCodeEntryExt::V1(v1) => VersionedContractCodeCostInputs::V1(
76 v1.cost_inputs.metered_clone(host.as_budget())?,
77 ),
78 };
79 self.parse_and_cache_module(host, hash, code, code_cost_inputs)?;
80 }
81 }
82 }
83 }
84 Ok(())
85 }
86
87 pub fn parse_and_cache_module(
88 &mut self,
89 host: &Host,
90 contract_id: &Hash,
91 wasm: &[u8],
92 cost_inputs: VersionedContractCodeCostInputs,
93 ) -> Result<(), HostError> {
94 if self.modules.contains_key(contract_id, host)? {
95 return Err(host.err(
96 ScErrorType::Context,
97 ScErrorCode::InternalError,
98 "module cache already contains contract",
99 &[],
100 ));
101 }
102 let parsed_module = ParsedModule::new(host, &self.engine, &wasm, cost_inputs)?;
103 self.modules =
104 self.modules
105 .insert(contract_id.metered_clone(host)?, parsed_module, host)?;
106 Ok(())
107 }
108
109 pub fn with_import_symbols<T>(
110 &self,
111 host: &Host,
112 callback: impl FnOnce(&BTreeSet<(&str, &str)>) -> Result<T, HostError>,
113 ) -> Result<T, HostError> {
114 let mut import_symbols = BTreeSet::new();
115 for module in self.modules.values(host)? {
116 module.with_import_symbols(host, |module_symbols| {
117 for hf in HOST_FUNCTIONS {
118 let sym = (hf.mod_str, hf.fn_str);
119 if module_symbols.contains(&sym) {
120 import_symbols.insert(sym);
121 }
122 }
123 Ok(())
124 })?;
125 }
126 Vec::<(&str, &str)>::charge_bulk_init_cpy(import_symbols.len() as u64, host)?;
134 callback(&import_symbols)
135 }
136
137 pub fn make_linker(&self, host: &Host) -> Result<wasmi::Linker<Host>, HostError> {
138 self.with_import_symbols(host, |symbols| Host::make_linker(&self.engine, symbols))
139 }
140
141 pub fn get_module(
142 &self,
143 host: &Host,
144 wasm_hash: &Hash,
145 ) -> Result<Option<Rc<ParsedModule>>, HostError> {
146 if let Some(m) = self.modules.get(wasm_hash, host)? {
147 Ok(Some(m.clone()))
148 } else {
149 Ok(None)
150 }
151 }
152}