Near Learn

Shrink your Wasm binary

Shrink NEAR contract Wasm size and the NEAR it locks: Cargo release profile settings, the wasm-opt pass in cargo-near, and trimming heavy dependencies.

Intermediate4 min read3-question check

Contract code lives in the contract account and counts toward its storage, so it is staked like any other data: every 100 KB of Wasm keeps 1 NEAR locked on the account for as long as that code is deployed. A 400 KB contract ties up about 4 NEAR before it stores a single record.

Size has smaller costs too: the deploy action charges gas per byte, every function call pays a small per-byte fee to load the code, and the protocol rejects contracts above max_contract_size (4 MiB on mainnet today).

Release profile#

Cargo.toml: the profile NEAR templates ship with
TOML
[profile.release]
codegen-units = 1
# optimise for size rather than speed
opt-level = "z"
lto = true
debug = false
panic = "abort"
# keep this on: overflow panics instead of silently wrapping
overflow-checks = true
SettingEffect
opt-level = "z"Tells LLVM to prefer smaller code over faster code
lto = trueLink-time optimisation across all crates, so unused code from dependencies can be dropped and the rest inlined
codegen-units = 1Compiles the crate as one unit, giving the optimiser the whole picture (slower builds, smaller output)
panic = "abort"Drops stack-unwinding machinery; a panic aborts the receipt either way, which reverts its state changes
debug = falseNo debug info in the release artifact
overflow-checks = trueCosts a few bytes, prevents silent integer wrap-around. Keep it.
What each setting does

Build with cargo-near#

cargo near build compiles for wasm32-unknown-unknown with your release profile, embeds the ABI if enabled, and then runs wasm-opt with size optimisation on the result (pass --no-wasmopt to skip it). There is no need to run wasm-opt again by hand. Check the size of the .wasm it reports at the end of the build and track it across changes, like you track gas.

Trim what you compile in#

  • Audit dependencies. Every crate you pull in can add code; run cargo tree and question anything you do not need on-chain. Disable default features you do not use.
  • Watch formatting. format!, {:?} and #[derive(Debug)] drag in Rust’s formatting machinery. In panics and require!, prefer static messages where you can.
  • Be careful with JSON. serde_json and many Serialize/Deserialize derives are a large part of a typical binary. Keep public argument and return types simple, and avoid serde_json for anything internal.
  • Mind generics. Each concrete type a generic function is used with produces another copy of its code.
  • Keep tests and helpers out. Put test-only code behind #[cfg(test)] so it never reaches the release build.
  • Many copies of the same code? If you deploy one contract to many accounts, look at global contracts (NEP-591): the code is published once and accounts reference it instead of each storing and staking its own copy.

Check yourself

3 questions · progress saved in this browser

  1. 1.Roughly how much NEAR does a 300 KB contract binary keep locked on its account?
  2. 2.Why do NEAR templates keep overflow-checks = true in the release profile even though it adds code?
  3. 3.What does panic = "abort" change for a NEAR contract?