1use crate::backend::{Backend, BackendApi, Querier, Storage};
2use crate::capabilities::required_capabilities_from_module;
3use crate::compatibility::check_wasm;
4use crate::config::{CacheOptions, Config, WasmLimits};
5use crate::errors::{VmError, VmResult};
6use crate::filesystem::mkdir_p;
7use crate::instance::{Instance, InstanceOptions};
8use crate::modules::{CachedModule, FileSystemCache, InMemoryCache, PinnedMemoryCache};
9use crate::parsed_wasm::ParsedWasm;
10use crate::size::Size;
11use crate::static_analysis::{Entrypoint, ExportInfo, REQUIRED_IBC_EXPORTS};
12use crate::wasm_backend::compile_module;
13use cosmwasm_std::Checksum;
14use std::collections::{BTreeSet, HashSet};
15use std::fs::{self, File, OpenOptions};
16use std::io::{Read, Write};
17use std::marker::PhantomData;
18use std::path::{Path, PathBuf};
19use std::str::FromStr;
20use std::sync::Mutex;
21use wasmer::{Module, Store};
22
23const STATE_DIR: &str = "state";
24const WASM_DIR: &str = "wasm";
26
27const CACHE_DIR: &str = "cache";
28const MODULES_DIR: &str = "modules";
30
31#[derive(Debug, Default, Clone, Copy)]
39pub struct Stats {
40 pub hits_pinned_memory_cache: u32,
41 pub hits_memory_cache: u32,
42 pub hits_fs_cache: u32,
43 pub misses: u32,
44}
45
46#[derive(Debug, Clone, Copy)]
47pub struct Metrics {
48 pub stats: Stats,
49 pub elements_pinned_memory_cache: usize,
50 pub elements_memory_cache: usize,
51 pub size_pinned_memory_cache: usize,
52 pub size_memory_cache: usize,
53}
54
55#[derive(Debug, Clone)]
56pub struct PerModuleMetrics {
57 pub hits: u32,
59 pub size: usize,
61}
62
63#[derive(Debug, Clone)]
64pub struct PinnedMetrics {
65 pub per_module: Vec<(Checksum, PerModuleMetrics)>,
69}
70
71pub struct CacheInner {
72 wasm_path: PathBuf,
74 pinned_memory_cache: PinnedMemoryCache,
75 memory_cache: InMemoryCache,
76 fs_cache: FileSystemCache,
77 stats: Stats,
78}
79
80pub struct Cache<A: BackendApi, S: Storage, Q: Querier> {
81 available_capabilities: HashSet<String>,
84 inner: Mutex<CacheInner>,
85 instance_memory_limit: Size,
86 type_api: PhantomData<A>,
88 type_storage: PhantomData<S>,
89 type_querier: PhantomData<Q>,
90 instantiation_lock: Mutex<()>,
92 wasm_limits: WasmLimits,
93}
94
95#[derive(PartialEq, Eq, Debug)]
96#[non_exhaustive]
97pub struct AnalysisReport {
98 pub has_ibc_entry_points: bool,
101 pub entrypoints: BTreeSet<Entrypoint>,
103 pub required_capabilities: BTreeSet<String>,
105 pub contract_migrate_version: Option<u64>,
107}
108
109impl<A, S, Q> Cache<A, S, Q>
110where
111 A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static, {
115 pub unsafe fn new(options: CacheOptions) -> VmResult<Self> {
123 Self::new_with_config(Config {
124 wasm_limits: WasmLimits::default(),
125 cache: options,
126 })
127 }
128
129 pub unsafe fn new_with_config(config: Config) -> VmResult<Self> {
138 let Config {
139 cache:
140 CacheOptions {
141 base_dir,
142 available_capabilities,
143 memory_cache_size_bytes,
144 instance_memory_limit_bytes,
145 },
146 wasm_limits,
147 } = config;
148
149 let state_path = base_dir.join(STATE_DIR);
150 let cache_path = base_dir.join(CACHE_DIR);
151
152 let wasm_path = state_path.join(WASM_DIR);
153
154 mkdir_p(&state_path).map_err(|_e| VmError::cache_err("Error creating state directory"))?;
156 mkdir_p(&cache_path).map_err(|_e| VmError::cache_err("Error creating cache directory"))?;
157 mkdir_p(&wasm_path).map_err(|_e| VmError::cache_err("Error creating wasm directory"))?;
158
159 let fs_cache = FileSystemCache::new(cache_path.join(MODULES_DIR), false)
160 .map_err(|e| VmError::cache_err(format!("Error file system cache: {e}")))?;
161 Ok(Cache {
162 available_capabilities,
163 inner: Mutex::new(CacheInner {
164 wasm_path,
165 pinned_memory_cache: PinnedMemoryCache::new(),
166 memory_cache: InMemoryCache::new(memory_cache_size_bytes),
167 fs_cache,
168 stats: Stats::default(),
169 }),
170 instance_memory_limit: instance_memory_limit_bytes,
171 type_storage: PhantomData::<S>,
172 type_api: PhantomData::<A>,
173 type_querier: PhantomData::<Q>,
174 instantiation_lock: Mutex::new(()),
175 wasm_limits,
176 })
177 }
178
179 pub fn set_module_unchecked(&mut self, unchecked: bool) {
182 self.inner
183 .lock()
184 .unwrap()
185 .fs_cache
186 .set_module_unchecked(unchecked);
187 }
188
189 pub fn stats(&self) -> Stats {
190 self.inner.lock().unwrap().stats
191 }
192
193 pub fn pinned_metrics(&self) -> PinnedMetrics {
194 let cache = self.inner.lock().unwrap();
195 let per_module = cache
196 .pinned_memory_cache
197 .iter()
198 .map(|(checksum, module)| {
199 let metrics = PerModuleMetrics {
200 hits: module.hits,
201 size: module.module.size_estimate,
202 };
203
204 (*checksum, metrics)
205 })
206 .collect();
207
208 PinnedMetrics { per_module }
209 }
210
211 pub fn metrics(&self) -> Metrics {
212 let cache = self.inner.lock().unwrap();
213 Metrics {
214 stats: cache.stats,
215 elements_pinned_memory_cache: cache.pinned_memory_cache.len(),
216 elements_memory_cache: cache.memory_cache.len(),
217 size_pinned_memory_cache: cache.pinned_memory_cache.size(),
218 size_memory_cache: cache.memory_cache.size(),
219 }
220 }
221
222 #[deprecated = "Use `store_code(wasm, true, true)` instead"]
230 pub fn save_wasm(&self, wasm: &[u8]) -> VmResult<Checksum> {
231 self.store_code(wasm, true, true)
232 }
233
234 pub fn store_code(&self, wasm: &[u8], checked: bool, persist: bool) -> VmResult<Checksum> {
243 if checked {
244 check_wasm(
245 wasm,
246 &self.available_capabilities,
247 &self.wasm_limits,
248 crate::internals::Logger::Off,
249 )?;
250 }
251
252 let (module, _) = compile_module(wasm, None)?;
253
254 if persist {
255 self.save_to_disk(wasm, &module)
256 } else {
257 Ok(Checksum::generate(wasm))
258 }
259 }
260
261 #[deprecated = "Use `store_code(wasm, false, true)` instead"]
270 pub fn save_wasm_unchecked(&self, wasm: &[u8]) -> VmResult<Checksum> {
271 self.store_code(wasm, false, true)
272 }
273
274 fn save_to_disk(&self, wasm: &[u8], module: &Module) -> VmResult<Checksum> {
275 let mut cache = self.inner.lock().unwrap();
276 let checksum = save_wasm_to_disk(&cache.wasm_path, wasm)?;
277 cache.fs_cache.store(&checksum, module)?;
278 Ok(checksum)
279 }
280
281 pub fn remove_wasm(&self, checksum: &Checksum) -> VmResult<()> {
287 let mut cache = self.inner.lock().unwrap();
288
289 cache.fs_cache.remove(checksum)?;
294
295 let path = &cache.wasm_path;
296 remove_wasm_from_disk(path, checksum)?;
297 Ok(())
298 }
299
300 pub fn load_wasm(&self, checksum: &Checksum) -> VmResult<Vec<u8>> {
306 self.load_wasm_with_path(&self.inner.lock().unwrap().wasm_path, checksum)
307 }
308
309 fn load_wasm_with_path(&self, wasm_path: &Path, checksum: &Checksum) -> VmResult<Vec<u8>> {
310 let code = load_wasm_from_disk(wasm_path, checksum)?;
311 if Checksum::generate(&code) != *checksum {
313 Err(VmError::integrity_err())
314 } else {
315 Ok(code)
316 }
317 }
318
319 pub fn analyze(&self, checksum: &Checksum) -> VmResult<AnalysisReport> {
324 let wasm = self.load_wasm(checksum)?;
326 let module = ParsedWasm::parse(&wasm)?;
327 let exports = module.exported_function_names(None);
328
329 let entrypoints = exports
330 .iter()
331 .filter_map(|export| Entrypoint::from_str(export).ok())
332 .collect();
333
334 Ok(AnalysisReport {
335 has_ibc_entry_points: REQUIRED_IBC_EXPORTS
336 .iter()
337 .all(|required| exports.contains(required.as_ref())),
338 entrypoints,
339 required_capabilities: required_capabilities_from_module(&module)
340 .into_iter()
341 .collect(),
342 contract_migrate_version: module.contract_migrate_version,
343 })
344 }
345
346 pub fn pin(&self, checksum: &Checksum) -> VmResult<()> {
355 let mut cache = self.inner.lock().unwrap();
356 if cache.pinned_memory_cache.has(checksum) {
357 return Ok(());
358 }
359
360 if let Some(cached_module) = cache
366 .fs_cache
367 .load(checksum, Some(self.instance_memory_limit))?
368 {
369 cache.stats.hits_fs_cache = cache.stats.hits_fs_cache.saturating_add(1);
370 return cache.pinned_memory_cache.store(checksum, cached_module);
371 }
372
373 let wasm = self.load_wasm_with_path(&cache.wasm_path, checksum)?;
375 cache.stats.misses = cache.stats.misses.saturating_add(1);
376 {
377 let (module, _) = compile_module(&wasm, None)?;
378 cache.fs_cache.store(checksum, &module)?;
379 }
380
381 let Some(cached_module) = cache
383 .fs_cache
384 .load(checksum, Some(self.instance_memory_limit))?
385 else {
386 return Err(VmError::generic_err(
387 "Can't load module from file system cache after storing it to file system cache (pin)",
388 ));
389 };
390
391 cache.pinned_memory_cache.store(checksum, cached_module)
392 }
393
394 pub fn unpin(&self, checksum: &Checksum) -> VmResult<()> {
399 self.inner
400 .lock()
401 .unwrap()
402 .pinned_memory_cache
403 .remove(checksum)
404 }
405
406 pub fn sync_pinned_codes(&self, checksums: &[Checksum]) -> VmResult<()> {
408 let mut add: Vec<Checksum> = vec![];
409 let mut del: Vec<Checksum> = vec![];
410 {
411 let cache = self.inner.lock().unwrap();
412 for (checksum, _) in cache.pinned_memory_cache.iter() {
413 if !checksums.contains(checksum) {
414 del.push(*checksum);
415 }
416 }
417 for checksum in checksums {
418 if !cache.pinned_memory_cache.has(checksum) {
419 add.push(*checksum);
420 }
421 }
422 }
423 for checksum in &add {
424 self.pin(checksum)?;
425 }
426 for checksum in &del {
427 self.unpin(checksum)?;
428 }
429 Ok(())
430 }
431
432 pub fn get_instance(
436 &self,
437 checksum: &Checksum,
438 backend: Backend<A, S, Q>,
439 options: InstanceOptions,
440 ) -> VmResult<Instance<A, S, Q>> {
441 let (module, store) = self.get_module(checksum)?;
442 let instance = Instance::from_module(
443 store,
444 &module,
445 backend,
446 options.gas_limit,
447 None,
448 Some(&self.instantiation_lock),
449 )?;
450 Ok(instance)
451 }
452
453 fn get_module(&self, checksum: &Checksum) -> VmResult<(Module, Store)> {
457 let mut cache = self.inner.lock().unwrap();
458 if let Some(element) = cache.pinned_memory_cache.load(checksum)? {
460 cache.stats.hits_pinned_memory_cache =
461 cache.stats.hits_pinned_memory_cache.saturating_add(1);
462 let CachedModule {
463 module,
464 engine,
465 size_estimate: _,
466 } = element;
467 let store = Store::new(engine);
468 return Ok((module, store));
469 }
470
471 if let Some(element) = cache.memory_cache.load(checksum)? {
473 cache.stats.hits_memory_cache = cache.stats.hits_memory_cache.saturating_add(1);
474 let CachedModule {
475 module,
476 engine,
477 size_estimate: _,
478 } = element;
479 let store = Store::new(engine);
480 return Ok((module, store));
481 }
482
483 if let Some(cached_module) = cache
485 .fs_cache
486 .load(checksum, Some(self.instance_memory_limit))?
487 {
488 cache.stats.hits_fs_cache = cache.stats.hits_fs_cache.saturating_add(1);
489
490 cache.memory_cache.store(checksum, cached_module.clone())?;
491
492 let CachedModule {
493 module,
494 engine,
495 size_estimate: _,
496 } = cached_module;
497 let store = Store::new(engine);
498 return Ok((module, store));
499 }
500
501 let wasm = self.load_wasm_with_path(&cache.wasm_path, checksum)?;
507 cache.stats.misses = cache.stats.misses.saturating_add(1);
508 {
509 let (module, _) = compile_module(&wasm, None)?;
510 cache.fs_cache.store(checksum, &module)?;
511 }
512
513 let Some(cached_module) = cache
515 .fs_cache
516 .load(checksum, Some(self.instance_memory_limit))?
517 else {
518 return Err(VmError::generic_err(
519 "Can't load module from file system cache after storing it to file system cache (get_module)",
520 ));
521 };
522 cache.memory_cache.store(checksum, cached_module.clone())?;
523
524 let CachedModule {
525 module,
526 engine,
527 size_estimate: _,
528 } = cached_module;
529 let store = Store::new(engine);
530 Ok((module, store))
531 }
532}
533
534unsafe impl<A, S, Q> Sync for Cache<A, S, Q>
535where
536 A: BackendApi + 'static,
537 S: Storage + 'static,
538 Q: Querier + 'static,
539{
540}
541
542unsafe impl<A, S, Q> Send for Cache<A, S, Q>
543where
544 A: BackendApi + 'static,
545 S: Storage + 'static,
546 Q: Querier + 'static,
547{
548}
549
550fn save_wasm_to_disk(dir: impl Into<PathBuf>, wasm: &[u8]) -> VmResult<Checksum> {
554 let checksum = Checksum::generate(wasm);
556 let filename = checksum.to_hex();
557 let filepath = dir.into().join(filename).with_extension("wasm");
558
559 let mut file = OpenOptions::new()
563 .write(true)
564 .create(true)
565 .truncate(true)
566 .open(filepath)
567 .map_err(|e| VmError::cache_err(format!("Error opening Wasm file for writing: {e}")))?;
568 file.write_all(wasm)
569 .map_err(|e| VmError::cache_err(format!("Error writing Wasm file: {e}")))?;
570
571 Ok(checksum)
572}
573
574fn load_wasm_from_disk(dir: impl Into<PathBuf>, checksum: &Checksum) -> VmResult<Vec<u8>> {
575 let path = dir.into().join(checksum.to_hex());
579 let mut file = File::open(path.with_extension("wasm"))
580 .or_else(|_| File::open(path))
581 .map_err(|_e| VmError::cache_err("Error opening Wasm file for reading"))?;
582
583 let mut wasm = Vec::<u8>::new();
584 file.read_to_end(&mut wasm)
585 .map_err(|_e| VmError::cache_err("Error reading Wasm file"))?;
586 Ok(wasm)
587}
588
589fn remove_wasm_from_disk(dir: impl Into<PathBuf>, checksum: &Checksum) -> VmResult<()> {
595 let path = dir.into().join(checksum.to_hex());
597 let wasm_path = path.with_extension("wasm");
598
599 let path_exists = path.exists();
600 let wasm_path_exists = wasm_path.exists();
601 if !path_exists && !wasm_path_exists {
602 return Err(VmError::cache_err("Wasm file does not exist"));
603 }
604
605 if path_exists {
606 fs::remove_file(path)
607 .map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?;
608 }
609
610 if wasm_path_exists {
611 fs::remove_file(wasm_path)
612 .map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?;
613 }
614
615 Ok(())
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621 use crate::calls::{call_execute, call_instantiate};
622 use crate::testing::{mock_backend, mock_env, mock_info, MockApi, MockQuerier, MockStorage};
623 use cosmwasm_std::{coins, Empty};
624 use std::borrow::Cow;
625 use std::fs::{create_dir_all, remove_dir_all};
626 use tempfile::TempDir;
627 use wasm_encoder::ComponentSection;
628
629 const TESTING_GAS_LIMIT: u64 = 500_000_000; const TESTING_MEMORY_LIMIT: Size = Size::mebi(16);
631 const TESTING_OPTIONS: InstanceOptions = InstanceOptions {
632 gas_limit: TESTING_GAS_LIMIT,
633 };
634 const TESTING_MEMORY_CACHE_SIZE: Size = Size::mebi(200);
635
636 static HACKATOM: &[u8] = include_bytes!("../testdata/hackatom.wasm");
637 static IBC_REFLECT: &[u8] = include_bytes!("../testdata/ibc_reflect.wasm");
638 static IBC2: &[u8] = include_bytes!("../testdata/ibc2.wasm");
639 static EMPTY: &[u8] = include_bytes!("../testdata/empty.wasm");
640 static INVALID_CONTRACT_WAT: &str = r#"(module
642 (type $t0 (func (param i32) (result i32)))
643 (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32)
644 local.get $p0
645 i32.const 1
646 i32.add))
647 "#;
648
649 fn default_capabilities() -> HashSet<String> {
650 HashSet::from([
651 "cosmwasm_1_1".to_string(),
652 "cosmwasm_1_2".to_string(),
653 "cosmwasm_1_3".to_string(),
654 "cosmwasm_1_4".to_string(),
655 "cosmwasm_1_4".to_string(),
656 "cosmwasm_2_0".to_string(),
657 "cosmwasm_2_1".to_string(),
658 "cosmwasm_2_2".to_string(),
659 "iterator".to_string(),
660 "staking".to_string(),
661 "stargate".to_string(),
662 ])
663 }
664
665 fn make_testing_options() -> (CacheOptions, TempDir) {
666 let temp_dir = TempDir::new().unwrap();
667 (
668 CacheOptions {
669 base_dir: temp_dir.path().into(),
670 available_capabilities: default_capabilities(),
671 memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
672 instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
673 },
674 temp_dir,
675 )
676 }
677
678 fn make_stargate_testing_options() -> (CacheOptions, TempDir) {
679 let temp_dir = TempDir::new().unwrap();
680 let mut capabilities = default_capabilities();
681 capabilities.insert("stargate".into());
682 (
683 CacheOptions {
684 base_dir: temp_dir.path().into(),
685 available_capabilities: capabilities,
686 memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
687 instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
688 },
689 temp_dir,
690 )
691 }
692
693 fn make_ibc2_testing_options() -> (CacheOptions, TempDir) {
694 let temp_dir = TempDir::new().unwrap();
695 let mut capabilities = default_capabilities();
696 capabilities.insert("ibc2".into());
697 (
698 CacheOptions {
699 base_dir: temp_dir.path().into(),
700 available_capabilities: capabilities,
701 memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
702 instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
703 },
704 temp_dir,
705 )
706 }
707
708 fn test_hackatom_instance_execution<S, Q>(instance: &mut Instance<MockApi, S, Q>)
710 where
711 S: Storage + 'static,
712 Q: Querier + 'static,
713 {
714 let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
716 let verifier = instance.api().addr_make("verifies");
717 let beneficiary = instance.api().addr_make("benefits");
718 let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
719 let response =
720 call_instantiate::<_, _, _, Empty>(instance, &mock_env(), &info, msg.as_bytes())
721 .unwrap()
722 .unwrap();
723 assert_eq!(response.messages.len(), 0);
724
725 let info = mock_info(&verifier, &coins(15, "earth"));
727 let msg = br#"{"release":{"denom":"earth"}}"#;
728 let response = call_execute::<_, _, _, Empty>(instance, &mock_env(), &info, msg)
729 .unwrap()
730 .unwrap();
731 assert_eq!(response.messages.len(), 1);
732 }
733
734 #[test]
735 fn new_base_dir_will_be_created() {
736 let temp_dir = TempDir::new().unwrap();
737 let my_base_dir = temp_dir.path().join("non-existent-sub-dir");
738 let (base_opts, _temp_dir) = make_testing_options();
739 let options = CacheOptions {
740 base_dir: my_base_dir.clone(),
741 ..base_opts
742 };
743 assert!(!my_base_dir.is_dir());
744 let _cache = unsafe { Cache::<MockApi, MockStorage, MockQuerier>::new(options).unwrap() };
745 assert!(my_base_dir.is_dir());
746 }
747
748 #[test]
749 fn store_code_checked_works() {
750 let (testing_opts, _temp_dir) = make_testing_options();
751 let cache: Cache<MockApi, MockStorage, MockQuerier> =
752 unsafe { Cache::new(testing_opts).unwrap() };
753 cache.store_code(HACKATOM, true, true).unwrap();
754 }
755
756 #[test]
757 fn store_code_without_persist_works() {
758 let (testing_opts, _temp_dir) = make_testing_options();
759 let cache: Cache<MockApi, MockStorage, MockQuerier> =
760 unsafe { Cache::new(testing_opts).unwrap() };
761 let checksum = cache.store_code(HACKATOM, true, false).unwrap();
762
763 assert!(
764 cache.load_wasm(&checksum).is_err(),
765 "wasm file should not be saved to disk"
766 );
767 }
768
769 #[test]
770 fn store_code_allows_saving_multiple_times() {
772 let (testing_opts, _temp_dir) = make_testing_options();
773 let cache: Cache<MockApi, MockStorage, MockQuerier> =
774 unsafe { Cache::new(testing_opts).unwrap() };
775 cache.store_code(HACKATOM, true, true).unwrap();
776 cache.store_code(HACKATOM, true, true).unwrap();
777 }
778
779 #[test]
780 fn store_code_checked_rejects_invalid_contract() {
781 let wasm = wat::parse_str(INVALID_CONTRACT_WAT).unwrap();
782
783 let (testing_opts, _temp_dir) = make_testing_options();
784 let cache: Cache<MockApi, MockStorage, MockQuerier> =
785 unsafe { Cache::new(testing_opts).unwrap() };
786 let save_result = cache.store_code(&wasm, true, true);
787 match save_result.unwrap_err() {
788 VmError::StaticValidationErr { msg, .. } => {
789 assert_eq!(msg, "Wasm contract must contain exactly one memory")
790 }
791 e => panic!("Unexpected error {e:?}"),
792 }
793 }
794
795 #[test]
796 fn store_code_fills_file_system_but_not_memory_cache() {
797 let (testing_opts, _temp_dir) = make_testing_options();
801 let cache = unsafe { Cache::new(testing_opts).unwrap() };
802 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
803
804 let backend = mock_backend(&[]);
805 let _ = cache
806 .get_instance(&checksum, backend, TESTING_OPTIONS)
807 .unwrap();
808 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
809 assert_eq!(cache.stats().hits_memory_cache, 0);
810 assert_eq!(cache.stats().hits_fs_cache, 1);
811 assert_eq!(cache.stats().misses, 0);
812 }
813
814 #[test]
815 fn store_code_unchecked_works() {
816 let (testing_opts, _temp_dir) = make_testing_options();
817 let cache: Cache<MockApi, MockStorage, MockQuerier> =
818 unsafe { Cache::new(testing_opts).unwrap() };
819 cache.store_code(HACKATOM, false, true).unwrap();
820 }
821
822 #[test]
823 fn store_code_unchecked_accepts_invalid_contract() {
824 let wasm = wat::parse_str(INVALID_CONTRACT_WAT).unwrap();
825
826 let (testing_opts, _temp_dir) = make_testing_options();
827 let cache: Cache<MockApi, MockStorage, MockQuerier> =
828 unsafe { Cache::new(testing_opts).unwrap() };
829 cache.store_code(&wasm, false, true).unwrap();
830 }
831
832 #[test]
833 fn load_wasm_works() {
834 let (testing_opts, _temp_dir) = make_testing_options();
835 let cache: Cache<MockApi, MockStorage, MockQuerier> =
836 unsafe { Cache::new(testing_opts).unwrap() };
837 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
838
839 let restored = cache.load_wasm(&checksum).unwrap();
840 assert_eq!(restored, HACKATOM);
841 }
842
843 #[test]
844 fn load_wasm_works_across_multiple_cache_instances() {
845 let tmp_dir = TempDir::new().unwrap();
846 let id: Checksum;
847
848 {
849 let options1 = CacheOptions {
850 base_dir: tmp_dir.path().to_path_buf(),
851 available_capabilities: default_capabilities(),
852 memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
853 instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
854 };
855 let cache1: Cache<MockApi, MockStorage, MockQuerier> =
856 unsafe { Cache::new(options1).unwrap() };
857 id = cache1.store_code(HACKATOM, true, true).unwrap();
858 }
859
860 {
861 let options2 = CacheOptions {
862 base_dir: tmp_dir.path().to_path_buf(),
863 available_capabilities: default_capabilities(),
864 memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
865 instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
866 };
867 let cache2: Cache<MockApi, MockStorage, MockQuerier> =
868 unsafe { Cache::new(options2).unwrap() };
869 let restored = cache2.load_wasm(&id).unwrap();
870 assert_eq!(restored, HACKATOM);
871 }
872 }
873
874 #[test]
875 fn load_wasm_errors_for_non_existent_id() {
876 let (testing_opts, _temp_dir) = make_testing_options();
877 let cache: Cache<MockApi, MockStorage, MockQuerier> =
878 unsafe { Cache::new(testing_opts).unwrap() };
879 let checksum = Checksum::from([
880 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
881 5, 5, 5,
882 ]);
883
884 match cache.load_wasm(&checksum).unwrap_err() {
885 VmError::CacheErr { msg, .. } => {
886 assert_eq!(msg, "Error opening Wasm file for reading")
887 }
888 e => panic!("Unexpected error: {e:?}"),
889 }
890 }
891
892 #[test]
893 fn load_wasm_errors_for_corrupted_wasm() {
894 let tmp_dir = TempDir::new().unwrap();
895 let options = CacheOptions {
896 base_dir: tmp_dir.path().to_path_buf(),
897 available_capabilities: default_capabilities(),
898 memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
899 instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
900 };
901 let cache: Cache<MockApi, MockStorage, MockQuerier> =
902 unsafe { Cache::new(options).unwrap() };
903 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
904
905 let filepath = tmp_dir
907 .path()
908 .join(STATE_DIR)
909 .join(WASM_DIR)
910 .join(checksum.to_hex())
911 .with_extension("wasm");
912 let mut file = OpenOptions::new().write(true).open(filepath).unwrap();
913 file.write_all(b"broken data").unwrap();
914
915 let res = cache.load_wasm(&checksum);
916 match res {
917 Err(VmError::IntegrityErr { .. }) => {}
918 Err(e) => panic!("Unexpected error: {e:?}"),
919 Ok(_) => panic!("This must not succeed"),
920 }
921 }
922
923 #[test]
924 fn remove_wasm_works() {
925 let (testing_opts, _temp_dir) = make_testing_options();
926 let cache: Cache<MockApi, MockStorage, MockQuerier> =
927 unsafe { Cache::new(testing_opts).unwrap() };
928
929 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
931
932 cache.load_wasm(&checksum).unwrap();
934
935 cache.remove_wasm(&checksum).unwrap();
937
938 match cache.load_wasm(&checksum).unwrap_err() {
940 VmError::CacheErr { msg, .. } => {
941 assert_eq!(msg, "Error opening Wasm file for reading")
942 }
943 e => panic!("Unexpected error: {e:?}"),
944 }
945
946 match cache.remove_wasm(&checksum).unwrap_err() {
948 VmError::CacheErr { msg, .. } => {
949 assert_eq!(msg, "Wasm file does not exist")
950 }
951 e => panic!("Unexpected error: {e:?}"),
952 }
953 }
954
955 #[test]
956 fn get_instance_finds_cached_module() {
957 let (testing_opts, _temp_dir) = make_testing_options();
958 let cache = unsafe { Cache::new(testing_opts).unwrap() };
959 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
960 let backend = mock_backend(&[]);
961 let _instance = cache
962 .get_instance(&checksum, backend, TESTING_OPTIONS)
963 .unwrap();
964 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
965 assert_eq!(cache.stats().hits_memory_cache, 0);
966 assert_eq!(cache.stats().hits_fs_cache, 1);
967 assert_eq!(cache.stats().misses, 0);
968 }
969
970 #[test]
971 fn get_instance_finds_cached_modules_and_stores_to_memory() {
972 let (testing_opts, _temp_dir) = make_testing_options();
973 let cache = unsafe { Cache::new(testing_opts).unwrap() };
974 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
975 let backend1 = mock_backend(&[]);
976 let backend2 = mock_backend(&[]);
977 let backend3 = mock_backend(&[]);
978 let backend4 = mock_backend(&[]);
979 let backend5 = mock_backend(&[]);
980
981 let _instance1 = cache
983 .get_instance(&checksum, backend1, TESTING_OPTIONS)
984 .unwrap();
985 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
986 assert_eq!(cache.stats().hits_memory_cache, 0);
987 assert_eq!(cache.stats().hits_fs_cache, 1);
988 assert_eq!(cache.stats().misses, 0);
989
990 let _instance2 = cache
992 .get_instance(&checksum, backend2, TESTING_OPTIONS)
993 .unwrap();
994 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
995 assert_eq!(cache.stats().hits_memory_cache, 1);
996 assert_eq!(cache.stats().hits_fs_cache, 1);
997 assert_eq!(cache.stats().misses, 0);
998
999 let _instance3 = cache
1001 .get_instance(&checksum, backend3, TESTING_OPTIONS)
1002 .unwrap();
1003 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1004 assert_eq!(cache.stats().hits_memory_cache, 2);
1005 assert_eq!(cache.stats().hits_fs_cache, 1);
1006 assert_eq!(cache.stats().misses, 0);
1007
1008 cache.pin(&checksum).unwrap();
1010 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1011 assert_eq!(cache.stats().hits_memory_cache, 2);
1012 assert_eq!(cache.stats().hits_fs_cache, 2);
1013 assert_eq!(cache.stats().misses, 0);
1014
1015 let _instance4 = cache
1017 .get_instance(&checksum, backend4, TESTING_OPTIONS)
1018 .unwrap();
1019 assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1020 assert_eq!(cache.stats().hits_memory_cache, 2);
1021 assert_eq!(cache.stats().hits_fs_cache, 2);
1022 assert_eq!(cache.stats().misses, 0);
1023
1024 let _instance5 = cache
1026 .get_instance(&checksum, backend5, TESTING_OPTIONS)
1027 .unwrap();
1028 assert_eq!(cache.stats().hits_pinned_memory_cache, 2);
1029 assert_eq!(cache.stats().hits_memory_cache, 2);
1030 assert_eq!(cache.stats().hits_fs_cache, 2);
1031 assert_eq!(cache.stats().misses, 0);
1032 }
1033
1034 #[test]
1035 fn get_instance_recompiles_module() {
1036 let (options, _temp_dir) = make_testing_options();
1037 let cache = unsafe { Cache::new(options.clone()).unwrap() };
1038 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1039
1040 remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1042
1043 let backend = mock_backend(&[]);
1045 let _instance = cache
1046 .get_instance(&checksum, backend, TESTING_OPTIONS)
1047 .unwrap();
1048 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1049 assert_eq!(cache.stats().hits_memory_cache, 0);
1050 assert_eq!(cache.stats().hits_fs_cache, 0);
1051 assert_eq!(cache.stats().misses, 1);
1052
1053 let backend = mock_backend(&[]);
1055 let _instance = cache
1056 .get_instance(&checksum, backend, TESTING_OPTIONS)
1057 .unwrap();
1058 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1059 assert_eq!(cache.stats().hits_memory_cache, 1);
1060 assert_eq!(cache.stats().hits_fs_cache, 0);
1061 assert_eq!(cache.stats().misses, 1);
1062 }
1063
1064 #[test]
1065 fn call_instantiate_on_cached_contract() {
1066 let (testing_opts, _temp_dir) = make_testing_options();
1067 let cache = unsafe { Cache::new(testing_opts).unwrap() };
1068 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1069
1070 {
1072 let mut instance = cache
1073 .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1074 .unwrap();
1075 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1076 assert_eq!(cache.stats().hits_memory_cache, 0);
1077 assert_eq!(cache.stats().hits_fs_cache, 1);
1078 assert_eq!(cache.stats().misses, 0);
1079
1080 let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1082 let verifier = instance.api().addr_make("verifies");
1083 let beneficiary = instance.api().addr_make("benefits");
1084 let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1085 let res = call_instantiate::<_, _, _, Empty>(
1086 &mut instance,
1087 &mock_env(),
1088 &info,
1089 msg.as_bytes(),
1090 )
1091 .unwrap();
1092 let msgs = res.unwrap().messages;
1093 assert_eq!(msgs.len(), 0);
1094 }
1095
1096 {
1098 let mut instance = cache
1099 .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1100 .unwrap();
1101 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1102 assert_eq!(cache.stats().hits_memory_cache, 1);
1103 assert_eq!(cache.stats().hits_fs_cache, 1);
1104 assert_eq!(cache.stats().misses, 0);
1105
1106 let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1108 let verifier = instance.api().addr_make("verifies");
1109 let beneficiary = instance.api().addr_make("benefits");
1110 let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1111 let res = call_instantiate::<_, _, _, Empty>(
1112 &mut instance,
1113 &mock_env(),
1114 &info,
1115 msg.as_bytes(),
1116 )
1117 .unwrap();
1118 let msgs = res.unwrap().messages;
1119 assert_eq!(msgs.len(), 0);
1120 }
1121
1122 {
1124 cache.pin(&checksum).unwrap();
1125
1126 let mut instance = cache
1127 .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1128 .unwrap();
1129 assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1130 assert_eq!(cache.stats().hits_memory_cache, 1);
1131 assert_eq!(cache.stats().hits_fs_cache, 2);
1132 assert_eq!(cache.stats().misses, 0);
1133
1134 let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1136 let verifier = instance.api().addr_make("verifies");
1137 let beneficiary = instance.api().addr_make("benefits");
1138 let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1139 let res = call_instantiate::<_, _, _, Empty>(
1140 &mut instance,
1141 &mock_env(),
1142 &info,
1143 msg.as_bytes(),
1144 )
1145 .unwrap();
1146 let msgs = res.unwrap().messages;
1147 assert_eq!(msgs.len(), 0);
1148 }
1149 }
1150
1151 #[test]
1152 fn call_execute_on_cached_contract() {
1153 let (testing_opts, _temp_dir) = make_testing_options();
1154 let cache = unsafe { Cache::new(testing_opts).unwrap() };
1155 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1156
1157 {
1159 let mut instance = cache
1160 .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1161 .unwrap();
1162 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1163 assert_eq!(cache.stats().hits_memory_cache, 0);
1164 assert_eq!(cache.stats().hits_fs_cache, 1);
1165 assert_eq!(cache.stats().misses, 0);
1166
1167 let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1169 let verifier = instance.api().addr_make("verifies");
1170 let beneficiary = instance.api().addr_make("benefits");
1171 let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1172 let response = call_instantiate::<_, _, _, Empty>(
1173 &mut instance,
1174 &mock_env(),
1175 &info,
1176 msg.as_bytes(),
1177 )
1178 .unwrap()
1179 .unwrap();
1180 assert_eq!(response.messages.len(), 0);
1181
1182 let info = mock_info(&verifier, &coins(15, "earth"));
1184 let msg = br#"{"release":{"denom":"earth"}}"#;
1185 let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1186 .unwrap()
1187 .unwrap();
1188 assert_eq!(response.messages.len(), 1);
1189 }
1190
1191 {
1193 let mut instance = cache
1194 .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1195 .unwrap();
1196 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1197 assert_eq!(cache.stats().hits_memory_cache, 1);
1198 assert_eq!(cache.stats().hits_fs_cache, 1);
1199 assert_eq!(cache.stats().misses, 0);
1200
1201 let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1203 let verifier = instance.api().addr_make("verifies");
1204 let beneficiary = instance.api().addr_make("benefits");
1205 let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1206 let response = call_instantiate::<_, _, _, Empty>(
1207 &mut instance,
1208 &mock_env(),
1209 &info,
1210 msg.as_bytes(),
1211 )
1212 .unwrap()
1213 .unwrap();
1214 assert_eq!(response.messages.len(), 0);
1215
1216 let info = mock_info(&verifier, &coins(15, "earth"));
1218 let msg = br#"{"release":{"denom":"earth"}}"#;
1219 let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1220 .unwrap()
1221 .unwrap();
1222 assert_eq!(response.messages.len(), 1);
1223 }
1224
1225 {
1227 cache.pin(&checksum).unwrap();
1228
1229 let mut instance = cache
1230 .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
1231 .unwrap();
1232 assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1233 assert_eq!(cache.stats().hits_memory_cache, 1);
1234 assert_eq!(cache.stats().hits_fs_cache, 2);
1235 assert_eq!(cache.stats().misses, 0);
1236
1237 let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
1239 let verifier = instance.api().addr_make("verifies");
1240 let beneficiary = instance.api().addr_make("benefits");
1241 let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
1242 let response = call_instantiate::<_, _, _, Empty>(
1243 &mut instance,
1244 &mock_env(),
1245 &info,
1246 msg.as_bytes(),
1247 )
1248 .unwrap()
1249 .unwrap();
1250 assert_eq!(response.messages.len(), 0);
1251
1252 let info = mock_info(&verifier, &coins(15, "earth"));
1254 let msg = br#"{"release":{"denom":"earth"}}"#;
1255 let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
1256 .unwrap()
1257 .unwrap();
1258 assert_eq!(response.messages.len(), 1);
1259 }
1260 }
1261
1262 #[test]
1263 fn call_execute_on_recompiled_contract() {
1264 let (options, _temp_dir) = make_testing_options();
1265 let cache = unsafe { Cache::new(options.clone()).unwrap() };
1266 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1267
1268 remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1270
1271 let backend = mock_backend(&[]);
1273 let mut instance = cache
1274 .get_instance(&checksum, backend, TESTING_OPTIONS)
1275 .unwrap();
1276 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1277 assert_eq!(cache.stats().hits_memory_cache, 0);
1278 assert_eq!(cache.stats().hits_fs_cache, 0);
1279 assert_eq!(cache.stats().misses, 1);
1280 test_hackatom_instance_execution(&mut instance);
1281 }
1282
1283 #[test]
1284 fn use_multiple_cached_instances_of_same_contract() {
1285 let (testing_opts, _temp_dir) = make_testing_options();
1286 let cache = unsafe { Cache::new(testing_opts).unwrap() };
1287 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1288
1289 let backend1 = mock_backend(&[]);
1291 let backend2 = mock_backend(&[]);
1292
1293 let mut instance = cache
1295 .get_instance(&checksum, backend1, TESTING_OPTIONS)
1296 .unwrap();
1297 let info = mock_info("owner1", &coins(1000, "earth"));
1298 let sue = instance.api().addr_make("sue");
1299 let mary = instance.api().addr_make("mary");
1300 let msg = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1301 let res =
1302 call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
1303 .unwrap();
1304 let msgs = res.unwrap().messages;
1305 assert_eq!(msgs.len(), 0);
1306 let backend1 = instance.recycle().unwrap();
1307
1308 let mut instance = cache
1310 .get_instance(&checksum, backend2, TESTING_OPTIONS)
1311 .unwrap();
1312 let info = mock_info("owner2", &coins(500, "earth"));
1313 let bob = instance.api().addr_make("bob");
1314 let john = instance.api().addr_make("john");
1315 let msg = format!(r#"{{"verifier": "{bob}", "beneficiary": "{john}"}}"#);
1316 let res =
1317 call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
1318 .unwrap();
1319 let msgs = res.unwrap().messages;
1320 assert_eq!(msgs.len(), 0);
1321 let backend2 = instance.recycle().unwrap();
1322
1323 let mut instance = cache
1325 .get_instance(&checksum, backend2, TESTING_OPTIONS)
1326 .unwrap();
1327 let info = mock_info(&bob, &coins(15, "earth"));
1328 let msg = br#"{"release":{"denom":"earth"}}"#;
1329 let res = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap();
1330 let msgs = res.unwrap().messages;
1331 assert_eq!(1, msgs.len());
1332
1333 let mut instance = cache
1335 .get_instance(&checksum, backend1, TESTING_OPTIONS)
1336 .unwrap();
1337 let info = mock_info(&sue, &coins(15, "earth"));
1338 let msg = br#"{"release":{"denom":"earth"}}"#;
1339 let res = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap();
1340 let msgs = res.unwrap().messages;
1341 assert_eq!(1, msgs.len());
1342 }
1343
1344 #[test]
1345 fn resets_gas_when_reusing_instance() {
1346 let (testing_opts, _temp_dir) = make_testing_options();
1347 let cache = unsafe { Cache::new(testing_opts).unwrap() };
1348 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1349
1350 let backend1 = mock_backend(&[]);
1351 let backend2 = mock_backend(&[]);
1352
1353 let mut instance1 = cache
1355 .get_instance(&checksum, backend1, TESTING_OPTIONS)
1356 .unwrap();
1357 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1358 assert_eq!(cache.stats().hits_memory_cache, 0);
1359 assert_eq!(cache.stats().hits_fs_cache, 1);
1360 assert_eq!(cache.stats().misses, 0);
1361 let original_gas = instance1.get_gas_left();
1362
1363 let info = mock_info("owner1", &coins(1000, "earth"));
1365 let sue = instance1.api().addr_make("sue");
1366 let mary = instance1.api().addr_make("mary");
1367 let msg = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1368 call_instantiate::<_, _, _, Empty>(&mut instance1, &mock_env(), &info, msg.as_bytes())
1369 .unwrap()
1370 .unwrap();
1371 assert!(instance1.get_gas_left() < original_gas);
1372
1373 let mut instance2 = cache
1375 .get_instance(&checksum, backend2, TESTING_OPTIONS)
1376 .unwrap();
1377 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1378 assert_eq!(cache.stats().hits_memory_cache, 1);
1379 assert_eq!(cache.stats().hits_fs_cache, 1);
1380 assert_eq!(cache.stats().misses, 0);
1381 assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT);
1382 }
1383
1384 #[test]
1385 fn recovers_from_out_of_gas() {
1386 let (testing_opts, _temp_dir) = make_testing_options();
1387 let cache = unsafe { Cache::new(testing_opts).unwrap() };
1388 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1389
1390 let backend1 = mock_backend(&[]);
1391 let backend2 = mock_backend(&[]);
1392
1393 let options = InstanceOptions { gas_limit: 10 };
1395 let mut instance1 = cache.get_instance(&checksum, backend1, options).unwrap();
1396 assert_eq!(cache.stats().hits_fs_cache, 1);
1397 assert_eq!(cache.stats().misses, 0);
1398
1399 let info1 = mock_info("owner1", &coins(1000, "earth"));
1401 let sue = instance1.api().addr_make("sue");
1402 let mary = instance1.api().addr_make("mary");
1403 let msg1 = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
1404
1405 match call_instantiate::<_, _, _, Empty>(
1406 &mut instance1,
1407 &mock_env(),
1408 &info1,
1409 msg1.as_bytes(),
1410 )
1411 .unwrap_err()
1412 {
1413 VmError::GasDepletion { .. } => (), e => panic!("unexpected error, {e:?}"),
1415 }
1416 assert_eq!(instance1.get_gas_left(), 0);
1417
1418 let options = InstanceOptions {
1420 gas_limit: TESTING_GAS_LIMIT,
1421 };
1422 let mut instance2 = cache.get_instance(&checksum, backend2, options).unwrap();
1423 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1424 assert_eq!(cache.stats().hits_memory_cache, 1);
1425 assert_eq!(cache.stats().hits_fs_cache, 1);
1426 assert_eq!(cache.stats().misses, 0);
1427 assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT);
1428
1429 let info2 = mock_info("owner2", &coins(500, "earth"));
1431 let bob = instance2.api().addr_make("bob");
1432 let john = instance2.api().addr_make("john");
1433 let msg2 = format!(r#"{{"verifier": "{bob}", "beneficiary": "{john}"}}"#);
1434 call_instantiate::<_, _, _, Empty>(&mut instance2, &mock_env(), &info2, msg2.as_bytes())
1435 .unwrap()
1436 .unwrap();
1437 }
1438
1439 #[test]
1440 fn save_wasm_to_disk_works_for_same_data_multiple_times() {
1441 let tmp_dir = TempDir::new().unwrap();
1442 let path = tmp_dir.path();
1443 let code = vec![12u8; 17];
1444
1445 save_wasm_to_disk(path, &code).unwrap();
1446 save_wasm_to_disk(path, &code).unwrap();
1447 }
1448
1449 #[test]
1450 fn save_wasm_to_disk_fails_on_non_existent_dir() {
1451 let tmp_dir = TempDir::new().unwrap();
1452 let path = tmp_dir.path().join("something");
1453 let code = vec![12u8; 17];
1454 let res = save_wasm_to_disk(path.to_str().unwrap(), &code);
1455 assert!(res.is_err());
1456 }
1457
1458 #[test]
1459 fn load_wasm_from_disk_works() {
1460 let tmp_dir = TempDir::new().unwrap();
1461 let path = tmp_dir.path();
1462 let code = vec![12u8; 17];
1463 let checksum = save_wasm_to_disk(path, &code).unwrap();
1464
1465 let loaded = load_wasm_from_disk(path, &checksum).unwrap();
1466 assert_eq!(code, loaded);
1467 }
1468
1469 #[test]
1470 fn load_wasm_from_disk_works_in_subfolder() {
1471 let tmp_dir = TempDir::new().unwrap();
1472 let path = tmp_dir.path().join("something");
1473 create_dir_all(&path).unwrap();
1474 let code = vec![12u8; 17];
1475 let checksum = save_wasm_to_disk(&path, &code).unwrap();
1476
1477 let loaded = load_wasm_from_disk(&path, &checksum).unwrap();
1478 assert_eq!(code, loaded);
1479 }
1480
1481 #[test]
1482 fn remove_wasm_from_disk_works() {
1483 let tmp_dir = TempDir::new().unwrap();
1484 let path = tmp_dir.path();
1485 let code = vec![12u8; 17];
1486 let checksum = save_wasm_to_disk(path, &code).unwrap();
1487
1488 remove_wasm_from_disk(path, &checksum).unwrap();
1489
1490 match remove_wasm_from_disk(path, &checksum).unwrap_err() {
1493 VmError::CacheErr { msg, .. } => assert_eq!(msg, "Wasm file does not exist"),
1494 err => panic!("Unexpected error: {err:?}"),
1495 }
1496 }
1497
1498 #[test]
1499 fn analyze_works() {
1500 use Entrypoint as E;
1501
1502 let (testing_opts, _temp_dir) = make_stargate_testing_options();
1503 let cache: Cache<MockApi, MockStorage, MockQuerier> =
1504 unsafe { Cache::new(testing_opts).unwrap() };
1505
1506 let checksum1 = cache.store_code(HACKATOM, true, true).unwrap();
1507 let report1 = cache.analyze(&checksum1).unwrap();
1508 assert_eq!(
1509 report1,
1510 AnalysisReport {
1511 has_ibc_entry_points: false,
1512 entrypoints: BTreeSet::from([
1513 E::Instantiate,
1514 E::Migrate,
1515 E::Sudo,
1516 E::Execute,
1517 E::Query
1518 ]),
1519 required_capabilities: BTreeSet::from([
1520 "cosmwasm_1_1".to_string(),
1521 "cosmwasm_1_2".to_string(),
1522 "cosmwasm_1_3".to_string(),
1523 "cosmwasm_1_4".to_string(),
1524 "cosmwasm_1_4".to_string(),
1525 "cosmwasm_2_0".to_string(),
1526 "cosmwasm_2_1".to_string(),
1527 "cosmwasm_2_2".to_string(),
1528 ]),
1529 contract_migrate_version: Some(420),
1530 }
1531 );
1532
1533 let checksum2 = cache.store_code(IBC_REFLECT, true, true).unwrap();
1534 let report2 = cache.analyze(&checksum2).unwrap();
1535 let mut ibc_contract_entrypoints =
1536 BTreeSet::from([E::Instantiate, E::Migrate, E::Execute, E::Reply, E::Query]);
1537 ibc_contract_entrypoints.extend(REQUIRED_IBC_EXPORTS);
1538 assert_eq!(
1539 report2,
1540 AnalysisReport {
1541 has_ibc_entry_points: true,
1542 entrypoints: ibc_contract_entrypoints,
1543 required_capabilities: BTreeSet::from_iter([
1544 "cosmwasm_1_1".to_string(),
1545 "cosmwasm_1_2".to_string(),
1546 "cosmwasm_1_3".to_string(),
1547 "cosmwasm_1_4".to_string(),
1548 "cosmwasm_1_4".to_string(),
1549 "cosmwasm_2_0".to_string(),
1550 "cosmwasm_2_1".to_string(),
1551 "cosmwasm_2_2".to_string(),
1552 "iterator".to_string(),
1553 "stargate".to_string()
1554 ]),
1555 contract_migrate_version: None,
1556 }
1557 );
1558
1559 let checksum3 = cache.store_code(EMPTY, true, true).unwrap();
1560 let report3 = cache.analyze(&checksum3).unwrap();
1561 assert_eq!(
1562 report3,
1563 AnalysisReport {
1564 has_ibc_entry_points: false,
1565 entrypoints: BTreeSet::new(),
1566 required_capabilities: BTreeSet::from(["iterator".to_string()]),
1567 contract_migrate_version: None,
1568 }
1569 );
1570
1571 let mut wasm_with_version = EMPTY.to_vec();
1572 let custom_section = wasm_encoder::CustomSection {
1573 name: Cow::Borrowed("cw_migrate_version"),
1574 data: Cow::Borrowed(b"21"),
1575 };
1576 custom_section.append_to_component(&mut wasm_with_version);
1577
1578 let checksum4 = cache.store_code(&wasm_with_version, true, true).unwrap();
1579 let report4 = cache.analyze(&checksum4).unwrap();
1580 assert_eq!(
1581 report4,
1582 AnalysisReport {
1583 has_ibc_entry_points: false,
1584 entrypoints: BTreeSet::new(),
1585 required_capabilities: BTreeSet::from(["iterator".to_string()]),
1586 contract_migrate_version: Some(21),
1587 }
1588 );
1589
1590 let (testing_opts, _temp_dir) = make_ibc2_testing_options();
1591 let cache: Cache<MockApi, MockStorage, MockQuerier> =
1592 unsafe { Cache::new(testing_opts).unwrap() };
1593 let checksum5 = cache.store_code(IBC2, true, true).unwrap();
1594 let report5 = cache.analyze(&checksum5).unwrap();
1595 let ibc2_contract_entrypoints = BTreeSet::from([
1596 E::Instantiate,
1597 E::Query,
1598 E::Ibc2PacketReceive,
1599 E::Ibc2PacketTimeout,
1600 E::Ibc2PacketAck,
1601 E::Ibc2PacketSend,
1602 ]);
1603 assert_eq!(
1604 report5,
1605 AnalysisReport {
1606 has_ibc_entry_points: false,
1607 entrypoints: ibc2_contract_entrypoints,
1608 required_capabilities: BTreeSet::from_iter([
1609 "iterator".to_string(),
1610 "ibc2".to_string()
1611 ]),
1612 contract_migrate_version: None,
1613 }
1614 );
1615 }
1616
1617 #[test]
1618 fn pinned_metrics_works() {
1619 let (testing_opts, _temp_dir) = make_testing_options();
1620 let cache = unsafe { Cache::new(testing_opts).unwrap() };
1621 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1622
1623 cache.pin(&checksum).unwrap();
1624
1625 let pinned_metrics = cache.pinned_metrics();
1626 assert_eq!(pinned_metrics.per_module.len(), 1);
1627 assert_eq!(pinned_metrics.per_module[0].0, checksum);
1628 assert_eq!(pinned_metrics.per_module[0].1.hits, 0);
1629
1630 let backend = mock_backend(&[]);
1631 let _ = cache
1632 .get_instance(&checksum, backend, TESTING_OPTIONS)
1633 .unwrap();
1634
1635 let pinned_metrics = cache.pinned_metrics();
1636 assert_eq!(pinned_metrics.per_module.len(), 1);
1637 assert_eq!(pinned_metrics.per_module[0].0, checksum);
1638 assert_eq!(pinned_metrics.per_module[0].1.hits, 1);
1639
1640 let empty_checksum = cache.store_code(EMPTY, true, true).unwrap();
1641 cache.pin(&empty_checksum).unwrap();
1642
1643 let pinned_metrics = cache.pinned_metrics();
1644 assert_eq!(pinned_metrics.per_module.len(), 2);
1645
1646 let get_module_hits = |checksum| {
1647 pinned_metrics
1648 .per_module
1649 .iter()
1650 .find(|(iter_checksum, _module)| *iter_checksum == checksum)
1651 .map(|(_checksum, module)| module)
1652 .cloned()
1653 .unwrap()
1654 };
1655
1656 assert_eq!(get_module_hits(checksum).hits, 1);
1657 assert_eq!(get_module_hits(empty_checksum).hits, 0);
1658 }
1659
1660 #[test]
1661 fn pin_unpin_works() {
1662 let (testing_opts, _temp_dir) = make_testing_options();
1663 let cache = unsafe { Cache::new(testing_opts).unwrap() };
1664 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1665
1666 let backend = mock_backend(&[]);
1668 let mut instance = cache
1669 .get_instance(&checksum, backend, TESTING_OPTIONS)
1670 .unwrap();
1671 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1672 assert_eq!(cache.stats().hits_memory_cache, 0);
1673 assert_eq!(cache.stats().hits_fs_cache, 1);
1674 assert_eq!(cache.stats().misses, 0);
1675 test_hackatom_instance_execution(&mut instance);
1676
1677 cache.pin(&checksum).unwrap();
1679 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1680 assert_eq!(cache.stats().hits_memory_cache, 0);
1681 assert_eq!(cache.stats().hits_fs_cache, 2);
1682 assert_eq!(cache.stats().misses, 0);
1683
1684 cache.pin(&checksum).unwrap();
1686 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1687 assert_eq!(cache.stats().hits_memory_cache, 0);
1688 assert_eq!(cache.stats().hits_fs_cache, 2);
1689 assert_eq!(cache.stats().misses, 0);
1690
1691 let backend = mock_backend(&[]);
1693 let mut instance = cache
1694 .get_instance(&checksum, backend, TESTING_OPTIONS)
1695 .unwrap();
1696 assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1697 assert_eq!(cache.stats().hits_memory_cache, 0);
1698 assert_eq!(cache.stats().hits_fs_cache, 2);
1699 assert_eq!(cache.stats().misses, 0);
1700 test_hackatom_instance_execution(&mut instance);
1701
1702 cache.unpin(&checksum).unwrap();
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, 1);
1712 assert_eq!(cache.stats().hits_fs_cache, 2);
1713 assert_eq!(cache.stats().misses, 0);
1714 test_hackatom_instance_execution(&mut instance);
1715
1716 cache.unpin(&checksum).unwrap();
1718
1719 let non_id = Checksum::generate(b"non_existent");
1721 cache.unpin(&non_id).unwrap();
1722 }
1723
1724 #[test]
1725 fn pin_recompiles_module() {
1726 let (options, _temp_dir) = make_testing_options();
1727 let cache: Cache<MockApi, MockStorage, MockQuerier> =
1728 unsafe { Cache::new(options.clone()).unwrap() };
1729 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1730
1731 remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
1733
1734 cache.pin(&checksum).unwrap();
1736 assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
1737 assert_eq!(cache.stats().hits_memory_cache, 0);
1738 assert_eq!(cache.stats().hits_fs_cache, 0);
1739 assert_eq!(cache.stats().misses, 1);
1740
1741 let backend = mock_backend(&[]);
1743 let mut instance = cache
1744 .get_instance(&checksum, backend, TESTING_OPTIONS)
1745 .unwrap();
1746 assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
1747 assert_eq!(cache.stats().hits_memory_cache, 0);
1748 assert_eq!(cache.stats().hits_fs_cache, 0);
1749 assert_eq!(cache.stats().misses, 1);
1750 test_hackatom_instance_execution(&mut instance);
1751 }
1752
1753 #[test]
1754 fn loading_without_extension_works() {
1755 let tmp_dir = TempDir::new().unwrap();
1756 let options = CacheOptions {
1757 base_dir: tmp_dir.path().to_path_buf(),
1758 available_capabilities: default_capabilities(),
1759 memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
1760 instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
1761 };
1762 let cache: Cache<MockApi, MockStorage, MockQuerier> =
1763 unsafe { Cache::new(options).unwrap() };
1764 let checksum = cache.store_code(HACKATOM, true, true).unwrap();
1765
1766 let old_path = tmp_dir
1768 .path()
1769 .join(STATE_DIR)
1770 .join(WASM_DIR)
1771 .join(checksum.to_hex());
1772 let new_path = old_path.with_extension("wasm");
1773 fs::rename(new_path, old_path).unwrap();
1774
1775 let restored = cache.load_wasm(&checksum).unwrap();
1777 assert_eq!(restored, HACKATOM);
1778 }
1779
1780 #[test]
1781 fn func_ref_test() {
1782 let wasm = wat::parse_str(
1783 r#"(module
1784 (type (func))
1785 (type (func (param funcref)))
1786 (import "env" "abort" (func $f (type 1)))
1787 (func (type 0) nop)
1788 (export "add_one" (func 0))
1789 (export "allocate" (func 0))
1790 (export "interface_version_8" (func 0))
1791 (export "deallocate" (func 0))
1792 (export "memory" (memory 0))
1793 (memory 3)
1794 )"#,
1795 )
1796 .unwrap();
1797
1798 let (testing_opts, _temp_dir) = make_testing_options();
1799 let cache: Cache<MockApi, MockStorage, MockQuerier> =
1800 unsafe { Cache::new(testing_opts).unwrap() };
1801
1802 let err = cache.store_code(&wasm, true, true).unwrap_err();
1804 assert!(err.to_string().contains("FuncRef"));
1805 }
1806
1807 #[test]
1808 fn test_wasm_limits_checked() {
1809 let tmp_dir = TempDir::new().unwrap();
1810
1811 let config = Config {
1812 wasm_limits: WasmLimits {
1813 max_function_params: Some(0),
1814 ..Default::default()
1815 },
1816 cache: CacheOptions {
1817 base_dir: tmp_dir.path().to_path_buf(),
1818 available_capabilities: default_capabilities(),
1819 memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
1820 instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
1821 },
1822 };
1823
1824 let cache: Cache<MockApi, MockStorage, MockQuerier> =
1825 unsafe { Cache::new_with_config(config).unwrap() };
1826 let err = cache.store_code(HACKATOM, true, true).unwrap_err();
1827 assert!(matches!(err, VmError::StaticValidationErr { .. }));
1828 }
1829}