Skip to main content

Reducing a contract's size

Advice & examples

This page is made for developers familiar with lower-level concepts who wish to reduce their contract size significantly, perhaps at the expense of code readability.

Some common scenarios where this approach may be helpful:

  • contracts intended to be tied to one's account management
  • contracts deployed using a factory
  • future advancements similar to the EVM on NEAR

There have been a few items that may add unwanted bytes to a contract's size when compiled. Some of these may be more easily swapped for other approaches while others require more internal knowledge about system calls.

Small wins

Using flags

When compiling a contract make sure to pass flag -C link-arg=-s to the rust compiler:

RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release

Here is the parameters we use for the most examples in Cargo.toml:

codegen-units = 1
opt-level = "s"
lto = true
debug = false
panic = "abort"
overflow-checks = true

You may want to experiment with using opt-level = "z" instead of opt-level = "s" to see if generates a smaller binary. See more details on this in The Cargo Book Profiles section. You may also reference this Shrinking .wasm Size resource.

Removing rlib from the manifest

Ensure that your manifest (Cargo.toml) doesn't contain rlib unless it needs to. Some NEAR examples have included this:

Adds unnecessary bloat
crate-type = ["cdylib", "rlib"]

when it could be:

crate-type = ["cdylib"]
  1. When using the Rust SDK, you may override the default JSON serialization to use Borsh instead. See this page for more information and an example.
  2. When using assertions or guards, avoid using the standard assert macros like assert!, assert_eq!, or assert_ne! as these may add bloat for information regarding the line number of the error. There are similar issues with unwrap, expect, and Rust's panic!() macro.

Example of a standard assertion:

Adds unnecessary bloat
assert_eq!(contract_owner, predecessor_account, "ERR_NOT_OWNER");

when it could be:

if contract_owner != predecessor_account {

Example of removing expect:

Adds unnecessary bloat
let owner_id = self.owner_by_id.get(&token_id).expect("Token not found");

when it could be:

fn expect_token_found<T>(option: Option<T>) -> T {
option.unwrap_or_else(|| env::panic_str("Token not found"))
let owner_id = expect_token_found(self.owner_by_id.get(&token_id));

Example of changing standard panic!():

Adds unnecessary bloat

when it could be:


Lower-level approach

For a no_std approach to minimal contracts, observe the following examples:

Information on system calls
Expand to see what's available from
Was this page helpful?