diff --git a/CHANGELOG.md b/CHANGELOG.md index c65815f979..0dc669fc17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ - Remove export `check_api_compatibility`. The VM will take care of calling it. - Let `check_api_compatibility` check imports by fully qualified identifier `.`. +- Make gas limit immutable in `cosmwasm_vm::instance::Instance`. It is passed + once at construction time and cannot publicly be manipulated anymore. ## 0.6 diff --git a/lib/vm/src/cache.rs b/lib/vm/src/cache.rs index ec77cae363..056249ff4b 100644 --- a/lib/vm/src/cache.rs +++ b/lib/vm/src/cache.rs @@ -73,7 +73,12 @@ where } /// get instance returns a wasmer Instance tied to a previously saved wasm - pub fn get_instance(&mut self, id: &[u8], deps: Extern) -> Result, Error> { + pub fn get_instance( + &mut self, + id: &[u8], + deps: Extern, + gas_limit: u64, + ) -> Result, Error> { let hash = WasmHash::generate(&id); // pop from lru cache if present @@ -88,12 +93,12 @@ where // try from the module cache let res = self.modules.load_with_backend(hash, backend()); if let Ok(module) = res { - return Instance::from_module(&module, deps); + return Instance::from_module(&module, deps, gas_limit); } // fall back to wasm cache (and re-compiling) - this is for backends that don't support serialization let wasm = self.load_wasm(id)?; - Instance::from_code(&wasm, deps) + Instance::from_code(&wasm, deps, gas_limit) } pub fn store_instance(&mut self, id: &[u8], instance: Instance) -> Option> { @@ -154,7 +159,7 @@ mod test { let mut cache = unsafe { CosmCache::new(tmp_dir.path(), 10).unwrap() }; let id = cache.save_wasm(CONTRACT_0_7).unwrap(); let deps = dependencies(20); - let mut instance = cache.get_instance(&id, deps).unwrap(); + let mut instance = cache.get_instance(&id, deps, 200_000).unwrap(); // run contract let params = mock_params(&instance.api, "creator", &coin("1000", "earth"), &[]); @@ -172,7 +177,7 @@ mod test { let mut cache = unsafe { CosmCache::new(tmp_dir.path(), 10).unwrap() }; let id = cache.save_wasm(CONTRACT_0_7).unwrap(); let deps = dependencies(20); - let mut instance = cache.get_instance(&id, deps).unwrap(); + let mut instance = cache.get_instance(&id, deps, 200_000).unwrap(); // init contract let params = mock_params(&instance.api, "creator", &coin("1000", "earth"), &[]); @@ -205,7 +210,7 @@ mod test { let deps2 = dependencies(20); // init instance 1 - let mut instance = cache.get_instance(&id, deps1).unwrap(); + let mut instance = cache.get_instance(&id, deps1, 400_000).unwrap(); let params = mock_params(&instance.api, "owner1", &coin("1000", "earth"), &[]); let msg = r#"{"verifier": "sue", "beneficiary": "mary"}"#.as_bytes(); let res = call_init(&mut instance, ¶ms, msg).unwrap(); @@ -214,7 +219,7 @@ mod test { let deps1 = cache.store_instance(&id, instance).unwrap(); // init instance 2 - let mut instance = cache.get_instance(&id, deps2).unwrap(); + let mut instance = cache.get_instance(&id, deps2, 400_000).unwrap(); let params = mock_params(&instance.api, "owner2", &coin("500", "earth"), &[]); let msg = r#"{"verifier": "bob", "beneficiary": "john"}"#.as_bytes(); let res = call_init(&mut instance, ¶ms, msg).unwrap(); @@ -223,7 +228,7 @@ mod test { let deps2 = cache.store_instance(&id, instance).unwrap(); // run contract 2 - just sanity check - results validate in contract unit tests - let mut instance = cache.get_instance(&id, deps2).unwrap(); + let mut instance = cache.get_instance(&id, deps2, 400_000).unwrap(); let params = mock_params( &instance.api, "bob", @@ -237,7 +242,7 @@ mod test { let _ = cache.store_instance(&id, instance).unwrap(); // run contract 1 - just sanity check - results validate in contract unit tests - let mut instance = cache.get_instance(&id, deps1).unwrap(); + let mut instance = cache.get_instance(&id, deps1, 400_000).unwrap(); let params = mock_params( &instance.api, "sue", diff --git a/lib/vm/src/instance.rs b/lib/vm/src/instance.rs index ca325aaa88..0b23ef577c 100644 --- a/lib/vm/src/instance.rs +++ b/lib/vm/src/instance.rs @@ -30,12 +30,12 @@ where S: Storage + 'static, A: Api + 'static, { - pub fn from_code(code: &[u8], deps: Extern) -> Result { + pub fn from_code(code: &[u8], deps: Extern, gas_limit: u64) -> Result { let module = compile(code)?; - Instance::from_module(&module, deps) + Instance::from_module(&module, deps, gas_limit) } - pub fn from_module(module: &Module, deps: Extern) -> Result { + pub fn from_module(module: &Module, deps: Extern, gas_limit: u64) -> Result { // copy this so it can be moved into the closures, without pulling in deps let api = deps.api; let import_obj = imports! { @@ -70,7 +70,8 @@ where }), }, }; - let instance = module.instantiate(&import_obj).context(WasmerErr {})?; + let mut instance = module.instantiate(&import_obj).context(WasmerErr {})?; + set_gas(&mut instance, gas_limit); let res = Instance { instance, api, @@ -84,10 +85,6 @@ where get_gas(&self.instance) } - pub fn set_gas(&mut self, gas: u64) { - set_gas(&mut self.instance, gas) - } - pub fn with_storage(&self, func: F) { with_storage_from_context(self.instance.context(), func) } @@ -134,7 +131,7 @@ where #[cfg(test)] mod test { use crate::calls::{call_handle, call_init, call_query}; - use crate::testing::mock_instance; + use crate::testing::{mock_instance, mock_instance_with_gas_limit}; use cosmwasm::mock::mock_params; use cosmwasm::types::coin; @@ -142,24 +139,18 @@ mod test { #[test] #[cfg(feature = "default-cranelift")] - fn get_and_set_gas_cranelift_noop() { - let mut instance = mock_instance(&CONTRACT_0_7); + 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!(orig_gas > 1000); - // this is a no-op - instance.set_gas(123456); - assert_eq!(orig_gas, instance.get_gas()); + assert_eq!(orig_gas, 1_000_000); } #[test] #[cfg(feature = "default-singlepass")] - fn get_and_set_gas_singlepass_works() { - let mut instance = mock_instance(&CONTRACT_0_7); + 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!(orig_gas > 1000000); - // it is updated to whatever we set it with - instance.set_gas(123456); - assert_eq!(123456, instance.get_gas()); + assert_eq!(orig_gas, 123321); } #[test] @@ -174,8 +165,7 @@ mod test { #[cfg(feature = "default-singlepass")] fn contract_deducts_gas() { let mut instance = mock_instance(&CONTRACT_0_7); - let orig_gas = 200_000; - instance.set_gas(orig_gas); + let orig_gas = instance.get_gas(); // init contract let params = mock_params(&instance.api, "creator", &coin("1000", "earth"), &[]); @@ -189,7 +179,7 @@ mod test { assert_eq!(init_used, 70533); // run contract - just sanity check - results validate in contract unit tests - instance.set_gas(orig_gas); + let gas_before_handle = instance.get_gas(); let params = mock_params( &instance.api, "verifies", @@ -201,7 +191,7 @@ mod test { let msgs = res.unwrap().messages; assert_eq!(1, msgs.len()); - let handle_used = orig_gas - instance.get_gas(); + let handle_used = gas_before_handle - instance.get_gas(); println!("handle used: {}", handle_used); assert_eq!(handle_used, 115423); } @@ -209,9 +199,7 @@ mod test { #[test] #[cfg(feature = "default-singlepass")] fn contract_enforces_gas_limit() { - let mut instance = mock_instance(&CONTRACT_0_7); - let orig_gas = 20_000; - instance.set_gas(orig_gas); + let mut instance = mock_instance_with_gas_limit(&CONTRACT_0_7, 20_000); // init contract let params = mock_params(&instance.api, "creator", &coin("1000", "earth"), &[]); @@ -224,8 +212,6 @@ mod test { #[cfg(feature = "default-singlepass")] fn query_works_with_metering() { let mut instance = mock_instance(&CONTRACT_0_7); - let orig_gas = 200_000; - instance.set_gas(orig_gas); // init contract let params = mock_params(&instance.api, "creator", &coin("1000", "earth"), &[]); @@ -233,14 +219,14 @@ mod test { let _res = call_init(&mut instance, ¶ms, msg).unwrap().unwrap(); // run contract - just sanity check - results validate in contract unit tests - instance.set_gas(orig_gas); + let gas_before_query = instance.get_gas(); // we need to encode the key in base64 let msg = r#"{"verifier":{}}"#.as_bytes(); let res = call_query(&mut instance, msg).unwrap(); let answer = res.unwrap(); assert_eq!(answer, "verifies".as_bytes()); - let query_used = orig_gas - instance.get_gas(); + let query_used = gas_before_query - instance.get_gas(); println!("query used: {}", query_used); assert_eq!(query_used, 60315); } diff --git a/lib/vm/src/testing.rs b/lib/vm/src/testing.rs index 201d27f429..a6ebccaae1 100644 --- a/lib/vm/src/testing.rs +++ b/lib/vm/src/testing.rs @@ -14,10 +14,17 @@ use crate::calls::{call_handle, call_init, call_query}; use crate::compatability::check_api_compatibility; use crate::instance::Instance; +/// Gas limit for testing +static DEFAULT_GAS_LIMIT: u64 = 500_000; + pub fn mock_instance(wasm: &[u8]) -> Instance { + mock_instance_with_gas_limit(wasm, DEFAULT_GAS_LIMIT) +} + +pub fn mock_instance_with_gas_limit(wasm: &[u8], gas_limit: u64) -> Instance { check_api_compatibility(wasm).unwrap(); let deps = dependencies(20); - Instance::from_code(wasm, deps).unwrap() + Instance::from_code(wasm, deps, gas_limit).unwrap() } // init mimicks the call signature of the smart contracts.