use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::ptr::NonNull;
use std::rc::Rc;
use std::sync::Mutex;
use wasmer::{
Exports, Function, FunctionEnv, Imports, Instance as WasmerInstance, Module, Store, Value,
};
use crate::backend::{Backend, BackendApi, Querier, Storage};
use crate::capabilities::required_capabilities_from_module;
use crate::conversion::{ref_to_u32, to_u32};
use crate::environment::Environment;
use crate::errors::{CommunicationError, VmError, VmResult};
use crate::imports::{
do_abort, do_addr_canonicalize, do_addr_humanize, do_addr_validate, do_bls12_381_aggregate_g1,
do_bls12_381_aggregate_g2, do_bls12_381_hash_to_g1, do_bls12_381_hash_to_g2,
do_bls12_381_pairing_equality, do_db_read, do_db_remove, do_db_write, do_debug,
do_ed25519_batch_verify, do_ed25519_verify, do_query_chain, do_secp256k1_recover_pubkey,
do_secp256k1_verify, do_secp256r1_recover_pubkey, do_secp256r1_verify,
};
#[cfg(feature = "iterator")]
use crate::imports::{do_db_next, do_db_next_key, do_db_next_value, do_db_scan};
use crate::memory::{read_region, write_region};
use crate::size::Size;
use crate::wasm_backend::{compile, make_compiling_engine};
pub use crate::environment::DebugInfo; #[derive(Copy, Clone, Debug)]
pub struct GasReport {
pub limit: u64,
pub remaining: u64,
pub used_externally: u64,
pub used_internally: u64,
}
#[derive(Copy, Clone, Debug)]
pub struct InstanceOptions {
pub gas_limit: u64,
}
pub struct Instance<A: BackendApi, S: Storage, Q: Querier> {
_inner: Box<WasmerInstance>,
fe: FunctionEnv<Environment<A, S, Q>>,
store: Store,
}
impl<A, S, Q> Instance<A, S, Q>
where
A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static, {
pub fn from_code(
code: &[u8],
backend: Backend<A, S, Q>,
options: InstanceOptions,
memory_limit: Option<Size>,
) -> VmResult<Self> {
let engine = make_compiling_engine(memory_limit);
let module = compile(&engine, code)?;
let store = Store::new(engine);
Instance::from_module(store, &module, backend, options.gas_limit, None, None)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn from_module(
mut store: Store,
module: &Module,
backend: Backend<A, S, Q>,
gas_limit: u64,
extra_imports: Option<HashMap<&str, Exports>>,
instantiation_lock: Option<&Mutex<()>>,
) -> VmResult<Self> {
let fe = FunctionEnv::new(&mut store, Environment::new(backend.api, gas_limit));
let mut import_obj = Imports::new();
let mut env_imports = Exports::new();
env_imports.insert(
"db_read",
Function::new_typed_with_env(&mut store, &fe, do_db_read),
);
env_imports.insert(
"db_write",
Function::new_typed_with_env(&mut store, &fe, do_db_write),
);
env_imports.insert(
"db_remove",
Function::new_typed_with_env(&mut store, &fe, do_db_remove),
);
env_imports.insert(
"addr_validate",
Function::new_typed_with_env(&mut store, &fe, do_addr_validate),
);
env_imports.insert(
"addr_canonicalize",
Function::new_typed_with_env(&mut store, &fe, do_addr_canonicalize),
);
env_imports.insert(
"addr_humanize",
Function::new_typed_with_env(&mut store, &fe, do_addr_humanize),
);
env_imports.insert(
"bls12_381_aggregate_g1",
Function::new_typed_with_env(&mut store, &fe, do_bls12_381_aggregate_g1),
);
env_imports.insert(
"bls12_381_aggregate_g2",
Function::new_typed_with_env(&mut store, &fe, do_bls12_381_aggregate_g2),
);
env_imports.insert(
"bls12_381_pairing_equality",
Function::new_typed_with_env(&mut store, &fe, do_bls12_381_pairing_equality),
);
env_imports.insert(
"bls12_381_hash_to_g1",
Function::new_typed_with_env(&mut store, &fe, do_bls12_381_hash_to_g1),
);
env_imports.insert(
"bls12_381_hash_to_g2",
Function::new_typed_with_env(&mut store, &fe, do_bls12_381_hash_to_g2),
);
env_imports.insert(
"secp256k1_verify",
Function::new_typed_with_env(&mut store, &fe, do_secp256k1_verify),
);
env_imports.insert(
"secp256k1_recover_pubkey",
Function::new_typed_with_env(&mut store, &fe, do_secp256k1_recover_pubkey),
);
env_imports.insert(
"secp256r1_verify",
Function::new_typed_with_env(&mut store, &fe, do_secp256r1_verify),
);
env_imports.insert(
"secp256r1_recover_pubkey",
Function::new_typed_with_env(&mut store, &fe, do_secp256r1_recover_pubkey),
);
env_imports.insert(
"ed25519_verify",
Function::new_typed_with_env(&mut store, &fe, do_ed25519_verify),
);
env_imports.insert(
"ed25519_batch_verify",
Function::new_typed_with_env(&mut store, &fe, do_ed25519_batch_verify),
);
env_imports.insert(
"debug",
Function::new_typed_with_env(&mut store, &fe, do_debug),
);
env_imports.insert(
"abort",
Function::new_typed_with_env(&mut store, &fe, do_abort),
);
env_imports.insert(
"query_chain",
Function::new_typed_with_env(&mut store, &fe, do_query_chain),
);
#[cfg(feature = "iterator")]
env_imports.insert(
"db_scan",
Function::new_typed_with_env(&mut store, &fe, do_db_scan),
);
#[cfg(feature = "iterator")]
env_imports.insert(
"db_next",
Function::new_typed_with_env(&mut store, &fe, do_db_next),
);
#[cfg(feature = "iterator")]
env_imports.insert(
"db_next_key",
Function::new_typed_with_env(&mut store, &fe, do_db_next_key),
);
#[cfg(feature = "iterator")]
env_imports.insert(
"db_next_value",
Function::new_typed_with_env(&mut store, &fe, do_db_next_value),
);
import_obj.register_namespace("env", env_imports);
if let Some(extra_imports) = extra_imports {
for (namespace, exports_obj) in extra_imports {
import_obj.register_namespace(namespace, exports_obj);
}
}
let wasmer_instance = Box::from(
{
let _lock = instantiation_lock.map(|l| l.lock().unwrap());
WasmerInstance::new(&mut store, module, &import_obj)
}
.map_err(|original| {
VmError::instantiation_err(format!("Error instantiating module: {original}"))
})?,
);
let memory = wasmer_instance
.exports
.get_memory("memory")
.map_err(|original| {
VmError::instantiation_err(format!("Could not get memory 'memory': {original}"))
})?
.clone();
let instance_ptr = NonNull::from(wasmer_instance.as_ref());
{
let mut fe_mut = fe.clone().into_mut(&mut store);
let (env, mut store) = fe_mut.data_and_store_mut();
env.memory = Some(memory);
env.set_wasmer_instance(Some(instance_ptr));
env.set_gas_left(&mut store, gas_limit);
env.move_in(backend.storage, backend.querier);
}
Ok(Instance {
_inner: wasmer_instance,
fe,
store,
})
}
pub fn api(&self) -> &A {
&self.fe.as_ref(&self.store).api
}
#[must_use = "Calling ::recycle() without reusing the returned backend just drops the instance"]
pub fn recycle(self) -> Option<Backend<A, S, Q>> {
let Instance {
_inner, fe, store, ..
} = self;
let env = fe.as_ref(&store);
if let (Some(storage), Some(querier)) = env.move_out() {
let api = env.api.clone();
Some(Backend {
api,
storage,
querier,
})
} else {
None
}
}
pub fn set_debug_handler<H>(&mut self, debug_handler: H)
where
H: for<'a, 'b> FnMut(&'a str, DebugInfo<'b>) + 'static,
{
self.fe
.as_ref(&self.store)
.set_debug_handler(Some(Rc::new(RefCell::new(debug_handler))));
}
pub fn unset_debug_handler(&mut self) {
self.fe.as_ref(&self.store).set_debug_handler(None);
}
pub fn required_capabilities(&self) -> HashSet<String> {
required_capabilities_from_module(self._inner.module())
}
pub fn memory_pages(&mut self) -> usize {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, store) = fe_mut.data_and_store_mut();
env.memory(&store).size().0 as _
}
pub fn get_gas_left(&mut self) -> u64 {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, mut store) = fe_mut.data_and_store_mut();
env.get_gas_left(&mut store)
}
pub fn create_gas_report(&mut self) -> GasReport {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, mut store) = fe_mut.data_and_store_mut();
let state = env.with_gas_state(|gas_state| gas_state.clone());
let gas_left = env.get_gas_left(&mut store);
GasReport {
limit: state.gas_limit,
remaining: gas_left,
used_externally: state.externally_used_gas,
used_internally: state
.gas_limit
.saturating_sub(state.externally_used_gas)
.saturating_sub(gas_left),
}
}
pub fn is_storage_readonly(&mut self) -> bool {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, _) = fe_mut.data_and_store_mut();
env.is_storage_readonly()
}
pub fn set_storage_readonly(&mut self, new_value: bool) {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, _) = fe_mut.data_and_store_mut();
env.set_storage_readonly(new_value);
}
pub fn with_storage<F: FnOnce(&mut S) -> VmResult<T>, T>(&mut self, func: F) -> VmResult<T> {
self.fe
.as_ref(&self.store)
.with_storage_from_context::<F, T>(func)
}
pub fn with_querier<F: FnOnce(&mut Q) -> VmResult<T>, T>(&mut self, func: F) -> VmResult<T> {
self.fe
.as_ref(&self.store)
.with_querier_from_context::<F, T>(func)
}
pub(crate) fn allocate(&mut self, size: usize) -> VmResult<u32> {
let ret = self.call_function1("allocate", &[to_u32(size)?.into()])?;
let ptr = ref_to_u32(&ret)?;
if ptr == 0 {
return Err(CommunicationError::zero_address().into());
}
Ok(ptr)
}
pub(crate) fn deallocate(&mut self, ptr: u32) -> VmResult<()> {
self.call_function0("deallocate", &[ptr.into()])?;
Ok(())
}
pub(crate) fn read_memory(&mut self, region_ptr: u32, max_length: usize) -> VmResult<Vec<u8>> {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, store) = fe_mut.data_and_store_mut();
read_region(&env.memory(&store), region_ptr, max_length)
}
pub(crate) fn write_memory(&mut self, region_ptr: u32, data: &[u8]) -> VmResult<()> {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, store) = fe_mut.data_and_store_mut();
write_region(&env.memory(&store), region_ptr, data)?;
Ok(())
}
pub(crate) fn call_function0(&mut self, name: &str, args: &[Value]) -> VmResult<()> {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, mut store) = fe_mut.data_and_store_mut();
env.call_function0(&mut store, name, args)
}
pub(crate) fn call_function1(&mut self, name: &str, args: &[Value]) -> VmResult<Value> {
let mut fe_mut = self.fe.clone().into_mut(&mut self.store);
let (env, mut store) = fe_mut.data_and_store_mut();
env.call_function1(&mut store, name, args)
}
}
pub fn instance_from_module<A, S, Q>(
store: Store,
module: &Module,
backend: Backend<A, S, Q>,
gas_limit: u64,
extra_imports: Option<HashMap<&str, Exports>>,
) -> VmResult<Instance<A, S, Q>>
where
A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static,
{
Instance::from_module(store, module, backend, gas_limit, extra_imports, None)
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use super::*;
use crate::calls::{call_execute, call_instantiate, call_query};
use crate::testing::{
mock_backend, mock_env, mock_info, mock_instance, mock_instance_options,
mock_instance_with_balances, mock_instance_with_failing_api, mock_instance_with_gas_limit,
mock_instance_with_options, MockInstanceOptions,
};
use cosmwasm_std::{
coin, coins, from_json, AllBalanceResponse, BalanceResponse, BankQuery, Empty, QueryRequest,
};
use wasmer::FunctionEnvMut;
const KIB: usize = 1024;
const MIB: usize = 1024 * 1024;
const DEFAULT_QUERY_GAS_LIMIT: u64 = 300_000;
static CONTRACT: &[u8] = include_bytes!("../testdata/hackatom.wasm");
static CYBERPUNK: &[u8] = include_bytes!("../testdata/cyberpunk.wasm");
#[test]
fn from_code_works() {
let backend = mock_backend(&[]);
let (instance_options, memory_limit) = mock_instance_options();
let _instance =
Instance::from_code(CONTRACT, backend, instance_options, memory_limit).unwrap();
}
#[test]
fn set_debug_handler_and_unset_debug_handler_work() {
const LIMIT: u64 = 70_000_000_000;
let mut instance = mock_instance_with_gas_limit(CYBERPUNK, LIMIT);
let info = mock_info("creator", &coins(1000, "earth"));
call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, br#"{}"#)
.unwrap()
.unwrap();
let info = mock_info("caller", &[]);
call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, br#"{"debug":{}}"#)
.unwrap()
.unwrap();
let start = SystemTime::now();
instance.set_debug_handler(move |msg, info| {
let gas = info.gas_remaining;
let runtime = SystemTime::now().duration_since(start).unwrap().as_micros();
eprintln!("{msg} (gas: {gas}, runtime: {runtime}µs)");
});
let info = mock_info("caller", &[]);
call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, br#"{"debug":{}}"#)
.unwrap()
.unwrap();
eprintln!("Unsetting debug handler. From here nothing is printed anymore.");
instance.unset_debug_handler();
let info = mock_info("caller", &[]);
call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, br#"{"debug":{}}"#)
.unwrap()
.unwrap();
}
#[test]
fn required_capabilities_works() {
let backend = mock_backend(&[]);
let (instance_options, memory_limit) = mock_instance_options();
let instance =
Instance::from_code(CONTRACT, backend, instance_options, memory_limit).unwrap();
assert_eq!(instance.required_capabilities().len(), 0);
}
#[test]
fn required_capabilities_works_for_many_exports() {
let wasm = wat::parse_str(
r#"(module
(memory 3)
(export "memory" (memory 0))
(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 backend = mock_backend(&[]);
let (instance_options, memory_limit) = mock_instance_options();
let instance = Instance::from_code(&wasm, backend, instance_options, memory_limit).unwrap();
assert_eq!(instance.required_capabilities().len(), 3);
assert!(instance.required_capabilities().contains("nutrients"));
assert!(instance.required_capabilities().contains("sun"));
assert!(instance.required_capabilities().contains("water"));
}
#[test]
fn extra_imports_get_added() {
let (instance_options, memory_limit) = mock_instance_options();
let wasm = wat::parse_str(
r#"(module
(import "foo" "bar" (func $bar))
(memory 3)
(export "memory" (memory 0))
(func (export "main") (call $bar))
)"#,
)
.unwrap();
let backend = mock_backend(&[]);
let engine = make_compiling_engine(memory_limit);
let module = compile(&engine, &wasm).unwrap();
let mut store = Store::new(engine);
let called = Arc::new(AtomicBool::new(false));
#[derive(Clone)]
struct MyEnv {
called: Arc<AtomicBool>,
}
let fe = FunctionEnv::new(
&mut store,
MyEnv {
called: called.clone(),
},
);
let fun =
Function::new_typed_with_env(&mut store, &fe, move |fe_mut: FunctionEnvMut<MyEnv>| {
fe_mut.data().called.store(true, Ordering::Relaxed);
});
let mut exports = Exports::new();
exports.insert("bar", fun);
let mut extra_imports = HashMap::new();
extra_imports.insert("foo", exports);
let mut instance = Instance::from_module(
store,
&module,
backend,
instance_options.gas_limit,
Some(extra_imports),
None,
)
.unwrap();
instance.call_function0("main", &[]).unwrap();
assert!(called.load(Ordering::Relaxed));
}
#[test]
fn call_function0_works() {
let mut instance = mock_instance(CONTRACT, &[]);
instance
.call_function0("interface_version_8", &[])
.expect("error calling function");
}
#[test]
fn call_function1_works() {
let mut instance = mock_instance(CONTRACT, &[]);
let result = instance
.call_function1("allocate", &[0u32.into()])
.expect("error calling allocate");
assert_ne!(result.unwrap_i32(), 0);
let result = instance
.call_function1("allocate", &[1u32.into()])
.expect("error calling allocate");
assert_ne!(result.unwrap_i32(), 0);
let result = instance
.call_function1("allocate", &[33u32.into()])
.expect("error calling allocate");
assert_ne!(result.unwrap_i32(), 0);
}
#[test]
fn allocate_deallocate_works() {
let mut instance = mock_instance_with_options(
CONTRACT,
MockInstanceOptions {
memory_limit: Some(Size::mebi(500)),
..Default::default()
},
);
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() {
let error_message = "Api failed intentionally";
let mut instance = mock_instance_with_failing_api(CONTRACT, &[], error_message);
let init_result = call_instantiate::<_, _, _, Empty>(
&mut instance,
&mock_env(),
&mock_info("someone", &[]),
b"{\"verifier\": \"some1\", \"beneficiary\": \"some2\"}",
);
match init_result.unwrap_err() {
VmError::RuntimeErr { msg, .. } => assert!(msg.contains(error_message)),
err => panic!("Unexpected error: {err:?}"),
}
}
#[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 memory_pages_returns_min_memory_size_by_default() {
let wasm = wat::parse_str(
r#"(module
(memory 0)
(export "memory" (memory 0))
(type (func))
(func (type 0) nop)
(export "interface_version_8" (func 0))
(export "instantiate" (func 0))
(export "allocate" (func 0))
(export "deallocate" (func 0))
)"#,
)
.unwrap();
let mut instance = mock_instance(&wasm, &[]);
assert_eq!(instance.memory_pages(), 0);
let wasm = wat::parse_str(
r#"(module
(memory 3)
(export "memory" (memory 0))
(type (func))
(func (type 0) nop)
(export "interface_version_8" (func 0))
(export "instantiate" (func 0))
(export "allocate" (func 0))
(export "deallocate" (func 0))
)"#,
)
.unwrap();
let mut instance = mock_instance(&wasm, &[]);
assert_eq!(instance.memory_pages(), 3);
}
#[test]
fn memory_pages_grows_with_usage() {
let mut instance = mock_instance(CONTRACT, &[]);
assert_eq!(instance.memory_pages(), 17);
let region_ptr = instance.allocate(100 * 1024).expect("error allocating");
assert_eq!(instance.memory_pages(), 19);
instance.deallocate(region_ptr).expect("error deallocating");
assert_eq!(instance.memory_pages(), 19);
}
#[test]
fn get_gas_left_works() {
let mut instance = mock_instance_with_gas_limit(CONTRACT, 123321);
let orig_gas = instance.get_gas_left();
assert_eq!(orig_gas, 123321);
}
#[test]
fn create_gas_report_works() {
const LIMIT: u64 = 700_000_000;
let mut instance = mock_instance_with_gas_limit(CONTRACT, LIMIT);
let report1 = instance.create_gas_report();
assert_eq!(report1.used_externally, 0);
assert_eq!(report1.used_internally, 0);
assert_eq!(report1.limit, LIMIT);
assert_eq!(report1.remaining, LIMIT);
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}"}}"#);
call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
.unwrap()
.unwrap();
let report2 = instance.create_gas_report();
assert_eq!(report2.used_externally, 251);
assert_eq!(report2.used_internally, 21589990);
assert_eq!(report2.limit, LIMIT);
assert_eq!(
report2.remaining,
LIMIT - report2.used_externally - report2.used_internally
);
}
#[test]
fn set_storage_readonly_works() {
let mut instance = mock_instance(CONTRACT, &[]);
assert!(instance.is_storage_readonly());
instance.set_storage_readonly(false);
assert!(!instance.is_storage_readonly());
instance.set_storage_readonly(false);
assert!(!instance.is_storage_readonly());
instance.set_storage_readonly(true);
assert!(instance.is_storage_readonly());
}
#[test]
fn with_storage_works() {
let mut instance = mock_instance(CONTRACT, &[]);
instance
.with_storage(|store| {
assert!(store.get(b"foo").0.unwrap().is_none());
Ok(())
})
.unwrap();
instance
.with_storage(|store| {
store.set(b"foo", b"bar").0.unwrap();
Ok(())
})
.unwrap();
instance
.with_storage(|store| {
assert_eq!(store.get(b"foo").0.unwrap(), 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]
#[allow(deprecated)]
fn with_querier_works_readonly() {
let rich_addr = String::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
.query::<Empty>(
&QueryRequest::Bank(BankQuery::Balance {
address: rich_addr.clone(),
denom: "silver".to_string(),
}),
DEFAULT_QUERY_GAS_LIMIT,
)
.0
.unwrap()
.unwrap()
.unwrap();
let BalanceResponse { amount, .. } = from_json(response).unwrap();
assert_eq!(amount.amount.u128(), 8000);
assert_eq!(amount.denom, "silver");
Ok(())
})
.unwrap();
instance
.with_querier(|querier| {
let response = querier
.query::<Empty>(
&QueryRequest::Bank(BankQuery::AllBalances {
address: rich_addr.clone(),
}),
DEFAULT_QUERY_GAS_LIMIT,
)
.0
.unwrap()
.unwrap()
.unwrap();
let AllBalanceResponse { amount, .. } = from_json(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 = String::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
.query::<Empty>(
&QueryRequest::Bank(BankQuery::Balance {
address: rich_addr.clone(),
denom: "silver".to_string(),
}),
DEFAULT_QUERY_GAS_LIMIT,
)
.0
.unwrap()
.unwrap()
.unwrap();
let BalanceResponse { amount, .. } = from_json(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
.query::<Empty>(
&QueryRequest::Bank(BankQuery::Balance {
address: rich_addr.clone(),
denom: "silver".to_string(),
}),
DEFAULT_QUERY_GAS_LIMIT,
)
.0
.unwrap()
.unwrap()
.unwrap();
let BalanceResponse { amount, .. } = from_json(response).unwrap();
assert_eq!(amount.amount.u128(), 8000);
Ok(())
})
.unwrap();
}
#[test]
fn contract_deducts_gas_init() {
let mut instance = mock_instance(CONTRACT, &[]);
let orig_gas = instance.get_gas_left();
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}"}}"#);
call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
.unwrap()
.unwrap();
let init_used = orig_gas - instance.get_gas_left();
assert_eq!(init_used, 21590241);
}
#[test]
fn contract_deducts_gas_execute() {
let mut instance = mock_instance(CONTRACT, &[]);
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}"}}"#);
call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg.as_bytes())
.unwrap()
.unwrap();
let gas_before_execute = instance.get_gas_left();
let info = mock_info(&verifier, &coins(15, "earth"));
let msg = br#"{"release":{}}"#;
call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
.unwrap()
.unwrap();
let execute_used = gas_before_execute - instance.get_gas_left();
assert_eq!(execute_used, 26961511);
}
#[test]
fn contract_enforces_gas_limit() {
let mut instance = mock_instance_with_gas_limit(CONTRACT, 20_000);
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());
assert!(res.is_err());
}
#[test]
fn query_works_with_gas_metering() {
let mut instance = mock_instance(CONTRACT, &[]);
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()
.unwrap();
let gas_before_query = instance.get_gas_left();
let msg = br#"{"verifier":{}}"#;
let res = call_query(&mut instance, &mock_env(), msg).unwrap();
let answer = res.unwrap();
assert_eq!(
answer.as_slice(),
format!("{{\"verifier\":\"{verifier}\"}}").as_bytes()
);
let query_used = gas_before_query - instance.get_gas_left();
assert_eq!(query_used, 15938086);
}
}