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