EOSIO programs are typically written in C++ and then compiled into a smart contract for execution on an EOSIO blockchain.
But did you know that C++ is not the only programming language that EOSIO programs can be written in? Some projects are working on Python and even Solidity based programs. These are all compiled down into platform independent code that can be run on the EOSIO WASM Virtual Machine (WAVM).
So the WAVM does not need to know anything at all about the high level programming language (C++, Python, Solidity etc). All it knows how to do is to execute its own binary instruction set called WebAssembly.
What is WebAssembly
The project to develop a new portable, size efficient binary format for the web was driven by the major browser vendors (Mozilla, Google, Apple and Microsoft). Apart from portability, WebAssembly’s other design goals were for it to be:
- Fast: Able to execute at near native speed
- Safe: code executes in a sandboxed environment and eliminating dangerous features
- Hardware independent: Implementable by web browsers and other completely different execution environments such as datacenters, IoT devices, mobile/desktops etc
- A human-editable text format that is convertible to/from the binary format
These goals, as well as the large browser ecosystem working to continuously improve it, make WASM a great choice for smart contract execution platforms. Apart from EOSIO, WASM is also planned to be used by Ethereum.
The EOSIO WAVM
EOSIO’s WASM Virtual Machine (WAVM) is based on Andrew Scheidecker’s standalone WAVM.
The WAVM is a stack machine with two main components:
- a last-in, first-out stack is used to hold temporary values. Most of the WAVM instructions therefore assume that operands will be taken from the stack and results placed back on the stack.
- A program counter that controls the execution of the program and is modified explicitly by control instructions and implicitly advanced when non-control instructions execute.
This is in contrast to most modern computers that implement a register machine where temporary values are held in a set of registers.Advantages of using a stack machine over a register machine are that stack machines allow for:
- Compact object code: as instructions deal with values from the stack and do not need to select which registers to use
- Simple compilers: as no register management is needed making code generation fairly trivial
- Simple interpreters: particularly for Virtual Machines interpreters for stack machines are simpler as the logic for handling memory accesses is just in one place
- Easier control flow: control flow is expressed as structured constructs like blocks, if and loops that have clear labels at their beginning and end. Implementing control flow like this makes it very easy to compile and manipulate WASM and verify the correctness of control flow in a WASM program.
A simple execution example
We can now go review a simple example to illustrate the whole flow from source code to WASM to execution of that code in the WAVM.As prerequisites we assume that you have:
- Installed the EOSIO Contract Development Toolkit (CDT)
- Have access to the Web Assembly Binary Toolkit (WABT pronounced wabbit). You can do this by running the eosio-wasm2wast/eosio-wast2wasm binaries that come with CDT. Alternatively you can also download and build the generic WABT version from https://github.com/WebAssembly/wabt. (A cursory examination seems to indicate that this versions are compatible if you disable “Generate Names” and “Fold Expressions”)
For this example we’ll be compiling the basic Hello World program:
Running the eosio C++ compiler eosio-cpp:
eosio-cpp -o hello.wasm hello.cpp
This takes the C++ source file and converts it into an intermediate representation (IR) using a compiler tool chain called LLVM. LLVM performs some optimisations and then a back-end converts from the LLVM IR into WASM.
We get a WASM file, hello.wasm which is in binary. To make it readable we need to convert it into a WASM text format as follows:
eosio-wasm2wast hello.wasm -o hello.wast
This WAST file is typically huge (approximately 900 lines) so we will only concentrate on the first 50 or so lines here to illustrate some important concepts.
The WebAssembly module is composed of several sections. Some sections are required in all WASM modules and some are optional (you may see the optional modules appearing at the end of the WAST file).
- Type. Contains the function signatures for functions defined in this module and any imported functions.
- Function. Gives an index to each function defined in this module.
- Code. The actual function bodies for each function in this module.
- Start. A function that will automatically run when the WebAssembly module is loaded (basically like a main function).
- Global. Declares global variables for the module.
- Memory. Defines the memory this module will use.
- Data. Initializes imported or local memory.
- Element. Initializes an imported or local table.
For our Hello.wast file the section part looks like so:
Here we see that there are 10 function signatures and indices for the functions in this WASM file
These signatures are similar to function prototypes and define the expected inputs and outputs for each type of function.
There are also some imported functions such as
We can also see our print statement being initialised in memory:
The beginning of the code section contains the actual function bodies for each function in this module. Looking back at the function definitions we see that there is one function exported which has an index of function 11:
This is the apply function that is called when an action is issued to this smart contract:
Deploy your smart contract!
The last thing to do after compiling your smart contract is to send it to the blockchain by issuing a set code transaction using the cleos command line tool.
cleos set contract hello $YOURDIRECTORY/hello -p hello@active
When the block producer (BP) node receives the set code action it proceeds to create an instance of the smart contract WebAssembly module in the EOSIO blockchain’s RAM. Instantiating the WebAssembly module is what makes it functional and the BP node does so by resolving memory references, linking external C++ functions and giving access to the exported functions like the apply() function.
Once the BP node completes this step the smart contract WebAssembly module will be on the chain’s RAM and any BP node can then access actions intended for the smart contract.
Trigger an action!
Now that the smart contract is on chain, you can trigger it by sending an action to the smart contract using cleos:
cleos push action hello hi '["bob"]' -p bob@active
The message will be sent to the BP node which will load the smart contract WebAssembly instance and call the apply() function.
If you are curious, here’s how the WebAssembly virtual machine would execute the first few instructions in the function:
- get_global 0: gets the value of the global variable at index 0 and pushes it onto the stack
- i32.const 16: Pushes the constant value sixteen onto the stack
- i32.sub: remember we said WebAssembly was a stack machine earlier? With a stack machine all values an operand needs are pushed onto the stack before the operation is performed. The sub instruction knows that it needs two operands and will simply take the last two values from the top of the stack and return the result to the top of the stack. This means that instructions can be short (one byte for sub) as the instructions don’t need to specify source or destination addresses which makes for more compact WASM files.
Finally after the whole WebAssembly module has been executed, you should then see the smart contract reply:
WebAssembly allows smart contract developers to write EOSIO applications in C++ (and in future other languages such as Solidity and Python) and ensures that the resulting code can run efficiently across different BP nodes.
It is perfectly possible for a smart contract developer to deply a distributed application (dApp) without knowledge of WebAssembly or how the WAVM works. However understanding how it works may help you to tackle difficult problems from time to time and deal better with security vulnerabilities in your smart contract.