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