Rust crates for building EOSIO smart contracts and full-stack applications.
Crate | Description |
---|---|
eosio |
Library for building EOSIO smart contracts. |
eosio_macros |
Procedural macros for EOSIO smart contract development. |
eosio_sys |
Low-level FFI bindings for EOSIO smart contract development. |
DISCLAIMER: This project is in early development and we looking for feedback on all APIs and features. All APIs and features should be considered unstable and insecure until version
1.0
is released. This code is not yet suitable for production environments where user funds are at risk. Thank you.
This project enables developers to write full-stack EOSIO applications using the Rust programming language. We believe Rust is an excellent choice for EOSIO smart contract development with its focus on safety, speed, and WebAssembly. Furthermore, projects like wasm-bindgen and stdweb make it possible to write full-stack Rust web applications, limiting the need for Javascript and enabling code reuse between browsers, servers, and smart contracts.
The primary goals of this project are to provide Rust crates that:
- Enable developers to write secure EOSIO smart contracts.
- Streamline the development of full-stack EOSIO web applications.
- Simplify managing and updating EOSIO table schemas.
- Allow developers to publish reusable smart contract code.
For a detailed look at planned features please see our roadmap.
If you find a bug or think of an improvement please open an issue and let us know!
Otherwise if you are stuck on something or run into problems, here are some resources that could help:
- For questions about this project:
- For questions about EOS development:
- For questions about Rust:
Install Rust with rustup
per the official instructions:
curl https://sh.rustup.rs -sSf | sh
This project requires nightly Rust and the wasm32-unknown-unknown
target to be available, which can be installed with rustup
:
rustup install nightly
rustup target add wasm32-unknown-unknown --toolchain nightly
rustup default nightly
An EOS node is required to deploy and test smart contracts. The easiest way to setup a node is to use Docker. See the official Docker quickstart guide for instructions.
We recommend using docker-compose
to manage nodeos
and keosd
containers. You can download the official docker-compose-latest.yml
file and start the containers using these commands:
wget https://raw.githubusercontent.com/EOSIO/eos/master/Docker/docker-compose-latest.yml
docker volume create --name=nodeos-data-volume
docker volume create --name=keosd-data-volume
docker-compose -f docker-compose-latest.yml up
Note #1! If you are using cleos
within a Docker container, you need to mount your project directory as a volume so that cleos
can deploy your files. If you're using Docker Compose, add your project directory to the volumes
section of the keosd
container like so (abbreviated):
services:
keosd:
volumes:
- ./:mnt/dev/project:ro
Note #2! If you are expecting to see console output from nodeos
then be sure to add --contracts-console
to the end of the nodeosd
command like so (abbreviated):
services:
nodeosd:
command: /opt/eosio/bin/nodeosd.sh ... --contracts-console
wasm-gc is a command-line tool that removes unused code in WASM files. It can be installed with Cargo:
cargo install wasm-gc
Binaryen comes with a command-line tool called wasm-opt
that optimizes WASM file sizes. Binaryen can be installed with most system package managers.
WABT comes with a command-line tool wasm2wat
that can be used to create textual representations of WASM files, which can be useful for debugging. WABT can be installed with most system package managers.
See the Getting Started section for a more thorough explanation of the code below.
Create the project:
cargo +nightly new hello --lib
cd hello
File Cargo.toml
:
[lib]
crate-type = ["cdylib"]
[dependencies]
eosio = "0.2"
[profile.release]
lto = true
File .cargo/config
:
[target.wasm32-unknown-unknown]
rustflags = [
"-C", "link-args=-z stack-size=48000"
]
File src/lib.rs
:
#![feature(proc_macro_hygiene)]
use eosio::*;
#[eosio_action]
fn hi(name: AccountName) {
eosio_print!("Hello, ", name);
}
eosio_abi!(hi);
File hello.abi.json
:
{
"version": "eosio::abi/1.0",
"structs": [
{
"name": "hi",
"base": "",
"fields": [
{
"name": "name",
"type": "name"
}
]
}
],
"actions": [
{
"name": "hi",
"type": "hi"
}
]
}
Compile and minify the smart contract (requires optional dependencies):
cargo build --release --target=wasm32-unknown-unknown
wasm-gc target/wasm32-unknown-unknown/release/hello.wasm hello_gc.wasm
wasm-opt hello_gc.wasm --output hello_gc_opt.wasm -Oz
Deploying the smart contract will depend on how you have your EOSIO node setup. Assuming you followed the docker-compose
instructions above, run these commands in a terminal:
alias cleos='docker-compose exec keosd cleos --url http://nodeosd:8888 --wallet-url http://127.0.0.1:8900'
# Create a wallet and the 'hello' account
PUBKEY=EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
PRIVKEY=5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
cleos wallet create --to-console
cleos wallet import --private-key $PRIVKEY
cleos create account eosio hello $PUBKEY $PUBKEY
# Deploy the ABI and WASM files
cleos set abi hello /mnt/dev/project/hello.abi.json
cleos set code hello /mnt/dev/project/hello_gc_opt.wasm
# Say hello
cleos push action hello hi '["world"]' -p 'hello@active'
If all went well you should see Hello, world
in the console.
In this section we will walk through writing the hello
C++ example in Rust. In this example you will learn how to setup and optimize a basic smart contract, accept an input, and print to the console.
First, let's create a new project with Cargo and change directories:
cargo +nightly new hello --lib
cd hello
You should now have a directory that looks like this:
src/
lib.rs
Cargo.toml
The Cargo.toml
file is used by Rust to manage dependencies and other configuration options. If you open this file now it should look similar to this:
[package]
name = "hello"
version = "0.1.0"
authors = []
edition = "2018"
[dependencies]
Let's change this to add eosio
as a dependency, and change the crate type so that Rust generates a .wasm
file:
[package]
name = "hello"
version = "0.1.0"
authors = []
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
eosio = "0.2"
At this point we can compile our project to produce a .wasm
file:
cargo build --release --target=wasm32-unknown-unknown
You should now see a generated file at target/wasm32-unknown-unknown/release/hello.wasm
:
$ ls -lh target/wasm32-unknown-unknown/release | grep wasm
-rwxr-xr-x 2 sagan sagan 1.9M Oct 25 16:34 hello.wasm
This file will be huge at almost 2MB! But we can significantly reduce file size by enabling link-time optimization. Add this to the bottom of our Cargo.toml
file:
[profile.release]
lto = true
Now if we rebuild the project we should see a much smaller .wasm
file:
$ cargo build --release --target=wasm32-unknown-unknown
$ ls -lh target/wasm32-unknown-unknown/release | grep wasm
-rwxr-xr-x 2 sagan sagan 52K Oct 25 16:48 hello_world.wasm
That's better, but 52KB is still heavy for an empty smart contract. Luckily we can use wasm-gc
and wasm-opt
to reduce the file size even further:
wasm-gc target/wasm32-unknown-unknown/release/hello.wasm hello_gc.wasm
wasm-opt hello_gc.wasm --output hello_gc_opt.wasm -Oz
$ ls -lh | grep wasm
-rw-r--r-- 1 sagan sagan 109 Oct 25 16:57 hello_gc_opt.wasm
-rw-r--r-- 1 sagan sagan 116 Oct 25 16:56 hello_gc.wasm
By using wasm-gc
and wasm-opt
we are able to get the file size down to just over 100 bytes! But this is before we've added any code. Realistically you can expect simple contracts to be under 15KB.
Now that we know how to prepare the .wasm
file, let's start coding. Open up src/lib.rs
and replace its contents with this:
#![feature(proc_macro_hygiene)] // Required for procedural macros
use eosio::*; // Include everything from the eosio crate
#[eosio_action] // Mark this function as an action
fn hi(name: AccountName) {
eosio_print!("Hello, ", name); // Print to the console
}
eosio_abi!(hi); // Create the 'apply' function
See the API documentation for more details on what this code is doing.
Let's recompile our project and minify the the WASM file again:
cargo build --release --target=wasm32-unknown-unknown
wasm-gc target/wasm32-unknown-unknown/release/hello.wasm hello_gc.wasm
wasm-opt hello_gc.wasm --output hello_gc_opt.wasm -Oz
In the future ABI files will be automatically generated, but for now they must be typed out manually. Copy this code into a file called hello.abi.json
:
{
"version": "eosio::abi/1.0",
"structs": [
{
"name": "hi",
"base": "",
"fields": [
{
"name": "name",
"type": "name"
}
]
}
],
"actions": [
{
"name": "hi",
"type": "hi"
}
]
}
At this point we have our WASM and ABI files, so we're ready to deploy our smart contract.
For this you will need to have access to an EOS node. Please see the Installing EOS section for instructions. The code below will assume that you've started nodeos
and keosd
containers using Docker Compose.
First create an alias for cleos
:
alias cleos='docker-compose exec keosd cleos --url http://nodeosd:8888 --wallet-url http://127.0.0.1:8900'
Then create a wallet, import a private key, and create the hello
account:
PUBKEY=EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
PRIVKEY=5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
cleos wallet create --to-console
cleos wallet import --private-key $PRIVKEY
cleos create account eosio hello $PUBKEY $PUBKEY
Deploy the ABI:
cleos set abi hello /mnt/dev/project/hello.abi.json
Deploy the WASM:
cleos set code hello /mnt/dev/project/hello_gc_opt.wasm
...but this will fail with an error!
$ cleos set code hello /mnt/dev/project/hello_gc_opt.wasm
Reading WASM from /mnt/dev/project/hello_gc_opt.wasm...
Setting Code...
Error 3070002: Runtime Error Processing WASM
In the nodeos
console log you will see this error message:
error 2018-10-26T03:46:12.176 thread-0 http_plugin.cpp:580 handle_exception ] FC Exception encountered while processing chain.push_transaction
debug 2018-10-26T03:46:12.176 thread-0 http_plugin.cpp:581 handle_exception ] Exception Details: 3070002 wasm_execution_error: Runtime Error Processing WASM
Smart contract data segments must lie in first 64KiB
{"k":64}
thread-0 wasm_eosio_validation.cpp:30 validate
pending console output:
{"console":""}
thread-0 apply_context.cpp:72 exec_one
This is happening because Rust by default reserves 1MB for the stack, but EOS expects data to be within the first 64KB.
We can fix this by telling the Rust compiler to reserve less than 64KB for the stack. Create a new file at .cargo/config
with these contents:
[target.wasm32-unknown-unknown]
rustflags = [
"-C", "link-args=-z stack-size=48000"
]
48KB seems to be a reasonable number, but feel free to experiment.
Now let's try to rebuild and redeploy our contract:
cargo build --release --target=wasm32-unknown-unknown
wasm-gc target/wasm32-unknown-unknown/release/hello.wasm hello_gc.wasm
wasm-opt hello_gc.wasm --output hello_gc_opt.wasm -Oz
cleos set code hello /mnt/dev/project/hello_gc_opt.wasm
Finally, say hello:
cleos push action hello hi '["world"]' -p 'hello@active'
If all went well you should see Hello, world
in the console. Otherwise, if the transaction was sent successfully but you don't see any output, you may need to use the --contract-console
option with nodeos
.
Examples can be found in the examples
directory. The equivalent C++ code has been provided where possible.
Directory | Description |
---|---|
addressbook |
An example of how to interact with tables using secondary indexes. |
eosio_token |
The standard eosio.token contract ported to Rust. |
hello |
The most basic contract using the eosio crate. |
hello_bare |
A bare bones version of the hello contract without any dependencies. |
tictactoe |
An example of how to interact with EOSIO tables. |
See the 1.0 milestone for a full list of fixes and features planned for 1.0.
Listed below are features that are planned for the 1.0 release. The goal is to have a 1.0 release candidate with all these features by Q1 2019, but this may change depending on community feedback and support.
Tracking this feature in issue #4
A proper test suite is crucial for developers to build secure and correct smart contracts.
EOS already supports unit tests for smart contracts (see eosio.contracts
for an example), so to support this in Rust we will likely need to:
- Generate more FFI bindings for EOS libraries.
- Create a new
eosio_test
crate that will be a test harness, similar to howwasm-bindgen
useswasm-bindgen-test
to support testing in headless browsers.
Tracking this feature in issue #5
Hand-written ABI files are unnecessary and expose developers to risk if they aren't kept updated.
Since we already have #[eosio_action]
and #[eosio_table]
attributes, it should be fairly straightforward to implement this feature by detecting these attributes and generating a JSON file.
Tracking this feature in issue #6
It would be nice to have a CLI command that would generate Rust code from on-chain ABIs. This would make it significantly easier to interact with external contracts through inline actions.
Implementing this feature would require fetching the ABI JSON from an EOS node and creating a Rust file containing the generated tables and actions.
Tracking this feature in issue #7
Making changes to EOS table fields is currently not a pleasant experience. It can be a fragile error-prone process that involves duplicating code to work with multiple versions of structs. We believe that a better solution can be found by taking inspiration from projects like Diesel and Django migrations.
Implementing this feature will require significant effort and discovery. This may be a 1.0+ feature.
Tracking this feature in issue #8
All EOS apps need a way to talk to EOS nodes, to fetch table rows and to send transactions. In order for full-stack Rust-based EOS applications to come to fruition, there needs to be a solid RPC API. In Javascript there is eosjs
, and something similar should exist for Rust.
Implementing this will be tricky since we need to support browser and server environments.
- For browsers we need to support
wasm-bindgen
andstdweb
- For servers we need to support
hyper
This could get even more complicated if we decide to optionally support futures. For the initial release futures will probably be mandatory.
There are a lot of things to consider so this may be a 1.0+ feature.
Tracking this feature in issue #9
We already have several features that need CLIs. Consolidating all our CLIs under one CLI will make things simpler for developers and allow us to add new commands later on.
Commands should be implemented to:
- Create a new
rust-eos
project, e.g.rust-eos new
- Generate an ABI file, e.g.
rust-eos to-abi
- Generate Rust from an ABI, e.g.
rust-eos from-abi
- Manage table schemas, e.g.
rust-eos schema
- Run unit tests, e.g.
rust-eos test
Tracking this feature in issue #10
A big selling point of Rust is its first-class support for WebAssembly and the possibility of writing full-stack web applications in one highly performant language. It would be great if we could use the same structs and functions from our smart contracts in our frontend code as well.
Implementing this may require rethinking some things, specifically traits that are implemented on primitive types like SecondaryTableKey
seem to be causing some issues.
Tracking this feature in issue #11
Serde is the defacto standard when it comes to serializing and deserializing data. It will be necessary for table structs to support Serde's Serialize
/Deserialize
traits in order to implement the RPC API later on.
Implementing this will require writing custom serializers/deserializers for EOS types, for example:
- Booleans are 0 or 1
- Large numbers can sometimes be integers, sometimes be strings
Licensed under either of these:
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion in the work, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.