Building a Hook
The previous chapter ran rshooks build without explaining what it
actually does. This chapter walks through the pipeline stage by stage, so
the printed report and the check subcommand make sense on their own. A
crate can declare more than one Hook (see Hook
Chains); this chapter describes the pipeline for
one declared entry, and the next section covers how it repeats.
The pipeline
rshooks build runs cargo, then a fixed sequence of post-processing
and validation steps:
- Discovery build —
cargo build --release --target wasm32v1-noneonce, with no entry selected, compiling every declared Hook andcbakinto one artifact. This build is never deployed; its only purpose is to read back the crate’s declarations (the#[hooks]struct’s shared schema and every#[hook]/#[cbak]entry’s metadata), extracted from dead, hex-encoded carrier exports the macros generate and this artifact alone carries. - Per-index build, once per declared entry — for each index the crate
declares,
cargo rustc --release --target wasm32v1-none -- --cfg 'rshooks_entry="<i>"'recompiles the same crate with that one entry selected. The--cfgflag steers the same#[hooks]-generated code to export exactlyhook(andcbak, if this index declares one) instead of the discovery build’s suffixed names — this is the only artifact that’s ever SetHook-valid for that index. The tool then re-extracts this build’s own carriers and checks them byte-for-byte against discovery’s — a mismatch (a build script orcfg-sensitive macro producing different declarations at a different--cfgvalue) is a build error naming exactly which entry and field diverged. - Hook-cleaner (per index) — strips the disallowed
memoryexport, the now-redundant carrier exports, and any other dead export, and flattens and inlines the crate’s call graph into thehook/cbakentry points for this index only, untangling the resulting block/loop/if nesting so it fits the host’s structural limits. Because each index is compiled and cleaned separately, one index’s unreachable code (another entry’s logic, in a multi-Hook chain) never counts against this index’s own size or nesting budget. - Guard checker (per index) — validates that every loop begins with
the exact guard call sequence the host requires, and computes the
static worst-case instruction count (WCE) for
hookand, if present,cbak, from those guards. - Validator (per index) — checks the complete SetHook rule set:
exactly one
hookexport (and at most onecbak, and only if this index declared one), no disallowed imports, no recursion, and a binary size at or under the 65,535-byte SetHook limit (unless--allow-oversizeis passed, in which case the output is still written but clearly marked invalid). - Sidecar and template generation — once every index has been built
and validated, writes one
<index>.<fn>.metadata.jsonsidecar per entry, then asethook.template.jsoncovering every declared index in oneHooksarray, plus itssethook.template.meta.jsongeneration sidecar. Covered in Hook Chains and Per-Hook Attributes. - Publish — stages every artifact from this run, then atomically
updates the output root’s
currententry (out/currentbelow — wherever--outpoints, or<target>/rshooks/<crate-name>by default; see Your First Hook) to point at it. A failed run never touchescurrent; it always resolves to the most recent complete, validated build.
Every wasm-producing step runs against the exact bytes that will be
deployed — the WCE and HookHash recorded in that entry’s metadata
sidecar describe the file actually written to out/current/, not an
intermediate artifact.
Reading the printed report
build prints its progress as it goes: a discovery line, one
building entry <index> (...) line per declared entry, then a wrote ...
line for every published artifact once the whole chain has built
successfully:
discovery build (accept-all)
building entry 0 (`main`)
wrote out/current/0.main.wasm (171 bytes, estimated SetHook fee 855000 drops)
wrote out/current/0.main.metadata.json
wrote out/current/sethook.template.json
wrote out/current/sethook.template.meta.json
build itself doesn’t print the guard checker’s or validator’s numbers —
those land in that entry’s <index>.<fn>.metadata.json sidecar (the WCE
object) instead. To see them on the terminal, run rshooks check against
the binary build just wrote:
$ rshooks check out/current/0.main.wasm
worst-case instructions: hook=14 cbak=0
max nesting depth: 0
OK: out/current/0.main.wasm is a valid SetHook wasm binary
size: 171 bytes
estimated SetHook fee: 855000 drops (0.855000 XAH)
worst-case instructionsis the guard checker’s static upper bound on instructions the host will ever execute for each entry point.max nesting depthis the deepest block/loop/if nesting in the final module, checked against the host’s structural limit — 32 for a Guard-type module. This is the number Hook Chains covers in more depth: dense use of the typed#[state]/#[hook_param]/#[otxn_param]accessors at one call site can push it close to that ceiling.size/estimated SetHook feeare computed directly from that entry’s final binary byte count — SetHook’s fee schedule isbytes × 5000drops, so this is the actual one-time deployment fee cost of the binary you just built, not an approximation. Because each index is its own independent wasm, each has its own size and its own fee — a multi-Hook crate’s total deployment cost is the sum across every declared index.
Validating a binary without building it
rshooks check <file> runs the same guard-checker and validator
steps against an existing wasm file, without invoking cargo or writing any
output. It works on any SetHook-shaped wasm, including one this toolchain
didn’t build — see The rshooks CLI for its full
flag reference, and build’s and clean’s
as well.
A note on compiler-generated loops
Guards are your responsibility: an unguarded loop is treated as a hard
build error, on the principle that a missing guard! in your own source
is a bug, not something the toolchain should paper over. This includes
loops the compiler generates that never appear in your Rust source at
all (certain array-equality and buffer-zeroing patterns can lower to an
unguarded loop at the WASM level). The source-level idioms that avoid
those loops entirely are covered in the
Guards and Loops chapter.