use std::collections::HashSet;
use std::marker::PhantomData;
use std::ptr::NonNull;
pub use wasmer_runtime_core::typed_func::Func;
use wasmer_runtime_core::{
imports,
module::Module,
typed_func::{Wasm, WasmTypeList},
vm::Ctx,
Instance as WasmerInstance,
};
use crate::backends::{compile, get_gas_left, set_gas_limit};
use crate::context::{
get_gas_state, move_into_context, move_out_of_context, set_storage_readonly,
set_wasmer_instance, setup_context, with_querier_from_context, with_storage_from_context,
};
use crate::conversion::to_u32;
use crate::errors::{CommunicationError, VmError, VmResult};
use crate::features::required_features_from_wasmer_instance;
use crate::imports::{
do_canonicalize_address, do_humanize_address, do_query_chain, do_read, do_remove, do_write,
};
#[cfg(feature = "iterator")]
use crate::imports::{do_next, do_scan};
use crate::memory::{get_memory_info, read_region, write_region};
use crate::traits::{Api, Extern, Querier, Storage};
static WASM_PAGE_SIZE: u64 = 64 * 1024;
pub struct Instance<S: Storage + 'static, A: Api + 'static, Q: Querier + 'static> {
inner: Box<WasmerInstance>,
pub api: A,
pub required_features: HashSet<String>,
type_storage: PhantomData<S>,
type_querier: PhantomData<Q>,
}
impl<S, A, Q> Instance<S, A, Q>
where
S: Storage + 'static,
A: Api + 'static,
Q: Querier + 'static,
{
pub fn from_code(code: &[u8], deps: Extern<S, A, Q>, gas_limit: u64) -> VmResult<Self> {
let module = compile(code)?;
Instance::from_module(&module, deps, gas_limit)
}
pub(crate) fn from_module(
module: &Module,
deps: Extern<S, A, Q>,
gas_limit: u64,
) -> VmResult<Self> {
let mut import_obj =
imports! { move || { setup_context::<S, Q>(gas_limit) }, "env" => {}, };
let api = deps.api;
import_obj.extend(imports! {
"env" => {
"db_read" => Func::new(move |ctx: &mut Ctx, key_ptr: u32| -> VmResult<u32> {
do_read::<S, Q>(ctx, key_ptr)
}),
"db_write" => Func::new(move |ctx: &mut Ctx, key_ptr: u32, value_ptr: u32| -> VmResult<()> {
do_write::<S, Q>(ctx, key_ptr, value_ptr)
}),
"db_remove" => Func::new(move |ctx: &mut Ctx, key_ptr: u32| -> VmResult<()> {
do_remove::<S, Q>(ctx, key_ptr)
}),
"canonicalize_address" => Func::new(move |ctx: &mut Ctx, source_ptr: u32, destination_ptr: u32| -> VmResult<u32> {
do_canonicalize_address::<A, S, Q>(api, ctx, source_ptr, destination_ptr)
}),
"humanize_address" => Func::new(move |ctx: &mut Ctx, source_ptr: u32, destination_ptr: u32| -> VmResult<u32> {
do_humanize_address(api, ctx, source_ptr, destination_ptr)
}),
"query_chain" => Func::new(move |ctx: &mut Ctx, request_ptr: u32| -> VmResult<u32> {
do_query_chain::<S, Q>(ctx, request_ptr)
}),
},
});
#[cfg(feature = "iterator")]
import_obj.extend(imports! {
"env" => {
"db_scan" => Func::new(move |ctx: &mut Ctx, start_ptr: u32, end_ptr: u32, order: i32| -> VmResult<u32> {
do_scan::<S, Q>(ctx, start_ptr, end_ptr, order)
}),
"db_next" => Func::new(move |ctx: &mut Ctx, iterator_id: u32| -> VmResult<u32> {
do_next::<S, Q>(ctx, iterator_id)
}),
},
});
let wasmer_instance = Box::from(module.instantiate(&import_obj).map_err(|original| {
VmError::instantiation_err(format!("Error instantiating module: {:?}", original))
})?);
Ok(Instance::from_wasmer(wasmer_instance, deps, gas_limit))
}
pub(crate) fn from_wasmer(
mut wasmer_instance: Box<WasmerInstance>,
deps: Extern<S, A, Q>,
gas_limit: u64,
) -> Self {
set_gas_limit(wasmer_instance.as_mut(), gas_limit);
get_gas_state::<S, Q>(wasmer_instance.context_mut()).set_gas_limit(gas_limit);
let required_features = required_features_from_wasmer_instance(wasmer_instance.as_ref());
let instance_ptr = NonNull::from(wasmer_instance.as_ref());
set_wasmer_instance::<S, Q>(wasmer_instance.context_mut(), Some(instance_ptr));
move_into_context(wasmer_instance.context_mut(), deps.storage, deps.querier);
Instance {
inner: wasmer_instance,
api: deps.api,
required_features,
type_storage: PhantomData::<S> {},
type_querier: PhantomData::<Q> {},
}
}
pub fn recycle(mut self) -> Option<Extern<S, A, Q>> {
if let (Some(storage), Some(querier)) = move_out_of_context(self.inner.context_mut()) {
Some(Extern {
storage,
api: self.api,
querier,
})
} else {
None
}
}
pub fn get_memory_size(&self) -> u64 {
(get_memory_info(self.inner.context()).size as u64) * WASM_PAGE_SIZE
}
pub fn get_gas_left(&self) -> u64 {
get_gas_left(&self.inner)
}
pub fn set_storage_readonly(&mut self, new_value: bool) {
set_storage_readonly::<S, Q>(self.inner.context_mut(), new_value);
}
pub fn with_storage<F: FnOnce(&mut S) -> VmResult<T>, T>(&mut self, func: F) -> VmResult<T> {
with_storage_from_context::<S, Q, F, T>(self.inner.context_mut(), func)
}
pub fn with_querier<F: FnOnce(&mut Q) -> VmResult<T>, T>(&mut self, func: F) -> VmResult<T> {
with_querier_from_context::<S, Q, F, T>(self.inner.context_mut(), func)
}
pub(crate) fn allocate(&mut self, size: usize) -> VmResult<u32> {
let alloc: Func<u32, u32> = self.func("allocate")?;
let ptr = alloc.call(to_u32(size)?)?;
if ptr == 0 {
return Err(CommunicationError::zero_address().into());
}
Ok(ptr)
}
pub(crate) fn deallocate(&mut self, ptr: u32) -> VmResult<()> {
let dealloc: Func<u32, ()> = self.func("deallocate")?;
dealloc.call(ptr)?;
Ok(())
}
pub(crate) fn read_memory(&self, region_ptr: u32, max_length: usize) -> VmResult<Vec<u8>> {
read_region(self.inner.context(), region_ptr, max_length)
}
pub(crate) fn write_memory(&mut self, region_ptr: u32, data: &[u8]) -> VmResult<()> {
write_region(self.inner.context(), region_ptr, data)?;
Ok(())
}
pub(crate) fn func<Args, Rets>(&self, name: &str) -> VmResult<Func<Args, Rets, Wasm>>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
let function = self.inner.exports.get(name)?;
Ok(function)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::context::is_storage_readonly;
use crate::errors::VmError;
use crate::testing::{
mock_dependencies, mock_env, mock_instance, mock_instance_with_balances,
mock_instance_with_failing_api, mock_instance_with_gas_limit, MockApi, MockQuerier,
MockStorage, MOCK_CONTRACT_ADDR,
};
use crate::traits::Storage;
use crate::{call_init, FfiError};
use cosmwasm_std::{
coin, from_binary, AllBalanceResponse, BalanceResponse, BankQuery, Empty, HumanAddr,
QueryRequest,
};
use wabt::wat2wasm;
static KIB: usize = 1024;
static MIB: usize = 1024 * 1024;
static CONTRACT: &[u8] = include_bytes!("../testdata/contract.wasm");
static DEFAULT_GAS_LIMIT: u64 = 500_000;
type MS = MockStorage;
type MQ = MockQuerier;
#[test]
fn required_features_works() {
let deps = mock_dependencies(20, &[]);
let instance = Instance::from_code(CONTRACT, deps, DEFAULT_GAS_LIMIT).unwrap();
assert_eq!(instance.required_features.len(), 0);
}
#[test]
fn required_features_works_for_many_exports() {
let wasm = wat2wasm(
r#"(module
(type (func))
(func (type 0) nop)
(export "requires_water" (func 0))
(export "requires_" (func 0))
(export "requires_nutrients" (func 0))
(export "require_milk" (func 0))
(export "REQUIRES_air" (func 0))
(export "requires_sun" (func 0))
)"#,
)
.unwrap();
let deps = mock_dependencies(20, &[]);
let instance = Instance::from_code(&wasm, deps, DEFAULT_GAS_LIMIT).unwrap();
assert_eq!(instance.required_features.len(), 3);
assert!(instance.required_features.contains("nutrients"));
assert!(instance.required_features.contains("sun"));
assert!(instance.required_features.contains("water"));
}
#[test]
fn func_works() {
let instance = mock_instance(&CONTRACT, &[]);
let allocate: Func<u32, u32> = instance.func("allocate").expect("error getting func");
let _ptr1 = allocate.call(0).expect("error calling allocate func");
let _ptr2 = allocate.call(1).expect("error calling allocate func");
let _ptr3 = allocate.call(33).expect("error calling allocate func");
}
#[test]
fn func_errors_for_non_existent_function() {
let instance = mock_instance(&CONTRACT, &[]);
let missing_function = "bar_foo345";
match instance.func::<(), ()>(missing_function).err().unwrap() {
VmError::ResolveErr { msg, .. } => assert_eq!(
msg,
"Wasmer resolve error: ExportNotFound { name: \"bar_foo345\" }"
),
e => panic!("unexpected error: {:?}", e),
}
}
#[test]
fn func_errors_for_wrong_signature() {
let instance = mock_instance(&CONTRACT, &[]);
match instance.func::<(), ()>("allocate").err().unwrap() {
VmError::ResolveErr { msg, .. } => assert_eq!(
msg,
"Wasmer resolve error: Signature { expected: FuncSig { params: [I32], returns: [I32] }, found: [] }"
),
e => panic!("unexpected error: {:?}", e),
}
}
#[test]
fn allocate_deallocate_works() {
let mut instance = mock_instance(&CONTRACT, &[]);
let sizes: Vec<usize> = vec![
0,
4,
40,
400,
4 * KIB,
40 * KIB,
400 * KIB,
4 * MIB,
40 * MIB,
400 * MIB,
];
for size in sizes.into_iter() {
let region_ptr = instance.allocate(size).expect("error allocating");
instance.deallocate(region_ptr).expect("error deallocating");
}
}
#[test]
fn write_and_read_memory_works() {
let mut instance = mock_instance(&CONTRACT, &[]);
let sizes: Vec<usize> = vec![
0,
4,
40,
400,
4 * KIB,
40 * KIB,
400 * KIB,
4 * MIB,
];
for size in sizes.into_iter() {
let region_ptr = instance.allocate(size).expect("error allocating");
let original = vec![170u8; size];
instance
.write_memory(region_ptr, &original)
.expect("error writing");
let data = instance
.read_memory(region_ptr, size)
.expect("error reading");
assert_eq!(data, original);
instance.deallocate(region_ptr).expect("error deallocating");
}
}
#[test]
fn errors_in_imports_are_unwrapped_from_wasmer_errors() {
let error_message = "Api failed intentionally";
let mut instance = mock_instance_with_failing_api(&CONTRACT, &[], error_message);
let init_result = call_init::<_, _, _, serde_json::Value>(
&mut instance,
&mock_env(&MockApi::new(MOCK_CONTRACT_ADDR.len()), "someone", &[]),
b"{\"verifier\": \"some1\", \"beneficiary\": \"some2\"}",
);
match init_result.unwrap_err() {
VmError::FfiErr {
source: FfiError::Other { error, .. },
} if error == error_message => {}
other => panic!("unexpected error: {:?}", other),
}
}
#[test]
fn read_memory_errors_when_when_length_is_too_long() {
let length = 6;
let max_length = 5;
let mut instance = mock_instance(&CONTRACT, &[]);
let region_ptr = instance.allocate(length).expect("error allocating");
let data = vec![170u8; length];
instance
.write_memory(region_ptr, &data)
.expect("error writing");
let result = instance.read_memory(region_ptr, max_length);
match result.unwrap_err() {
VmError::CommunicationErr {
source:
CommunicationError::RegionLengthTooBig {
length, max_length, ..
},
} => {
assert_eq!(length, 6);
assert_eq!(max_length, 5);
}
err => panic!("unexpected error: {:?}", err),
};
instance.deallocate(region_ptr).expect("error deallocating");
}
#[test]
fn get_memory_size_works() {
let mut instance = mock_instance(&CONTRACT, &[]);
assert_eq!(instance.get_memory_size(), 17 * WASM_PAGE_SIZE);
let region_ptr = instance.allocate(100 * 1024).expect("error allocating");
assert_eq!(instance.get_memory_size(), 19 * WASM_PAGE_SIZE);
instance.deallocate(region_ptr).expect("error deallocating");
assert_eq!(instance.get_memory_size(), 19 * WASM_PAGE_SIZE);
}
#[test]
#[cfg(feature = "default-cranelift")]
fn set_get_and_gas_cranelift() {
let instance = mock_instance_with_gas_limit(&CONTRACT, 123321);
let orig_gas = instance.get_gas_left();
assert_eq!(orig_gas, 1_000_000);
}
#[test]
#[cfg(feature = "default-singlepass")]
fn set_get_and_gas_singlepass() {
let instance = mock_instance_with_gas_limit(&CONTRACT, 123321);
let orig_gas = instance.get_gas_left();
assert_eq!(orig_gas, 123321);
}
#[test]
fn set_storage_readonly_works() {
let mut instance = mock_instance(&CONTRACT, &[]);
assert_eq!(
is_storage_readonly::<MS, MQ>(instance.inner.context()),
true
);
instance.set_storage_readonly(false);
assert_eq!(
is_storage_readonly::<MS, MQ>(instance.inner.context()),
false
);
instance.set_storage_readonly(false);
assert_eq!(
is_storage_readonly::<MS, MQ>(instance.inner.context()),
false
);
instance.set_storage_readonly(true);
assert_eq!(
is_storage_readonly::<MS, MQ>(instance.inner.context()),
true
);
}
#[test]
fn with_storage_works() {
let mut instance = mock_instance(&CONTRACT, &[]);
instance
.with_storage(|store| {
assert!(store.get(b"foo").unwrap().0.is_none());
Ok(())
})
.unwrap();
instance
.with_storage(|store| {
store.set(b"foo", b"bar").unwrap();
Ok(())
})
.unwrap();
instance
.with_storage(|store| {
assert_eq!(store.get(b"foo").unwrap().0, Some(b"bar".to_vec()));
Ok(())
})
.unwrap();
}
#[test]
#[should_panic]
fn with_storage_safe_for_panic() {
let mut instance = mock_instance(&CONTRACT, &[]);
instance
.with_storage::<_, ()>(|_store| panic!("trigger failure"))
.unwrap();
}
#[test]
fn with_querier_works_readonly() {
let rich_addr = HumanAddr::from("foobar");
let rich_balance = vec![coin(10000, "gold"), coin(8000, "silver")];
let mut instance = mock_instance_with_balances(&CONTRACT, &[(&rich_addr, &rich_balance)]);
instance
.with_querier(|querier| {
let response = querier
.handle_query::<Empty>(&QueryRequest::Bank(BankQuery::Balance {
address: rich_addr.clone(),
denom: "silver".to_string(),
}))?
.0
.unwrap()
.unwrap();
let BalanceResponse { amount } = from_binary(&response).unwrap();
assert_eq!(amount.amount.u128(), 8000);
assert_eq!(amount.denom, "silver");
Ok(())
})
.unwrap();
instance
.with_querier(|querier| {
let response = querier
.handle_query::<Empty>(&QueryRequest::Bank(BankQuery::AllBalances {
address: rich_addr.clone(),
}))?
.0
.unwrap()
.unwrap();
let AllBalanceResponse { amount } = from_binary(&response).unwrap();
assert_eq!(amount.len(), 2);
assert_eq!(amount[0].amount.u128(), 10000);
assert_eq!(amount[0].denom, "gold");
assert_eq!(amount[1].amount.u128(), 8000);
assert_eq!(amount[1].denom, "silver");
Ok(())
})
.unwrap();
}
#[test]
fn with_querier_allows_updating_balances() {
let rich_addr = HumanAddr::from("foobar");
let rich_balance1 = vec![coin(10000, "gold"), coin(500, "silver")];
let rich_balance2 = vec![coin(10000, "gold"), coin(8000, "silver")];
let mut instance = mock_instance_with_balances(&CONTRACT, &[(&rich_addr, &rich_balance1)]);
instance
.with_querier(|querier| {
let response = querier
.handle_query::<Empty>(&QueryRequest::Bank(BankQuery::Balance {
address: rich_addr.clone(),
denom: "silver".to_string(),
}))?
.0
.unwrap()
.unwrap();
let BalanceResponse { amount } = from_binary(&response).unwrap();
assert_eq!(amount.amount.u128(), 500);
Ok(())
})
.unwrap();
instance
.with_querier(|querier| {
querier.update_balance(&rich_addr, rich_balance2);
Ok(())
})
.unwrap();
instance
.with_querier(|querier| {
let response = querier
.handle_query::<Empty>(&QueryRequest::Bank(BankQuery::Balance {
address: rich_addr.clone(),
denom: "silver".to_string(),
}))?
.0
.unwrap()
.unwrap();
let BalanceResponse { amount } = from_binary(&response).unwrap();
assert_eq!(amount.amount.u128(), 8000);
Ok(())
})
.unwrap();
}
}
#[cfg(test)]
#[cfg(feature = "default-singlepass")]
mod singlepass_test {
use cosmwasm_std::{coins, Empty};
use crate::calls::{call_handle, call_init, call_query};
use crate::testing::{mock_env, mock_instance, mock_instance_with_gas_limit};
static CONTRACT: &[u8] = include_bytes!("../testdata/contract.wasm");
#[test]
fn contract_deducts_gas_init() {
let mut instance = mock_instance(&CONTRACT, &[]);
let orig_gas = instance.get_gas_left();
let env = mock_env(&instance.api, "creator", &coins(1000, "earth"));
let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes();
call_init::<_, _, _, Empty>(&mut instance, &env, msg)
.unwrap()
.unwrap();
let init_used = orig_gas - instance.get_gas_left();
println!("init used: {}", init_used);
assert_eq!(init_used, 70810);
}
#[test]
fn contract_deducts_gas_handle() {
let mut instance = mock_instance(&CONTRACT, &[]);
let env = mock_env(&instance.api, "creator", &coins(1000, "earth"));
let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes();
call_init::<_, _, _, Empty>(&mut instance, &env, msg)
.unwrap()
.unwrap();
let gas_before_handle = instance.get_gas_left();
let env = mock_env(&instance.api, "verifies", &coins(15, "earth"));
let msg = br#"{"release":{}}"#;
call_handle::<_, _, _, Empty>(&mut instance, &env, msg)
.unwrap()
.unwrap();
let handle_used = gas_before_handle - instance.get_gas_left();
println!("handle used: {}", handle_used);
assert_eq!(handle_used, 97825);
}
#[test]
fn contract_enforces_gas_limit() {
let mut instance = mock_instance_with_gas_limit(&CONTRACT, 20_000);
let env = mock_env(&instance.api, "creator", &coins(1000, "earth"));
let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes();
let res = call_init::<_, _, _, Empty>(&mut instance, &env, msg);
assert!(res.is_err());
}
#[test]
fn query_works_with_metering() {
let mut instance = mock_instance(&CONTRACT, &[]);
let env = mock_env(&instance.api, "creator", &coins(1000, "earth"));
let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes();
let _res = call_init::<_, _, _, Empty>(&mut instance, &env, msg)
.unwrap()
.unwrap();
let gas_before_query = instance.get_gas_left();
let msg = r#"{"verifier":{}}"#.as_bytes();
let res = call_query(&mut instance, msg).unwrap();
let answer = res.unwrap();
assert_eq!(answer.as_slice(), b"{\"verifier\":\"verifies\"}");
let query_used = gas_before_query - instance.get_gas_left();
println!("query used: {}", query_used);
assert_eq!(query_used, 32558);
}
}