Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
2 contributors

Users who have contributed to this file

@ethanfrey @webmaster128
158 lines (117 sloc) 5.57 KB

Building WebAssembly Smart Contracts

The subdirectories are various examples of compiling smart contracts. Here are some tips useful for creating your own.

Setup

This needs to be compiled as a c dynamic lib. To do so, first generate the crate via cargo new --lib sample. Then add the following to Cargo.toml:

[lib]
crate-type = ["cdylib", "rlib"]

The cdylib is needed for the wasm target. The rlib is needed to compile artifacts for integration tests (and benchmarking).

Note throughout this demo I will use the name hackatom for the project. Please replace it with the real name of your crate. I intentionally didn't use <name>, so you can cut and paste for a quick demo. Just update this when making real code.

Requirements

You must support the rust target wasm32-unknown-unknown.

Check which ones you currently have installed via rustup target list --installed. If wasm32 is not on that list, install using rustup target add wasm32-unknown-unknown

Building

Go into the subdirectory, called sample from now on:

To compile the code, run cargo build --release --target wasm32-unknown-unknown. The output will be in target/wasm32-unknown-unknown/release/hackatom.wasm

You probably don't want to explicitly set the target every time, so you can just add the following to .cargo/config:

[alias]
wasm = "build --release --target wasm32-unknown-unknown"

And you can now just call cargo wasm to build it, and cargo test to run tests.

Note Using build.target seems to force tests to use that target as well, remove this or find a work-around. This discussion and closed PR seem to suggest this will never be done.

Optimizations

The size of the wasm output is critical if it is supposed to go on a blockchain. Here are some things to make it smaller.

Smaller builds

If you want to request the compiler to make smaller binaries, you can hit a few flags (which raise compile time significantly). Try adding this custom profile to Cargo.toml:

[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'unwind'
incremental = false
overflow-checks = true

IMPORTANT it is essential that codegen-units is set to 1 for deterministic builds. Otherwise, you will have a different wasm output with each compile.

Shrinking the output

After compiling your contract, take a look at the size of the original output: du -sh target/wasm32-unknown-unknown/release/hackatom.wasm, it is likely around 1.5 MB. Most of that is unneeded and can easily be trimmed. The first approach is to use wasm-pack to build it. This is designed for exporting small wasm builds and js bindings for the web, but is also the most actively maintained stack for trimmed wasm builds with rust. wasm-gc, the older alternative, has been deprecated for this approach. (Note you must install wasm-pack first):

cargo wasm
du -h target/wasm32-unknown-unknown/release/hackatom.wasm

wasm-pack build
du -h pkg/hackatom_bg.wasm

A bit smaller, huh? For the sample contract, this is around 64kB, but this varies a lot contract-by-contract. If you have plenty of dependencies and this is still too big, you can do a bit of investigation of where the size comes from, and maybe change your dependencies:

cargo install twiggy
twiggy top contract.wasm | head -20
twiggy garbage contract.wasm
twiggy dominators contract.wasm | less

Look at the imports and exports:

cargo install wasm-nm
wasm-nm -e contract.wasm
wasm-nm -i contract.wasm

Ultra-Compression

You can still get a bit smaller. Note the symbol names that were used in twiggy. Well, those come from inside the wasm build. You can strip out these symbols and other debug info, that won't be usable when running anyway. For this we use wasm-opt from the enscripten toolchain. This is a bunch of C++ code that needs to be compiled, and to simplify the whole process, as well as create reproduceable builds, we have created cosmwasm-opt, which contains a Dockerfile that you can use to run both wasm-pack and wasm-opt. To make the build, just run the following in the project directory:

docker run --rm -u $(id -u):$(id -g) -v $(pwd):/code confio/cosmwasm-opt:0.4.1

du -h contract.wasm

Note that this always outputs the file as contract.wasm, not with the name of the project (yes, every tool chain has a different output location). For the hackatom sample, I now get down to 52kB, an 18% improvement. This is as far as you can minimize the input, without removing actual functionality.

While we cannot trim down the wasm code anymore, we can still reduce the size a bit for loading in blockchain transactions. We soon plan to allow gzip-ed wasm in the transactions posted to the chain, which will further reduce gas cost of the code upload. Check out this final output:

$ gzip -k contract.wasm
$ du -h contract.wasm*
52K     contract.wasm
20K     contract.wasm.gz

And there you have it. We have gone from 1.5MB for the naive build, to 72kB with the standard minification tooling, all the way down to 20kB with very aggressive trimming and compression. Less than 1.5% of the original size. This is indeed something you can easily fit inside a transaction.

You can’t perform that action at this time.