Skip to content

Commit

Permalink
Add debug_with_gas functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
maurolacy committed Mar 21, 2023
1 parent 50079b4 commit ad61d0c
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to

## [Unreleased]

- Add `Api::debug_with_gas` for emitting debug messages with gas information
during development.

## [1.2.2] - 2023-03-08

### Added
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions contracts/hackatom/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub fn instantiate(
msg: InstantiateMsg,
) -> Result<Response, HackError> {
deps.api.debug("here we go 🚀");
deps.api.debug_with_gas("here we go with gas 🚀");

deps.storage.set(
CONFIG_KEY,
Expand Down
13 changes: 13 additions & 0 deletions packages/std/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 6 additions & 2 deletions packages/std/src/testing/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions packages/std/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion packages/vm/src/compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -587,6 +588,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);
Expand All @@ -595,7 +597,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),
Expand Down
16 changes: 16 additions & 0 deletions packages/vm/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,21 @@ pub fn do_debug<A: BackendApi, S: Storage, Q: Querier>(
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<A: BackendApi, S: Storage, Q: Querier>(
env: &Environment<A, S, Q>,
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<A: BackendApi, S: Storage, Q: Querier>(
env: &Environment<A, S, Q>,
Expand Down Expand Up @@ -561,6 +576,7 @@ mod tests {
"ed25519_verify" => Function::new_native(store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
"ed25519_batch_verify" => Function::new_native(store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
"debug" => 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());
Expand Down
14 changes: 12 additions & 2 deletions packages/vm/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit ad61d0c

Please sign in to comment.