use std::collections::{BTreeSet, HashSet};
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Mutex;
use wasmer::{Module, Store};
use cosmwasm_std::Checksum;
use crate::backend::{Backend, BackendApi, Querier, Storage};
use crate::capabilities::required_capabilities_from_module;
use crate::compatibility::check_wasm;
use crate::config::{CacheOptions, Config, WasmLimits};
use crate::errors::{VmError, VmResult};
use crate::filesystem::mkdir_p;
use crate::instance::{Instance, InstanceOptions};
use crate::modules::{CachedModule, FileSystemCache, InMemoryCache, PinnedMemoryCache};
use crate::parsed_wasm::ParsedWasm;
use crate::size::Size;
use crate::static_analysis::{Entrypoint, ExportInfo, REQUIRED_IBC_EXPORTS};
use crate::wasm_backend::{compile, make_compiling_engine};
const STATE_DIR: &str = "state";
const WASM_DIR: &str = "wasm";
const CACHE_DIR: &str = "cache";
const MODULES_DIR: &str = "modules";
#[derive(Debug, Default, Clone, Copy)]
pub struct Stats {
pub hits_pinned_memory_cache: u32,
pub hits_memory_cache: u32,
pub hits_fs_cache: u32,
pub misses: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct Metrics {
pub stats: Stats,
pub elements_pinned_memory_cache: usize,
pub elements_memory_cache: usize,
pub size_pinned_memory_cache: usize,
pub size_memory_cache: usize,
}
#[derive(Debug, Clone)]
pub struct PerModuleMetrics {
pub hits: u32,
pub size: usize,
}
#[derive(Debug, Clone)]
pub struct PinnedMetrics {
pub per_module: Vec<(Checksum, PerModuleMetrics)>,
}
pub struct CacheInner {
wasm_path: PathBuf,
pinned_memory_cache: PinnedMemoryCache,
memory_cache: InMemoryCache,
fs_cache: FileSystemCache,
stats: Stats,
}
pub struct Cache<A: BackendApi, S: Storage, Q: Querier> {
available_capabilities: HashSet<String>,
inner: Mutex<CacheInner>,
instance_memory_limit: Size,
type_api: PhantomData<A>,
type_storage: PhantomData<S>,
type_querier: PhantomData<Q>,
instantiation_lock: Mutex<()>,
wasm_limits: WasmLimits,
}
#[derive(PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct AnalysisReport {
pub has_ibc_entry_points: bool,
pub entrypoints: BTreeSet<Entrypoint>,
pub required_capabilities: BTreeSet<String>,
pub contract_migrate_version: Option<u64>,
}
impl<A, S, Q> Cache<A, S, Q>
where
A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static, {
pub unsafe fn new(options: CacheOptions) -> VmResult<Self> {
Self::new_with_config(Config {
wasm_limits: WasmLimits::default(),
cache: options,
})
}
pub unsafe fn new_with_config(config: Config) -> VmResult<Self> {
let Config {
cache:
CacheOptions {
base_dir,
available_capabilities,
memory_cache_size_bytes,
instance_memory_limit_bytes,
},
wasm_limits,
} = config;
let state_path = base_dir.join(STATE_DIR);
let cache_path = base_dir.join(CACHE_DIR);
let wasm_path = state_path.join(WASM_DIR);
mkdir_p(&state_path).map_err(|_e| VmError::cache_err("Error creating state directory"))?;
mkdir_p(&cache_path).map_err(|_e| VmError::cache_err("Error creating cache directory"))?;
mkdir_p(&wasm_path).map_err(|_e| VmError::cache_err("Error creating wasm directory"))?;
let fs_cache = FileSystemCache::new(cache_path.join(MODULES_DIR), false)
.map_err(|e| VmError::cache_err(format!("Error file system cache: {e}")))?;
Ok(Cache {
available_capabilities,
inner: Mutex::new(CacheInner {
wasm_path,
pinned_memory_cache: PinnedMemoryCache::new(),
memory_cache: InMemoryCache::new(memory_cache_size_bytes),
fs_cache,
stats: Stats::default(),
}),
instance_memory_limit: instance_memory_limit_bytes,
type_storage: PhantomData::<S>,
type_api: PhantomData::<A>,
type_querier: PhantomData::<Q>,
instantiation_lock: Mutex::new(()),
wasm_limits,
})
}
pub fn set_module_unchecked(&mut self, unchecked: bool) {
self.inner
.lock()
.unwrap()
.fs_cache
.set_module_unchecked(unchecked);
}
pub fn stats(&self) -> Stats {
self.inner.lock().unwrap().stats
}
pub fn pinned_metrics(&self) -> PinnedMetrics {
let cache = self.inner.lock().unwrap();
let per_module = cache
.pinned_memory_cache
.iter()
.map(|(checksum, module)| {
let metrics = PerModuleMetrics {
hits: module.hits,
size: module.module.size_estimate,
};
(*checksum, metrics)
})
.collect();
PinnedMetrics { per_module }
}
pub fn metrics(&self) -> Metrics {
let cache = self.inner.lock().unwrap();
Metrics {
stats: cache.stats,
elements_pinned_memory_cache: cache.pinned_memory_cache.len(),
elements_memory_cache: cache.memory_cache.len(),
size_pinned_memory_cache: cache.pinned_memory_cache.size(),
size_memory_cache: cache.memory_cache.size(),
}
}
#[deprecated = "Use `store_code(wasm, true, true)` instead"]
pub fn save_wasm(&self, wasm: &[u8]) -> VmResult<Checksum> {
self.store_code(wasm, true, true)
}
pub fn store_code(&self, wasm: &[u8], checked: bool, persist: bool) -> VmResult<Checksum> {
if checked {
check_wasm(
wasm,
&self.available_capabilities,
&self.wasm_limits,
crate::internals::Logger::Off,
)?;
}
let module = compile_module(wasm)?;
if persist {
self.save_to_disk(wasm, &module)
} else {
Ok(Checksum::generate(wasm))
}
}
#[deprecated = "Use `store_code(wasm, false, true)` instead"]
pub fn save_wasm_unchecked(&self, wasm: &[u8]) -> VmResult<Checksum> {
self.store_code(wasm, false, true)
}
fn save_to_disk(&self, wasm: &[u8], module: &Module) -> VmResult<Checksum> {
let mut cache = self.inner.lock().unwrap();
let checksum = save_wasm_to_disk(&cache.wasm_path, wasm)?;
cache.fs_cache.store(&checksum, module)?;
Ok(checksum)
}
pub fn remove_wasm(&self, checksum: &Checksum) -> VmResult<()> {
let mut cache = self.inner.lock().unwrap();
cache.fs_cache.remove(checksum)?;
let path = &cache.wasm_path;
remove_wasm_from_disk(path, checksum)?;
Ok(())
}
pub fn load_wasm(&self, checksum: &Checksum) -> VmResult<Vec<u8>> {
self.load_wasm_with_path(&self.inner.lock().unwrap().wasm_path, checksum)
}
fn load_wasm_with_path(&self, wasm_path: &Path, checksum: &Checksum) -> VmResult<Vec<u8>> {
let code = load_wasm_from_disk(wasm_path, checksum)?;
if Checksum::generate(&code) != *checksum {
Err(VmError::integrity_err())
} else {
Ok(code)
}
}
pub fn analyze(&self, checksum: &Checksum) -> VmResult<AnalysisReport> {
let wasm = self.load_wasm(checksum)?;
let module = ParsedWasm::parse(&wasm)?;
let exports = module.exported_function_names(None);
let entrypoints = exports
.iter()
.filter_map(|export| Entrypoint::from_str(export).ok())
.collect();
Ok(AnalysisReport {
has_ibc_entry_points: REQUIRED_IBC_EXPORTS
.iter()
.all(|required| exports.contains(required.as_ref())),
entrypoints,
required_capabilities: required_capabilities_from_module(&module)
.into_iter()
.collect(),
contract_migrate_version: module.contract_migrate_version,
})
}
pub fn pin(&self, checksum: &Checksum) -> VmResult<()> {
let mut cache = self.inner.lock().unwrap();
if cache.pinned_memory_cache.has(checksum) {
return Ok(());
}
if let Some(cached_module) = cache
.fs_cache
.load(checksum, Some(self.instance_memory_limit))?
{
cache.stats.hits_fs_cache = cache.stats.hits_fs_cache.saturating_add(1);
return cache.pinned_memory_cache.store(checksum, cached_module);
}
let wasm = self.load_wasm_with_path(&cache.wasm_path, checksum)?;
cache.stats.misses = cache.stats.misses.saturating_add(1);
{
let compiling_engine = make_compiling_engine(None);
let module = compile(&compiling_engine, &wasm)?;
cache.fs_cache.store(checksum, &module)?;
}
let Some(cached_module) = cache
.fs_cache
.load(checksum, Some(self.instance_memory_limit))?
else {
return Err(VmError::generic_err(
"Can't load module from file system cache after storing it to file system cache (pin)",
));
};
cache.pinned_memory_cache.store(checksum, cached_module)
}
pub fn unpin(&self, checksum: &Checksum) -> VmResult<()> {
self.inner
.lock()
.unwrap()
.pinned_memory_cache
.remove(checksum)
}
pub fn get_instance(
&self,
checksum: &Checksum,
backend: Backend<A, S, Q>,
options: InstanceOptions,
) -> VmResult<Instance<A, S, Q>> {
let (module, store) = self.get_module(checksum)?;
let instance = Instance::from_module(
store,
&module,
backend,
options.gas_limit,
None,
Some(&self.instantiation_lock),
)?;
Ok(instance)
}
fn get_module(&self, checksum: &Checksum) -> VmResult<(Module, Store)> {
let mut cache = self.inner.lock().unwrap();
if let Some(element) = cache.pinned_memory_cache.load(checksum)? {
cache.stats.hits_pinned_memory_cache =
cache.stats.hits_pinned_memory_cache.saturating_add(1);
let CachedModule {
module,
engine,
size_estimate: _,
} = element;
let store = Store::new(engine);
return Ok((module, store));
}
if let Some(element) = cache.memory_cache.load(checksum)? {
cache.stats.hits_memory_cache = cache.stats.hits_memory_cache.saturating_add(1);
let CachedModule {
module,
engine,
size_estimate: _,
} = element;
let store = Store::new(engine);
return Ok((module, store));
}
if let Some(cached_module) = cache
.fs_cache
.load(checksum, Some(self.instance_memory_limit))?
{
cache.stats.hits_fs_cache = cache.stats.hits_fs_cache.saturating_add(1);
cache.memory_cache.store(checksum, cached_module.clone())?;
let CachedModule {
module,
engine,
size_estimate: _,
} = cached_module;
let store = Store::new(engine);
return Ok((module, store));
}
let wasm = self.load_wasm_with_path(&cache.wasm_path, checksum)?;
cache.stats.misses = cache.stats.misses.saturating_add(1);
{
let compiling_engine = make_compiling_engine(None);
let module = compile(&compiling_engine, &wasm)?;
cache.fs_cache.store(checksum, &module)?;
}
let Some(cached_module) = cache
.fs_cache
.load(checksum, Some(self.instance_memory_limit))?
else {
return Err(VmError::generic_err(
"Can't load module from file system cache after storing it to file system cache (get_module)",
));
};
cache.memory_cache.store(checksum, cached_module.clone())?;
let CachedModule {
module,
engine,
size_estimate: _,
} = cached_module;
let store = Store::new(engine);
Ok((module, store))
}
}
fn compile_module(wasm: &[u8]) -> Result<Module, VmError> {
let compiling_engine = make_compiling_engine(None);
let module = compile(&compiling_engine, wasm)?;
Ok(module)
}
unsafe impl<A, S, Q> Sync for Cache<A, S, Q>
where
A: BackendApi + 'static,
S: Storage + 'static,
Q: Querier + 'static,
{
}
unsafe impl<A, S, Q> Send for Cache<A, S, Q>
where
A: BackendApi + 'static,
S: Storage + 'static,
Q: Querier + 'static,
{
}
fn save_wasm_to_disk(dir: impl Into<PathBuf>, wasm: &[u8]) -> VmResult<Checksum> {
let checksum = Checksum::generate(wasm);
let filename = checksum.to_hex();
let filepath = dir.into().join(filename).with_extension("wasm");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(filepath)
.map_err(|e| VmError::cache_err(format!("Error opening Wasm file for writing: {e}")))?;
file.write_all(wasm)
.map_err(|e| VmError::cache_err(format!("Error writing Wasm file: {e}")))?;
Ok(checksum)
}
fn load_wasm_from_disk(dir: impl Into<PathBuf>, checksum: &Checksum) -> VmResult<Vec<u8>> {
let path = dir.into().join(checksum.to_hex());
let mut file = File::open(path.with_extension("wasm"))
.or_else(|_| File::open(path))
.map_err(|_e| VmError::cache_err("Error opening Wasm file for reading"))?;
let mut wasm = Vec::<u8>::new();
file.read_to_end(&mut wasm)
.map_err(|_e| VmError::cache_err("Error reading Wasm file"))?;
Ok(wasm)
}
fn remove_wasm_from_disk(dir: impl Into<PathBuf>, checksum: &Checksum) -> VmResult<()> {
let path = dir.into().join(checksum.to_hex());
let wasm_path = path.with_extension("wasm");
let path_exists = path.exists();
let wasm_path_exists = wasm_path.exists();
if !path_exists && !wasm_path_exists {
return Err(VmError::cache_err("Wasm file does not exist"));
}
if path_exists {
fs::remove_file(path)
.map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?;
}
if wasm_path_exists {
fs::remove_file(wasm_path)
.map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::calls::{call_execute, call_instantiate};
use crate::testing::{mock_backend, mock_env, mock_info, MockApi, MockQuerier, MockStorage};
use cosmwasm_std::{coins, Empty};
use std::borrow::Cow;
use std::fs::{create_dir_all, remove_dir_all};
use tempfile::TempDir;
use wasm_encoder::ComponentSection;
const TESTING_GAS_LIMIT: u64 = 500_000_000; const TESTING_MEMORY_LIMIT: Size = Size::mebi(16);
const TESTING_OPTIONS: InstanceOptions = InstanceOptions {
gas_limit: TESTING_GAS_LIMIT,
};
const TESTING_MEMORY_CACHE_SIZE: Size = Size::mebi(200);
static HACKATOM: &[u8] = include_bytes!("../testdata/hackatom.wasm");
static IBC_REFLECT: &[u8] = include_bytes!("../testdata/ibc_reflect.wasm");
static IBC2: &[u8] = include_bytes!("../testdata/ibc2.wasm");
static EMPTY: &[u8] = include_bytes!("../testdata/empty.wasm");
static INVALID_CONTRACT_WAT: &str = r#"(module
(type $t0 (func (param i32) (result i32)))
(func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32)
local.get $p0
i32.const 1
i32.add))
"#;
fn default_capabilities() -> HashSet<String> {
HashSet::from([
"cosmwasm_1_1".to_string(),
"cosmwasm_1_2".to_string(),
"cosmwasm_1_3".to_string(),
"cosmwasm_1_4".to_string(),
"cosmwasm_1_4".to_string(),
"cosmwasm_2_0".to_string(),
"cosmwasm_2_1".to_string(),
"cosmwasm_2_2".to_string(),
"iterator".to_string(),
"staking".to_string(),
"stargate".to_string(),
])
}
fn make_testing_options() -> (CacheOptions, TempDir) {
let temp_dir = TempDir::new().unwrap();
(
CacheOptions {
base_dir: temp_dir.path().into(),
available_capabilities: default_capabilities(),
memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
},
temp_dir,
)
}
fn make_stargate_testing_options() -> (CacheOptions, TempDir) {
let temp_dir = TempDir::new().unwrap();
let mut capabilities = default_capabilities();
capabilities.insert("stargate".into());
(
CacheOptions {
base_dir: temp_dir.path().into(),
available_capabilities: capabilities,
memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
},
temp_dir,
)
}
fn make_ibc2_testing_options() -> (CacheOptions, TempDir) {
let temp_dir = TempDir::new().unwrap();
let mut capabilities = default_capabilities();
capabilities.insert("ibc2".into());
(
CacheOptions {
base_dir: temp_dir.path().into(),
available_capabilities: capabilities,
memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
},
temp_dir,
)
}
fn test_hackatom_instance_execution<S, Q>(instance: &mut Instance<MockApi, S, Q>)
where
S: Storage + 'static,
Q: Querier + 'static,
{
let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
let verifier = instance.api().addr_make("verifies");
let beneficiary = instance.api().addr_make("benefits");
let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
let response =
call_instantiate::<_, _, _, Empty>(instance, &mock_env(), &info, msg.as_bytes())
.unwrap()
.unwrap();
assert_eq!(response.messages.len(), 0);
let info = mock_info(&verifier, &coins(15, "earth"));
let msg = br#"{"release":{"denom":"earth"}}"#;
let response = call_execute::<_, _, _, Empty>(instance, &mock_env(), &info, msg)
.unwrap()
.unwrap();
assert_eq!(response.messages.len(), 1);
}
#[test]
fn new_base_dir_will_be_created() {
let temp_dir = TempDir::new().unwrap();
let my_base_dir = temp_dir.path().join("non-existent-sub-dir");
let (base_opts, _temp_dir) = make_testing_options();
let options = CacheOptions {
base_dir: my_base_dir.clone(),
..base_opts
};
assert!(!my_base_dir.is_dir());
let _cache = unsafe { Cache::<MockApi, MockStorage, MockQuerier>::new(options).unwrap() };
assert!(my_base_dir.is_dir());
}
#[test]
fn store_code_checked_works() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
cache.store_code(HACKATOM, true, true).unwrap();
}
#[test]
fn store_code_without_persist_works() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, false).unwrap();
assert!(
cache.load_wasm(&checksum).is_err(),
"wasm file should not be saved to disk"
);
}
#[test]
fn store_code_allows_saving_multiple_times() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
cache.store_code(HACKATOM, true, true).unwrap();
cache.store_code(HACKATOM, true, true).unwrap();
}
#[test]
fn store_code_checked_rejects_invalid_contract() {
let wasm = wat::parse_str(INVALID_CONTRACT_WAT).unwrap();
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
let save_result = cache.store_code(&wasm, true, true);
match save_result.unwrap_err() {
VmError::StaticValidationErr { msg, .. } => {
assert_eq!(msg, "Wasm contract must contain exactly one memory")
}
e => panic!("Unexpected error {e:?}"),
}
}
#[test]
fn store_code_fills_file_system_but_not_memory_cache() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let backend = mock_backend(&[]);
let _ = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
}
#[test]
fn store_code_unchecked_works() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
cache.store_code(HACKATOM, false, true).unwrap();
}
#[test]
fn store_code_unchecked_accepts_invalid_contract() {
let wasm = wat::parse_str(INVALID_CONTRACT_WAT).unwrap();
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
cache.store_code(&wasm, false, true).unwrap();
}
#[test]
fn load_wasm_works() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let restored = cache.load_wasm(&checksum).unwrap();
assert_eq!(restored, HACKATOM);
}
#[test]
fn load_wasm_works_across_multiple_cache_instances() {
let tmp_dir = TempDir::new().unwrap();
let id: Checksum;
{
let options1 = CacheOptions {
base_dir: tmp_dir.path().to_path_buf(),
available_capabilities: default_capabilities(),
memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
};
let cache1: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options1).unwrap() };
id = cache1.store_code(HACKATOM, true, true).unwrap();
}
{
let options2 = CacheOptions {
base_dir: tmp_dir.path().to_path_buf(),
available_capabilities: default_capabilities(),
memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
};
let cache2: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options2).unwrap() };
let restored = cache2.load_wasm(&id).unwrap();
assert_eq!(restored, HACKATOM);
}
}
#[test]
fn load_wasm_errors_for_non_existent_id() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
let checksum = Checksum::from([
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,
5, 5, 5,
]);
match cache.load_wasm(&checksum).unwrap_err() {
VmError::CacheErr { msg, .. } => {
assert_eq!(msg, "Error opening Wasm file for reading")
}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn load_wasm_errors_for_corrupted_wasm() {
let tmp_dir = TempDir::new().unwrap();
let options = CacheOptions {
base_dir: tmp_dir.path().to_path_buf(),
available_capabilities: default_capabilities(),
memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
};
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let filepath = tmp_dir
.path()
.join(STATE_DIR)
.join(WASM_DIR)
.join(checksum.to_hex())
.with_extension("wasm");
let mut file = OpenOptions::new().write(true).open(filepath).unwrap();
file.write_all(b"broken data").unwrap();
let res = cache.load_wasm(&checksum);
match res {
Err(VmError::IntegrityErr { .. }) => {}
Err(e) => panic!("Unexpected error: {e:?}"),
Ok(_) => panic!("This must not succeed"),
}
}
#[test]
fn remove_wasm_works() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
cache.load_wasm(&checksum).unwrap();
cache.remove_wasm(&checksum).unwrap();
match cache.load_wasm(&checksum).unwrap_err() {
VmError::CacheErr { msg, .. } => {
assert_eq!(msg, "Error opening Wasm file for reading")
}
e => panic!("Unexpected error: {e:?}"),
}
match cache.remove_wasm(&checksum).unwrap_err() {
VmError::CacheErr { msg, .. } => {
assert_eq!(msg, "Wasm file does not exist")
}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn get_instance_finds_cached_module() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let backend = mock_backend(&[]);
let _instance = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
}
#[test]
fn get_instance_finds_cached_modules_and_stores_to_memory() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let backend1 = mock_backend(&[]);
let backend2 = mock_backend(&[]);
let backend3 = mock_backend(&[]);
let backend4 = mock_backend(&[]);
let backend5 = mock_backend(&[]);
let _instance1 = cache
.get_instance(&checksum, backend1, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
let _instance2 = cache
.get_instance(&checksum, backend2, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
let _instance3 = cache
.get_instance(&checksum, backend3, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 2);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
cache.pin(&checksum).unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 2);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
let _instance4 = cache
.get_instance(&checksum, backend4, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
assert_eq!(cache.stats().hits_memory_cache, 2);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
let _instance5 = cache
.get_instance(&checksum, backend5, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 2);
assert_eq!(cache.stats().hits_memory_cache, 2);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
}
#[test]
fn get_instance_recompiles_module() {
let (options, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(options.clone()).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
let backend = mock_backend(&[]);
let _instance = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 0);
assert_eq!(cache.stats().misses, 1);
let backend = mock_backend(&[]);
let _instance = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 0);
assert_eq!(cache.stats().misses, 1);
}
#[test]
fn call_instantiate_on_cached_contract() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
{
let mut instance = cache
.get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
let verifier = instance.api().addr_make("verifies");
let beneficiary = instance.api().addr_make("benefits");
let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
let res = call_instantiate::<_, _, _, Empty>(
&mut instance,
&mock_env(),
&info,
msg.as_bytes(),
)
.unwrap();
let msgs = res.unwrap().messages;
assert_eq!(msgs.len(), 0);
}
{
let mut instance = cache
.get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
let verifier = instance.api().addr_make("verifies");
let beneficiary = instance.api().addr_make("benefits");
let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
let res = call_instantiate::<_, _, _, Empty>(
&mut instance,
&mock_env(),
&info,
msg.as_bytes(),
)
.unwrap();
let msgs = res.unwrap().messages;
assert_eq!(msgs.len(), 0);
}
{
cache.pin(&checksum).unwrap();
let mut instance = cache
.get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
let verifier = instance.api().addr_make("verifies");
let beneficiary = instance.api().addr_make("benefits");
let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
let res = call_instantiate::<_, _, _, Empty>(
&mut instance,
&mock_env(),
&info,
msg.as_bytes(),
)
.unwrap();
let msgs = res.unwrap().messages;
assert_eq!(msgs.len(), 0);
}
}
#[test]
fn call_execute_on_cached_contract() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
{
let mut instance = cache
.get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
let verifier = instance.api().addr_make("verifies");
let beneficiary = instance.api().addr_make("benefits");
let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
let response = call_instantiate::<_, _, _, Empty>(
&mut instance,
&mock_env(),
&info,
msg.as_bytes(),
)
.unwrap()
.unwrap();
assert_eq!(response.messages.len(), 0);
let info = mock_info(&verifier, &coins(15, "earth"));
let msg = br#"{"release":{"denom":"earth"}}"#;
let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
.unwrap()
.unwrap();
assert_eq!(response.messages.len(), 1);
}
{
let mut instance = cache
.get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
let verifier = instance.api().addr_make("verifies");
let beneficiary = instance.api().addr_make("benefits");
let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
let response = call_instantiate::<_, _, _, Empty>(
&mut instance,
&mock_env(),
&info,
msg.as_bytes(),
)
.unwrap()
.unwrap();
assert_eq!(response.messages.len(), 0);
let info = mock_info(&verifier, &coins(15, "earth"));
let msg = br#"{"release":{"denom":"earth"}}"#;
let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
.unwrap()
.unwrap();
assert_eq!(response.messages.len(), 1);
}
{
cache.pin(&checksum).unwrap();
let mut instance = cache
.get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
let info = mock_info(&instance.api().addr_make("creator"), &coins(1000, "earth"));
let verifier = instance.api().addr_make("verifies");
let beneficiary = instance.api().addr_make("benefits");
let msg = format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#);
let response = call_instantiate::<_, _, _, Empty>(
&mut instance,
&mock_env(),
&info,
msg.as_bytes(),
)
.unwrap()
.unwrap();
assert_eq!(response.messages.len(), 0);
let info = mock_info(&verifier, &coins(15, "earth"));
let msg = br#"{"release":{"denom":"earth"}}"#;
let response = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
.unwrap()
.unwrap();
assert_eq!(response.messages.len(), 1);
}
}
#[test]
fn call_execute_on_recompiled_contract() {
let (options, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(options.clone()).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
let backend = mock_backend(&[]);
let mut instance = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 0);
assert_eq!(cache.stats().misses, 1);
test_hackatom_instance_execution(&mut instance);
}
#[test]
fn use_multiple_cached_instances_of_same_contract() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let backend1 = mock_backend(&[]);
let backend2 = mock_backend(&[]);
let mut instance = cache
.get_instance(&checksum, backend1, TESTING_OPTIONS)
.unwrap();
let info = mock_info("owner1", &coins(1000, "earth"));
let sue = instance.api().addr_make("sue");
let mary = instance.api().addr_make("mary");
let msg = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
let res =
call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
.unwrap();
let msgs = res.unwrap().messages;
assert_eq!(msgs.len(), 0);
let backend1 = instance.recycle().unwrap();
let mut instance = cache
.get_instance(&checksum, backend2, TESTING_OPTIONS)
.unwrap();
let info = mock_info("owner2", &coins(500, "earth"));
let bob = instance.api().addr_make("bob");
let john = instance.api().addr_make("john");
let msg = format!(r#"{{"verifier": "{bob}", "beneficiary": "{john}"}}"#);
let res =
call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
.unwrap();
let msgs = res.unwrap().messages;
assert_eq!(msgs.len(), 0);
let backend2 = instance.recycle().unwrap();
let mut instance = cache
.get_instance(&checksum, backend2, TESTING_OPTIONS)
.unwrap();
let info = mock_info(&bob, &coins(15, "earth"));
let msg = br#"{"release":{"denom":"earth"}}"#;
let res = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap();
let msgs = res.unwrap().messages;
assert_eq!(1, msgs.len());
let mut instance = cache
.get_instance(&checksum, backend1, TESTING_OPTIONS)
.unwrap();
let info = mock_info(&sue, &coins(15, "earth"));
let msg = br#"{"release":{"denom":"earth"}}"#;
let res = call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap();
let msgs = res.unwrap().messages;
assert_eq!(1, msgs.len());
}
#[test]
fn resets_gas_when_reusing_instance() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let backend1 = mock_backend(&[]);
let backend2 = mock_backend(&[]);
let mut instance1 = cache
.get_instance(&checksum, backend1, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
let original_gas = instance1.get_gas_left();
let info = mock_info("owner1", &coins(1000, "earth"));
let sue = instance1.api().addr_make("sue");
let mary = instance1.api().addr_make("mary");
let msg = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
call_instantiate::<_, _, _, Empty>(&mut instance1, &mock_env(), &info, msg.as_bytes())
.unwrap()
.unwrap();
assert!(instance1.get_gas_left() < original_gas);
let mut instance2 = cache
.get_instance(&checksum, backend2, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT);
}
#[test]
fn recovers_from_out_of_gas() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let backend1 = mock_backend(&[]);
let backend2 = mock_backend(&[]);
let options = InstanceOptions { gas_limit: 10 };
let mut instance1 = cache.get_instance(&checksum, backend1, options).unwrap();
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
let info1 = mock_info("owner1", &coins(1000, "earth"));
let sue = instance1.api().addr_make("sue");
let mary = instance1.api().addr_make("mary");
let msg1 = format!(r#"{{"verifier": "{sue}", "beneficiary": "{mary}"}}"#);
match call_instantiate::<_, _, _, Empty>(
&mut instance1,
&mock_env(),
&info1,
msg1.as_bytes(),
)
.unwrap_err()
{
VmError::GasDepletion { .. } => (), e => panic!("unexpected error, {e:?}"),
}
assert_eq!(instance1.get_gas_left(), 0);
let options = InstanceOptions {
gas_limit: TESTING_GAS_LIMIT,
};
let mut instance2 = cache.get_instance(&checksum, backend2, options).unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
assert_eq!(instance2.get_gas_left(), TESTING_GAS_LIMIT);
let info2 = mock_info("owner2", &coins(500, "earth"));
let bob = instance2.api().addr_make("bob");
let john = instance2.api().addr_make("john");
let msg2 = format!(r#"{{"verifier": "{bob}", "beneficiary": "{john}"}}"#);
call_instantiate::<_, _, _, Empty>(&mut instance2, &mock_env(), &info2, msg2.as_bytes())
.unwrap()
.unwrap();
}
#[test]
fn save_wasm_to_disk_works_for_same_data_multiple_times() {
let tmp_dir = TempDir::new().unwrap();
let path = tmp_dir.path();
let code = vec![12u8; 17];
save_wasm_to_disk(path, &code).unwrap();
save_wasm_to_disk(path, &code).unwrap();
}
#[test]
fn save_wasm_to_disk_fails_on_non_existent_dir() {
let tmp_dir = TempDir::new().unwrap();
let path = tmp_dir.path().join("something");
let code = vec![12u8; 17];
let res = save_wasm_to_disk(path.to_str().unwrap(), &code);
assert!(res.is_err());
}
#[test]
fn load_wasm_from_disk_works() {
let tmp_dir = TempDir::new().unwrap();
let path = tmp_dir.path();
let code = vec![12u8; 17];
let checksum = save_wasm_to_disk(path, &code).unwrap();
let loaded = load_wasm_from_disk(path, &checksum).unwrap();
assert_eq!(code, loaded);
}
#[test]
fn load_wasm_from_disk_works_in_subfolder() {
let tmp_dir = TempDir::new().unwrap();
let path = tmp_dir.path().join("something");
create_dir_all(&path).unwrap();
let code = vec![12u8; 17];
let checksum = save_wasm_to_disk(&path, &code).unwrap();
let loaded = load_wasm_from_disk(&path, &checksum).unwrap();
assert_eq!(code, loaded);
}
#[test]
fn remove_wasm_from_disk_works() {
let tmp_dir = TempDir::new().unwrap();
let path = tmp_dir.path();
let code = vec![12u8; 17];
let checksum = save_wasm_to_disk(path, &code).unwrap();
remove_wasm_from_disk(path, &checksum).unwrap();
match remove_wasm_from_disk(path, &checksum).unwrap_err() {
VmError::CacheErr { msg, .. } => assert_eq!(msg, "Wasm file does not exist"),
err => panic!("Unexpected error: {err:?}"),
}
}
#[test]
fn analyze_works() {
use Entrypoint as E;
let (testing_opts, _temp_dir) = make_stargate_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
let checksum1 = cache.store_code(HACKATOM, true, true).unwrap();
let report1 = cache.analyze(&checksum1).unwrap();
assert_eq!(
report1,
AnalysisReport {
has_ibc_entry_points: false,
entrypoints: BTreeSet::from([
E::Instantiate,
E::Migrate,
E::Sudo,
E::Execute,
E::Query
]),
required_capabilities: BTreeSet::from([
"cosmwasm_1_1".to_string(),
"cosmwasm_1_2".to_string(),
"cosmwasm_1_3".to_string(),
"cosmwasm_1_4".to_string(),
"cosmwasm_1_4".to_string(),
"cosmwasm_2_0".to_string(),
"cosmwasm_2_1".to_string(),
"cosmwasm_2_2".to_string(),
]),
contract_migrate_version: Some(420),
}
);
let checksum2 = cache.store_code(IBC_REFLECT, true, true).unwrap();
let report2 = cache.analyze(&checksum2).unwrap();
let mut ibc_contract_entrypoints =
BTreeSet::from([E::Instantiate, E::Migrate, E::Execute, E::Reply, E::Query]);
ibc_contract_entrypoints.extend(REQUIRED_IBC_EXPORTS);
assert_eq!(
report2,
AnalysisReport {
has_ibc_entry_points: true,
entrypoints: ibc_contract_entrypoints,
required_capabilities: BTreeSet::from_iter([
"cosmwasm_1_1".to_string(),
"cosmwasm_1_2".to_string(),
"cosmwasm_1_3".to_string(),
"cosmwasm_1_4".to_string(),
"cosmwasm_1_4".to_string(),
"cosmwasm_2_0".to_string(),
"cosmwasm_2_1".to_string(),
"cosmwasm_2_2".to_string(),
"iterator".to_string(),
"stargate".to_string()
]),
contract_migrate_version: None,
}
);
let checksum3 = cache.store_code(EMPTY, true, true).unwrap();
let report3 = cache.analyze(&checksum3).unwrap();
assert_eq!(
report3,
AnalysisReport {
has_ibc_entry_points: false,
entrypoints: BTreeSet::new(),
required_capabilities: BTreeSet::from(["iterator".to_string()]),
contract_migrate_version: None,
}
);
let mut wasm_with_version = EMPTY.to_vec();
let custom_section = wasm_encoder::CustomSection {
name: Cow::Borrowed("cw_migrate_version"),
data: Cow::Borrowed(b"21"),
};
custom_section.append_to_component(&mut wasm_with_version);
let checksum4 = cache.store_code(&wasm_with_version, true, true).unwrap();
let report4 = cache.analyze(&checksum4).unwrap();
assert_eq!(
report4,
AnalysisReport {
has_ibc_entry_points: false,
entrypoints: BTreeSet::new(),
required_capabilities: BTreeSet::from(["iterator".to_string()]),
contract_migrate_version: Some(21),
}
);
let (testing_opts, _temp_dir) = make_ibc2_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
let checksum5 = cache.store_code(IBC2, true, true).unwrap();
let report5 = cache.analyze(&checksum5).unwrap();
let ibc2_contract_entrypoints = BTreeSet::from([
E::Instantiate,
E::Query,
E::Ibc2PacketReceive,
E::Ibc2PacketTimeout,
E::Ibc2PacketAck,
E::Ibc2PacketSend,
]);
assert_eq!(
report5,
AnalysisReport {
has_ibc_entry_points: false,
entrypoints: ibc2_contract_entrypoints,
required_capabilities: BTreeSet::from_iter([
"iterator".to_string(),
"ibc2".to_string()
]),
contract_migrate_version: None,
}
);
}
#[test]
fn pinned_metrics_works() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
cache.pin(&checksum).unwrap();
let pinned_metrics = cache.pinned_metrics();
assert_eq!(pinned_metrics.per_module.len(), 1);
assert_eq!(pinned_metrics.per_module[0].0, checksum);
assert_eq!(pinned_metrics.per_module[0].1.hits, 0);
let backend = mock_backend(&[]);
let _ = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
let pinned_metrics = cache.pinned_metrics();
assert_eq!(pinned_metrics.per_module.len(), 1);
assert_eq!(pinned_metrics.per_module[0].0, checksum);
assert_eq!(pinned_metrics.per_module[0].1.hits, 1);
let empty_checksum = cache.store_code(EMPTY, true, true).unwrap();
cache.pin(&empty_checksum).unwrap();
let pinned_metrics = cache.pinned_metrics();
assert_eq!(pinned_metrics.per_module.len(), 2);
let get_module_hits = |checksum| {
pinned_metrics
.per_module
.iter()
.find(|(iter_checksum, _module)| *iter_checksum == checksum)
.map(|(_checksum, module)| module)
.cloned()
.unwrap()
};
assert_eq!(get_module_hits(checksum).hits, 1);
assert_eq!(get_module_hits(empty_checksum).hits, 0);
}
#[test]
fn pin_unpin_works() {
let (testing_opts, _temp_dir) = make_testing_options();
let cache = unsafe { Cache::new(testing_opts).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let backend = mock_backend(&[]);
let mut instance = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 1);
assert_eq!(cache.stats().misses, 0);
test_hackatom_instance_execution(&mut instance);
cache.pin(&checksum).unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
cache.pin(&checksum).unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
let backend = mock_backend(&[]);
let mut instance = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
test_hackatom_instance_execution(&mut instance);
cache.unpin(&checksum).unwrap();
let backend = mock_backend(&[]);
let mut instance = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
assert_eq!(cache.stats().hits_memory_cache, 1);
assert_eq!(cache.stats().hits_fs_cache, 2);
assert_eq!(cache.stats().misses, 0);
test_hackatom_instance_execution(&mut instance);
cache.unpin(&checksum).unwrap();
let non_id = Checksum::generate(b"non_existent");
cache.unpin(&non_id).unwrap();
}
#[test]
fn pin_recompiles_module() {
let (options, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options.clone()).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
remove_dir_all(options.base_dir.join(CACHE_DIR).join(MODULES_DIR)).unwrap();
cache.pin(&checksum).unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 0);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 0);
assert_eq!(cache.stats().misses, 1);
let backend = mock_backend(&[]);
let mut instance = cache
.get_instance(&checksum, backend, TESTING_OPTIONS)
.unwrap();
assert_eq!(cache.stats().hits_pinned_memory_cache, 1);
assert_eq!(cache.stats().hits_memory_cache, 0);
assert_eq!(cache.stats().hits_fs_cache, 0);
assert_eq!(cache.stats().misses, 1);
test_hackatom_instance_execution(&mut instance);
}
#[test]
fn loading_without_extension_works() {
let tmp_dir = TempDir::new().unwrap();
let options = CacheOptions {
base_dir: tmp_dir.path().to_path_buf(),
available_capabilities: default_capabilities(),
memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
};
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options).unwrap() };
let checksum = cache.store_code(HACKATOM, true, true).unwrap();
let old_path = tmp_dir
.path()
.join(STATE_DIR)
.join(WASM_DIR)
.join(checksum.to_hex());
let new_path = old_path.with_extension("wasm");
fs::rename(new_path, old_path).unwrap();
let restored = cache.load_wasm(&checksum).unwrap();
assert_eq!(restored, HACKATOM);
}
#[test]
fn func_ref_test() {
let wasm = wat::parse_str(
r#"(module
(type (func))
(type (func (param funcref)))
(import "env" "abort" (func $f (type 1)))
(func (type 0) nop)
(export "add_one" (func 0))
(export "allocate" (func 0))
(export "interface_version_8" (func 0))
(export "deallocate" (func 0))
(export "memory" (memory 0))
(memory 3)
)"#,
)
.unwrap();
let (testing_opts, _temp_dir) = make_testing_options();
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(testing_opts).unwrap() };
let err = cache.store_code(&wasm, true, true).unwrap_err();
assert!(err.to_string().contains("FuncRef"));
}
#[test]
fn test_wasm_limits_checked() {
let tmp_dir = TempDir::new().unwrap();
let config = Config {
wasm_limits: WasmLimits {
max_function_params: Some(0),
..Default::default()
},
cache: CacheOptions {
base_dir: tmp_dir.path().to_path_buf(),
available_capabilities: default_capabilities(),
memory_cache_size_bytes: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit_bytes: TESTING_MEMORY_LIMIT,
},
};
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new_with_config(config).unwrap() };
let err = cache.store_code(HACKATOM, true, true).unwrap_err();
assert!(matches!(err, VmError::StaticValidationErr { .. }));
}
}