Write EOS contracts in Rust instead of C++

in #crypto7 years ago (edited)

I decided to learn to write smart contract for EOS, but I’m not a fan of C++. So I decided that it would be cool to use a functional language for this. Unfortunately the Ethereum Virtual Machine is not ready for EOS and there are not a lot of languages that compile down to WASM. Rust just recently added this feature natively (here the best guide I found).

Disclaimer: I don’t know Rust or WASM, I forgot C++ (and never learned C++11/14 for sure), just started following EOS, so I hope to get some feedback on this hack.

How does the current system work?

Actually it’s easier than expected. There is a tool called eoscpp that compiles the contract from C++ to a .wasm file. What it actually does is just launch clang with a bunch of flags. Namely it sets the target to wasm32 then it get rid of the standard library and notifies the compiler that there won’t be any main entrypoint. Here the full line, which is the core of it:

clang -emit-llvm -O3 --std=c++14 --target=wasm32 -ffreestanding -nostdlib \
-fno-threadsafe-statics -fno-rtti -fno-exceptions -I ${EOSIO_INSTALL_DIR} \
-I $filePath -c $file -o $workdir/built/$name

So I thought it shouldn’t be too hard to create a eosrustc that would do the same for Rust.

The most basic contract in C++

Here is the most basic contract I could come up with (still untested on the blockchain, mind you):

#include <eoslib/print.h>
extern "C" {
    void init()  { printi(5); }
    void apply( uint64_t code, uint64_t action ) {}
}

which, as you may imagine, just prints 5 when deployed.
The file print.h, to avoid further dependencies, was cut down to just the following lines

#pragma once
typedef unsigned long long   uint64_t;
extern "C" {
   void printi( uint64_t value );
}

How to create the same contract in Rust

  1. I created a new project with cargo new eos
  2. Edited the Cargo.toml to look like this
[package]
name = "eos"
version = "0.1.0"
authors = ["Andrea Passaglia <[email protected]>"]
[lib]
path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
  1. After many trials and errors I ended up with the following lib.rs in my src folder:
#![feature(lang_items)]
#![no_std]
extern "C" {
    fn printi(c: u64);
}
#[no_mangle]
pub extern "C" fn init() {
    unsafe { printi(5); }
}
#[no_mangle]
pub extern "C" fn apply(_: u64, _: u64) {}
#[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }
  1. Compiled with the command
cargo +nightly build --target wasm32-unknown-unknown --release

which was taken from the above guide.

The two contract side by side

I could get the following contract in WASM from the Rust code:

(module
  (type $t0 (func (param i64)))
  (type $t1 (func))
  (type $t2 (func (param i64 i64)))
  (import "env" "printi" (func $env.printi (type $t0)))
  (func $init (type $t1)
    (call $env.printi
      (i64.const 5)))
  (func $apply (type $t2) (param $p0 i64) (param $p1 i64))
  (table $T0 0 anyfunc)
  (memory $memory 17)
  (export "memory" (memory 0))
  (export "init" (func $init))
  (export "apply" (func $apply))
  (data (i32.const 4) "\10\00\10\00"))

While C++ gave me this (reordered for easier comparison):

(module
 (type $FUNCSIG$vj (func (param i64)))
 (import "env" "printi" (func $printi (param i64)))
 (func $init
  (call $printi
   (i64.const 5)))
 (func $apply (param $0 i64) (param $1 i64))
 (table 0 anyfunc)
 (memory $0 1)
 (export "memory" (memory $0))
 (export "init" (func $init))
 (export "apply" (func $apply))
 (data (i32.const 4) "\10@\00\00")
)

Conclusions

I haven’t had time to actually test them on the blockchain, but it’s pretty good to see that I could get a similar result.
I imagine what seems to be type declarations on the Rust contract won’t hurt, but I’m puzzled by the $memory 17 line. Also I’m not sure about the $printi vs $env.printi significance.

Anyways, I hope you had a good dose of inspiration. I’m sure we can soon demonstrate that writing EOS smart contracts in Rust is possible and that the contract libraries will be rewritten soon for rust (actually maybe they can be just linked for now🤔)

Happy hacking.

Sort:  

Awesome work! I hope one day we can write contracts in more languages. Web Assembly is a standard but it seems like there aren't many easily accessible platforms to build WASM let alone contracts. Long term I want to write something that would allow people to use an online editor in js then output a wasm and abi file to usage in contracts.

Hi, What do you think about Eos ABI generation with Rust? eoscpp cmd can do it but I have no idea about it

Sorry, I didn’t get what you are asking here.

I'm wondering the same thing. Is anyone working on a contract ABI generator for Rust? It's great to see that we can use Rust to compile to WASM, but that's only 1/2 of the smart contract equation in EOS. Without the ABI, how are developers expected to interact with deployed contract?

This is a great Post, many thanks and its encouraging to see the possibility of Rust being used for EOS.

It looks like the nightly build of rust has removed panic_fmt. If I try running your code I get:
definition of unknown language item 'panic_fmt'

This is documented here https://users.rust-lang.org/t/psa-breaking-change-panic-fmt-language-item-removed-in-favor-of-panic-implementation/17875

If you could update your lib.rs for this, I'd be grateful!