Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
contracts: Add host function tracing (#13648)
Browse files Browse the repository at this point in the history
  • Loading branch information
pgherveou authored and gpestana committed Apr 20, 2023
1 parent f0bcd0d commit f94fce0
Show file tree
Hide file tree
Showing 4 changed files with 959 additions and 908 deletions.
12 changes: 12 additions & 0 deletions frame/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ to `error` in order to prevent them from spamming the console.
`--dev`: Use a dev chain spec
`--tmp`: Use temporary storage for chain data (the chain state is deleted on exit)

## Host function tracing

For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments, and what the result was.

In order to see these messages on the node console, the log level for the `runtime::contracts::strace` target needs to be raised to the `trace` level.

Example:

```bash
cargo run --release -- --dev -lerror,runtime::contracts::strace=trace,runtime::contracts=debug
```

## Unstable Interfaces

Driven by the desire to have an iterative approach in developing new contract interfaces
Expand Down
36 changes: 35 additions & 1 deletion frame/contracts/proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
let impls = def.host_funcs.iter().map(|f| {
// skip the context and memory argument
let params = f.item.sig.inputs.iter().skip(2);

let (module, name, body, wasm_output, output) = (
f.module(),
&f.name,
Expand All @@ -606,6 +607,39 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
let is_stable = f.is_stable;
let not_deprecated = f.not_deprecated;

// wrapped host function body call with host function traces
// see https://github.com/paritytech/substrate/tree/master/frame/contracts#host-function-tracing
let wrapped_body_with_trace = {
let trace_fmt_args = params.clone().filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(p) => {
match *p.pat.clone() {
syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()),
_ => None,
}
},
});

let params_fmt_str = trace_fmt_args.clone().map(|s| format!("{s}: {{:?}}")).collect::<Vec<_>>().join(", ");
let trace_fmt_str = format!("{}::{}({}) = {{:?}}\n", module, name, params_fmt_str);

quote! {
if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) {
let result = #body;
{
use sp_std::fmt::Write;
let mut w = sp_std::Writer::default();
let _ = core::write!(&mut w, #trace_fmt_str, #( #trace_fmt_args, )* result);
let msg = core::str::from_utf8(&w.inner()).unwrap_or_default();
ctx.ext().append_debug_buffer(msg);
}
result
} else {
#body
}
}
};

// If we don't expand blocks (implementing for `()`) we change a few things:
// - We replace any code by unreachable!
// - Allow unused variables as the code that uses is not expanded
Expand All @@ -617,7 +651,7 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
.memory()
.expect("Memory must be set when setting up host data; qed")
.data_and_store_mut(&mut __caller__);
#body
#wrapped_body_with_trace
} }
} else {
quote! { || -> #wasm_output {
Expand Down
1 change: 1 addition & 0 deletions frame/contracts/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ enum KeyType {
/// will not be changed or removed. This means that any contract **must not** exhaustively
/// match return codes. Instead, contracts should prepare for unknown variants and deal with
/// those errors gracefully in order to be forward compatible.
#[derive(Debug)]
#[repr(u32)]
pub enum ReturnCode {
/// API call successful.
Expand Down
Loading

0 comments on commit f94fce0

Please sign in to comment.