From f097f599afdc08a961d47727617817d44e5f4f18 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 20 Apr 2023 14:16:56 +0200 Subject: [PATCH 1/9] Add configurable debug_handler to Environment --- packages/vm/src/environment.rs | 15 ++++++++++++--- packages/vm/src/imports.rs | 5 +++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index 02270726ec..0ca0753f5b 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -1,6 +1,7 @@ //! Internal details to be used by instance.rs only use std::borrow::{Borrow, BorrowMut}; use std::ptr::NonNull; +use std::rc::Rc; use std::sync::{Arc, RwLock}; use wasmer::{HostEnvInitError, Instance as WasmerInstance, Memory, Val, WasmerEnv}; @@ -79,12 +80,14 @@ impl GasState { } } +pub type DebugHandlerFn = dyn for<'a> Fn(/* msg */ &'a str, /* gas remaining */ u64); + /// A environment that provides access to the ContextData. /// The environment is clonable but clones access the same underlying data. pub struct Environment { pub api: A, - pub print_debug: bool, pub gas_config: GasConfig, + pub debug_handler: Option>, data: Arc>>, } @@ -96,8 +99,8 @@ impl Clone for Environment { fn clone(&self) -> Self { Environment { api: self.api, - print_debug: self.print_debug, gas_config: self.gas_config.clone(), + debug_handler: self.debug_handler.clone(), data: self.data.clone(), } } @@ -113,8 +116,14 @@ impl Environment { pub fn new(api: A, gas_limit: u64, print_debug: bool) -> Self { Environment { api, - print_debug, gas_config: GasConfig::default(), + debug_handler: if print_debug { + Some(Rc::new(|msg: &str, _gas_remaining| { + println!("{msg}"); + })) + } else { + None + }, data: Arc::new(RwLock::new(ContextData::new(gas_limit))), } } diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index bae11f403c..cdc27bf1d9 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -387,10 +387,11 @@ pub fn do_debug( env: &Environment, message_ptr: u32, ) -> VmResult<()> { - if env.print_debug { + if let Some(debug_handler) = env.debug_handler.as_ref() { let message_data = read_region(&env.memory(), message_ptr, MAX_LENGTH_DEBUG)?; let msg = String::from_utf8_lossy(&message_data); - println!("{}", msg); + let gas_remaining = env.get_gas_left(); + (*debug_handler)(&msg, gas_remaining); } Ok(()) } From 2d365a8397cda21d7d2dba3fc90d64efea8f5302 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 21 Apr 2023 10:41:46 +0200 Subject: [PATCH 2/9] Add set_debug_handler/unset_debug_handler to Instance; fix internal cloning --- packages/vm/src/environment.rs | 34 +++++++++++++++++++++++----------- packages/vm/src/imports.rs | 4 ++-- packages/vm/src/instance.rs | 19 ++++++++++++++++++- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index 0ca0753f5b..7dba979c95 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -80,6 +80,12 @@ impl GasState { } } +// Unfortunately we cannot create an alias for the trait (https://github.com/rust-lang/rust/issues/41517). +// So we need to copy it in a few places. +// +// /- BEGIN TRAIT END TRAIT \ +// | | +// v v pub type DebugHandlerFn = dyn for<'a> Fn(/* msg */ &'a str, /* gas remaining */ u64); /// A environment that provides access to the ContextData. @@ -87,7 +93,6 @@ pub type DebugHandlerFn = dyn for<'a> Fn(/* msg */ &'a str, /* gas remaining */ pub struct Environment { pub api: A, pub gas_config: GasConfig, - pub debug_handler: Option>, data: Arc>>, } @@ -100,7 +105,6 @@ impl Clone for Environment { Environment { api: self.api, gas_config: self.gas_config.clone(), - debug_handler: self.debug_handler.clone(), data: self.data.clone(), } } @@ -113,21 +117,27 @@ impl WasmerEnv for Environment { } impl Environment { - pub fn new(api: A, gas_limit: u64, print_debug: bool) -> Self { + pub fn new(api: A, gas_limit: u64) -> Self { Environment { api, gas_config: GasConfig::default(), - debug_handler: if print_debug { - Some(Rc::new(|msg: &str, _gas_remaining| { - println!("{msg}"); - })) - } else { - None - }, data: Arc::new(RwLock::new(ContextData::new(gas_limit))), } } + pub fn set_debug_handler(&self, debug_handler: Option>) { + self.with_context_data_mut(|context_data| { + context_data.debug_handler = debug_handler; + }) + } + + pub fn debug_handler(&self) -> Option> { + self.with_context_data(|context_data| { + // This clone here requires us to wrap the function in Rc instead of Box + context_data.debug_handler.clone() + }) + } + fn with_context_data_mut(&self, callback: C) -> R where C: FnOnce(&mut ContextData) -> R, @@ -362,6 +372,7 @@ pub struct ContextData { storage_readonly: bool, call_depth: usize, querier: Option, + debug_handler: Option>, /// A non-owning link to the wasmer instance wasmer_instance: Option>, } @@ -374,6 +385,7 @@ impl ContextData { storage_readonly: true, call_depth: 0, querier: None, + debug_handler: None, wasmer_instance: None, } } @@ -438,7 +450,7 @@ mod tests { Environment, Box, ) { - let env = Environment::new(MockApi::default(), gas_limit, false); + let env = Environment::new(MockApi::default(), gas_limit); let module = compile(CONTRACT, TESTING_MEMORY_LIMIT, &[]).unwrap(); let store = module.store(); diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index cdc27bf1d9..fe394adf6e 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -387,7 +387,7 @@ pub fn do_debug( env: &Environment, message_ptr: u32, ) -> VmResult<()> { - if let Some(debug_handler) = env.debug_handler.as_ref() { + if let Some(debug_handler) = env.debug_handler() { let message_data = read_region(&env.memory(), message_ptr, MAX_LENGTH_DEBUG)?; let msg = String::from_utf8_lossy(&message_data); let gas_remaining = env.get_gas_left(); @@ -541,7 +541,7 @@ mod tests { Box, ) { let gas_limit = TESTING_GAS_LIMIT; - let env = Environment::new(api, gas_limit, false); + let env = Environment::new(api, gas_limit); let module = compile(CONTRACT, TESTING_MEMORY_LIMIT, &[]).unwrap(); let store = module.store(); diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index dd33e90b2c..e63378c4ae 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -1,5 +1,6 @@ use std::collections::{HashMap, HashSet}; use std::ptr::NonNull; +use std::rc::Rc; use std::sync::Mutex; use wasmer::{Exports, Function, ImportObject, Instance as WasmerInstance, Module, Val}; @@ -85,7 +86,12 @@ where ) -> VmResult { let store = module.store(); - let env = Environment::new(backend.api, gas_limit, print_debug); + let env = Environment::new(backend.api, gas_limit); + if print_debug { + env.set_debug_handler(Some(Rc::new(|msg: &str, _gas_remaining| { + println!("{msg}"); + }))) + } let mut import_obj = ImportObject::new(); let mut env_imports = Exports::new(); @@ -265,6 +271,17 @@ where } } + pub fn set_debug_handler(&mut self, debug_handler: H) + where + H: for<'a> Fn(/* msg */ &'a str, /* gas remaining */ u64) + 'static, + { + self.env.set_debug_handler(Some(Rc::new(debug_handler))); + } + + pub fn unset_debug_handler(&mut self) { + self.env.set_debug_handler(None); + } + /// Returns the features required by this contract. /// /// This is not needed for production because we can do static analysis From 6affd2517c0dd5d4138b31f61111cede0088a389 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 24 Apr 2023 18:09:41 +0200 Subject: [PATCH 3/9] Print debug to STDERR by default --- CHANGELOG.md | 3 +++ packages/vm/src/instance.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08dbcc5e15..42b8872fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,11 +25,14 @@ and this project adheres to - cosmwasm-vm: Add target (triple + CPU features) into the module cache directory to avoid using modules compiled for a different system. Bump `MODULE_SERIALIZATION_VERSION` to "v5". ([#1664]) +- cosmwasm-vm: When enabling `print_debug` the debug logs are now printed to + STDERR instead of STDOUT by default ([#1667]). [#1511]: https://github.com/CosmWasm/cosmwasm/issues/1511 [#1629]: https://github.com/CosmWasm/cosmwasm/pull/1629 [#1631]: https://github.com/CosmWasm/cosmwasm/pull/1631 [#1664]: https://github.com/CosmWasm/cosmwasm/pull/1664 +[#1667]: https://github.com/CosmWasm/cosmwasm/pull/1667 ## [1.2.4] - 2023-04-17 diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index e63378c4ae..b82d1a6efb 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -89,7 +89,7 @@ where let env = Environment::new(backend.api, gas_limit); if print_debug { env.set_debug_handler(Some(Rc::new(|msg: &str, _gas_remaining| { - println!("{msg}"); + eprintln!("{msg}"); }))) } From 60feac8115c0b1abdec1da52e306ae5a02082e7f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 21 Apr 2023 13:00:55 +0200 Subject: [PATCH 4/9] Add debug example to cyberpunk --- contracts/cyberpunk/schema/cyberpunk.json | 14 +++++ contracts/cyberpunk/schema/raw/execute.json | 14 +++++ contracts/cyberpunk/src/contract.rs | 61 ++++++++++++++++++++- contracts/cyberpunk/src/msg.rs | 2 + contracts/cyberpunk/tests/integration.rs | 23 ++++++++ 5 files changed, 113 insertions(+), 1 deletion(-) diff --git a/contracts/cyberpunk/schema/cyberpunk.json b/contracts/cyberpunk/schema/cyberpunk.json index 1584ac900e..dcce96d609 100644 --- a/contracts/cyberpunk/schema/cyberpunk.json +++ b/contracts/cyberpunk/schema/cyberpunk.json @@ -165,6 +165,20 @@ } }, "additionalProperties": false + }, + { + "description": "Does a bit of work and calls debug", + "type": "object", + "required": [ + "debug" + ], + "properties": { + "debug": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, diff --git a/contracts/cyberpunk/schema/raw/execute.json b/contracts/cyberpunk/schema/raw/execute.json index e096feab6f..8ea521b97f 100644 --- a/contracts/cyberpunk/schema/raw/execute.json +++ b/contracts/cyberpunk/schema/raw/execute.json @@ -155,6 +155,20 @@ } }, "additionalProperties": false + }, + { + "description": "Does a bit of work and calls debug", + "type": "object", + "required": [ + "debug" + ], + "properties": { + "debug": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] } diff --git a/contracts/cyberpunk/src/contract.rs b/contracts/cyberpunk/src/contract.rs index f5f1b98655..55acddaf6b 100644 --- a/contracts/cyberpunk/src/contract.rs +++ b/contracts/cyberpunk/src/contract.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - entry_point, to_binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Response, + entry_point, to_binary, Api, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Response, StdError, StdResult, WasmMsg, }; @@ -38,6 +38,7 @@ pub fn execute( Panic {} => execute_panic(), Unreachable {} => execute_unreachable(), MirrorEnv {} => execute_mirror_env(env), + Debug {} => execute_debug(deps.api), } } @@ -150,6 +151,33 @@ fn execute_mirror_env(env: Env) -> Result { Ok(Response::new().set_data(to_binary(&env)?)) } +fn execute_debug(api: &dyn Api) -> Result { + api.debug("Hey, ho – let's go"); + + let password = b"password"; + let salt = b"othersalt"; + + for r in 1..10 { + api.debug(&format!("Round {r} starting")); + let config = argon2::Config { + variant: argon2::Variant::Argon2i, + version: argon2::Version::Version13, + mem_cost: 32, + time_cost: r, + lanes: 4, + thread_mode: argon2::ThreadMode::Sequential, + secret: &[], + ad: &[], + hash_length: 32, + }; + let _hash = argon2::hash_encoded(password, salt, &config).unwrap(); + api.debug(&format!("Round {r} done")); + } + + api.debug("Work completed, bye"); + Ok(Response::default()) +} + #[entry_point] pub fn query(_deps: Deps, env: Env, msg: QueryMsg) -> StdResult { use QueryMsg::*; @@ -162,3 +190,34 @@ pub fn query(_deps: Deps, env: Env, msg: QueryMsg) -> StdResult { fn query_mirror_env(env: Env) -> Env { env } + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, + }; + use cosmwasm_std::OwnedDeps; + + fn setup() -> OwnedDeps { + let mut deps = mock_dependencies(); + let msg = Empty {}; + let info = mock_info("creator", &[]); + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + deps + } + + #[test] + fn instantiate_works() { + setup(); + } + + #[test] + fn debug_works() { + let mut deps = setup(); + + let msg = ExecuteMsg::Debug {}; + execute(deps.as_mut(), mock_env(), mock_info("caller", &[]), msg).unwrap(); + } +} diff --git a/contracts/cyberpunk/src/msg.rs b/contracts/cyberpunk/src/msg.rs index 33d73a51b1..7abb882009 100644 --- a/contracts/cyberpunk/src/msg.rs +++ b/contracts/cyberpunk/src/msg.rs @@ -28,6 +28,8 @@ pub enum ExecuteMsg { Unreachable {}, /// Returns the env for testing MirrorEnv {}, + /// Does a bit of work and calls debug + Debug {}, } #[cw_serde] diff --git a/contracts/cyberpunk/tests/integration.rs b/contracts/cyberpunk/tests/integration.rs index 3de369d239..c7f04693e0 100644 --- a/contracts/cyberpunk/tests/integration.rs +++ b/contracts/cyberpunk/tests/integration.rs @@ -21,6 +21,7 @@ use cosmwasm_std::{from_binary, Empty, Env, Response}; use cosmwasm_vm::testing::{ execute, instantiate, mock_env, mock_info, mock_instance, mock_instance_with_gas_limit, query, }; +use std::time::SystemTime; use cyberpunk::msg::{ExecuteMsg, QueryMsg}; @@ -53,6 +54,28 @@ fn execute_argon2() { assert!(gas_used < expected * 120 / 100, "Gas used: {}", gas_used); } +// Test with +// cargo integration-test debug_works -- --nocapture +#[test] +fn debug_works() { + let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000_000); + + let _res: Response = + instantiate(&mut deps, mock_env(), mock_info("admin", &[]), Empty {}).unwrap(); + + let msg = ExecuteMsg::Debug {}; + let _res: Response = execute(&mut deps, mock_env(), mock_info("caller", &[]), msg).unwrap(); + + let start = SystemTime::now(); + deps.set_debug_handler(move |msg, gas_remaining| { + let runtime = SystemTime::now().duration_since(start).unwrap().as_micros(); + eprintln!("{msg} (gas: {gas_remaining}, runtime: {runtime}µs)"); + }); + + let msg = ExecuteMsg::Debug {}; + let _res: Response = execute(&mut deps, mock_env(), mock_info("caller", &[]), msg).unwrap(); +} + #[test] fn test_env() { let mut deps = mock_instance(WASM, &[]); From dd9bb4765b639927c6c2bb363233de8d0d7e17dd Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 24 Apr 2023 18:11:55 +0200 Subject: [PATCH 5/9] Create extensible DebugInfo struct --- Cargo.lock | 1 + contracts/burner/Cargo.lock | 1 + contracts/crypto-verify/Cargo.lock | 1 + contracts/cyberpunk/Cargo.lock | 1 + contracts/cyberpunk/tests/integration.rs | 5 +++-- contracts/floaty/Cargo.lock | 1 + contracts/hackatom/Cargo.lock | 1 + contracts/ibc-reflect-send/Cargo.lock | 1 + contracts/ibc-reflect/Cargo.lock | 1 + contracts/queue/Cargo.lock | 1 + contracts/reflect/Cargo.lock | 1 + contracts/staking/Cargo.lock | 1 + contracts/virus/Cargo.lock | 1 + packages/vm/Cargo.toml | 1 + packages/vm/src/environment.rs | 26 ++++++++++++++++++++---- packages/vm/src/imports.rs | 11 ++++++++-- packages/vm/src/instance.rs | 4 +++- packages/vm/src/lib.rs | 2 +- 18 files changed, 51 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f327920bd0..908cd9a779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,6 +387,7 @@ dependencies = [ "cosmwasm-std", "crc32fast", "criterion", + "derivative", "enumset", "glob", "hex", diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index 34e6a44f5c..7c79465c26 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -238,6 +238,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index cb0b85462e..1d15e1a799 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -241,6 +241,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/cyberpunk/Cargo.lock b/contracts/cyberpunk/Cargo.lock index 670c06f60e..81b6eb4876 100644 --- a/contracts/cyberpunk/Cargo.lock +++ b/contracts/cyberpunk/Cargo.lock @@ -264,6 +264,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/cyberpunk/tests/integration.rs b/contracts/cyberpunk/tests/integration.rs index c7f04693e0..aa2f8cf894 100644 --- a/contracts/cyberpunk/tests/integration.rs +++ b/contracts/cyberpunk/tests/integration.rs @@ -67,9 +67,10 @@ fn debug_works() { let _res: Response = execute(&mut deps, mock_env(), mock_info("caller", &[]), msg).unwrap(); let start = SystemTime::now(); - deps.set_debug_handler(move |msg, gas_remaining| { + deps.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_remaining}, runtime: {runtime}µs)"); + eprintln!("{msg} (gas: {gas}, runtime: {runtime}µs)"); }); let msg = ExecuteMsg::Debug {}; diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index adb80440c3..9a99000538 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index 6a6f8b1f75..452f2fa57b 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index 635c031955..da048e234c 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index 5f7eaa7c25..20148d0bc9 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index 9331333d93..7687cbe7e0 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -227,6 +227,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index cfe2547ef8..45197abab8 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index cd8b5c088e..ef63f9f593 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/contracts/virus/Cargo.lock b/contracts/virus/Cargo.lock index 9a0324f83c..577a77cba6 100644 --- a/contracts/virus/Cargo.lock +++ b/contracts/virus/Cargo.lock @@ -227,6 +227,7 @@ dependencies = [ "cosmwasm-crypto", "cosmwasm-std", "crc32fast", + "derivative", "enumset", "hex", "loupe", diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml index 74a5ac1a00..331a04eb0d 100644 --- a/packages/vm/Cargo.toml +++ b/packages/vm/Cargo.toml @@ -40,6 +40,7 @@ crc32fast = "1.3.2" # Uses the path when built locally; uses the given version from crates.io when published cosmwasm-std = { path = "../std", version = "1.2.4", default-features = false } cosmwasm-crypto = { path = "../crypto", version = "1.2.4" } +derivative = "2" hex = "0.4" parity-wasm = "0.45" schemars = "0.8.3" diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index 7dba979c95..e9be792609 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -1,9 +1,11 @@ //! Internal details to be used by instance.rs only use std::borrow::{Borrow, BorrowMut}; +use std::marker::PhantomData; use std::ptr::NonNull; use std::rc::Rc; use std::sync::{Arc, RwLock}; +use derivative::Derivative; use wasmer::{HostEnvInitError, Instance as WasmerInstance, Memory, Val, WasmerEnv}; use wasmer_middlewares::metering::{get_remaining_points, set_remaining_points, MeteringPoints}; @@ -80,13 +82,29 @@ impl GasState { } } +/// Additional environmental information in a debug call. +/// +/// The currently unused lifetime parameter 'a allows accessing referenced data in the debug implementation +/// without cloning it. +#[derive(Derivative)] +#[derivative(Debug)] +#[non_exhaustive] +pub struct DebugInfo<'a> { + pub gas_remaining: u64, + // This field is just to allow us to add the unused lifetime parameter. It can be removed + // at any time. + #[doc(hidden)] + #[derivative(Debug = "ignore")] + pub(crate) __lifetime: PhantomData<&'a ()>, +} + // Unfortunately we cannot create an alias for the trait (https://github.com/rust-lang/rust/issues/41517). // So we need to copy it in a few places. // -// /- BEGIN TRAIT END TRAIT \ -// | | -// v v -pub type DebugHandlerFn = dyn for<'a> Fn(/* msg */ &'a str, /* gas remaining */ u64); +// /- BEGIN TRAIT END TRAIT \ +// | | +// v v +pub type DebugHandlerFn = dyn for<'a> Fn(/* msg */ &'a str, DebugInfo<'a>); /// A environment that provides access to the ContextData. /// The environment is clonable but clones access the same underlying data. diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index fe394adf6e..91e35f59b8 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -1,6 +1,7 @@ //! Import implementations use std::cmp::max; +use std::marker::PhantomData; use cosmwasm_crypto::{ ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify, CryptoError, @@ -14,7 +15,7 @@ use cosmwasm_std::Order; use crate::backend::{BackendApi, BackendError, Querier, Storage}; use crate::conversion::{ref_to_u32, to_u32}; -use crate::environment::{process_gas_info, Environment}; +use crate::environment::{process_gas_info, DebugInfo, Environment}; use crate::errors::{CommunicationError, VmError, VmResult}; #[cfg(feature = "iterator")] use crate::memory::maybe_read_region; @@ -391,7 +392,13 @@ pub fn do_debug( let message_data = read_region(&env.memory(), message_ptr, MAX_LENGTH_DEBUG)?; let msg = String::from_utf8_lossy(&message_data); let gas_remaining = env.get_gas_left(); - (*debug_handler)(&msg, gas_remaining); + (*debug_handler)( + &msg, + DebugInfo { + gas_remaining, + __lifetime: PhantomData::default(), + }, + ); } Ok(()) } diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index b82d1a6efb..b01ee51ae4 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -21,6 +21,8 @@ use crate::memory::{read_region, write_region}; use crate::size::Size; use crate::wasm_backend::compile; +pub use crate::environment::DebugInfo; // Re-exported as public via to be usable for set_debug_handler + #[derive(Copy, Clone, Debug)] pub struct GasReport { /// The original limit the instance was created with @@ -273,7 +275,7 @@ where pub fn set_debug_handler(&mut self, debug_handler: H) where - H: for<'a> Fn(/* msg */ &'a str, /* gas remaining */ u64) + 'static, + H: for<'a> Fn(/* msg */ &'a str, DebugInfo<'a>) + 'static, { self.env.set_debug_handler(Some(Rc::new(debug_handler))); } diff --git a/packages/vm/src/lib.rs b/packages/vm/src/lib.rs index 19bb888f82..1e5ba03837 100644 --- a/packages/vm/src/lib.rs +++ b/packages/vm/src/lib.rs @@ -45,7 +45,7 @@ pub use crate::errors::{ CommunicationError, CommunicationResult, RegionValidationError, RegionValidationResult, VmError, VmResult, }; -pub use crate::instance::{GasReport, Instance, InstanceOptions}; +pub use crate::instance::{DebugInfo, GasReport, Instance, InstanceOptions}; pub use crate::serde::{from_slice, to_vec}; pub use crate::size::Size; From 004e5d6e85c8b4284fb6b12d28bc114032ed03b1 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 21 Apr 2023 13:30:37 +0200 Subject: [PATCH 6/9] Test unset_debug_handler in integration test --- contracts/cyberpunk/tests/integration.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contracts/cyberpunk/tests/integration.rs b/contracts/cyberpunk/tests/integration.rs index aa2f8cf894..c05e061dea 100644 --- a/contracts/cyberpunk/tests/integration.rs +++ b/contracts/cyberpunk/tests/integration.rs @@ -75,6 +75,12 @@ fn debug_works() { let msg = ExecuteMsg::Debug {}; let _res: Response = execute(&mut deps, mock_env(), mock_info("caller", &[]), msg).unwrap(); + + eprintln!("Unsetting debug handler. From here nothing is printed anymore."); + deps.unset_debug_handler(); + + let msg = ExecuteMsg::Debug {}; + let _res: Response = execute(&mut deps, mock_env(), mock_info("caller", &[]), msg).unwrap(); } #[test] From a15c725cb8456dbb5dee6e85f8e268c01283d0dc Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 25 Apr 2023 10:32:13 +0200 Subject: [PATCH 7/9] Rebuild cyberpunk testing contract --- packages/vm/testdata/cyberpunk.wasm | Bin 170650 -> 172461 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/vm/testdata/cyberpunk.wasm b/packages/vm/testdata/cyberpunk.wasm index 96737a33880c757c4f74550ac4627474067a83a2..68aa44257f0d1ffeea63324d3af51528474f473d 100644 GIT binary patch delta 52267 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 From b31170cc8cbb2d582342a59df0c58aac797a0798 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 25 Apr 2023 10:33:13 +0200 Subject: [PATCH 8/9] Test set_debug_handler/unset_debug_handler --- packages/vm/src/instance.rs | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index b01ee51ae4..92679422a2 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -405,6 +405,7 @@ where mod tests { use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; + use std::time::SystemTime; use super::*; use crate::backend::Storage; @@ -424,6 +425,44 @@ mod tests { 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 set_debug_handler_and_unset_debug_handler_work() { + const LIMIT: u64 = 70_000_000_000_000; + let mut instance = mock_instance_with_gas_limit(CYBERPUNK, LIMIT); + + // init contract + 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() { From f5c9843b917935ee7f0db0635c0c18ede9940ce1 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 25 Apr 2023 10:39:53 +0200 Subject: [PATCH 9/9] Add CHANGELOG entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42b8872fb6..eff24ae6e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ and this project adheres to `MODULE_SERIALIZATION_VERSION` to "v5". ([#1664]) - cosmwasm-vm: When enabling `print_debug` the debug logs are now printed to STDERR instead of STDOUT by default ([#1667]). +- cosmwasm-vm: Add `Instance::set_debug_handler`/`unset_debug_handler` to allow + customizing the handling of debug messages emitted by the contract ([#1667]). [#1511]: https://github.com/CosmWasm/cosmwasm/issues/1511 [#1629]: https://github.com/CosmWasm/cosmwasm/pull/1629