Skip to main content

cosmwasm_vm/
cache.rs

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