Skip to main content

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    /// Synchronizes the set of pinned modules with the provided `checksums`.
412    pub fn sync_pinned_codes(&self, checksums: &[Checksum]) -> VmResult<()> {
413        let mut add: Vec<Checksum> = vec![];
414        let mut del: Vec<Checksum> = vec![];
415        {
416            let cache = self.inner.lock().unwrap();
417            for (checksum, _) in cache.pinned_memory_cache.iter() {
418                if !checksums.contains(checksum) {
419                    del.push(*checksum);
420                }
421            }
422            for checksum in checksums {
423                if !cache.pinned_memory_cache.has(checksum) {
424                    add.push(*checksum);
425                }
426            }
427        }
428        for checksum in &add {
429            self.pin(checksum)?;
430        }
431        for checksum in &del {
432            self.unpin(checksum)?;
433        }
434        Ok(())
435    }
436
437    /// Returns an Instance tied to a previously saved Wasm.
438    ///
439    /// It takes a module from cache or Wasm code and instantiates it.
440    pub fn get_instance(
441        &self,
442        checksum: &Checksum,
443        backend: Backend<A, S, Q>,
444        options: InstanceOptions,
445    ) -> VmResult<Instance<A, S, Q>> {
446        let (module, store) = self.get_module(checksum)?;
447        let instance = Instance::from_module(
448            store,
449            &module,
450            backend,
451            options.gas_limit,
452            None,
453            Some(&self.instantiation_lock),
454        )?;
455        Ok(instance)
456    }
457
458    /// Returns a module tied to a previously saved Wasm.
459    /// Depending on availability, this is either generated from a memory cache, file system cache or Wasm code.
460    /// This is part of `get_instance` but pulled out to reduce the locking time.
461    fn get_module(&self, checksum: &Checksum) -> VmResult<(Module, Store)> {
462        let mut cache = self.inner.lock().unwrap();
463        // Try to get module from the pinned memory cache
464        if let Some(element) = cache.pinned_memory_cache.load(checksum)? {
465            cache.stats.hits_pinned_memory_cache =
466                cache.stats.hits_pinned_memory_cache.saturating_add(1);
467            let CachedModule {
468                module,
469                engine,
470                size_estimate: _,
471            } = element;
472            let store = Store::new(engine);
473            return Ok((module, store));
474        }
475
476        // Get module from memory cache
477        if let Some(element) = cache.memory_cache.load(checksum)? {
478            cache.stats.hits_memory_cache = cache.stats.hits_memory_cache.saturating_add(1);
479            let CachedModule {
480                module,
481                engine,
482                size_estimate: _,
483            } = element;
484            let store = Store::new(engine);
485            return Ok((module, store));
486        }
487
488        // Get module from file system cache
489        if let Some(cached_module) = cache
490            .fs_cache
491            .load(checksum, Some(self.instance_memory_limit))?
492        {
493            cache.stats.hits_fs_cache = cache.stats.hits_fs_cache.saturating_add(1);
494
495            cache.memory_cache.store(checksum, cached_module.clone())?;
496
497            let CachedModule {
498                module,
499                engine,
500                size_estimate: _,
501            } = cached_module;
502            let store = Store::new(engine);
503            return Ok((module, store));
504        }
505
506        // Re-compile module from wasm
507        //
508        // This is needed for chains that upgrade their node software in a way that changes the module
509        // serialization format. If you do not replay all transactions, previous calls of `store_code`
510        // stored the old module format.
511        let wasm = self.load_wasm_with_path(&cache.wasm_path, checksum)?;
512        cache.stats.misses = cache.stats.misses.saturating_add(1);
513        {
514            // Module will run with a different engine, so we can set memory limit to None
515            let compiling_engine = make_compiling_engine(None);
516            // This module cannot be executed directly as it was not created with the runtime engine
517            let module = compile(&compiling_engine, &wasm)?;
518            cache.fs_cache.store(checksum, &module)?;
519        }
520
521        // This time we'll hit the file-system cache.
522        let Some(cached_module) = cache
523            .fs_cache
524            .load(checksum, Some(self.instance_memory_limit))?
525        else {
526            return Err(VmError::generic_err(
527                "Can't load module from file system cache after storing it to file system cache (get_module)",
528            ));
529        };
530        cache.memory_cache.store(checksum, cached_module.clone())?;
531
532        let CachedModule {
533            module,
534            engine,
535            size_estimate: _,
536        } = cached_module;
537        let store = Store::new(engine);
538        Ok((module, store))
539    }
540}
541
542fn compile_module(wasm: &[u8]) -> Result<Module, VmError> {
543    let compiling_engine = make_compiling_engine(None);
544    let module = compile(&compiling_engine, wasm)?;
545    Ok(module)
546}
547
548unsafe impl<A, S, Q> Sync for Cache<A, S, Q>
549where
550    A: BackendApi + 'static,
551    S: Storage + 'static,
552    Q: Querier + 'static,
553{
554}
555
556unsafe impl<A, S, Q> Send for Cache<A, S, Q>
557where
558    A: BackendApi + 'static,
559    S: Storage + 'static,
560    Q: Querier + 'static,
561{
562}
563
564/// save stores the wasm code in the given directory and returns an ID for lookup.
565/// It will create the directory if it doesn't exist.
566/// Saving the same byte code multiple times is allowed.
567fn save_wasm_to_disk(dir: impl Into<PathBuf>, wasm: &[u8]) -> VmResult<Checksum> {
568    // calculate filename
569    let checksum = Checksum::generate(wasm);
570    let filename = checksum.to_hex();
571    let filepath = dir.into().join(filename).with_extension("wasm");
572
573    // write data to file
574    // Since the same filename (a collision resistant hash) cannot be generated from two different byte codes
575    // (even if a malicious actor tried), it is safe to override.
576    let mut file = OpenOptions::new()
577        .write(true)
578        .create(true)
579        .truncate(true)
580        .open(filepath)
581        .map_err(|e| VmError::cache_err(format!("Error opening Wasm file for writing: {e}")))?;
582    file.write_all(wasm)
583        .map_err(|e| VmError::cache_err(format!("Error writing Wasm file: {e}")))?;
584
585    Ok(checksum)
586}
587
588fn load_wasm_from_disk(dir: impl Into<PathBuf>, checksum: &Checksum) -> VmResult<Vec<u8>> {
589    // this requires the directory and file to exist
590    // The files previously had no extension, so to allow for a smooth transition,
591    // we also try to load the file without the wasm extension.
592    let path = dir.into().join(checksum.to_hex());
593    let mut file = File::open(path.with_extension("wasm"))
594        .or_else(|_| File::open(path))
595        .map_err(|_e| VmError::cache_err("Error opening Wasm file for reading"))?;
596
597    let mut wasm = Vec::<u8>::new();
598    file.read_to_end(&mut wasm)
599        .map_err(|_e| VmError::cache_err("Error reading Wasm file"))?;
600    Ok(wasm)
601}
602
603/// Removes the Wasm blob for the given checksum from disk.
604///
605/// In contrast to the file system cache, the existence of the original
606/// code is required. So a non-existent file leads to an error as it
607/// indicates a bug.
608fn remove_wasm_from_disk(dir: impl Into<PathBuf>, checksum: &Checksum) -> VmResult<()> {
609    // the files previously had no extension, so to allow for a smooth transition, we delete both
610    let path = dir.into().join(checksum.to_hex());
611    let wasm_path = path.with_extension("wasm");
612
613    let path_exists = path.exists();
614    let wasm_path_exists = wasm_path.exists();
615    if !path_exists && !wasm_path_exists {
616        return Err(VmError::cache_err("Wasm file does not exist"));
617    }
618
619    if path_exists {
620        fs::remove_file(path)
621            .map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?;
622    }
623
624    if wasm_path_exists {
625        fs::remove_file(wasm_path)
626            .map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?;
627    }
628
629    Ok(())
630}
631
632#[cfg(test)]
633mod tests {
634    use super::*;
635    use crate::calls::{call_execute, call_instantiate};
636    use crate::testing::{mock_backend, mock_env, mock_info, MockApi, MockQuerier, MockStorage};
637    use cosmwasm_std::{coins, Empty};
638    use std::borrow::Cow;
639    use std::fs::{create_dir_all, remove_dir_all};
640    use tempfile::TempDir;
641    use wasm_encoder::ComponentSection;
642
643    const TESTING_GAS_LIMIT: u64 = 500_000_000; // ~0.5ms
644    const TESTING_MEMORY_LIMIT: Size = Size::mebi(16);
645    const TESTING_OPTIONS: InstanceOptions = InstanceOptions {
646        gas_limit: TESTING_GAS_LIMIT,
647    };
648    const TESTING_MEMORY_CACHE_SIZE: Size = Size::mebi(200);
649
650    static HACKATOM: &[u8] = include_bytes!("../testdata/hackatom.wasm");
651    static IBC_REFLECT: &[u8] = include_bytes!("../testdata/ibc_reflect.wasm");
652    static IBC2: &[u8] = include_bytes!("../testdata/ibc2.wasm");
653    static EMPTY: &[u8] = include_bytes!("../testdata/empty.wasm");
654    // Invalid because it doesn't contain required memory and exports
655    static INVALID_CONTRACT_WAT: &str = r#"(module
656        (type $t0 (func (param i32) (result i32)))
657        (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32)
658            local.get $p0
659            i32.const 1
660            i32.add))
661    "#;
662
663    fn default_capabilities() -> HashSet<String> {
664        HashSet::from([
665            "cosmwasm_1_1".to_string(),
666            "cosmwasm_1_2".to_string(),
667            "cosmwasm_1_3".to_string(),
668            "cosmwasm_1_4".to_string(),
669            "cosmwasm_1_4".to_string(),
670            "cosmwasm_2_0".to_string(),
671            "cosmwasm_2_1".to_string(),
672            "cosmwasm_2_2".to_string(),
673            "iterator".to_string(),
674            "staking".to_string(),
675            "stargate".to_string(),
676        ])
677    }
678
679    fn make_testing_options() -> (CacheOptions, TempDir) {
680        let temp_dir = TempDir::new().unwrap();
681        (
682            CacheOptions {
683                base_dir: temp_dir.path().into(),
684                available_capabilities: default_capabilities(),
685                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
686                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
687            },
688            temp_dir,
689        )
690    }
691
692    fn make_stargate_testing_options() -> (CacheOptions, TempDir) {
693        let temp_dir = TempDir::new().unwrap();
694        let mut capabilities = default_capabilities();
695        capabilities.insert("stargate".into());
696        (
697            CacheOptions {
698                base_dir: temp_dir.path().into(),
699                available_capabilities: capabilities,
700                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
701                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
702            },
703            temp_dir,
704        )
705    }
706
707    fn make_ibc2_testing_options() -> (CacheOptions, TempDir) {
708        let temp_dir = TempDir::new().unwrap();
709        let mut capabilities = default_capabilities();
710        capabilities.insert("ibc2".into());
711        (
712            CacheOptions {
713                base_dir: temp_dir.path().into(),
714                available_capabilities: capabilities,
715                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
716                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
717            },
718            temp_dir,
719        )
720    }
721
722    /// Takes an instance and executes it
723    fn test_hackatom_instance_execution<S, Q>(instance: &mut Instance<MockApi, S, Q>)
724    where
725        S: Storage + 'static,
726        Q: Querier + 'static,
727    {
728        // instantiate
729        let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
730        let verifier = instance.api().addr_make("verifies");
731        let beneficiary = instance.api().addr_make("benefits");
732        let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
733        let response =
734            call_instantiate::<_, _, _, Empty>(instance, &mock_env(), &info, msg.as_bytes())
735                .unwrap()
736                .unwrap();
737        assert_eq!(response.messages.len(), 0);
738
739        // execute
740        let info = mock_info(&verifier, &coins(15, "earth"));
741        let msg = br#"{"release":{"denom":"earth"}}"#;
742        let response = call_execute::<_, _, _, Empty>(instance, &mock_env(), &info, msg)
743            .unwrap()
744            .unwrap();
745        assert_eq!(response.messages.len(), 1);
746    }
747
748    #[test]
749    fn new_base_dir_will_be_created() {
750        let temp_dir = TempDir::new().unwrap();
751        let my_base_dir = temp_dir.path().join("non-existent-sub-dir");
752        let (base_opts, _temp_dir) = make_testing_options();
753        let options = CacheOptions {
754            base_dir: my_base_dir.clone(),
755            ..base_opts
756        };
757        assert!(!my_base_dir.is_dir());
758        let _cache = unsafe { Cache::<MockApi, MockStorage, MockQuerier>::new(options).unwrap() };
759        assert!(my_base_dir.is_dir());
760    }
761
762    #[test]
763    fn store_code_checked_works() {
764        let (testing_opts, _temp_dir) = make_testing_options();
765        let cache: Cache<MockApi, MockStorage, MockQuerier> =
766            unsafe { Cache::new(testing_opts).unwrap() };
767        cache.store_code(HACKATOM, true, true).unwrap();
768    }
769
770    #[test]
771    fn store_code_without_persist_works() {
772        let (testing_opts, _temp_dir) = make_testing_options();
773        let cache: Cache<MockApi, MockStorage, MockQuerier> =
774            unsafe { Cache::new(testing_opts).unwrap() };
775        let checksum = cache.store_code(HACKATOM, true, false).unwrap();
776
777        assert!(
778            cache.load_wasm(&checksum).is_err(),
779            "wasm file should not be saved to disk"
780        );
781    }
782
783    #[test]
784    // This property is required when the same bytecode is uploaded multiple times
785    fn store_code_allows_saving_multiple_times() {
786        let (testing_opts, _temp_dir) = make_testing_options();
787        let cache: Cache<MockApi, MockStorage, MockQuerier> =
788            unsafe { Cache::new(testing_opts).unwrap() };
789        cache.store_code(HACKATOM, true, true).unwrap();
790        cache.store_code(HACKATOM, true, true).unwrap();
791    }
792
793    #[test]
794    fn store_code_checked_rejects_invalid_contract() {
795        let wasm = wat::parse_str(INVALID_CONTRACT_WAT).unwrap();
796
797        let (testing_opts, _temp_dir) = make_testing_options();
798        let cache: Cache<MockApi, MockStorage, MockQuerier> =
799            unsafe { Cache::new(testing_opts).unwrap() };
800        let save_result = cache.store_code(&wasm, true, true);
801        match save_result.unwrap_err() {
802            VmError::StaticValidationErr { msg, .. } => {
803                assert_eq!(msg, "Wasm contract must contain exactly one memory")
804            }
805            e => panic!("Unexpected error {e:?}"),
806        }
807    }
808
809    #[test]
810    fn store_code_fills_file_system_but_not_memory_cache() {
811        // Who knows if and when the uploaded contract will be executed. Don't pollute
812        // memory cache before the init call.
813
814        let (testing_opts, _temp_dir) = make_testing_options();
815        let cache = unsafe { Cache::new(testing_opts).unwrap() };
816        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
817
818        let backend = mock_backend(&[]);
819        let _ = cache
820            .get_instance(&checksum, backend, TESTING_OPTIONS)
821            .unwrap();
822        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
823        assert_eq!(cache.stats().hits_memory_cache, 0);
824        assert_eq!(cache.stats().hits_fs_cache, 1);
825        assert_eq!(cache.stats().misses, 0);
826    }
827
828    #[test]
829    fn store_code_unchecked_works() {
830        let (testing_opts, _temp_dir) = make_testing_options();
831        let cache: Cache<MockApi, MockStorage, MockQuerier> =
832            unsafe { Cache::new(testing_opts).unwrap() };
833        cache.store_code(HACKATOM, false, true).unwrap();
834    }
835
836    #[test]
837    fn store_code_unchecked_accepts_invalid_contract() {
838        let wasm = wat::parse_str(INVALID_CONTRACT_WAT).unwrap();
839
840        let (testing_opts, _temp_dir) = make_testing_options();
841        let cache: Cache<MockApi, MockStorage, MockQuerier> =
842            unsafe { Cache::new(testing_opts).unwrap() };
843        cache.store_code(&wasm, false, true).unwrap();
844    }
845
846    #[test]
847    fn load_wasm_works() {
848        let (testing_opts, _temp_dir) = make_testing_options();
849        let cache: Cache<MockApi, MockStorage, MockQuerier> =
850            unsafe { Cache::new(testing_opts).unwrap() };
851        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
852
853        let restored = cache.load_wasm(&checksum).unwrap();
854        assert_eq!(restored, HACKATOM);
855    }
856
857    #[test]
858    fn load_wasm_works_across_multiple_cache_instances() {
859        let tmp_dir = TempDir::new().unwrap();
860        let id: Checksum;
861
862        {
863            let options1 = CacheOptions {
864                base_dir: tmp_dir.path().to_path_buf(),
865                available_capabilities: default_capabilities(),
866                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
867                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
868            };
869            let cache1: Cache<MockApi, MockStorage, MockQuerier> =
870                unsafe { Cache::new(options1).unwrap() };
871            id = cache1.store_code(HACKATOM, true, true).unwrap();
872        }
873
874        {
875            let options2 = CacheOptions {
876                base_dir: tmp_dir.path().to_path_buf(),
877                available_capabilities: default_capabilities(),
878                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
879                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
880            };
881            let cache2: Cache<MockApi, MockStorage, MockQuerier> =
882                unsafe { Cache::new(options2).unwrap() };
883            let restored = cache2.load_wasm(&id).unwrap();
884            assert_eq!(restored, HACKATOM);
885        }
886    }
887
888    #[test]
889    fn load_wasm_errors_for_non_existent_id() {
890        let (testing_opts, _temp_dir) = make_testing_options();
891        let cache: Cache<MockApi, MockStorage, MockQuerier> =
892            unsafe { Cache::new(testing_opts).unwrap() };
893        let checksum = Checksum::from([
894            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,
895            5, 5, 5,
896        ]);
897
898        match cache.load_wasm(&checksum).unwrap_err() {
899            VmError::CacheErr { msg, .. } => {
900                assert_eq!(msg, "Error opening Wasm file for reading")
901            }
902            e => panic!("Unexpected error: {e:?}"),
903        }
904    }
905
906    #[test]
907    fn load_wasm_errors_for_corrupted_wasm() {
908        let tmp_dir = TempDir::new().unwrap();
909        let options = CacheOptions {
910            base_dir: tmp_dir.path().to_path_buf(),
911            available_capabilities: default_capabilities(),
912            memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
913            instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
914        };
915        let cache: Cache<MockApi, MockStorage, MockQuerier> =
916            unsafe { Cache::new(options).unwrap() };
917        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
918
919        // Corrupt cache file
920        let filepath = tmp_dir
921            .path()
922            .join(STATE_DIR)
923            .join(WASM_DIR)
924            .join(checksum.to_hex())
925            .with_extension("wasm");
926        let mut file = OpenOptions::new().write(true).open(filepath).unwrap();
927        file.write_all(b"broken data").unwrap();
928
929        let res = cache.load_wasm(&checksum);
930        match res {
931            Err(VmError::IntegrityErr { .. }) => {}
932            Err(e) => panic!("Unexpected error: {e:?}"),
933            Ok(_) => panic!("This must not succeed"),
934        }
935    }
936
937    #[test]
938    fn remove_wasm_works() {
939        let (testing_opts, _temp_dir) = make_testing_options();
940        let cache: Cache<MockApi, MockStorage, MockQuerier> =
941            unsafe { Cache::new(testing_opts).unwrap() };
942
943        // Store
944        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
945
946        // Exists
947        cache.load_wasm(&checksum).unwrap();
948
949        // Remove
950        cache.remove_wasm(&checksum).unwrap();
951
952        // Does not exist anymore
953        match cache.load_wasm(&checksum).unwrap_err() {
954            VmError::CacheErr { msg, .. } => {
955                assert_eq!(msg, "Error opening Wasm file for reading")
956            }
957            e => panic!("Unexpected error: {e:?}"),
958        }
959
960        // Removing again fails
961        match cache.remove_wasm(&checksum).unwrap_err() {
962            VmError::CacheErr { msg, .. } => {
963                assert_eq!(msg, "Wasm file does not exist")
964            }
965            e => panic!("Unexpected error: {e:?}"),
966        }
967    }
968
969    #[test]
970    fn get_instance_finds_cached_module() {
971        let (testing_opts, _temp_dir) = make_testing_options();
972        let cache = unsafe { Cache::new(testing_opts).unwrap() };
973        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
974        let backend = mock_backend(&[]);
975        let _instance = cache
976            .get_instance(&checksum, backend, TESTING_OPTIONS)
977            .unwrap();
978        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
979        assert_eq!(cache.stats().hits_memory_cache, 0);
980        assert_eq!(cache.stats().hits_fs_cache, 1);
981        assert_eq!(cache.stats().misses, 0);
982    }
983
984    #[test]
985    fn get_instance_finds_cached_modules_and_stores_to_memory() {
986        let (testing_opts, _temp_dir) = make_testing_options();
987        let cache = unsafe { Cache::new(testing_opts).unwrap() };
988        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
989        let backend1 = mock_backend(&[]);
990        let backend2 = mock_backend(&[]);
991        let backend3 = mock_backend(&[]);
992        let backend4 = mock_backend(&[]);
993        let backend5 = mock_backend(&[]);
994
995        // from file system
996        let _instance1 = cache
997            .get_instance(&checksum, backend1, TESTING_OPTIONS)
998            .unwrap();
999        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1000        assert_eq!(cache.stats().hits_memory_cache, 0);
1001        assert_eq!(cache.stats().hits_fs_cache, 1);
1002        assert_eq!(cache.stats().misses, 0);
1003
1004        // from memory
1005        let _instance2 = cache
1006            .get_instance(&checksum, backend2, TESTING_OPTIONS)
1007            .unwrap();
1008        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1009        assert_eq!(cache.stats().hits_memory_cache, 1);
1010        assert_eq!(cache.stats().hits_fs_cache, 1);
1011        assert_eq!(cache.stats().misses, 0);
1012
1013        // from memory again
1014        let _instance3 = cache
1015            .get_instance(&checksum, backend3, TESTING_OPTIONS)
1016            .unwrap();
1017        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1018        assert_eq!(cache.stats().hits_memory_cache, 2);
1019        assert_eq!(cache.stats().hits_fs_cache, 1);
1020        assert_eq!(cache.stats().misses, 0);
1021
1022        // pinning hits the file system cache
1023        cache.pin(&checksum).unwrap();
1024        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1025        assert_eq!(cache.stats().hits_memory_cache, 2);
1026        assert_eq!(cache.stats().hits_fs_cache, 2);
1027        assert_eq!(cache.stats().misses, 0);
1028
1029        // from pinned memory cache
1030        let _instance4 = cache
1031            .get_instance(&checksum, backend4, TESTING_OPTIONS)
1032            .unwrap();
1033        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1034        assert_eq!(cache.stats().hits_memory_cache, 2);
1035        assert_eq!(cache.stats().hits_fs_cache, 2);
1036        assert_eq!(cache.stats().misses, 0);
1037
1038        // from pinned memory cache again
1039        let _instance5 = cache
1040            .get_instance(&checksum, backend5, TESTING_OPTIONS)
1041            .unwrap();
1042        assert_eq!(cache.stats().hits_pinned_memory_cache, 2);
1043        assert_eq!(cache.stats().hits_memory_cache, 2);
1044        assert_eq!(cache.stats().hits_fs_cache, 2);
1045        assert_eq!(cache.stats().misses, 0);
1046    }
1047
1048    #[test]
1049    fn get_instance_recompiles_module() {
1050        let (options, _temp_dir) = make_testing_options();
1051        let cache = unsafe { Cache::new(options.clone()).unwrap() };
1052        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1053
1054        // Remove compiled module from disk
1055        remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1056
1057        // The first get_instance recompiles the Wasm (miss)
1058        let backend = mock_backend(&[]);
1059        let _instance = cache
1060            .get_instance(&checksum, backend, TESTING_OPTIONS)
1061            .unwrap();
1062        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1063        assert_eq!(cache.stats().hits_memory_cache, 0);
1064        assert_eq!(cache.stats().hits_fs_cache, 0);
1065        assert_eq!(cache.stats().misses, 1);
1066
1067        // The second get_instance finds the module in cache (hit)
1068        let backend = mock_backend(&[]);
1069        let _instance = cache
1070            .get_instance(&checksum, backend, TESTING_OPTIONS)
1071            .unwrap();
1072        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1073        assert_eq!(cache.stats().hits_memory_cache, 1);
1074        assert_eq!(cache.stats().hits_fs_cache, 0);
1075        assert_eq!(cache.stats().misses, 1);
1076    }
1077
1078    #[test]
1079    fn call_instantiate_on_cached_contract() {
1080        let (testing_opts, _temp_dir) = make_testing_options();
1081        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1082        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1083
1084        // from file system
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, 0);
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 memory
1111        {
1112            let mut instance = cache
1113                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1114                .unwrap();
1115            assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1116            assert_eq!(cache.stats().hits_memory_cache, 1);
1117            assert_eq!(cache.stats().hits_fs_cache, 1);
1118            assert_eq!(cache.stats().misses, 0);
1119
1120            // instantiate
1121            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1122            let verifier = instance.api().addr_make("verifies");
1123            let beneficiary = instance.api().addr_make("benefits");
1124            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1125            let res = call_instantiate::<_, _, _, Empty>(
1126                &mut instance,
1127                &mock_env(),
1128                &info,
1129                msg.as_bytes(),
1130            )
1131            .unwrap();
1132            let msgs = res.unwrap().messages;
1133            assert_eq!(msgs.len(), 0);
1134        }
1135
1136        // from pinned memory
1137        {
1138            cache.pin(&checksum).unwrap();
1139
1140            let mut instance = cache
1141                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1142                .unwrap();
1143            assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1144            assert_eq!(cache.stats().hits_memory_cache, 1);
1145            assert_eq!(cache.stats().hits_fs_cache, 2);
1146            assert_eq!(cache.stats().misses, 0);
1147
1148            // instantiate
1149            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1150            let verifier = instance.api().addr_make("verifies");
1151            let beneficiary = instance.api().addr_make("benefits");
1152            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1153            let res = call_instantiate::<_, _, _, Empty>(
1154                &mut instance,
1155                &mock_env(),
1156                &info,
1157                msg.as_bytes(),
1158            )
1159            .unwrap();
1160            let msgs = res.unwrap().messages;
1161            assert_eq!(msgs.len(), 0);
1162        }
1163    }
1164
1165    #[test]
1166    fn call_execute_on_cached_contract() {
1167        let (testing_opts, _temp_dir) = make_testing_options();
1168        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1169        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1170
1171        // from file system
1172        {
1173            let mut instance = cache
1174                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1175                .unwrap();
1176            assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1177            assert_eq!(cache.stats().hits_memory_cache, 0);
1178            assert_eq!(cache.stats().hits_fs_cache, 1);
1179            assert_eq!(cache.stats().misses, 0);
1180
1181            // instantiate
1182            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1183            let verifier = instance.api().addr_make("verifies");
1184            let beneficiary = instance.api().addr_make("benefits");
1185            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1186            let response = call_instantiate::<_, _, _, Empty>(
1187                &mut instance,
1188                &mock_env(),
1189                &info,
1190                msg.as_bytes(),
1191            )
1192            .unwrap()
1193            .unwrap();
1194            assert_eq!(response.messages.len(), 0);
1195
1196            // execute
1197            let info = mock_info(&verifier, &coins(15, "earth"));
1198            let msg = br#"{"release":{"denom":"earth"}}"#;
1199            let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1200                .unwrap()
1201                .unwrap();
1202            assert_eq!(response.messages.len(), 1);
1203        }
1204
1205        // from memory
1206        {
1207            let mut instance = cache
1208                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1209                .unwrap();
1210            assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1211            assert_eq!(cache.stats().hits_memory_cache, 1);
1212            assert_eq!(cache.stats().hits_fs_cache, 1);
1213            assert_eq!(cache.stats().misses, 0);
1214
1215            // instantiate
1216            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1217            let verifier = instance.api().addr_make("verifies");
1218            let beneficiary = instance.api().addr_make("benefits");
1219            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1220            let response = call_instantiate::<_, _, _, Empty>(
1221                &mut instance,
1222                &mock_env(),
1223                &info,
1224                msg.as_bytes(),
1225            )
1226            .unwrap()
1227            .unwrap();
1228            assert_eq!(response.messages.len(), 0);
1229
1230            // execute
1231            let info = mock_info(&verifier, &coins(15, "earth"));
1232            let msg = br#"{"release":{"denom":"earth"}}"#;
1233            let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1234                .unwrap()
1235                .unwrap();
1236            assert_eq!(response.messages.len(), 1);
1237        }
1238
1239        // from pinned memory
1240        {
1241            cache.pin(&checksum).unwrap();
1242
1243            let mut instance = cache
1244                .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1245                .unwrap();
1246            assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1247            assert_eq!(cache.stats().hits_memory_cache, 1);
1248            assert_eq!(cache.stats().hits_fs_cache, 2);
1249            assert_eq!(cache.stats().misses, 0);
1250
1251            // instantiate
1252            let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1253            let verifier = instance.api().addr_make("verifies");
1254            let beneficiary = instance.api().addr_make("benefits");
1255            let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1256            let response = call_instantiate::<_, _, _, Empty>(
1257                &mut instance,
1258                &mock_env(),
1259                &info,
1260                msg.as_bytes(),
1261            )
1262            .unwrap()
1263            .unwrap();
1264            assert_eq!(response.messages.len(), 0);
1265
1266            // execute
1267            let info = mock_info(&verifier, &coins(15, "earth"));
1268            let msg = br#"{"release":{"denom":"earth"}}"#;
1269            let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1270                .unwrap()
1271                .unwrap();
1272            assert_eq!(response.messages.len(), 1);
1273        }
1274    }
1275
1276    #[test]
1277    fn call_execute_on_recompiled_contract() {
1278        let (options, _temp_dir) = make_testing_options();
1279        let cache = unsafe { Cache::new(options.clone()).unwrap() };
1280        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1281
1282        // Remove compiled module from disk
1283        remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1284
1285        // Recompiles the Wasm (miss on all caches)
1286        let backend = mock_backend(&[]);
1287        let mut instance = cache
1288            .get_instance(&checksum, backend, TESTING_OPTIONS)
1289            .unwrap();
1290        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1291        assert_eq!(cache.stats().hits_memory_cache, 0);
1292        assert_eq!(cache.stats().hits_fs_cache, 0);
1293        assert_eq!(cache.stats().misses, 1);
1294        test_hackatom_instance_execution(&mut instance);
1295    }
1296
1297    #[test]
1298    fn use_multiple_cached_instances_of_same_contract() {
1299        let (testing_opts, _temp_dir) = make_testing_options();
1300        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1301        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1302
1303        // these differentiate the two instances of the same contract
1304        let backend1 = mock_backend(&[]);
1305        let backend2 = mock_backend(&[]);
1306
1307        // instantiate instance 1
1308        let mut instance = cache
1309            .get_instance(&checksum, backend1, TESTING_OPTIONS)
1310            .unwrap();
1311        let info = mock_info("owner1", &coins(1000, "earth"));
1312        let sue = instance.api().addr_make("sue");
1313        let mary = instance.api().addr_make("mary");
1314        let msg = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1315        let res =
1316            call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
1317                .unwrap();
1318        let msgs = res.unwrap().messages;
1319        assert_eq!(msgs.len(), 0);
1320        let backend1 = instance.recycle().unwrap();
1321
1322        // instantiate instance 2
1323        let mut instance = cache
1324            .get_instance(&checksum, backend2, TESTING_OPTIONS)
1325            .unwrap();
1326        let info = mock_info("owner2", &coins(500, "earth"));
1327        let bob = instance.api().addr_make("bob");
1328        let john = instance.api().addr_make("john");
1329        let msg = format!(r#"{{"verifier": "{bob}", "beneficiary": "{john}"}}"#);
1330        let res =
1331            call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
1332                .unwrap();
1333        let msgs = res.unwrap().messages;
1334        assert_eq!(msgs.len(), 0);
1335        let backend2 = instance.recycle().unwrap();
1336
1337        // run contract 2 - just sanity check - results validate in contract unit tests
1338        let mut instance = cache
1339            .get_instance(&checksum, backend2, TESTING_OPTIONS)
1340            .unwrap();
1341        let info = mock_info(&bob, &coins(15, "earth"));
1342        let msg = br#"{"release":{"denom":"earth"}}"#;
1343        let res = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap();
1344        let msgs = res.unwrap().messages;
1345        assert_eq!(1, msgs.len());
1346
1347        // run contract 1 - just sanity check - results validate in contract unit tests
1348        let mut instance = cache
1349            .get_instance(&checksum, backend1, TESTING_OPTIONS)
1350            .unwrap();
1351        let info = mock_info(&sue, &coins(15, "earth"));
1352        let msg = br#"{"release":{"denom":"earth"}}"#;
1353        let res = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap();
1354        let msgs = res.unwrap().messages;
1355        assert_eq!(1, msgs.len());
1356    }
1357
1358    #[test]
1359    fn resets_gas_when_reusing_instance() {
1360        let (testing_opts, _temp_dir) = make_testing_options();
1361        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1362        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1363
1364        let backend1 = mock_backend(&[]);
1365        let backend2 = mock_backend(&[]);
1366
1367        // Init from module cache
1368        let mut instance1 = cache
1369            .get_instance(&checksum, backend1, TESTING_OPTIONS)
1370            .unwrap();
1371        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1372        assert_eq!(cache.stats().hits_memory_cache, 0);
1373        assert_eq!(cache.stats().hits_fs_cache, 1);
1374        assert_eq!(cache.stats().misses, 0);
1375        let original_gas = instance1.get_gas_left();
1376
1377        // Consume some gas
1378        let info = mock_info("owner1", &coins(1000, "earth"));
1379        let sue = instance1.api().addr_make("sue");
1380        let mary = instance1.api().addr_make("mary");
1381        let msg = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1382        call_instantiate::<_, _, _, Empty>(&mut instance1, &mock_env(), &info, msg.as_bytes())
1383            .unwrap()
1384            .unwrap();
1385        assert!(instance1.get_gas_left() < original_gas);
1386
1387        // Init from memory cache
1388        let mut instance2 = cache
1389            .get_instance(&checksum, backend2, TESTING_OPTIONS)
1390            .unwrap();
1391        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1392        assert_eq!(cache.stats().hits_memory_cache, 1);
1393        assert_eq!(cache.stats().hits_fs_cache, 1);
1394        assert_eq!(cache.stats().misses, 0);
1395        assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT);
1396    }
1397
1398    #[test]
1399    fn recovers_from_out_of_gas() {
1400        let (testing_opts, _temp_dir) = make_testing_options();
1401        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1402        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1403
1404        let backend1 = mock_backend(&[]);
1405        let backend2 = mock_backend(&[]);
1406
1407        // Init from module cache
1408        let options = InstanceOptions { gas_limit: 10 };
1409        let mut instance1 = cache.get_instance(&checksum, backend1, options).unwrap();
1410        assert_eq!(cache.stats().hits_fs_cache, 1);
1411        assert_eq!(cache.stats().misses, 0);
1412
1413        // Consume some gas. This fails
1414        let info1 = mock_info("owner1", &coins(1000, "earth"));
1415        let sue = instance1.api().addr_make("sue");
1416        let mary = instance1.api().addr_make("mary");
1417        let msg1 = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1418
1419        match call_instantiate::<_, _, _, Empty>(
1420            &mut instance1,
1421            &mock_env(),
1422            &info1,
1423            msg1.as_bytes(),
1424        )
1425        .unwrap_err()
1426        {
1427            VmError::GasDepletion { .. } => (), // all good, continue
1428            e => panic!("unexpected error, {e:?}"),
1429        }
1430        assert_eq!(instance1.get_gas_left(), 0);
1431
1432        // Init from memory cache
1433        let options = InstanceOptions {
1434            gas_limit: TESTING_GAS_LIMIT,
1435        };
1436        let mut instance2 = cache.get_instance(&checksum, backend2, options).unwrap();
1437        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1438        assert_eq!(cache.stats().hits_memory_cache, 1);
1439        assert_eq!(cache.stats().hits_fs_cache, 1);
1440        assert_eq!(cache.stats().misses, 0);
1441        assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT);
1442
1443        // Now it works
1444        let info2 = mock_info("owner2", &coins(500, "earth"));
1445        let bob = instance2.api().addr_make("bob");
1446        let john = instance2.api().addr_make("john");
1447        let msg2 = format!(r#"{{"verifier": "{bob}", "beneficiary": "{john}"}}"#);
1448        call_instantiate::<_, _, _, Empty>(&mut instance2, &mock_env(), &info2, msg2.as_bytes())
1449            .unwrap()
1450            .unwrap();
1451    }
1452
1453    #[test]
1454    fn save_wasm_to_disk_works_for_same_data_multiple_times() {
1455        let tmp_dir = TempDir::new().unwrap();
1456        let path = tmp_dir.path();
1457        let code = vec![12u8; 17];
1458
1459        save_wasm_to_disk(path, &code).unwrap();
1460        save_wasm_to_disk(path, &code).unwrap();
1461    }
1462
1463    #[test]
1464    fn save_wasm_to_disk_fails_on_non_existent_dir() {
1465        let tmp_dir = TempDir::new().unwrap();
1466        let path = tmp_dir.path().join("something");
1467        let code = vec![12u8; 17];
1468        let res = save_wasm_to_disk(path.to_str().unwrap(), &code);
1469        assert!(res.is_err());
1470    }
1471
1472    #[test]
1473    fn load_wasm_from_disk_works() {
1474        let tmp_dir = TempDir::new().unwrap();
1475        let path = tmp_dir.path();
1476        let code = vec![12u8; 17];
1477        let checksum = save_wasm_to_disk(path, &code).unwrap();
1478
1479        let loaded = load_wasm_from_disk(path, &checksum).unwrap();
1480        assert_eq!(code, loaded);
1481    }
1482
1483    #[test]
1484    fn load_wasm_from_disk_works_in_subfolder() {
1485        let tmp_dir = TempDir::new().unwrap();
1486        let path = tmp_dir.path().join("something");
1487        create_dir_all(&path).unwrap();
1488        let code = vec![12u8; 17];
1489        let checksum = save_wasm_to_disk(&path, &code).unwrap();
1490
1491        let loaded = load_wasm_from_disk(&path, &checksum).unwrap();
1492        assert_eq!(code, loaded);
1493    }
1494
1495    #[test]
1496    fn remove_wasm_from_disk_works() {
1497        let tmp_dir = TempDir::new().unwrap();
1498        let path = tmp_dir.path();
1499        let code = vec![12u8; 17];
1500        let checksum = save_wasm_to_disk(path, &code).unwrap();
1501
1502        remove_wasm_from_disk(path, &checksum).unwrap();
1503
1504        // removing again fails
1505
1506        match remove_wasm_from_disk(path, &checksum).unwrap_err() {
1507            VmError::CacheErr { msg, .. } => assert_eq!(msg, "Wasm file does not exist"),
1508            err => panic!("Unexpected error: {err:?}"),
1509        }
1510    }
1511
1512    #[test]
1513    fn analyze_works() {
1514        use Entrypoint as E;
1515
1516        let (testing_opts, _temp_dir) = make_stargate_testing_options();
1517        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1518            unsafe { Cache::new(testing_opts).unwrap() };
1519
1520        let checksum1 = cache.store_code(HACKATOM, true, true).unwrap();
1521        let report1 = cache.analyze(&checksum1).unwrap();
1522        assert_eq!(
1523            report1,
1524            AnalysisReport {
1525                has_ibc_entry_points: false,
1526                entrypoints: BTreeSet::from([
1527                    E::Instantiate,
1528                    E::Migrate,
1529                    E::Sudo,
1530                    E::Execute,
1531                    E::Query
1532                ]),
1533                required_capabilities: BTreeSet::from([
1534                    "cosmwasm_1_1".to_string(),
1535                    "cosmwasm_1_2".to_string(),
1536                    "cosmwasm_1_3".to_string(),
1537                    "cosmwasm_1_4".to_string(),
1538                    "cosmwasm_1_4".to_string(),
1539                    "cosmwasm_2_0".to_string(),
1540                    "cosmwasm_2_1".to_string(),
1541                    "cosmwasm_2_2".to_string(),
1542                ]),
1543                contract_migrate_version: Some(420),
1544            }
1545        );
1546
1547        let checksum2 = cache.store_code(IBC_REFLECT, true, true).unwrap();
1548        let report2 = cache.analyze(&checksum2).unwrap();
1549        let mut ibc_contract_entrypoints =
1550            BTreeSet::from([E::Instantiate, E::Migrate, E::Execute, E::Reply, E::Query]);
1551        ibc_contract_entrypoints.extend(REQUIRED_IBC_EXPORTS);
1552        assert_eq!(
1553            report2,
1554            AnalysisReport {
1555                has_ibc_entry_points: true,
1556                entrypoints: ibc_contract_entrypoints,
1557                required_capabilities: BTreeSet::from_iter([
1558                    "cosmwasm_1_1".to_string(),
1559                    "cosmwasm_1_2".to_string(),
1560                    "cosmwasm_1_3".to_string(),
1561                    "cosmwasm_1_4".to_string(),
1562                    "cosmwasm_1_4".to_string(),
1563                    "cosmwasm_2_0".to_string(),
1564                    "cosmwasm_2_1".to_string(),
1565                    "cosmwasm_2_2".to_string(),
1566                    "iterator".to_string(),
1567                    "stargate".to_string()
1568                ]),
1569                contract_migrate_version: None,
1570            }
1571        );
1572
1573        let checksum3 = cache.store_code(EMPTY, true, true).unwrap();
1574        let report3 = cache.analyze(&checksum3).unwrap();
1575        assert_eq!(
1576            report3,
1577            AnalysisReport {
1578                has_ibc_entry_points: false,
1579                entrypoints: BTreeSet::new(),
1580                required_capabilities: BTreeSet::from(["iterator".to_string()]),
1581                contract_migrate_version: None,
1582            }
1583        );
1584
1585        let mut wasm_with_version = EMPTY.to_vec();
1586        let custom_section = wasm_encoder::CustomSection {
1587            name: Cow::Borrowed("cw_migrate_version"),
1588            data: Cow::Borrowed(b"21"),
1589        };
1590        custom_section.append_to_component(&mut wasm_with_version);
1591
1592        let checksum4 = cache.store_code(&wasm_with_version, true, true).unwrap();
1593        let report4 = cache.analyze(&checksum4).unwrap();
1594        assert_eq!(
1595            report4,
1596            AnalysisReport {
1597                has_ibc_entry_points: false,
1598                entrypoints: BTreeSet::new(),
1599                required_capabilities: BTreeSet::from(["iterator".to_string()]),
1600                contract_migrate_version: Some(21),
1601            }
1602        );
1603
1604        let (testing_opts, _temp_dir) = make_ibc2_testing_options();
1605        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1606            unsafe { Cache::new(testing_opts).unwrap() };
1607        let checksum5 = cache.store_code(IBC2, true, true).unwrap();
1608        let report5 = cache.analyze(&checksum5).unwrap();
1609        let ibc2_contract_entrypoints = BTreeSet::from([
1610            E::Instantiate,
1611            E::Query,
1612            E::Ibc2PacketReceive,
1613            E::Ibc2PacketTimeout,
1614            E::Ibc2PacketAck,
1615            E::Ibc2PacketSend,
1616        ]);
1617        assert_eq!(
1618            report5,
1619            AnalysisReport {
1620                has_ibc_entry_points: false,
1621                entrypoints: ibc2_contract_entrypoints,
1622                required_capabilities: BTreeSet::from_iter([
1623                    "iterator".to_string(),
1624                    "ibc2".to_string()
1625                ]),
1626                contract_migrate_version: None,
1627            }
1628        );
1629    }
1630
1631    #[test]
1632    fn pinned_metrics_works() {
1633        let (testing_opts, _temp_dir) = make_testing_options();
1634        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1635        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1636
1637        cache.pin(&checksum).unwrap();
1638
1639        let pinned_metrics = cache.pinned_metrics();
1640        assert_eq!(pinned_metrics.per_module.len(), 1);
1641        assert_eq!(pinned_metrics.per_module[0].0, checksum);
1642        assert_eq!(pinned_metrics.per_module[0].1.hits, 0);
1643
1644        let backend = mock_backend(&[]);
1645        let _ = cache
1646            .get_instance(&checksum, backend, TESTING_OPTIONS)
1647            .unwrap();
1648
1649        let pinned_metrics = cache.pinned_metrics();
1650        assert_eq!(pinned_metrics.per_module.len(), 1);
1651        assert_eq!(pinned_metrics.per_module[0].0, checksum);
1652        assert_eq!(pinned_metrics.per_module[0].1.hits, 1);
1653
1654        let empty_checksum = cache.store_code(EMPTY, true, true).unwrap();
1655        cache.pin(&empty_checksum).unwrap();
1656
1657        let pinned_metrics = cache.pinned_metrics();
1658        assert_eq!(pinned_metrics.per_module.len(), 2);
1659
1660        let get_module_hits = |checksum| {
1661            pinned_metrics
1662                .per_module
1663                .iter()
1664                .find(|(iter_checksum, _module)| *iter_checksum == checksum)
1665                .map(|(_checksum, module)| module)
1666                .cloned()
1667                .unwrap()
1668        };
1669
1670        assert_eq!(get_module_hits(checksum).hits, 1);
1671        assert_eq!(get_module_hits(empty_checksum).hits, 0);
1672    }
1673
1674    #[test]
1675    fn pin_unpin_works() {
1676        let (testing_opts, _temp_dir) = make_testing_options();
1677        let cache = unsafe { Cache::new(testing_opts).unwrap() };
1678        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1679
1680        // check not pinned
1681        let backend = mock_backend(&[]);
1682        let mut instance = cache
1683            .get_instance(&checksum, backend, TESTING_OPTIONS)
1684            .unwrap();
1685        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1686        assert_eq!(cache.stats().hits_memory_cache, 0);
1687        assert_eq!(cache.stats().hits_fs_cache, 1);
1688        assert_eq!(cache.stats().misses, 0);
1689        test_hackatom_instance_execution(&mut instance);
1690
1691        // first pin hits file system cache
1692        cache.pin(&checksum).unwrap();
1693        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1694        assert_eq!(cache.stats().hits_memory_cache, 0);
1695        assert_eq!(cache.stats().hits_fs_cache, 2);
1696        assert_eq!(cache.stats().misses, 0);
1697
1698        // consecutive pins are no-ops
1699        cache.pin(&checksum).unwrap();
1700        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1701        assert_eq!(cache.stats().hits_memory_cache, 0);
1702        assert_eq!(cache.stats().hits_fs_cache, 2);
1703        assert_eq!(cache.stats().misses, 0);
1704
1705        // check pinned
1706        let backend = mock_backend(&[]);
1707        let mut instance = cache
1708            .get_instance(&checksum, backend, TESTING_OPTIONS)
1709            .unwrap();
1710        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1711        assert_eq!(cache.stats().hits_memory_cache, 0);
1712        assert_eq!(cache.stats().hits_fs_cache, 2);
1713        assert_eq!(cache.stats().misses, 0);
1714        test_hackatom_instance_execution(&mut instance);
1715
1716        // unpin
1717        cache.unpin(&checksum).unwrap();
1718
1719        // verify unpinned
1720        let backend = mock_backend(&[]);
1721        let mut instance = cache
1722            .get_instance(&checksum, backend, TESTING_OPTIONS)
1723            .unwrap();
1724        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1725        assert_eq!(cache.stats().hits_memory_cache, 1);
1726        assert_eq!(cache.stats().hits_fs_cache, 2);
1727        assert_eq!(cache.stats().misses, 0);
1728        test_hackatom_instance_execution(&mut instance);
1729
1730        // unpin again has no effect
1731        cache.unpin(&checksum).unwrap();
1732
1733        // unpin non existent id has no effect
1734        let non_id = Checksum::generate(b"non_existent");
1735        cache.unpin(&non_id).unwrap();
1736    }
1737
1738    #[test]
1739    fn pin_recompiles_module() {
1740        let (options, _temp_dir) = make_testing_options();
1741        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1742            unsafe { Cache::new(options.clone()).unwrap() };
1743        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1744
1745        // Remove compiled module from disk
1746        remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1747
1748        // Pin misses, forcing a re-compile of the module
1749        cache.pin(&checksum).unwrap();
1750        assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1751        assert_eq!(cache.stats().hits_memory_cache, 0);
1752        assert_eq!(cache.stats().hits_fs_cache, 0);
1753        assert_eq!(cache.stats().misses, 1);
1754
1755        // After the compilation in pin, the module can be used from pinned memory cache
1756        let backend = mock_backend(&[]);
1757        let mut instance = cache
1758            .get_instance(&checksum, backend, TESTING_OPTIONS)
1759            .unwrap();
1760        assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1761        assert_eq!(cache.stats().hits_memory_cache, 0);
1762        assert_eq!(cache.stats().hits_fs_cache, 0);
1763        assert_eq!(cache.stats().misses, 1);
1764        test_hackatom_instance_execution(&mut instance);
1765    }
1766
1767    #[test]
1768    fn loading_without_extension_works() {
1769        let tmp_dir = TempDir::new().unwrap();
1770        let options = CacheOptions {
1771            base_dir: tmp_dir.path().to_path_buf(),
1772            available_capabilities: default_capabilities(),
1773            memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
1774            instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
1775        };
1776        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1777            unsafe { Cache::new(options).unwrap() };
1778        let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1779
1780        // Move the saved wasm to the old path (without extension)
1781        let old_path = tmp_dir
1782            .path()
1783            .join(STATE_DIR)
1784            .join(WASM_DIR)
1785            .join(checksum.to_hex());
1786        let new_path = old_path.with_extension("wasm");
1787        fs::rename(new_path, old_path).unwrap();
1788
1789        // loading wasm from before the wasm extension was added should still work
1790        let restored = cache.load_wasm(&checksum).unwrap();
1791        assert_eq!(restored, HACKATOM);
1792    }
1793
1794    #[test]
1795    fn func_ref_test() {
1796        let wasm = wat::parse_str(
1797            r#"(module
1798                (type (func))
1799                (type (func (param funcref)))
1800                (import "env" "abort" (func $f (type 1)))
1801                (func (type 0) nop)
1802                (export "add_one" (func 0))
1803                (export "allocate" (func 0))
1804                (export "interface_version_8" (func 0))
1805                (export "deallocate" (func 0))
1806                (export "memory" (memory 0))
1807                (memory 3)
1808            )"#,
1809        )
1810        .unwrap();
1811
1812        let (testing_opts, _temp_dir) = make_testing_options();
1813        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1814            unsafe { Cache::new(testing_opts).unwrap() };
1815
1816        // making sure this doesn't panic
1817        let err = cache.store_code(&wasm, true, true).unwrap_err();
1818        assert!(err.to_string().contains("FuncRef"));
1819    }
1820
1821    #[test]
1822    fn test_wasm_limits_checked() {
1823        let tmp_dir = TempDir::new().unwrap();
1824
1825        let config = Config {
1826            wasm_limits: WasmLimits {
1827                max_function_params: Some(0),
1828                ..Default::default()
1829            },
1830            cache: CacheOptions {
1831                base_dir: tmp_dir.path().to_path_buf(),
1832                available_capabilities: default_capabilities(),
1833                memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
1834                instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
1835            },
1836        };
1837
1838        let cache: Cache<MockApi, MockStorage, MockQuerier> =
1839            unsafe { Cache::new_with_config(config).unwrap() };
1840        let err = cache.store_code(HACKATOM, true, true).unwrap_err();
1841        assert!(matches!(err, VmError::StaticValidationErr { .. }));
1842    }
1843}