use std::marker::PhantomData;
use snafu::ResultExt;
pub use wasmer_runtime_core::typed_func::Func;
use wasmer_runtime_core::{
imports,
module::Module,
typed_func::{Wasm, WasmTypeList},
vm::Ctx,
};
use cosmwasm::traits::{Api, Extern, Storage};
use crate::backends::{compile, get_gas, set_gas};
use crate::context::{
do_canonical_address, do_human_address, do_read, do_write, leave_storage, setup_context,
take_storage, with_storage_from_context,
};
use crate::errors::{ResolveErr, Result, RuntimeErr, WasmerErr};
use crate::memory::{read_region, write_region};
pub struct Instance<S: Storage + 'static, A: Api + 'static> {
wasmer_instance: wasmer_runtime_core::instance::Instance,
pub api: A,
type_storage: PhantomData<S>,
}
impl<S, A> Instance<S, A>
where
S: Storage + 'static,
A: Api + 'static,
{
pub fn from_code(code: &[u8], deps: Extern<S, A>, gas_limit: u64) -> Result<Self> {
let module = compile(code)?;
Instance::from_module(&module, deps, gas_limit)
}
pub fn from_module(module: &Module, deps: Extern<S, A>, gas_limit: u64) -> Result<Self> {
let api = deps.api;
let import_obj = imports! {
|| { setup_context::<S>() },
"env" => {
"read_db" => Func::new(move |ctx: &mut Ctx, key_ptr: u32, value_ptr: u32| -> i32 {
do_read::<S>(ctx, key_ptr, value_ptr)
}),
"write_db" => Func::new(move |ctx: &mut Ctx, key_ptr: u32, value_ptr: u32| {
do_write::<S>(ctx, key_ptr, value_ptr)
}),
"canonicalize_address" => Func::new(move |ctx: &mut Ctx, human_ptr: u32, canonical_ptr: u32| -> i32 {
do_canonical_address(api, ctx, human_ptr, canonical_ptr)
}),
"humanize_address" => Func::new(move |ctx: &mut Ctx, canonical_ptr: u32, human_ptr: u32| -> i32 {
do_human_address(api, ctx, canonical_ptr, human_ptr)
}),
},
};
let wasmer_instance = module.instantiate(&import_obj).context(WasmerErr {})?;
Ok(Instance::from_wasmer(wasmer_instance, deps, gas_limit))
}
pub fn from_wasmer(
mut wasmer_instance: wasmer_runtime_core::Instance,
deps: Extern<S, A>,
gas_limit: u64,
) -> Self {
set_gas(&mut wasmer_instance, gas_limit);
leave_storage(wasmer_instance.context(), Some(deps.storage));
Instance {
wasmer_instance: wasmer_instance,
api: deps.api,
type_storage: PhantomData::<S> {},
}
}
pub fn recycle(instance: Self) -> (wasmer_runtime_core::Instance, Option<Extern<S, A>>) {
let ext = if let Some(storage) = take_storage(instance.wasmer_instance.context()) {
Some(Extern {
storage: storage,
api: instance.api,
})
} else {
None
};
(instance.wasmer_instance, ext)
}
pub fn get_gas(&self) -> u64 {
get_gas(&self.wasmer_instance)
}
pub fn with_storage<F: FnMut(&mut S)>(&self, func: F) {
with_storage_from_context(self.wasmer_instance.context(), func)
}
pub fn memory(&self, ptr: u32) -> Vec<u8> {
read_region(self.wasmer_instance.context(), ptr)
}
pub fn allocate(&mut self, data: &[u8]) -> Result<u32> {
let alloc: Func<u32, u32> = self.func("allocate")?;
let ptr = alloc.call(data.len() as u32).context(RuntimeErr {})?;
write_region(self.wasmer_instance.context(), ptr, data)?;
Ok(ptr)
}
pub fn deallocate(&mut self, ptr: u32) -> Result<()> {
let dealloc: Func<u32, ()> = self.func("deallocate")?;
dealloc.call(ptr).context(RuntimeErr {})?;
Ok(())
}
pub fn func<Args, Rets>(&self, name: &str) -> Result<Func<Args, Rets, Wasm>>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
self.wasmer_instance.func(name).context(ResolveErr {})
}
}
#[cfg(test)]
mod test {
use crate::calls::{call_handle, call_init, call_query};
use crate::testing::{mock_instance, mock_instance_with_gas_limit};
use cosmwasm::mock::mock_env;
use cosmwasm::types::coin;
static CONTRACT_0_7: &[u8] = include_bytes!("../testdata/contract_0.7.wasm");
#[test]
#[cfg(feature = "default-cranelift")]
fn set_get_and_gas_cranelift_noop() {
let instance = mock_instance_with_gas_limit(&CONTRACT_0_7, 123321);
let orig_gas = instance.get_gas();
assert_eq!(orig_gas, 1_000_000);
}
#[test]
#[cfg(feature = "default-singlepass")]
fn set_get_and_gas_singlepass_works() {
let instance = mock_instance_with_gas_limit(&CONTRACT_0_7, 123321);
let orig_gas = instance.get_gas();
assert_eq!(orig_gas, 123321);
}
#[test]
#[should_panic]
fn with_context_safe_for_panic() {
let instance = mock_instance(&CONTRACT_0_7);
instance.with_storage(|_store| assert_eq!(1, 2));
}
#[test]
#[cfg(feature = "default-singlepass")]
fn contract_deducts_gas_init() {
let mut instance = mock_instance(&CONTRACT_0_7);
let orig_gas = instance.get_gas();
let env = mock_env(&instance.api, "creator", &coin("1000", "earth"), &[]);
let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes();
call_init(&mut instance, &env, msg).unwrap();
let init_used = orig_gas - instance.get_gas();
println!("init used: {}", init_used);
assert_eq!(init_used, 52_541);
}
#[test]
#[cfg(feature = "default-singlepass")]
fn contract_deducts_gas_handle() {
let mut instance = mock_instance(&CONTRACT_0_7);
let env = mock_env(&instance.api, "creator", &coin("1000", "earth"), &[]);
let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes();
call_init(&mut instance, &env, msg).unwrap();
let gas_before_handle = instance.get_gas();
let env = mock_env(
&instance.api,
"verifies",
&coin("15", "earth"),
&coin("1015", "earth"),
);
let msg = br#"{"release":{}}"#;
call_handle(&mut instance, &env, msg).unwrap();
let handle_used = gas_before_handle - instance.get_gas();
println!("handle used: {}", handle_used);
assert_eq!(handle_used, 91_482);
}
#[test]
#[cfg(feature = "default-singlepass")]
fn contract_enforces_gas_limit() {
let mut instance = mock_instance_with_gas_limit(&CONTRACT_0_7, 20_000);
let env = mock_env(&instance.api, "creator", &coin("1000", "earth"), &[]);
let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes();
let res = call_init(&mut instance, &env, msg);
assert!(res.is_err());
}
#[test]
#[cfg(feature = "default-singlepass")]
fn query_works_with_metering() {
let mut instance = mock_instance(&CONTRACT_0_7);
let env = mock_env(&instance.api, "creator", &coin("1000", "earth"), &[]);
let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes();
let _res = call_init(&mut instance, &env, msg).unwrap().unwrap();
let gas_before_query = instance.get_gas();
let msg = r#"{"verifier":{}}"#.as_bytes();
let res = call_query(&mut instance, msg).unwrap();
let answer = res.unwrap();
assert_eq!(answer.as_slice(), "verifies".as_bytes());
let query_used = gas_before_query - instance.get_gas();
println!("query used: {}", query_used);
assert_eq!(query_used, 44_918);
}
}