From e300e145d34050951bee1e7f70a4ce91e3f0ba51 Mon Sep 17 00:00:00 2001 From: Jake Lang Date: Tue, 4 Dec 2018 15:24:31 -0500 Subject: [PATCH 1/4] Write documentation for Rust contracts --- README.md | 5 +- rust.md | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 rust.md diff --git a/README.md b/README.md index 97358aa..c90e5fc 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,10 @@ module.exports = { Replace `PRIVATE_KEY` with the appropriate private key for the account you used to tap the faucet, above. After doing this, the usual truffle commands (such as `truffle migrate --network ewasm`) should work. +### Rust + +Documentation for writing contracts using Rust is available [here](./rust.md). + ### Other languages One of the [design goals](https://github.com/ewasm/design/blob/master/rationale.md) of the Ewasm project is to support smart contract development in a wide range of languages, using a range of tooling, including existing, mature toolchains such as LLVM (C/C++, Kotlin, Rust, and [many others](https://en.wikipedia.org/wiki/LLVM)) and JavaScript/TypeScript. In theory, any language that can be compiled to Wasm can be used to write a smart contract (as long as it implements the [contract interface](https://github.com/ewasm/design/blob/master/contract_interface.md) and [Ethereum interface](https://github.com/ewasm/design/blob/master/eth_interface.md)). See [awesome-wasm-langs](https://github.com/appcypher/awesome-wasm-langs) for a list of such languages. @@ -90,7 +94,6 @@ One of the [design goals](https://github.com/ewasm/design/blob/master/rationale. At present, we've developed support for the following languages and toolchains: - [C/C++ (LLVM) WebAssembly tutorial](./clang.md) -- Rust: documentation pending - [AssemblyScript](https://github.com/AssemblyScript/assemblyscript), a subset of TypeScript, which uses the JavaScript toolchain: see the [etherts org](https://github.com/etherts/docs) for more information on writing contracts in AssemblyScript. If you're interested in adding support for another language, framework, or toolset, see the Contributing section above and reach out. diff --git a/rust.md b/rust.md new file mode 100644 index 0000000..6a4b278 --- /dev/null +++ b/rust.md @@ -0,0 +1,146 @@ +# Writing Ewasm contracts in Rust + +Rust is a modern systems programming language. +Its type system and memory model allow for strong assumptions about memory safety at little or no runtime cost. +Furthermore, its sophisticated compiler optimizations minimize the overhead of high-level language features. + +Such a combination of performance and safety lends itself to use in developing Ethereum smart contracts. +This document serves as a comprehensive guide to using Rust in your Ewasm project. + +### Assumptions + +This document assumes: +* A Unix-like OS (although hopefully some of this document is applicable to Windows as well) +* The presence of a working Rust and `cargo` installation via `rustup` +* Basic familiarity with the command line, Rust, and Ethereum. + +## 1. Toolchain Setup + +In order to compile to WebAssembly, Rust needs the WebAssembly backend installed. + +`rustup` makes this extremely simple. Just run: +```console +$ rustup target add wasm32-unknown-unknown +``` +Make sure you are installing the target `wasm32-unknown-unknown` and not `wasm32-unknown-emscripten`. + +## 2. Configuration and Build + +Now that the WebAssembly backend is installed, it can be used to compile almost any Rust project to a WASM binary. +However, Ewasm also specifies a set of imports and exports for contracts. These are roughly analogous to symbols in a native binary. + +In order to properly export and import said symbols, the project must be compiled as a "shared library". +While this has no meaning in WebAssembly (all binaries are modules), the flag ensures that the compiler will export and import the correct symbols. + +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`: +```toml +[lib] +crate-type = ["cdylib"] +``` +Compile the project using the `--target` flag: +```console +$ cargo build --target=wasm32-unknown-unknown +``` +Alternatively, to set WebAssembly as a default target, one can specify it in `.cargo/config`. +Create a `.cargo` directory in the project root, if it does not exist already: +```console +$ mkdir .cargo +``` +Proceed to create or modify `.cargo/config` and add the following: +```toml +[build] +target = "wasm32-unknown-unknown" +``` +Once this is done, `cargo build` will compile the project to WebAssembly by default. + +### Optimizing Ewasm binaries + +By default, the Rust compiler can generate huge WebAssembly binaries, even in release mode. +These are not suitable for use as Ewasm contracts, and include lots of unnecessary cruft. + +The following steps will show how to optimize for size and remove unnecessary code segments from the resulting WASM binary. + +#### Compiler optimizations + +The simplest way to slim down Rust-generated binaries is by enabling certain compiler optimizations: + +* Enabling `lto`, or link-time optimizations, allows the compiler to prune or inline parts of the code at link-time. +* 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. + +Enable these options in the `release` profile by adding the following to `Cargo.toml`. +```toml +[profile.release] +lto = true +opt-level = 's' +``` + +#### Using [wasm-snip](https://github.com/rustwasm/wasm-snip) + +The Rust compiler, by default, includes panicking and formatting code in generated binaries. For the purpose of Ewasm contracts, this is useless. +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. + +`wasm-snip` can be easily installed using `cargo`: +```console +$ cargo install wasm-snip +``` +`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`: +```toml +debug = true +``` +After build, run `wasm-snip` on the resulting binary. It should be located in `target/wasm32-unknown-unknown/release`: +```console +$ wasm-snip --snip-rust-panicking-code --snip-rust-fmt-code input.wasm -o output.wasm +``` + +## 3. Writing Ewasm contracts + +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. + +The following is a list of rules and best practices for writing Ewasm contracts. + +### `lib.rs`, not `main.rs` + +As stated earlier, using `main.rs` as a crate root will produce an executable rather than a library. + +### Export `main` + +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: +```rust +#[no_mangle] +pub extern "C" fn main() { + // function body goes here +} +``` +The `no_mangle` directive should be included as well, to ensure that the compiler will not mangle the `main` identifier and invalidate it. + +### Use `finish` and `revert` to end execution + +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: + +* `finish` behaves like EVM's `return`. Simply put, it sets the output data and halts the VM. +* `revert` is the exact same thing as EVM's `revert`. It halts the VM and refunds any gas. + +### Minimize the use of the standard library + +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. + +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: +* Minimize the usage of the heap, in general. Don't use `Box` or `Rc`, or any other smart pointers. +* Avoid using types with implicitly heap-allocated data. Use arrays instead of `Vec`, and `str` instead of `String`. +* Borrowing is your friend. Stop using `clone()` and passing non-trivial structs by value, use lifetime parameters and references instead. + +### Use the [ewasm-rust-api](https://github.com/ewasm/ewasm-rust-api) + +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. + +To use it in your contract, simply add `ewasm_api` as a dependency to `Cargo.toml` and include it in your project. + +## 4. Deploying Ewasm contracts + +There is one final step that must be done before contract creation: post-processing. + +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. + +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. + +If done by hand, the most common errors will be import names being prefixed with `ethereum_` and globals or tables being unnecessarily exported. From 569550f88c3a93af3a8bdb79acec856cdf0aaaa3 Mon Sep 17 00:00:00 2001 From: Lane Rettig Date: Tue, 11 Dec 2018 21:33:42 +0700 Subject: [PATCH 2/4] Add Rust to list of other languages It doesn't need its own header --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index c90e5fc..6051ba2 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,6 @@ module.exports = { Replace `PRIVATE_KEY` with the appropriate private key for the account you used to tap the faucet, above. After doing this, the usual truffle commands (such as `truffle migrate --network ewasm`) should work. -### Rust - -Documentation for writing contracts using Rust is available [here](./rust.md). - ### Other languages One of the [design goals](https://github.com/ewasm/design/blob/master/rationale.md) of the Ewasm project is to support smart contract development in a wide range of languages, using a range of tooling, including existing, mature toolchains such as LLVM (C/C++, Kotlin, Rust, and [many others](https://en.wikipedia.org/wiki/LLVM)) and JavaScript/TypeScript. In theory, any language that can be compiled to Wasm can be used to write a smart contract (as long as it implements the [contract interface](https://github.com/ewasm/design/blob/master/contract_interface.md) and [Ethereum interface](https://github.com/ewasm/design/blob/master/eth_interface.md)). See [awesome-wasm-langs](https://github.com/appcypher/awesome-wasm-langs) for a list of such languages. @@ -94,6 +90,7 @@ One of the [design goals](https://github.com/ewasm/design/blob/master/rationale. At present, we've developed support for the following languages and toolchains: - [C/C++ (LLVM) WebAssembly tutorial](./clang.md) +- [Rust](./rust.md) - [AssemblyScript](https://github.com/AssemblyScript/assemblyscript), a subset of TypeScript, which uses the JavaScript toolchain: see the [etherts org](https://github.com/etherts/docs) for more information on writing contracts in AssemblyScript. If you're interested in adding support for another language, framework, or toolset, see the Contributing section above and reach out. From f64c5b42ac6f4dd611034545b92149b53b4953ea Mon Sep 17 00:00:00 2001 From: Lane Rettig Date: Tue, 11 Dec 2018 21:51:04 +0700 Subject: [PATCH 3/4] Wasm shouldn't be capitalized --- rust.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust.md b/rust.md index 6a4b278..242dba1 100644 --- a/rust.md +++ b/rust.md @@ -26,7 +26,7 @@ Make sure you are installing the target `wasm32-unknown-unknown` and not `wasm32 ## 2. Configuration and Build -Now that the WebAssembly backend is installed, it can be used to compile almost any Rust project to a WASM binary. +Now that the WebAssembly backend is installed, it can be used to compile almost any Rust project to a Wasm binary. However, Ewasm also specifies a set of imports and exports for contracts. These are roughly analogous to symbols in a native binary. In order to properly export and import said symbols, the project must be compiled as a "shared library". @@ -58,7 +58,7 @@ Once this is done, `cargo build` will compile the project to WebAssembly by defa By default, the Rust compiler can generate huge WebAssembly binaries, even in release mode. These are not suitable for use as Ewasm contracts, and include lots of unnecessary cruft. -The following steps will show how to optimize for size and remove unnecessary code segments from the resulting WASM binary. +The following steps will show how to optimize for size and remove unnecessary code segments from the resulting Wasm binary. #### Compiler optimizations @@ -83,7 +83,7 @@ Using the `wasm-snip` tool, it is possible to replace the function bodies of suc ```console $ cargo install wasm-snip ``` -`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`: +`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`: ```toml debug = true ``` @@ -124,7 +124,7 @@ When writing code for a native platform, ending execution properly is usually ha 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. -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: +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: * Minimize the usage of the heap, in general. Don't use `Box` or `Rc`, or any other smart pointers. * Avoid using types with implicitly heap-allocated data. Use arrays instead of `Vec`, and `str` instead of `String`. * Borrowing is your friend. Stop using `clone()` and passing non-trivial structs by value, use lifetime parameters and references instead. @@ -141,6 +141,6 @@ There is one final step that must be done before contract creation: post-process 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. -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. +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. If done by hand, the most common errors will be import names being prefixed with `ethereum_` and globals or tables being unnecessarily exported. From ed854ab7b8dfddc511e13ffb281d46845c001608 Mon Sep 17 00:00:00 2001 From: Lane Rettig Date: Tue, 11 Dec 2018 21:59:48 +0700 Subject: [PATCH 4/4] Some minor improvements Add links to some other Ewasm docs --- rust.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rust.md b/rust.md index 242dba1..4bc19ca 100644 --- a/rust.md +++ b/rust.md @@ -29,7 +29,7 @@ Make sure you are installing the target `wasm32-unknown-unknown` and not `wasm32 Now that the WebAssembly backend is installed, it can be used to compile almost any Rust project to a Wasm binary. However, Ewasm also specifies a set of imports and exports for contracts. These are roughly analogous to symbols in a native binary. -In order to properly export and import said symbols, the project must be compiled as a "shared library". +In order to properly export and import said symbols, the project must be compiled as a "shared library." While this has no meaning in WebAssembly (all binaries are modules), the flag ensures that the compiler will export and import the correct symbols. 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`: @@ -56,7 +56,7 @@ Once this is done, `cargo build` will compile the project to WebAssembly by defa ### Optimizing Ewasm binaries By default, the Rust compiler can generate huge WebAssembly binaries, even in release mode. -These are not suitable for use as Ewasm contracts, and include lots of unnecessary cruft. +These include lots of unnecessary cruft and are not suitable for use as Ewasm contracts. The following steps will show how to optimize for size and remove unnecessary code segments from the resulting Wasm binary. @@ -67,7 +67,7 @@ The simplest way to slim down Rust-generated binaries is by enabling certain com * Enabling `lto`, or link-time optimizations, allows the compiler to prune or inline parts of the code at link-time. * 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. -Enable these options in the `release` profile by adding the following to `Cargo.toml`. +Enable these options in the `release` profile by adding the following to `Cargo.toml`: ```toml [profile.release] lto = true @@ -94,7 +94,7 @@ $ wasm-snip --snip-rust-panicking-code --snip-rust-fmt-code input.wasm -o output ## 3. Writing Ewasm contracts -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. +In addition to the normal constraints of the blockchain, the Ewasm [specification](https://github.com/ewasm/design) makes writing code for Ewasm contracts a little different from writing plain Rust. The following is a list of rules and best practices for writing Ewasm contracts. @@ -117,8 +117,8 @@ The `no_mangle` directive should be included as well, to ensure that the compile 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: -* `finish` behaves like EVM's `return`. Simply put, it sets the output data and halts the VM. -* `revert` is the exact same thing as EVM's `revert`. It halts the VM and refunds any gas. +* [`finish`](https://github.com/ewasm/design/blob/master/eth_interface.md#finish) behaves like EVM's `return`. Simply put, it sets the output data and halts the VM. +* [`revert`](https://github.com/ewasm/design/blob/master/eth_interface.md#revert) is the exact same thing as EVM's `revert`. It halts the VM and refunds any gas. ### Minimize the use of the standard library