From 0eb0e68e0b76540472ee74700f48f9e4c0bf5eba Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 21 Mar 2023 11:42:29 +0100 Subject: [PATCH] Add debug_with_gas functionality --- CHANGELOG.md | 2 ++ README.md | 6 ++++++ contracts/hackatom/src/contract.rs | 1 + packages/std/src/imports.rs | 13 +++++++++++++ packages/std/src/testing/mock.rs | 8 ++++++-- packages/std/src/traits.rs | 5 +++++ packages/vm/src/compatibility.rs | 4 +++- packages/vm/src/imports.rs | 16 ++++++++++++++++ packages/vm/src/instance.rs | 14 ++++++++++++-- 9 files changed, 64 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a94e7347..1cda537199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to ### Added +- Add `Api::debug_with_gas` for emitting debug messages with gas information + during development. - cosmwasm-vm: Add `Cache::save_wasm_unchecked` to save Wasm blobs that have been checked before. This is useful for state-sync where we know the Wasm code was checked when it was first uploaded. ([#1635]) diff --git a/README.md b/README.md index cb619b1dff..ee399aed43 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,12 @@ extern "C" { /// In production environments it is expected that those messages are discarded. fn debug(source_ptr: u32); + /// Writes a debug message (UFT-8 encoded) to the host for debugging purposes. + /// Include remaining gas information. + /// The host is free to log or process this in any way it considers appropriate. + /// In production environments it is expected that those messages are discarded. + fn debug_with_gas(source_ptr: u32); + /// Executes a query on the chain (import). Not to be confused with the /// query export, which queries the state of the contract. fn query_chain(request: u32) -> u32; diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 26ffed649a..0a2d7e30ee 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -21,6 +21,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { deps.api.debug("here we go 🚀"); + deps.api.debug_with_gas("here we go with gas 🚀"); deps.storage.set( CONFIG_KEY, diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index f7438a9df0..3de1a53189 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -71,6 +71,12 @@ extern "C" { /// In production environments it is expected that those messages are discarded. fn debug(source_ptr: u32); + /// Writes a debug message (UFT-8 encoded) to the host for debugging purposes. + /// Include remaining gas information. + /// The host is free to log or process this in any way it considers appropriate. + /// In production environments it is expected that those messages are discarded. + fn debug_with_gas(source_ptr: u32); + /// Executes a query on the chain (import). Not to be confused with the /// query export, which queries the state of the contract. fn query_chain(request: u32) -> u32; @@ -362,6 +368,13 @@ impl Api for ExternalApi { let region_ptr = region.as_ref() as *const Region as u32; unsafe { debug(region_ptr) }; } + + fn debug_with_gas(&self, message: &str) { + // keep the boxes in scope, so we free it at the end (don't cast to pointers same line as build_region) + let region = build_region(message.as_bytes()); + let region_ptr = region.as_ref() as *const Region as u32; + unsafe { debug_with_gas(region_ptr) }; + } } /// Takes a pointer to a Region and reads the data into a String. diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index d219236578..d9cd70fd26 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -92,9 +92,9 @@ const CANONICAL_LENGTH: usize = 90; // n = 45 const SHUFFLES_ENCODE: usize = 10; const SHUFFLES_DECODE: usize = 2; -// MockPrecompiles zero pads all human addresses to make them fit the canonical_length +// Mock zero pads all human addresses to make them fit the canonical_length // it trims off zeros for the reverse operation. -// not really smart, but allows us to see a difference (and consistent length for canonical adddresses) +// not really smart, but allows us to see a difference (and consistent length for canonical addresses) #[derive(Copy, Clone)] pub struct MockApi { /// Length of canonical addresses created with this API. Contracts should not make any assumptions @@ -228,6 +228,10 @@ impl Api for MockApi { fn debug(&self, message: &str) { println!("{}", message); } + + fn debug_with_gas(&self, message: &str) { + println!("{}, gas_left: NA", message); + } } /// Returns a default enviroment with height, time, chain_id, and contract address diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index e3ba067b6e..fcab3f94a1 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -141,6 +141,11 @@ pub trait Api { /// Emits a debugging message that is handled depending on the environment (typically printed to console or ignored). /// Those messages are not persisted to chain. fn debug(&self, message: &str); + + /// Emits a debugging message that is handled depending on the environment (typically printed to console or ignored). + /// Include gas left information. + /// Those messages are not persisted to chain. + fn debug_with_gas(&self, message: &str); } /// A short-hand alias for the two-level query result (1. accessing the contract, 2. executing query in the contract) diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index 428d51554f..9f3474e727 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -22,6 +22,7 @@ const SUPPORTED_IMPORTS: &[&str] = &[ "env.ed25519_verify", "env.ed25519_batch_verify", "env.debug", + "env.debug_with_gas", "env.query_chain", #[cfg(feature = "iterator")] "env.db_scan", @@ -798,6 +799,7 @@ mod tests { "env.addr_canonicalize", "env.addr_humanize", "env.debug", + "env.debug_with_gas", "env.query_chain", ]; let result = check_wasm_imports(&deserialize_wasm(&wasm).unwrap(), supported_imports); @@ -806,7 +808,7 @@ mod tests { println!("{}", msg); assert_eq!( msg, - r#"Wasm contract requires unsupported import: "env.foo". Required imports: {"env.bar", "env.foo", "env.spammyspam01", "env.spammyspam02", "env.spammyspam03", "env.spammyspam04", "env.spammyspam05", "env.spammyspam06", "env.spammyspam07", "env.spammyspam08", ... 2 more}. Available imports: ["env.db_read", "env.db_write", "env.db_remove", "env.addr_canonicalize", "env.addr_humanize", "env.debug", "env.query_chain"]."# + r#"Wasm contract requires unsupported import: "env.foo". Required imports: {"env.bar", "env.foo", "env.spammyspam01", "env.spammyspam02", "env.spammyspam03", "env.spammyspam04", "env.spammyspam05", "env.spammyspam06", "env.spammyspam07", "env.spammyspam08", ... 2 more}. Available imports: ["env.db_read", "env.db_write", "env.db_remove", "env.addr_canonicalize", "env.addr_humanize", "env.debug", "env.debug_with_gas", "env.query_chain"]."# ); } err => panic!("Unexpected error: {:?}", err), diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index bae11f403c..78a5fd333a 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -395,6 +395,21 @@ pub fn do_debug( Ok(()) } +/// Prints a debug message to console, including remaining gas information. +/// This does not charge gas, so debug printing should be disabled when used in a blockchain module. +pub fn do_debug_with_gas( + env: &Environment, + message_ptr: u32, +) -> VmResult<()> { + if env.print_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(); + println!("{}, gas_left: {}", msg, gas_remaining); + } + Ok(()) +} + /// Aborts the contract and shows the given error message pub fn do_abort( env: &Environment, @@ -562,6 +577,7 @@ mod tests { "ed25519_batch_verify" => Function::new_native(store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "debug" => Function::new_native(store, |_a: u32| {}), "abort" => Function::new_native(store, |_a: u32| {}), + "debug_with_gas" => Function::new_native(store, |_a: u32| {}), }, }; let instance = Box::from(WasmerInstance::new(&module, &import_obj).unwrap()); diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index dd33e90b2c..cb6495a56c 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -11,8 +11,8 @@ 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_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_db_write, do_debug, do_debug_with_gas, do_ed25519_batch_verify, do_ed25519_verify, + do_query_chain, do_secp256k1_recover_pubkey, do_secp256k1_verify, }; #[cfg(feature = "iterator")] use crate::imports::{do_db_next, do_db_scan}; @@ -181,6 +181,16 @@ where Function::new_native_with_env(store, env.clone(), do_debug), ); + // Allows the contract to emit debug logs that the host can either process or ignore. + // Include remaining gas information. + // This is never written to chain. + // Takes a pointer argument of a memory region that must contain an UTF-8 encoded string. + // Ownership of both input and output pointer is not transferred to the host. + env_imports.insert( + "debug_with_gas", + Function::new_native_with_env(store, env.clone(), do_debug_with_gas), + ); + // Aborts the contract execution with an error message provided by the contract. // Takes a pointer argument of a memory region that must contain an UTF-8 encoded string. // Ownership of both input and output pointer is not transferred to the host.