Estimates only. The analysis works from the compiled WASM file alone, without access to the contract source code, so its numbers may be incorrect. If you compile the contract with a smaller stack, test it thoroughly before deployment, including the entry points with the deepest call chains and the largest inputs.
What it measures
- Rust contracts keep locals that do not fit into WASM values (structs, buffers,
Valarrays) on a shadow stack, addressed through a stack pointer global. - The estimator finds that stack pointer, reads how many bytes every function reserves, and walks the call graph to get the deepest path.
- Compare the peak with the configured stack size (1 MiB by default) to see how much of the contract's linear memory you can safely give back.
Same analysis from the command line: node wasm-frames.js contract.wasm
(source code and CLI on GitHub)
Changing the stack size
Create a build.rs file in the contract crate's root directory, next to its
Cargo.toml (not inside src/).
Cargo runs it automatically on every build,
including stellar contract build. In a workspace, add one to each contract crate.
my-contract/
├── Cargo.toml
├── build.rs ← here
└── src/
└── lib.rs
fn main() {
// Shrink the WASM shadow stack (default 1 MiB). Wasm only: native test
// binaries must not receive this linker flag.
let target = std::env::var("TARGET").unwrap_or_default();
if target.starts_with("wasm32") {
println!("cargo:rustc-link-arg=-zstack-size=65536");
}
}
Rebuild the contract and load the new .wasm here: the Stack size card should show the new
value. Then test the contract thoroughly before deployment.
Entry points worst-case shadow stack per export
| Export | Worst case | Call chain function (frame, bytes) |
|---|
Functions by frame size
| Frame | Worst path | Name |
|---|
Explain
wasm-frames.js output
wasm-frames.js --json output