Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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