|
| 1 | +# Writing Ewasm contracts in Rust |
| 2 | + |
| 3 | +Rust is a modern systems programming language. |
| 4 | +Its type system and memory model allow for strong assumptions about memory safety at little or no runtime cost. |
| 5 | +Furthermore, its sophisticated compiler optimizations minimize the overhead of high-level language features. |
| 6 | + |
| 7 | +Such a combination of performance and safety lends itself to use in developing Ethereum smart contracts. |
| 8 | +This document serves as a comprehensive guide to using Rust in your Ewasm project. |
| 9 | + |
| 10 | +### Assumptions |
| 11 | + |
| 12 | +This document assumes: |
| 13 | +* A Unix-like OS (although hopefully some of this document is applicable to Windows as well) |
| 14 | +* The presence of a working Rust and `cargo` installation via `rustup` |
| 15 | +* Basic familiarity with the command line, Rust, and Ethereum. |
| 16 | + |
| 17 | +## 1. Toolchain Setup |
| 18 | + |
| 19 | +In order to compile to WebAssembly, Rust needs the WebAssembly backend installed. |
| 20 | + |
| 21 | +`rustup` makes this extremely simple. Just run: |
| 22 | +```console |
| 23 | +$ rustup target add wasm32-unknown-unknown |
| 24 | +``` |
| 25 | +Make sure you are installing the target `wasm32-unknown-unknown` and not `wasm32-unknown-emscripten`. |
| 26 | + |
| 27 | +## 2. Configuration and Build |
| 28 | + |
| 29 | +Now that the WebAssembly backend is installed, it can be used to compile almost any Rust project to a WASM binary. |
| 30 | +However, Ewasm also specifies a set of imports and exports for contracts. These are roughly analogous to symbols in a native binary. |
| 31 | + |
| 32 | +In order to properly export and import said symbols, the project must be compiled as a "shared library". |
| 33 | +While this has no meaning in WebAssembly (all binaries are modules), the flag ensures that the compiler will export and import the correct symbols. |
| 34 | + |
| 35 | +To do this, ensure that the project uses `lib.rs` as the crate root (not `main.rs`), and add the following lines to `Cargo.toml`: |
| 36 | +```toml |
| 37 | +[lib] |
| 38 | +crate-type = ["cdylib"] |
| 39 | +``` |
| 40 | +Compile the project using the `--target` flag: |
| 41 | +```console |
| 42 | +$ cargo build --target=wasm32-unknown-unknown |
| 43 | +``` |
| 44 | +Alternatively, to set WebAssembly as a default target, one can specify it in `.cargo/config`. |
| 45 | +Create a `.cargo` directory in the project root, if it does not exist already: |
| 46 | +```console |
| 47 | +$ mkdir .cargo |
| 48 | +``` |
| 49 | +Proceed to create or modify `.cargo/config` and add the following: |
| 50 | +```toml |
| 51 | +[build] |
| 52 | +target = "wasm32-unknown-unknown" |
| 53 | +``` |
| 54 | +Once this is done, `cargo build` will compile the project to WebAssembly by default. |
| 55 | + |
| 56 | +### Optimizing Ewasm binaries |
| 57 | + |
| 58 | +By default, the Rust compiler can generate huge WebAssembly binaries, even in release mode. |
| 59 | +These are not suitable for use as Ewasm contracts, and include lots of unnecessary cruft. |
| 60 | + |
| 61 | +The following steps will show how to optimize for size and remove unnecessary code segments from the resulting WASM binary. |
| 62 | + |
| 63 | +#### Compiler optimizations |
| 64 | + |
| 65 | +The simplest way to slim down Rust-generated binaries is by enabling certain compiler optimizations: |
| 66 | + |
| 67 | +* Enabling `lto`, or link-time optimizations, allows the compiler to prune or inline parts of the code at link-time. |
| 68 | +* Setting `opt-level` to `'s'` tells the compiler to optimize for binary size rather than speed. `opt-level = 'z'` will optimize more aggressively for size, although at the cost of performance. |
| 69 | + |
| 70 | +Enable these options in the `release` profile by adding the following to `Cargo.toml`. |
| 71 | +```toml |
| 72 | +[profile.release] |
| 73 | +lto = true |
| 74 | +opt-level = 's' |
| 75 | +``` |
| 76 | + |
| 77 | +#### Using [wasm-snip](https://github.com/rustwasm/wasm-snip) |
| 78 | + |
| 79 | +The Rust compiler, by default, includes panicking and formatting code in generated binaries. For the purpose of Ewasm contracts, this is useless. |
| 80 | +Using the `wasm-snip` tool, it is possible to replace the function bodies of such code with a single `unreachable` opcode, further shrinking the binary size. |
| 81 | + |
| 82 | +`wasm-snip` can be easily installed using `cargo`: |
| 83 | +```console |
| 84 | +$ cargo install wasm-snip |
| 85 | +``` |
| 86 | +`wasm-snip` depends on the name section in the WASM binary being present. Therefore, we must build with `debug` enabled. Add the following to the `[profile.release]` section in `Cargo.toml`: |
| 87 | +```toml |
| 88 | +debug = true |
| 89 | +``` |
| 90 | +After build, run `wasm-snip` on the resulting binary. It should be located in `target/wasm32-unknown-unknown/release`: |
| 91 | +```console |
| 92 | +$ wasm-snip --snip-rust-panicking-code --snip-rust-fmt-code input.wasm -o output.wasm |
| 93 | +``` |
| 94 | + |
| 95 | +## 3. Writing Ewasm contracts |
| 96 | + |
| 97 | +The Ewasm specification, in addition to the normal constraints of the blockchain, makes writing code for Ewasm contracts a little different from writing plain Rust. |
| 98 | + |
| 99 | +The following is a list of rules and best practices for writing Ewasm contracts. |
| 100 | + |
| 101 | +### `lib.rs`, not `main.rs` |
| 102 | + |
| 103 | +As stated earlier, using `main.rs` as a crate root will produce an executable rather than a library. |
| 104 | + |
| 105 | +### Export `main` |
| 106 | + |
| 107 | +Every Ewasm contract is required to export a `main` function as the main entry point for execution. In Rust, simply add the `pub` and `extern "C"` qualifiers to do this: |
| 108 | +```rust |
| 109 | +#[no_mangle] |
| 110 | +pub extern "C" fn main() { |
| 111 | + // function body goes here |
| 112 | +} |
| 113 | +``` |
| 114 | +The `no_mangle` directive should be included as well, to ensure that the compiler will not mangle the `main` identifier and invalidate it. |
| 115 | + |
| 116 | +### Use `finish` and `revert` to end execution |
| 117 | + |
| 118 | +When writing code for a native platform, ending execution properly is usually handled behind the scenes. In Ewasm, there are two EEI functions that signal to end execution: |
| 119 | + |
| 120 | +* `finish` behaves like EVM's `return`. Simply put, it sets the output data and halts the VM. |
| 121 | +* `revert` is the exact same thing as EVM's `revert`. It halts the VM and refunds any gas. |
| 122 | + |
| 123 | +### Minimize the use of the standard library |
| 124 | + |
| 125 | +Many constructs in Rust's standard library are not optimized for usage in Ewasm, or are outright unavailable due to dependence on a native syscall interface. |
| 126 | + |
| 127 | +Minimizing the use of non-primitive standard library constructs will improve the size, performance, and gas efficiency of resulting WASM binaries. Where possible, follow these rules of thumb: |
| 128 | +* Minimize the usage of the heap, in general. Don't use `Box<T>` or `Rc<T>`, or any other smart pointers. |
| 129 | +* Avoid using types with implicitly heap-allocated data. Use arrays instead of `Vec`, and `str` instead of `String`. |
| 130 | +* Borrowing is your friend. Stop using `clone()` and passing non-trivial structs by value, use lifetime parameters and references instead. |
| 131 | + |
| 132 | +### Use the [ewasm-rust-api](https://github.com/ewasm/ewasm-rust-api) |
| 133 | + |
| 134 | +The `ewasm-rust-api` is a convenient library providing safe wrappers around the native EEI functions, in addition to a set of useful EVM-style types. |
| 135 | + |
| 136 | +To use it in your contract, simply add `ewasm_api` as a dependency to `Cargo.toml` and include it in your project. |
| 137 | + |
| 138 | +## 4. Deploying Ewasm contracts |
| 139 | + |
| 140 | +There is one final step that must be done before contract creation: post-processing. |
| 141 | + |
| 142 | +The Rust compiler, at the moment, does not have fine-grained control over the imports and exports generated other than by their identifiers in the source. As a result, unnecessary exports must be removed and malformed imports corrected. |
| 143 | + |
| 144 | +This can be done using an automated tool such as [wasm-chisel](https://github.com/wasmx/wasm-chisel), or by hand in the `.wast` file produced by disassembling the WASM binary. |
| 145 | + |
| 146 | +If done by hand, the most common errors will be import names being prefixed with `ethereum_` and globals or tables being unnecessarily exported. |
0 commit comments