cosmwasm_vm/
cache.rs

1use std::collections::{BTreeSet, HashSet};
2use std::fs::{self, File, OpenOptions};
3use std::io::{Read, Write};
4use std::marker::PhantomData;
5use std::path::{Path, PathBuf};
6use std::str::FromStr;
7use std::sync::Mutex;
8use wasmer::{Module, Store};
9
10use cosmwasm_std::Checksum;
11
12use crate::backend::{Backend, BackendApi, Querier, Storage};
13use crate::capabilities::required_capabilities_from_module;
14use crate::compatibility::check_wasm;
15use crate::config::{CacheOptions, Config, WasmLimits};
16use crate::errors::{VmError, VmResult};
17use crate::filesystem::mkdir_p;
18use crate::instance::{Instance, InstanceOptions};
19use crate::modules::{CachedModule, FileSystemCache, InMemoryCache, PinnedMemoryCache};
20use crate::parsed_wasm::ParsedWasm;
21use crate::size::Size;
22use crate::static_analysis::{Entrypoint, ExportInfo, REQUIRED_IBC_EXPORTS};
23use crate::wasm_backend::{compile, make_compiling_engine};
24
25const STATE_DIR: &str = "state";
26// Things related to the state of the blockchain.
27const WASM_DIR: &str = "wasm";
28
29const CACHE_DIR: &str = "cache";
30// Cacheable things.
31const MODULES_DIR: &str = "modules";
32
33/// Statistics about the usage of a cache instance. Those values are node
34/// specific and must not be used in a consensus critical context.
35/// When a node is hit by a client for simulations or other queries, hits and misses
36/// increase. Also a node restart will reset the values.
37///
38/// All values should be increment using saturated addition to ensure the node does not
39/// crash in case the stats exceed the integer limit.
40#[derive(Debug, Default, Clone, Copy)]
41pub struct Stats {
42    pub hits_pinned_memory_cache: u32,
43    pub hits_memory_cache: u32,
44    pub hits_fs_cache: u32,
45    pub misses: u32,
46}
47
48#[derive(Debug, Clone, Copy)]
49pub struct Metrics {
50    pub stats: Stats,
51    pub elements_pinned_memory_cache: usize,
52    pub elements_memory_cache: usize,
53    pub size_pinned_memory_cache: usize,
54    pub size_memory_cache: usize,
55}
56
57#[derive(Debug, Clone)]
58pub struct PerModuleMetrics {
59    /// Hits (i.e. loads) of the module from the cache
60    pub hits: u32,
61    /// Size the module takes up in memory
62    pub size: usize,
63}
64
65#[derive(Debug, Clone)]
66pub struct PinnedMetrics {
67    // It is *intentional* that this is only a vector
68    // We don't need a potentially expensive hashing algorithm here
69    // The checksums are sourced from a hashmap already, ensuring uniqueness of the checksums
70    pub per_module: Vec<(Checksum, PerModuleMetrics)>,
71}
72
73pub struct CacheInner {
74    /// The directory in which the Wasm blobs are stored in the file system.
75    wasm_path: PathBuf,
76    pinned_memory_cache: PinnedMemoryCache,
77    memory_cache: InMemoryCache,
78    fs_cache: FileSystemCache,
79    stats: Stats,
80}
81
82pub struct Cache<A: BackendApi, S: Storage, Q: Querier> {
83    /// Available capabilities are immutable for the lifetime of the cache,
84    /// i.e. any number of read-only references is allowed to access it concurrently.
85    available_capabilities: HashSet<String>,
86    inner: Mutex<CacheInner>,
87    instance_memory_limit: Size,
88    // Those two don't store data but only fix type information
89    type_api: PhantomData<A>,
90    type_storage: PhantomData<S>,
91    type_querier: PhantomData<Q>,
92    /// To prevent concurrent access to `WasmerInstance::new`
93    instantiation_lock: Mutex<()>,
94    wasm_limits: WasmLimits,
95}
96
97#[derive(PartialEq, Eq, Debug)]
98#[non_exhaustive]
99pub struct AnalysisReport {
100    /// `true` if and only if all [`REQUIRED_IBC_EXPORTS`] exist as exported functions.
101    /// This does not guarantee they are functional or even have the correct signatures.
102    pub has_ibc_entry_points: bool,
103    /// A set of all entrypoints that are exported by the contract.
104    pub entrypoints: BTreeSet<Entrypoint>,
105    /// The set of capabilities the contract requires.
106    pub required_capabilities: BTreeSet<String>,
107    /// The contract migrate version exported set by the contract developer
108    pub contract_migrate_version: Option<u64>,
109}
110
111impl<A, S, Q> Cache<A, S, Q>
112where
113    A: BackendApi + 'static, // 'static is needed by `impl<…> Instance`
114    S: Storage + 'static,    // 'static is needed by `impl<…> Instance`
115    Q: Querier + 'static,    // 'static is needed by `impl<…> Instance`
116{
117    /// Creates a new cache that stores data in `base_dir`.
118    ///
119    /// # Safety
120    ///
121    /// This function is marked unsafe due to `FileSystemCache::new`, which implicitly
122    /// assumes the disk contents are correct, and there's no way to ensure the artifacts
123    /// stored in the cache haven't been corrupted or tampered with.
124    pub unsafe fn new(options: CacheOptions) -> VmResult<Self> {
125        Self::new_with_config(Config {
126            wasm_limits: WasmLimits::default(),
127            cache: options,
128        })
129    }
130
131    /// Creates a new cache with the given configuration.
132    /// This allows configuring lots of limits and sizes.
133    ///
134    /// # Safety
135    ///
136    /// This function is marked unsafe due to `FileSystemCache::new`, which implicitly
137    /// assumes the disk contents are correct, and there's no way to ensure the artifacts
138    /// stored in the cache haven't been corrupted or tampered with.
139    pub unsafe fn new_with_config(config: Config) -> VmResult<Self> {
140        let Config {
141            cache:
142                CacheOptions {
143                    base_dir,
144                    available_capabilities,
145                    memory_cache_size_bytes,
146                    instance_memory_limit_bytes,
147                },
148            wasm_limits,
149        } = config;
150
151        let state_path = base_dir.join(STATE_DIR);
152        let cache_path = base_dir.join(CACHE_DIR);
153
154        let wasm_path = state_path.join(WASM_DIR);
155
156        // Ensure all the needed directories exist on disk.
157        mkdir_p(&state_path).map_err(|_e| VmError::cache_err("Error creating state directory"))?;
158        mkdir_p(&cache_path).map_err(|_e| VmError::cache_err("Error creating cache directory"))?;
159        mkdir_p(&wasm_path).map_err(|_e| VmError::cache_err("Error creating wasm directory"))?;
160
161        let fs_cache = FileSystemCache::new(cache_path.join(MODULES_DIR), false)
162            .map_err(|e| VmError::cache_err(format!("Error file system cache: {e}")))?;
163        Ok(Cache {
164            available_capabilities,
165            inner: Mutex::new(CacheInner {
166                wasm_path,
167                pinned_memory_cache: PinnedMemoryCache::new(),
168                memory_cache: InMemoryCache::new(memory_cache_size_bytes),
169                fs_cache,
170                stats: Stats::default(),
171            }),
172            instance_memory_limit: instance_memory_limit_bytes,
173            type_storage: PhantomData::<S>,
174            type_api: PhantomData::<A>,
175            type_querier: PhantomData::<Q>,
176            instantiation_lock: Mutex::new(()),
177            wasm_limits,
178        })
179    }
180
181    /// If `unchecked` is true, the filesystem cache will use the `*_unchecked` wasmer functions for
182    /// loading modules from disk.
183    pub fn set_module_unchecked(&mut self, unchecked: bool) {
184        self.inner
185            .lock()
186            .unwrap()
187            .fs_cache
188            .set_module_unchecked(unchecked);
189    }
190
191    pub fn stats(&self) -> Stats {
192        self.inner.lock().unwrap().stats
193    }
194
195    pub fn pinned_metrics(&self) -> PinnedMetrics {
196        let cache = self.inner.lock().unwrap();
197        let per_module = cache
198            .pinned_memory_cache
199            .iter()
200            .map(|(checksum, module)| {
201                let metrics = PerModuleMetrics {
202                    hits: module.hits,
203                    size: module.module.size_estimate,
204                };
205
206                (*checksum, metrics)
207            })
208            .collect();
209
210        PinnedMetrics { per_module }
211    }
212
213    pub fn metrics(&self) -> Metrics {
214        let cache = self.inner.lock().unwrap();
215        Metrics {
216            stats: cache.stats,
217            elements_pinned_memory_cache: cache.pinned_memory_cache.len(),
218            elements_memory_cache: cache.memory_cache.len(),
219            size_pinned_memory_cache: cache.pinned_memory_cache.size(),
220            size_memory_cache: cache.memory_cache.size(),
221        }
222    }
223
224    /// Takes a Wasm bytecode and stores it to the cache.
225    ///
226    /// This performs static checks, compiles the bytescode to a module and
227    /// stores the Wasm file on disk.
228    ///
229    /// This does the same as [`Cache::save_wasm_unchecked`] plus the static checks.
230    /// When a Wasm blob is stored the first time, use this function.
231    #[deprecated = "Use `store_code(wasm, true, true)` instead"]
232    pub fn save_wasm(&self, wasm: &[u8]) -> VmResult<Checksum> {
233        self.store_code(wasm, true, true)
234    }
235
236    /// Takes a Wasm bytecode and stores it to the cache.
237    ///
238    /// This performs static checks if `checked` is `true`,
239    /// compiles the bytescode to a module and
240    /// stores the Wasm file on disk if `persist` is `true`.
241    ///
242    /// Only set `checked = false` when a Wasm blob is stored which was previously checked
243    /// (e.g. as part of state sync).
244    pub fn store_code(&self, wasm: &[u8], checked: bool, persist: bool) -> VmResult<Checksum> {
245        if checked {
246            check_wasm(
247                wasm,
248                &self.available_capabilities,
249                &self.wasm_limits,
250                crate::internals::Logger::Off,
251            )?;
252        }
253
254        let module = compile_module(wasm)?;
255
256        if persist {
257            self.save_to_disk(wasm, &module)
258        } else {
259            Ok(Checksum::generate(wasm))
260        }
261    }
262
263    /// Takes a Wasm bytecode and stores it to the cache.
264    ///
265    /// This compiles the bytescode to a module and
266    /// stores the Wasm file on disk.
267    ///
268    /// This does the same as [`Cache::save_wasm`] but without the static checks.
269    /// When a Wasm blob is stored which was previously checked (e.g. as part of state sync),
270    /// use this function.
271    #[deprecated = "Use `store_code(wasm, false, true)` instead"]
272    pub fn save_wasm_unchecked(&self, wasm: &[u8]) -> VmResult<Checksum> {
273        self.store_code(wasm, false, true)
274    }
275
276    fn save_to_disk(&self, wasm: &[u8], module: &Module) -> VmResult<Checksum> {
277        let mut cache = self.inner.lock().unwrap();
278        let checksum = save_wasm_to_disk(&cache.wasm_path, wasm)?;
279        cache.fs_cache.store(&checksum, module)?;
280        Ok(checksum)
281    }
282
283    /// Removes the Wasm blob for the given checksum from disk and its
284    /// compiled module from the file system cache.
285    ///
286    /// The existence of the original code is required since the caller (wasmd)
287    /// has to keep track of which entries we have here.
288    pub fn remove_wasm(&self, checksum: &Checksum) -> VmResult<()> {
289        let mut cache = self.inner.lock().unwrap();
290
291        // Remove compiled moduled from disk (if it exists).
292        // Here we could also delete from memory caches but this is not really
293        // necessary as they are pushed out from the LRU over time or disappear
294        // when the node process restarts.
295        cache.fs_cache.remove(checksum)?;
296
297        let path = &cache.wasm_path;
298        remove_wasm_from_disk(path, checksum)?;
299        Ok(())
300    }
301
302    /// Retrieves a Wasm blob that was previously stored via [`Cache::store_code`].
303    /// When the cache is instantiated with the same base dir, this finds Wasm files on disc across multiple cache instances (i.e. node restarts).
304    /// This function is public to allow a checksum to Wasm lookup in the blockchain.
305    ///
306    /// If the given ID is not found or the content does not match the hash (=ID), an error is returned.
307    pub fn load_wasm(&self, checksum: &Checksum) -> VmResult<Vec<u8>> {
308        self.load_wasm_with_path(&self.inner.lock().unwrap().wasm_path, checksum)
309    }
310
311    fn load_wasm_with_path(&self, wasm_path: &Path, checksum: &Checksum) -> VmResult<Vec<u8>> {
312        let code = load_wasm_from_disk(wasm_path, checksum)?;
313        // verify hash matches (integrity check)
314        if Checksum::generate(&code) != *checksum {
315            Err(VmError::integrity_err())
316        } else {
317            Ok(code)
318        }
319    }
320
321    /// Performs static anlyzation on this Wasm without compiling or instantiating it.
322    ///
323    /// Once the contract was stored via [`Cache::store_code`], this can be called at any point in time.
324    /// It does not depend on any caching of the contract.
325    pub fn analyze(&self, checksum: &Checksum) -> VmResult<AnalysisReport> {
326        // Here we could use a streaming deserializer to slightly improve performance. However, this way it is DRYer.
327        let wasm = self.load_wasm(checksum)?;
328        let module = ParsedWasm::parse(&wasm)?;
329        let exports = module.exported_function_names(None);
330
331        let entrypoints = exports
332            .iter()
333            .filter_map(|export| Entrypoint::from_str(export).ok())
334            .collect();
335
336        Ok(AnalysisReport {
337            has_ibc_entry_points: REQUIRED_IBC_EXPORTS
338                .iter()
339                .all(|required| exports.contains(required.as_ref())),
340            entrypoints,
341            required_capabilities: required_capabilities_from_module(&module)
342                .into_iter()
343                .collect(),
344            contract_migrate_version: module.contract_migrate_version,
345        })
346    }
347
348    /// Pins a Module that was previously stored via [`Cache::store_code`].
349    ///
350    /// The module is looked up first in the file system cache. If not found,
351    /// the code is loaded from the file system, compiled, and stored into the
352    /// pinned cache.
353    ///
354    /// If the given contract for the given checksum is not found, or the content
355    /// does not match the checksum, an error is returned.
356    pub fn pin(&self, checksum: &Checksum) -> VmResult<()> {
357        let mut cache = self.inner.lock().unwrap();
358        if cache.pinned_memory_cache.has(checksum) {
359            return Ok(());
360        }
361
362        // We don't load from the memory cache because we had to create new store here and
363        // serialize/deserialize the artifact to get a full clone. Could be done but adds some code
364        // for a not-so-relevant use case.
365
366        // Try to get module from file system cache
367        if let Some(cached_module) = cache
368            .fs_cache
369            .load(checksum, Some(self.instance_memory_limit))?
370        {
371            cache.stats.hits_fs_cache = cache.stats.hits_fs_cache.saturating_add(1);
372            return cache.pinned_memory_cache.store(checksum, cached_module);
373        }
374
375        // Re-compile from original Wasm bytecode
376        let wasm = self.load_wasm_with_path(&cache.wasm_path, checksum)?;
377        cache.stats.misses = cache.stats.misses.saturating_add(1);
378        {
379            // Module will run with a different engine, so we can set memory limit to None
380            let compiling_engine = make_compiling_engine(None);
381            // This module cannot be executed directly as it was not created with the runtime engine
382            let module = compile(&compiling_engine, &wasm)?;
383            cache.fs_cache.store(checksum, &module)?;
384        }
385
386        // This time we'll hit the file-system cache.
387        let Some(cached_module) = cache
388            .fs_cache
389            .load(checksum, Some(self.instance_memory_limit))?
390        else {
391            return Err(VmError::generic_err(
392                "Can't load module from file system cache after storing it to file system cache (pin)",
393            ));
394        };
395
396        cache.pinned_memory_cache.store(checksum, cached_module)
397    }
398
399    /// Unpins a Module, i.e. removes it from the pinned memory cache.
400    ///
401    /// Not found IDs are silently ignored, and no integrity check (checksum validation) is done
402    /// on the removed value.
403    pub fn unpin(&self, checksum: &Checksum) -> VmResult<()> {
404        self.inner
405            .lock()
406            .unwrap()
407            .pinned_memory_cache
408            .remove(checksum)
409    }
410
411    /// Returns an Instance tied to a previously saved Wasm.
412    ///
413    /// It takes a module from cache or Wasm code and instantiates it.
414    pub fn get_instance(
415        &self,
416        checksum: &Checksum,
417        backend: Backend<A, S, Q>,
418        options: InstanceOptions,
419    ) -> VmResult<Instance<A, S, Q>> {
420        let (module, store) = self.get_module(checksum)?;
421        let instance = Instance::from_module(
422            store,
423            &module,
424            backend,
425            options.gas_limit,
426            None,
427            Some(&self.instantiation_lock),
428        )?;
429        Ok(instance)
430    }
431
432    /// Returns a module tied to a previously saved Wasm.
433    /// Depending on availability, this is either generated from a memory cache, file system cache or Wasm code.
434    /// This is part of `get_instance` but pulled out to reduce the locking time.
435    fn get_module(&self, checksum: &Checksum) -> VmResult<(Module, Store)> {
436        let mut cache = self.inner.lock().unwrap();
437        // Try to get module from the pinned memory cache
438        if let Some(element) = cache.pinned_memory_cache.load(checksum)? {
439            cache.stats.hits_pinned_memory_cache =
440                cache.stats.hits_pinned_memory_cache.saturating_add(1);
441            let CachedModule {
442                module,
443                engine,
444                size_estimate: _,
445            } = element;
446            let store = Store::new(engine);
447            return Ok((module, store));
448        }
449
450        // Get module from memory cache
451        if let Some(element) = cache.memory_cache.load(checksum)? {
452            cache.stats.hits_memory_cache = cache.stats.hits_memory_cache.saturating_add(1);
453            let CachedModule {
454                module,
455                engine,
456                size_estimate: _,
457            } = element;
458            let store = Store::new(engine);
459            return Ok((module, store));
460        }
461
462        // Get module from file system cache
463        if let Some(cached_module) = cache
464            .fs_cache
465            .load(checksum, Some(self.instance_memory_limit))?
466        {
467            cache.stats.hits_fs_cache = cache.stats.hits_fs_cache.saturating_add(1);
468
469            cache.memory_cache.store(checksum, cached_module.clone())?;
470
471            let CachedModule {
472                module,
473                engine,
474                size_estimate: _,
475            } = cached_module;
476            let store = Store::new(engine);
477            return Ok((module, store));
478        }
479
480        // Re-compile module from wasm
481        //
482        // This is needed for chains that upgrade their node software in a way that changes the module
483        // serialization format. If you do not replay all transactions, previous calls of `store_code`
484        // stored the old module format.
485        let wasm = self.load_wasm_with_path(&cache.wasm_path, checksum)?;
486        cache.stats.misses = cache.stats.misses.saturating_add(1);
487        {
488            // Module will run with a different engine, so we can set memory limit to None
489            let compiling_engine = make_compiling_engine(None);
490            // This module cannot be executed directly as it was not created with the runtime engine
491            let module = compile(&compiling_engine, &wasm)?;
492            cache.fs_cache.store(checksum, &module)?;
493        }
494
495        // This time we'll hit the file-system cache.
496        let Some(cached_module) = cache
497            .fs_cache
498            .load(checksum, Some(self.instance_memory_limit))?
499        else {
500            return Err(VmError::generic_err(
501                "Can't load module from file system cache after storing it to file system cache (get_module)",
502            ));
503        };
504        cache.memory_cache.store(checksum, cached_module.clone())?;
505
506        let CachedModule {
507            module,
508            engine,
509            size_estimate: _,
510        } = cached_module;
511        let store = Store::new(engine);
512        Ok((module, store))
513    }
514}
515
516fn compile_module(wasm: &[u8]) -> Result<Module, VmError> {
517    let compiling_engine = make_compiling_engine(None);
518    let module = compile(&compiling_engine, wasm)?;
519    Ok(module)
520}
521
522unsafe impl<A, S, Q> Sync for Cache<A, S, Q>
523where
524    A: BackendApi + 'static,
525    S: Storage + 'static,
526    Q: Querier + 'static,
527{
528}
529
530unsafe impl<A, S, Q> Send for Cache<A, S, Q>
531where
532    A: BackendApi + 'static,
533    S: Storage + 'static,
534    Q: Querier + 'static,
535{
536}
537
538/// save stores the wasm code in the given directory and returns an ID for lookup.
539/// It will create the directory if it doesn't exist.
540/// Saving the same byte code multiple times is allowed.
541fn save_wasm_to_disk(dir: impl Into<PathBuf>, wasm: &[u8]) -> VmResult<Checksum> {
542    // calculate filename
543    let checksum = Checksum::generate(wasm);
544    let filename = checksum.to_hex();
545    let filepath = dir.into().join(filename).with_extension("wasm");
546
547    // write data to file
548    // Since the same filename (a collision resistant hash) cannot be generated from two different byte codes
549    // (even if a malicious actor tried), it is safe to override.
550    let mut file = OpenOptions::new()
551        .write(true)
552        .create(true)
553        .truncate(true)
554        .open(filepath)
555        .map_err(|e| VmError::cache_err(format!("Error opening Wasm file for writing: {e}")))?;
556    file.write_all(wasm)
557        .map_err(|e| VmError::cache_err(format!("Error writing Wasm file: {e}")))?;
558
559    Ok(checksum)
560}
561
562fn load_wasm_from_disk(dir: impl Into<PathBuf>, checksum: &Checksum) -> VmResult<Vec<u8>> {
563    // this requires the directory and file to exist
564    // The files previously had no extension, so to allow for a smooth transition,
565    // we also try to load the file without the wasm extension.
566    let path = dir.into().join(checksum.to_hex());
567    let mut file = File::open(path.with_extension("wasm"))
568        .or_else(|_| File::open(path))
569        .map_err(|_e| VmError::cache_err("Error opening Wasm file for reading"))?;
570
571    let mut wasm = Vec::<u8>::new();
572    file.read_to_end(&mut wasm)
573        .map_err(|_e| VmError::cache_err("Error reading Wasm file"))?;
574    Ok(wasm)
575}
576
577/// Removes the Wasm blob for the given checksum from disk.
578///
579/// In contrast to the file system cache, the existence of the original
580/// code is required. So a non-existent file leads to an error as it
581/// indicates a bug.
582fn remove_wasm_from_disk(dir: impl Into<PathBuf>, checksum: &Checksum) -> VmResult<()> {
583    // the files previously had no extension, so to allow for a smooth transition, we delete both
584    let path = dir.into().join(checksum.to_hex());
585    let wasm_path = path.with_extension("wasm");
586
587    let path_exists = path.exists();
588    let wasm_path_exists = wasm_path.exists();
589    if !path_exists && !wasm_path_exists {
590        return Err(VmError::cache_err("Wasm file does not exist"));
591    }
592
593    if path_exists {
594        fs::remove_file(path)
595            .map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?;
596    }
597
598    if wasm_path_exists {
599        fs::remove_file(wasm_path)
600            .map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?;
601    }
602
603    Ok(())
604}
605
606#[cfg(test)]
607mod tests {
608    use super::*;
609    use crate::calls::{call_execute, call_instantiate};
610    use crate::testing::{mock_backend, mock_env, mock_info, MockApi, MockQuerier, MockStorage};
611    use cosmwasm_std::{coins, Empty};
612    use std::borrow::Cow;
613    use std::fs::{create_dir_all, remove_dir_all};
614    use tempfile::TempDir;
615    use wasm_encoder::ComponentSection;
616
617    const TESTING_GAS_LIMIT: u64 = 500_000_000; // ~0.5ms
618    const TESTING_MEMORY_LIMIT: Size = Size::mebi(16);
619    const TESTING_OPTIONS: InstanceOptions = InstanceOptions {
620        gas_limit: TESTING_GAS_LIMIT,
621    };
622    const TESTING_MEMORY_CACHE_SIZE: Size = Size::mebi(200);
623
624    static HACKATOM: &[u8] = include_bytes!("../testdata/hackatom.wasm");
625    static IBC_REFLECT: &[u8] = include_bytes!("../testdata/ibc_reflect.wasm");
626    static IBC2: &[u8] = include_bytes!("../testdata/ibc2.wasm");
627    static EMPTY: &[u8] = include_bytes!("../testdata/empty.wasm");
628    // Invalid because it doesn't contain required memory and exports
629    static INVALID_CONTRACT_WAT: &str = r#"(module
630        (type $t0 (func (param i32) (result i32)))
631        (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32)
632            local.get $p0
633            i32.const 1
634            i32.add))
635    "#;
636
637    fn default_capabilities() -> HashSet<String> {
638        HashSet::from([
639            "cosmwasm_1_1".to_string(),
640            "cosmwasm_1_2".to_string(),
641            "cosmwasm_1_3".to_string(),
642            "cosmwasm_1_4".to_string(),
643            "cosmwasm_1_4".to_string(),
644            "cosmwasm_2_0".to_string(),
645            "cosmwasm_2_1".to_string(),
646            "cosmwasm_2_2".to_string(),
647            "iterator".to_string(),
648            "staking".to_string(),
649            "stargate".to_string(),
650        ])
651    }
652
653    fn make_testing_options() -> (CacheOptions, TempDir) {
654        let temp_dir = TempDir::new().unwrap();
655        (
656            CacheOptions {
657                base_dir: temp_dir.path().into(),
658                available_capabilities: default_capabilities(),
659                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
660                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
661            },
662            temp_dir,
663        )
664    }
665
666    fn make_stargate_testing_options() -> (CacheOptions, TempDir) {
667        let temp_dir = TempDir::new().unwrap();
668        let mut capabilities = default_capabilities();
669        capabilities.insert("stargate".into());
670        (
671            CacheOptions {
672                base_dir: temp_dir.path().into(),
673                available_capabilities: capabilities,
674                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
675                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
676            },
677            temp_dir,
678        )
679    }
680
681    fn make_ibc2_testing_options() -> (CacheOptions, TempDir) {
682        let temp_dir = TempDir::new().unwrap();
683        let mut capabilities = default_capabilities();
684        capabilities.insert("ibc2".into());
685        (
686            CacheOptions {
687                base_dir: temp_dir.path().into(),
688                available_capabilities: capabilities,
689                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
690                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
691            },
692            temp_dir,
693        )
694    }
695
696    /// Takes an instance and executes it
697    fn test_hackatom_instance_execution<S, Q>(instance: &mut Instance<MockApi, S, Q>)
698    where
699        S: Storage + 'static,
700        Q: Querier + 'static,
701    {
702        // instantiate
703        let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
704        let verifier = instance.api().addr_make("verifies");
705        let beneficiary = instance.api().addr_make("benefits");
706        let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
707        let response =
708            call_instantiate::<_, _, _, Empty>(instance, &mock_env(), &info, msg.as_bytes())
709                .unwrap()
710                .unwrap();
711        assert_eq!(response.messages.len(), 0);
712
713        // execute
714        let info = mock_info(&verifier, &coins(15, "earth"));
715        let msg = br#"{"release":{"denom":"earth"}}"#;
716        let response = call_execute::<_, _, _, Empty>(instance, &mock_env(), &info, msg)
717            .unwrap()
718            .unwrap();
719        assert_eq!(response.messages.len(), 1);
720    }
721
722    #[test]
723    fn new_base_dir_will_be_created() {
724        let temp_dir = TempDir::new().unwrap();
725        let my_base_dir = temp_dir.path().join("non-existent-sub-dir");
726        let (base_opts, _temp_dir) = make_testing_options();
727        let options = CacheOptions {
728            base_dir: my_base_dir.clone(),
729            ..base_opts
730        };
731        assert!(!my_base_dir.is_dir());
732        let _cache = unsafe { Cache::<MockApi, MockStorage, MockQuerier>::new(options).unwrap() };
733        assert!(my_base_dir.is_dir());
734    }
735
736    #[test]
737    fn store_code_checked_works() {
738        let (testing_opts, _temp_dir) = make_testing_options();
739        let cache: Cache<MockApi, MockStorage, MockQuerier> =
740            unsafe { Cache::new(testing_opts).unwrap() };
741        cache.store_code(HACKATOM, true, true).unwrap();
742    }
743
744    #[test]
745    fn store_code_without_persist_works() {
746        let (testing_opts, _temp_dir) = make_testing_options();
747        let cache: Cache<MockApi, MockStorage, MockQuerier> =
748            unsafe { Cache::new(testing_opts).unwrap() };
749        let checksum = cache.store_code(HACKATOM, true, false).unwrap();
750
751        assert!(
752            cache.load_wasm(&checksum).is_err(),
753            "wasm file should not be saved to disk"
754        );
755    }
756
757    #[test]
758    // This property is required when the same bytecode is uploaded multiple times
759    fn store_code_allows_saving_multiple_times() {
760        let (testing_opts, _temp_dir) = make_testing_options();
761        let cache: Cache<MockApi, MockStorage, MockQuerier> =
762            unsafe { Cache::new(testing_opts).unwrap() };
763        cache.store_code(HACKATOM, true, true).unwrap();
764        cache.store_code(HACKATOM, true, true).unwrap();
765    }
766
767    #[test]
768    fn store_code_checked_rejects_invalid_contract() {
769        let wasm = wat::parse_str(INVALID_CONTRACT_WAT).unwrap();
770
771        let (testing_opts, _temp_dir) = make_testing_options();
772        let cache: Cache<MockApi, MockStorage, MockQuerier> =
773            unsafe { Cache::new(testing_opts).unwrap() };
774        let save_result = cache.store_code(&wasm, true, true);
775        match save_result.unwrap_err() {
776            VmError::StaticValidationErr { msg, .. } => {
777                assert_eq!(msg, "Wasm contract must contain exactly one memory")
778            }
779            e => panic!("Unexpected error {e:?}"),
780        }
781    }
782
783    #[test]
784    fn store_code_fills_file_system_but_not_memory_cache() {
785        // Who knows if and when the uploaded contract will be executed. Don't pollute
786        // memory cache before the init call.
787
788        let (testing_opts, _temp_dir) = make_testing_options();
789        let cache = unsafe { Cache::new(testing_opts).unwrap() };
790        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
791
792        let backend = mock_backend(&[]);
793        let _ = cache
794            .get_instance(&checksum, backend, TESTING_OPTIONS)
795            .unwrap();
796        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
797        assert_eq!(cache.stats().hits_memory_cache, 0);
798        assert_eq!(cache.stats().hits_fs_cache, 1);
799        assert_eq!(cache.stats().misses, 0);
800    }
801
802    #[test]
803    fn store_code_unchecked_works() {
804        let (testing_opts, _temp_dir) = make_testing_options();
805        let cache: Cache<MockApi, MockStorage, MockQuerier> =
806            unsafe { Cache::new(testing_opts).unwrap() };
807        cache.store_code(HACKATOM, false, true).unwrap();
808    }
809
810    #[test]
811    fn store_code_unchecked_accepts_invalid_contract() {
812        let wasm = wat::parse_str(INVALID_CONTRACT_WAT).unwrap();
813
814        let (testing_opts, _temp_dir) = make_testing_options();
815        let cache: Cache<MockApi, MockStorage, MockQuerier> =
816            unsafe { Cache::new(testing_opts).unwrap() };
817        cache.store_code(&wasm, false, true).unwrap();
818    }
819
820    #[test]
821    fn load_wasm_works() {
822        let (testing_opts, _temp_dir) = make_testing_options();
823        let cache: Cache<MockApi, MockStorage, MockQuerier> =
824            unsafe { Cache::new(testing_opts).unwrap() };
825        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
826
827        let restored = cache.load_wasm(&checksum).unwrap();
828        assert_eq!(restored, HACKATOM);
829    }
830
831    #[test]
832    fn load_wasm_works_across_multiple_cache_instances() {
833        let tmp_dir = TempDir::new().unwrap();
834        let id: Checksum;
835
836        {
837            let options1 = CacheOptions {
838                base_dir: tmp_dir.path().to_path_buf(),
839                available_capabilities: default_capabilities(),
840                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
841                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
842            };
843            let cache1: Cache<MockApi, MockStorage, MockQuerier> =
844                unsafe { Cache::new(options1).unwrap() };
845            id = cache1.store_code(HACKATOM, true, true).unwrap();
846        }
847
848        {
849            let options2 = CacheOptions {
850                base_dir: tmp_dir.path().to_path_buf(),
851                available_capabilities: default_capabilities(),
852                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
853                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
854            };
855            let cache2: Cache<MockApi, MockStorage, MockQuerier> =
856                unsafe { Cache::new(options2).unwrap() };
857            let restored = cache2.load_wasm(&id).unwrap();
858            assert_eq!(restored, HACKATOM);
859        }
860    }
861
862    #[test]
863    fn load_wasm_errors_for_non_existent_id() {
864        let (testing_opts, _temp_dir) = make_testing_options();
865        let cache: Cache<MockApi, MockStorage, MockQuerier> =
866            unsafe { Cache::new(testing_opts).unwrap() };
867        let checksum = Checksum::from([
868            5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
869            5, 5, 5,
870        ]);
871
872        match cache.load_wasm(&checksum).unwrap_err() {
873            VmError::CacheErr { msg, .. } => {
874                assert_eq!(msg, "Error opening Wasm file for reading")
875            }
876            e => panic!("Unexpected error: {e:?}"),
877        }
878    }
879
880    #[test]
881    fn load_wasm_errors_for_corrupted_wasm() {
882        let tmp_dir = TempDir::new().unwrap();
883        let options = CacheOptions {
884            base_dir: tmp_dir.path().to_path_buf(),
885            available_capabilities: default_capabilities(),
886            memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
887            instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
888        };
889        let cache: Cache<MockApi, MockStorage, MockQuerier> =
890            unsafe { Cache::new(options).unwrap() };
891        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
892
893        // Corrupt cache file
894        let filepath = tmp_dir
895            .path()
896            .join(STATE_DIR)
897            .join(WASM_DIR)
898            .join(checksum.to_hex())
899            .with_extension("wasm");
900        let mut file = OpenOptions::new().write(true).open(filepath).unwrap();
901        file.write_all(b"broken data").unwrap();
902
903        let res = cache.load_wasm(&checksum);
904        match res {
905            Err(VmError::IntegrityErr { .. }) => {}
906            Err(e) => panic!("Unexpected error: {e:?}"),
907            Ok(_) => panic!("This must not succeed"),
908        }
909    }
910
911    #[test]
912    fn remove_wasm_works() {
913        let (testing_opts, _temp_dir) = make_testing_options();
914        let cache: Cache<MockApi, MockStorage, MockQuerier> =
915            unsafe { Cache::new(testing_opts).unwrap() };
916
917        // Store
918        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
919
920        // Exists
921        cache.load_wasm(&checksum).unwrap();
922
923        // Remove
924        cache.remove_wasm(&checksum).unwrap();
925
926        // Does not exist anymore
927        match cache.load_wasm(&checksum).unwrap_err() {
928            VmError::CacheErr { msg, .. } => {
929                assert_eq!(msg, "Error opening Wasm file for reading")
930            }
931            e => panic!("Unexpected error: {e:?}"),
932        }
933
934        // Removing again fails
935        match cache.remove_wasm(&checksum).unwrap_err() {
936            VmError::CacheErr { msg, .. } => {
937                assert_eq!(msg, "Wasm file does not exist")
938            }
939            e => panic!("Unexpected error: {e:?}"),
940        }
941    }
942
943    #[test]
944    fn get_instance_finds_cached_module() {
945        let (testing_opts, _temp_dir) = make_testing_options();
946        let cache = unsafe { Cache::new(testing_opts).unwrap() };
947        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
948        let backend = mock_backend(&[]);
949        let _instance = cache
950            .get_instance(&checksum, backend, TESTING_OPTIONS)
951            .unwrap();
952        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
953        assert_eq!(cache.stats().hits_memory_cache, 0);
954        assert_eq!(cache.stats().hits_fs_cache, 1);
955        assert_eq!(cache.stats().misses, 0);
956    }
957
958    #[test]
959    fn get_instance_finds_cached_modules_and_stores_to_memory() {
960        let (testing_opts, _temp_dir) = make_testing_options();
961        let cache = unsafe { Cache::new(testing_opts).unwrap() };
962        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
963        let backend1 = mock_backend(&[]);
964        let backend2 = mock_backend(&[]);
965        let backend3 = mock_backend(&[]);
966        let backend4 = mock_backend(&[]);
967        let backend5 = mock_backend(&[]);
968
969        // from file system
970        let _instance1 = cache
971            .get_instance(&checksum, backend1, TESTING_OPTIONS)
972            .unwrap();
973        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
974        assert_eq!(cache.stats().hits_memory_cache, 0);
975        assert_eq!(cache.stats().hits_fs_cache, 1);
976        assert_eq!(cache.stats().misses, 0);
977
978        // from memory
979        let _instance2 = cache
980            .get_instance(&checksum, backend2, TESTING_OPTIONS)
981            .unwrap();
982        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
983        assert_eq!(cache.stats().hits_memory_cache, 1);
984        assert_eq!(cache.stats().hits_fs_cache, 1);
985        assert_eq!(cache.stats().misses, 0);
986
987        // from memory again
988        let _instance3 = cache
989            .get_instance(&checksum, backend3, TESTING_OPTIONS)
990            .unwrap();
991        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
992        assert_eq!(cache.stats().hits_memory_cache, 2);
993        assert_eq!(cache.stats().hits_fs_cache, 1);
994        assert_eq!(cache.stats().misses, 0);
995
996        // pinning hits the file system cache
997        cache.pin(&checksum).unwrap();
998        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
999        assert_eq!(cache.stats().hits_memory_cache, 2);
1000        assert_eq!(cache.stats().hits_fs_cache, 2);
1001        assert_eq!(cache.stats().misses, 0);
1002
1003        // from pinned memory cache
1004        let _instance4 = cache
1005            .get_instance(&checksum, backend4, TESTING_OPTIONS)
1006            .unwrap();
1007        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1008        assert_eq!(cache.stats().hits_memory_cache, 2);
1009        assert_eq!(cache.stats().hits_fs_cache, 2);
1010        assert_eq!(cache.stats().misses, 0);
1011
1012        // from pinned memory cache again
1013        let _instance5 = cache
1014            .get_instance(&checksum, backend5, TESTING_OPTIONS)
1015            .unwrap();
1016        assert_eq!(cache.stats().hits_pinned_memory_cache, 2);
1017        assert_eq!(cache.stats().hits_memory_cache, 2);
1018        assert_eq!(cache.stats().hits_fs_cache, 2);
1019        assert_eq!(cache.stats().misses, 0);
1020    }
1021
1022    #[test]
1023    fn get_instance_recompiles_module() {
1024        let (options, _temp_dir) = make_testing_options();
1025        let cache = unsafe { Cache::new(options.clone()).unwrap() };
1026        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1027
1028        // Remove compiled module from disk
1029        remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1030
1031        // The first get_instance recompiles the Wasm (miss)
1032        let backend = mock_backend(&[]);
1033        let _instance = cache
1034            .get_instance(&checksum, backend, TESTING_OPTIONS)
1035            .unwrap();
1036        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1037        assert_eq!(cache.stats().hits_memory_cache, 0);
1038        assert_eq!(cache.stats().hits_fs_cache, 0);
1039        assert_eq!(cache.stats().misses, 1);
1040
1041        // The second get_instance finds the module in cache (hit)
1042        let backend = mock_backend(&[]);
1043        let _instance = cache
1044            .get_instance(&checksum, backend, TESTING_OPTIONS)
1045            .unwrap();
1046        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1047        assert_eq!(cache.stats().hits_memory_cache, 1);
1048        assert_eq!(cache.stats().hits_fs_cache, 0);
1049        assert_eq!(cache.stats().misses, 1);
1050    }
1051
1052    #[test]
1053    fn call_instantiate_on_cached_contract() {
1054        let (testing_opts, _temp_dir) = make_testing_options();
1055        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1056        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1057
1058        // from file system
1059        {
1060            let mut instance = cache
1061                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1062                .unwrap();
1063            assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1064            assert_eq!(cache.stats().hits_memory_cache, 0);
1065            assert_eq!(cache.stats().hits_fs_cache, 1);
1066            assert_eq!(cache.stats().misses, 0);
1067
1068            // instantiate
1069            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1070            let verifier = instance.api().addr_make("verifies");
1071            let beneficiary = instance.api().addr_make("benefits");
1072            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1073            let res = call_instantiate::<_, _, _, Empty>(
1074                &mut instance,
1075                &mock_env(),
1076                &info,
1077                msg.as_bytes(),
1078            )
1079            .unwrap();
1080            let msgs = res.unwrap().messages;
1081            assert_eq!(msgs.len(), 0);
1082        }
1083
1084        // from memory
1085        {
1086            let mut instance = cache
1087                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1088                .unwrap();
1089            assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1090            assert_eq!(cache.stats().hits_memory_cache, 1);
1091            assert_eq!(cache.stats().hits_fs_cache, 1);
1092            assert_eq!(cache.stats().misses, 0);
1093
1094            // instantiate
1095            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1096            let verifier = instance.api().addr_make("verifies");
1097            let beneficiary = instance.api().addr_make("benefits");
1098            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1099            let res = call_instantiate::<_, _, _, Empty>(
1100                &mut instance,
1101                &mock_env(),
1102                &info,
1103                msg.as_bytes(),
1104            )
1105            .unwrap();
1106            let msgs = res.unwrap().messages;
1107            assert_eq!(msgs.len(), 0);
1108        }
1109
1110        // from pinned memory
1111        {
1112            cache.pin(&checksum).unwrap();
1113
1114            let mut instance = cache
1115                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1116                .unwrap();
1117            assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1118            assert_eq!(cache.stats().hits_memory_cache, 1);
1119            assert_eq!(cache.stats().hits_fs_cache, 2);
1120            assert_eq!(cache.stats().misses, 0);
1121
1122            // instantiate
1123            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1124            let verifier = instance.api().addr_make("verifies");
1125            let beneficiary = instance.api().addr_make("benefits");
1126            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1127            let res = call_instantiate::<_, _, _, Empty>(
1128                &mut instance,
1129                &mock_env(),
1130                &info,
1131                msg.as_bytes(),
1132            )
1133            .unwrap();
1134            let msgs = res.unwrap().messages;
1135            assert_eq!(msgs.len(), 0);
1136        }
1137    }
1138
1139    #[test]
1140    fn call_execute_on_cached_contract() {
1141        let (testing_opts, _temp_dir) = make_testing_options();
1142        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1143        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1144
1145        // from file system
1146        {
1147            let mut instance = cache
1148                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1149                .unwrap();
1150            assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1151            assert_eq!(cache.stats().hits_memory_cache, 0);
1152            assert_eq!(cache.stats().hits_fs_cache, 1);
1153            assert_eq!(cache.stats().misses, 0);
1154
1155            // instantiate
1156            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1157            let verifier = instance.api().addr_make("verifies");
1158            let beneficiary = instance.api().addr_make("benefits");
1159            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1160            let response = call_instantiate::<_, _, _, Empty>(
1161                &mut instance,
1162                &mock_env(),
1163                &info,
1164                msg.as_bytes(),
1165            )
1166            .unwrap()
1167            .unwrap();
1168            assert_eq!(response.messages.len(), 0);
1169
1170            // execute
1171            let info = mock_info(&verifier, &coins(15, "earth"));
1172            let msg = br#"{"release":{"denom":"earth"}}"#;
1173            let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1174                .unwrap()
1175                .unwrap();
1176            assert_eq!(response.messages.len(), 1);
1177        }
1178
1179        // from memory
1180        {
1181            let mut instance = cache
1182                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1183                .unwrap();
1184            assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1185            assert_eq!(cache.stats().hits_memory_cache, 1);
1186            assert_eq!(cache.stats().hits_fs_cache, 1);
1187            assert_eq!(cache.stats().misses, 0);
1188
1189            // instantiate
1190            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1191            let verifier = instance.api().addr_make("verifies");
1192            let beneficiary = instance.api().addr_make("benefits");
1193            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1194            let response = call_instantiate::<_, _, _, Empty>(
1195                &mut instance,
1196                &mock_env(),
1197                &info,
1198                msg.as_bytes(),
1199            )
1200            .unwrap()
1201            .unwrap();
1202            assert_eq!(response.messages.len(), 0);
1203
1204            // execute
1205            let info = mock_info(&verifier, &coins(15, "earth"));
1206            let msg = br#"{"release":{"denom":"earth"}}"#;
1207            let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1208                .unwrap()
1209                .unwrap();
1210            assert_eq!(response.messages.len(), 1);
1211        }
1212
1213        // from pinned memory
1214        {
1215            cache.pin(&checksum).unwrap();
1216
1217            let mut instance = cache
1218                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1219                .unwrap();
1220            assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1221            assert_eq!(cache.stats().hits_memory_cache, 1);
1222            assert_eq!(cache.stats().hits_fs_cache, 2);
1223            assert_eq!(cache.stats().misses, 0);
1224
1225            // instantiate
1226            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1227            let verifier = instance.api().addr_make("verifies");
1228            let beneficiary = instance.api().addr_make("benefits");
1229            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1230            let response = call_instantiate::<_, _, _, Empty>(
1231                &mut instance,
1232                &mock_env(),
1233                &info,
1234                msg.as_bytes(),
1235            )
1236            .unwrap()
1237            .unwrap();
1238            assert_eq!(response.messages.len(), 0);
1239
1240            // execute
1241            let info = mock_info(&verifier, &coins(15, "earth"));
1242            let msg = br#"{"release":{"denom":"earth"}}"#;
1243            let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1244                .unwrap()
1245                .unwrap();
1246            assert_eq!(response.messages.len(), 1);
1247        }
1248    }
1249
1250    #[test]
1251    fn call_execute_on_recompiled_contract() {
1252        let (options, _temp_dir) = make_testing_options();
1253        let cache = unsafe { Cache::new(options.clone()).unwrap() };
1254        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1255
1256        // Remove compiled module from disk
1257        remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1258
1259        // Recompiles the Wasm (miss on all caches)
1260        let backend = mock_backend(&[]);
1261        let mut instance = cache
1262            .get_instance(&checksum, backend, TESTING_OPTIONS)
1263            .unwrap();
1264        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1265        assert_eq!(cache.stats().hits_memory_cache, 0);
1266        assert_eq!(cache.stats().hits_fs_cache, 0);
1267        assert_eq!(cache.stats().misses, 1);
1268        test_hackatom_instance_execution(&mut instance);
1269    }
1270
1271    #[test]
1272    fn use_multiple_cached_instances_of_same_contract() {
1273        let (testing_opts, _temp_dir) = make_testing_options();
1274        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1275        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1276
1277        // these differentiate the two instances of the same contract
1278        let backend1 = mock_backend(&[]);
1279        let backend2 = mock_backend(&[]);
1280
1281        // instantiate instance 1
1282        let mut instance = cache
1283            .get_instance(&checksum, backend1, TESTING_OPTIONS)
1284            .unwrap();
1285        let info = mock_info("owner1", &coins(1000, "earth"));
1286        let sue = instance.api().addr_make("sue");
1287        let mary = instance.api().addr_make("mary");
1288        let msg = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1289        let res =
1290            call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
1291                .unwrap();
1292        let msgs = res.unwrap().messages;
1293        assert_eq!(msgs.len(), 0);
1294        let backend1 = instance.recycle().unwrap();
1295
1296        // instantiate instance 2
1297        let mut instance = cache
1298            .get_instance(&checksum, backend2, TESTING_OPTIONS)
1299            .unwrap();
1300        let info = mock_info("owner2", &coins(500, "earth"));
1301        let bob = instance.api().addr_make("bob");
1302        let john = instance.api().addr_make("john");
1303        let msg = format!(r#"{{"verifier": "{bob}", "beneficiary": "{john}"}}"#);
1304        let res =
1305            call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
1306                .unwrap();
1307        let msgs = res.unwrap().messages;
1308        assert_eq!(msgs.len(), 0);
1309        let backend2 = instance.recycle().unwrap();
1310
1311        // run contract 2 - just sanity check - results validate in contract unit tests
1312        let mut instance = cache
1313            .get_instance(&checksum, backend2, TESTING_OPTIONS)
1314            .unwrap();
1315        let info = mock_info(&bob, &coins(15, "earth"));
1316        let msg = br#"{"release":{"denom":"earth"}}"#;
1317        let res = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap();
1318        let msgs = res.unwrap().messages;
1319        assert_eq!(1, msgs.len());
1320
1321        // run contract 1 - just sanity check - results validate in contract unit tests
1322        let mut instance = cache
1323            .get_instance(&checksum, backend1, TESTING_OPTIONS)
1324            .unwrap();
1325        let info = mock_info(&sue, &coins(15, "earth"));
1326        let msg = br#"{"release":{"denom":"earth"}}"#;
1327        let res = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap();
1328        let msgs = res.unwrap().messages;
1329        assert_eq!(1, msgs.len());
1330    }
1331
1332    #[test]
1333    fn resets_gas_when_reusing_instance() {
1334        let (testing_opts, _temp_dir) = make_testing_options();
1335        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1336        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1337
1338        let backend1 = mock_backend(&[]);
1339        let backend2 = mock_backend(&[]);
1340
1341        // Init from module cache
1342        let mut instance1 = cache
1343            .get_instance(&checksum, backend1, TESTING_OPTIONS)
1344            .unwrap();
1345        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1346        assert_eq!(cache.stats().hits_memory_cache, 0);
1347        assert_eq!(cache.stats().hits_fs_cache, 1);
1348        assert_eq!(cache.stats().misses, 0);
1349        let original_gas = instance1.get_gas_left();
1350
1351        // Consume some gas
1352        let info = mock_info("owner1", &coins(1000, "earth"));
1353        let sue = instance1.api().addr_make("sue");
1354        let mary = instance1.api().addr_make("mary");
1355        let msg = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1356        call_instantiate::<_, _, _, Empty>(&mut instance1, &mock_env(), &info, msg.as_bytes())
1357            .unwrap()
1358            .unwrap();
1359        assert!(instance1.get_gas_left() < original_gas);
1360
1361        // Init from memory cache
1362        let mut instance2 = cache
1363            .get_instance(&checksum, backend2, TESTING_OPTIONS)
1364            .unwrap();
1365        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1366        assert_eq!(cache.stats().hits_memory_cache, 1);
1367        assert_eq!(cache.stats().hits_fs_cache, 1);
1368        assert_eq!(cache.stats().misses, 0);
1369        assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT);
1370    }
1371
1372    #[test]
1373    fn recovers_from_out_of_gas() {
1374        let (testing_opts, _temp_dir) = make_testing_options();
1375        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1376        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1377
1378        let backend1 = mock_backend(&[]);
1379        let backend2 = mock_backend(&[]);
1380
1381        // Init from module cache
1382        let options = InstanceOptions { gas_limit: 10 };
1383        let mut instance1 = cache.get_instance(&checksum, backend1, options).unwrap();
1384        assert_eq!(cache.stats().hits_fs_cache, 1);
1385        assert_eq!(cache.stats().misses, 0);
1386
1387        // Consume some gas. This fails
1388        let info1 = mock_info("owner1", &coins(1000, "earth"));
1389        let sue = instance1.api().addr_make("sue");
1390        let mary = instance1.api().addr_make("mary");
1391        let msg1 = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1392
1393        match call_instantiate::<_, _, _, Empty>(
1394            &mut instance1,
1395            &mock_env(),
1396            &info1,
1397            msg1.as_bytes(),
1398        )
1399        .unwrap_err()
1400        {
1401            VmError::GasDepletion { .. } => (), // all good, continue
1402            e => panic!("unexpected error, {e:?}"),
1403        }
1404        assert_eq!(instance1.get_gas_left(), 0);
1405
1406        // Init from memory cache
1407        let options = InstanceOptions {
1408            gas_limit: TESTING_GAS_LIMIT,
1409        };
1410        let mut instance2 = cache.get_instance(&checksum, backend2, options).unwrap();
1411        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1412        assert_eq!(cache.stats().hits_memory_cache, 1);
1413        assert_eq!(cache.stats().hits_fs_cache, 1);
1414        assert_eq!(cache.stats().misses, 0);
1415        assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT);
1416
1417        // Now it works
1418        let info2 = mock_info("owner2", &coins(500, "earth"));
1419        let bob = instance2.api().addr_make("bob");
1420        let john = instance2.api().addr_make("john");
1421        let msg2 = format!(r#"{{"verifier": "{bob}", "beneficiary": "{john}"}}"#);
1422        call_instantiate::<_, _, _, Empty>(&mut instance2, &mock_env(), &info2, msg2.as_bytes())
1423            .unwrap()
1424            .unwrap();
1425    }
1426
1427    #[test]
1428    fn save_wasm_to_disk_works_for_same_data_multiple_times() {
1429        let tmp_dir = TempDir::new().unwrap();
1430        let path = tmp_dir.path();
1431        let code = vec![12u8; 17];
1432
1433        save_wasm_to_disk(path, &code).unwrap();
1434        save_wasm_to_disk(path, &code).unwrap();
1435    }
1436
1437    #[test]
1438    fn save_wasm_to_disk_fails_on_non_existent_dir() {
1439        let tmp_dir = TempDir::new().unwrap();
1440        let path = tmp_dir.path().join("something");
1441        let code = vec![12u8; 17];
1442        let res = save_wasm_to_disk(path.to_str().unwrap(), &code);
1443        assert!(res.is_err());
1444    }
1445
1446    #[test]
1447    fn load_wasm_from_disk_works() {
1448        let tmp_dir = TempDir::new().unwrap();
1449        let path = tmp_dir.path();
1450        let code = vec![12u8; 17];
1451        let checksum = save_wasm_to_disk(path, &code).unwrap();
1452
1453        let loaded = load_wasm_from_disk(path, &checksum).unwrap();
1454        assert_eq!(code, loaded);
1455    }
1456
1457    #[test]
1458    fn load_wasm_from_disk_works_in_subfolder() {
1459        let tmp_dir = TempDir::new().unwrap();
1460        let path = tmp_dir.path().join("something");
1461        create_dir_all(&path).unwrap();
1462        let code = vec![12u8; 17];
1463        let checksum = save_wasm_to_disk(&path, &code).unwrap();
1464
1465        let loaded = load_wasm_from_disk(&path, &checksum).unwrap();
1466        assert_eq!(code, loaded);
1467    }
1468
1469    #[test]
1470    fn remove_wasm_from_disk_works() {
1471        let tmp_dir = TempDir::new().unwrap();
1472        let path = tmp_dir.path();
1473        let code = vec![12u8; 17];
1474        let checksum = save_wasm_to_disk(path, &code).unwrap();
1475
1476        remove_wasm_from_disk(path, &checksum).unwrap();
1477
1478        // removing again fails
1479
1480        match remove_wasm_from_disk(path, &checksum).unwrap_err() {
1481            VmError::CacheErr { msg, .. } => assert_eq!(msg, "Wasm file does not exist"),
1482            err => panic!("Unexpected error: {err:?}"),
1483        }
1484    }
1485
1486    #[test]
1487    fn analyze_works() {
1488        use Entrypoint as E;
1489
1490        let (testing_opts, _temp_dir) = make_stargate_testing_options();
1491        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1492            unsafe { Cache::new(testing_opts).unwrap() };
1493
1494        let checksum1 = cache.store_code(HACKATOM, true, true).unwrap();
1495        let report1 = cache.analyze(&checksum1).unwrap();
1496        assert_eq!(
1497            report1,
1498            AnalysisReport {
1499                has_ibc_entry_points: false,
1500                entrypoints: BTreeSet::from([
1501                    E::Instantiate,
1502                    E::Migrate,
1503                    E::Sudo,
1504                    E::Execute,
1505                    E::Query
1506                ]),
1507                required_capabilities: BTreeSet::from([
1508                    "cosmwasm_1_1".to_string(),
1509                    "cosmwasm_1_2".to_string(),
1510                    "cosmwasm_1_3".to_string(),
1511                    "cosmwasm_1_4".to_string(),
1512                    "cosmwasm_1_4".to_string(),
1513                    "cosmwasm_2_0".to_string(),
1514                    "cosmwasm_2_1".to_string(),
1515                    "cosmwasm_2_2".to_string(),
1516                ]),
1517                contract_migrate_version: Some(420),
1518            }
1519        );
1520
1521        let checksum2 = cache.store_code(IBC_REFLECT, true, true).unwrap();
1522        let report2 = cache.analyze(&checksum2).unwrap();
1523        let mut ibc_contract_entrypoints =
1524            BTreeSet::from([E::Instantiate, E::Migrate, E::Execute, E::Reply, E::Query]);
1525        ibc_contract_entrypoints.extend(REQUIRED_IBC_EXPORTS);
1526        assert_eq!(
1527            report2,
1528            AnalysisReport {
1529                has_ibc_entry_points: true,
1530                entrypoints: ibc_contract_entrypoints,
1531                required_capabilities: BTreeSet::from_iter([
1532                    "cosmwasm_1_1".to_string(),
1533                    "cosmwasm_1_2".to_string(),
1534                    "cosmwasm_1_3".to_string(),
1535                    "cosmwasm_1_4".to_string(),
1536                    "cosmwasm_1_4".to_string(),
1537                    "cosmwasm_2_0".to_string(),
1538                    "cosmwasm_2_1".to_string(),
1539                    "cosmwasm_2_2".to_string(),
1540                    "iterator".to_string(),
1541                    "stargate".to_string()
1542                ]),
1543                contract_migrate_version: None,
1544            }
1545        );
1546
1547        let checksum3 = cache.store_code(EMPTY, true, true).unwrap();
1548        let report3 = cache.analyze(&checksum3).unwrap();
1549        assert_eq!(
1550            report3,
1551            AnalysisReport {
1552                has_ibc_entry_points: false,
1553                entrypoints: BTreeSet::new(),
1554                required_capabilities: BTreeSet::from(["iterator".to_string()]),
1555                contract_migrate_version: None,
1556            }
1557        );
1558
1559        let mut wasm_with_version = EMPTY.to_vec();
1560        let custom_section = wasm_encoder::CustomSection {
1561            name: Cow::Borrowed("cw_migrate_version"),
1562            data: Cow::Borrowed(b"21"),
1563        };
1564        custom_section.append_to_component(&mut wasm_with_version);
1565
1566        let checksum4 = cache.store_code(&wasm_with_version, true, true).unwrap();
1567        let report4 = cache.analyze(&checksum4).unwrap();
1568        assert_eq!(
1569            report4,
1570            AnalysisReport {
1571                has_ibc_entry_points: false,
1572                entrypoints: BTreeSet::new(),
1573                required_capabilities: BTreeSet::from(["iterator".to_string()]),
1574                contract_migrate_version: Some(21),
1575            }
1576        );
1577
1578        let (testing_opts, _temp_dir) = make_ibc2_testing_options();
1579        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1580            unsafe { Cache::new(testing_opts).unwrap() };
1581        let checksum5 = cache.store_code(IBC2, true, true).unwrap();
1582        let report5 = cache.analyze(&checksum5).unwrap();
1583        let ibc2_contract_entrypoints = BTreeSet::from([
1584            E::Instantiate,
1585            E::Query,
1586            E::Ibc2PacketReceive,
1587            E::Ibc2PacketTimeout,
1588            E::Ibc2PacketAck,
1589            E::Ibc2PacketSend,
1590        ]);
1591        assert_eq!(
1592            report5,
1593            AnalysisReport {
1594                has_ibc_entry_points: false,
1595                entrypoints: ibc2_contract_entrypoints,
1596                required_capabilities: BTreeSet::from_iter([
1597                    "iterator".to_string(),
1598                    "ibc2".to_string()
1599                ]),
1600                contract_migrate_version: None,
1601            }
1602        );
1603    }
1604
1605    #[test]
1606    fn pinned_metrics_works() {
1607        let (testing_opts, _temp_dir) = make_testing_options();
1608        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1609        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1610
1611        cache.pin(&checksum).unwrap();
1612
1613        let pinned_metrics = cache.pinned_metrics();
1614        assert_eq!(pinned_metrics.per_module.len(), 1);
1615        assert_eq!(pinned_metrics.per_module[0].0, checksum);
1616        assert_eq!(pinned_metrics.per_module[0].1.hits, 0);
1617
1618        let backend = mock_backend(&[]);
1619        let _ = cache
1620            .get_instance(&checksum, backend, TESTING_OPTIONS)
1621            .unwrap();
1622
1623        let pinned_metrics = cache.pinned_metrics();
1624        assert_eq!(pinned_metrics.per_module.len(), 1);
1625        assert_eq!(pinned_metrics.per_module[0].0, checksum);
1626        assert_eq!(pinned_metrics.per_module[0].1.hits, 1);
1627
1628        let empty_checksum = cache.store_code(EMPTY, true, true).unwrap();
1629        cache.pin(&empty_checksum).unwrap();
1630
1631        let pinned_metrics = cache.pinned_metrics();
1632        assert_eq!(pinned_metrics.per_module.len(), 2);
1633
1634        let get_module_hits = |checksum| {
1635            pinned_metrics
1636                .per_module
1637                .iter()
1638                .find(|(iter_checksum, _module)| *iter_checksum == checksum)
1639                .map(|(_checksum, module)| module)
1640                .cloned()
1641                .unwrap()
1642        };
1643
1644        assert_eq!(get_module_hits(checksum).hits, 1);
1645        assert_eq!(get_module_hits(empty_checksum).hits, 0);
1646    }
1647
1648    #[test]
1649    fn pin_unpin_works() {
1650        let (testing_opts, _temp_dir) = make_testing_options();
1651        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1652        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1653
1654        // check not pinned
1655        let backend = mock_backend(&[]);
1656        let mut instance = cache
1657            .get_instance(&checksum, backend, TESTING_OPTIONS)
1658            .unwrap();
1659        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1660        assert_eq!(cache.stats().hits_memory_cache, 0);
1661        assert_eq!(cache.stats().hits_fs_cache, 1);
1662        assert_eq!(cache.stats().misses, 0);
1663        test_hackatom_instance_execution(&mut instance);
1664
1665        // first pin hits file system cache
1666        cache.pin(&checksum).unwrap();
1667        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1668        assert_eq!(cache.stats().hits_memory_cache, 0);
1669        assert_eq!(cache.stats().hits_fs_cache, 2);
1670        assert_eq!(cache.stats().misses, 0);
1671
1672        // consecutive pins are no-ops
1673        cache.pin(&checksum).unwrap();
1674        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1675        assert_eq!(cache.stats().hits_memory_cache, 0);
1676        assert_eq!(cache.stats().hits_fs_cache, 2);
1677        assert_eq!(cache.stats().misses, 0);
1678
1679        // check pinned
1680        let backend = mock_backend(&[]);
1681        let mut instance = cache
1682            .get_instance(&checksum, backend, TESTING_OPTIONS)
1683            .unwrap();
1684        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1685        assert_eq!(cache.stats().hits_memory_cache, 0);
1686        assert_eq!(cache.stats().hits_fs_cache, 2);
1687        assert_eq!(cache.stats().misses, 0);
1688        test_hackatom_instance_execution(&mut instance);
1689
1690        // unpin
1691        cache.unpin(&checksum).unwrap();
1692
1693        // verify unpinned
1694        let backend = mock_backend(&[]);
1695        let mut instance = cache
1696            .get_instance(&checksum, backend, TESTING_OPTIONS)
1697            .unwrap();
1698        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1699        assert_eq!(cache.stats().hits_memory_cache, 1);
1700        assert_eq!(cache.stats().hits_fs_cache, 2);
1701        assert_eq!(cache.stats().misses, 0);
1702        test_hackatom_instance_execution(&mut instance);
1703
1704        // unpin again has no effect
1705        cache.unpin(&checksum).unwrap();
1706
1707        // unpin non existent id has no effect
1708        let non_id = Checksum::generate(b"non_existent");
1709        cache.unpin(&non_id).unwrap();
1710    }
1711
1712    #[test]
1713    fn pin_recompiles_module() {
1714        let (options, _temp_dir) = make_testing_options();
1715        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1716            unsafe { Cache::new(options.clone()).unwrap() };
1717        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1718
1719        // Remove compiled module from disk
1720        remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1721
1722        // Pin misses, forcing a re-compile of the module
1723        cache.pin(&checksum).unwrap();
1724        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1725        assert_eq!(cache.stats().hits_memory_cache, 0);
1726        assert_eq!(cache.stats().hits_fs_cache, 0);
1727        assert_eq!(cache.stats().misses, 1);
1728
1729        // After the compilation in pin, the module can be used from pinned memory cache
1730        let backend = mock_backend(&[]);
1731        let mut instance = cache
1732            .get_instance(&checksum, backend, TESTING_OPTIONS)
1733            .unwrap();
1734        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1735        assert_eq!(cache.stats().hits_memory_cache, 0);
1736        assert_eq!(cache.stats().hits_fs_cache, 0);
1737        assert_eq!(cache.stats().misses, 1);
1738        test_hackatom_instance_execution(&mut instance);
1739    }
1740
1741    #[test]
1742    fn loading_without_extension_works() {
1743        let tmp_dir = TempDir::new().unwrap();
1744        let options = CacheOptions {
1745            base_dir: tmp_dir.path().to_path_buf(),
1746            available_capabilities: default_capabilities(),
1747            memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
1748            instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
1749        };
1750        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1751            unsafe { Cache::new(options).unwrap() };
1752        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1753
1754        // Move the saved wasm to the old path (without extension)
1755        let old_path = tmp_dir
1756            .path()
1757            .join(STATE_DIR)
1758            .join(WASM_DIR)
1759            .join(checksum.to_hex());
1760        let new_path = old_path.with_extension("wasm");
1761        fs::rename(new_path, old_path).unwrap();
1762
1763        // loading wasm from before the wasm extension was added should still work
1764        let restored = cache.load_wasm(&checksum).unwrap();
1765        assert_eq!(restored, HACKATOM);
1766    }
1767
1768    #[test]
1769    fn func_ref_test() {
1770        let wasm = wat::parse_str(
1771            r#"(module
1772                (type (func))
1773                (type (func (param funcref)))
1774                (import "env" "abort" (func $f (type 1)))
1775                (func (type 0) nop)
1776                (export "add_one" (func 0))
1777                (export "allocate" (func 0))
1778                (export "interface_version_8" (func 0))
1779                (export "deallocate" (func 0))
1780                (export "memory" (memory 0))
1781                (memory 3)
1782            )"#,
1783        )
1784        .unwrap();
1785
1786        let (testing_opts, _temp_dir) = make_testing_options();
1787        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1788            unsafe { Cache::new(testing_opts).unwrap() };
1789
1790        // making sure this doesn't panic
1791        let err = cache.store_code(&wasm, true, true).unwrap_err();
1792        assert!(err.to_string().contains("FuncRef"));
1793    }
1794
1795    #[test]
1796    fn test_wasm_limits_checked() {
1797        let tmp_dir = TempDir::new().unwrap();
1798
1799        let config = Config {
1800            wasm_limits: WasmLimits {
1801                max_function_params: Some(0),
1802                ..Default::default()
1803            },
1804            cache: CacheOptions {
1805                base_dir: tmp_dir.path().to_path_buf(),
1806                available_capabilities: default_capabilities(),
1807                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
1808                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
1809            },
1810        };
1811
1812        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1813            unsafe { Cache::new_with_config(config).unwrap() };
1814        let err = cache.store_code(HACKATOM, true, true).unwrap_err();
1815        assert!(matches!(err, VmError::StaticValidationErr { .. }));
1816    }
1817}