Beginner’s Guide to Deploying Your First Smart Contract with EOSIO.CDT

MistyWest
MistyWest

--

Written by Ryan Walker, Roboticist at MistyWest

Blockchain may have captured the interest of the general public, but EOS, the “blockchain for commercial scale”, has captured both the interest and funding of investors. Block.one, the company behind the underlying software, managed to raise roughly $4 billion in funding in just over a year. This is not only the highest initial coin offering (ICO) of all time but also the highest grossing crowdfunding campaign ever, with the second highest raising a mere $250 million.

Source: https://en.bitcoinwiki.org/wiki/EOS

As I’ve been working with the software lately, I’ve decided to share my learnings through a simple tutorial on how to build smart contracts using eosio.cdt (contract development toolkit). Here I will be discussing smart contract development using eosio, which is the actual software that is used to host the EOS network. Several online tutorials are using eosiocpp, which has been recently deprecated. For this reason, I decided to make something that uses eosio.cdt, which uses eosio-cpp as it’s compiler.

This will be a fairly technical tutorial which caters to people who are comfortable with Linux, C++ and JSON. All the code used in this tutorial lives in this repository.

Required Background Knowledge

  1. C / C++
  2. Linux / Mac OS
  3. Command Line Experience
  4. 4.Basic Blockchain Knowledge
  5. Good attitude

Building And Installing The Source

To start, you need a machine running one of the following operating systems:

  1. Amazon 2017.09 and higher
  2. Centos 7
  3. Fedora 25 and higher (Fedora 27 recommended)
  4. Mint 18
  5. Ubuntu 16.04 (Ubuntu 16.10 recommended)*
  6. Ubuntu 18.04
  7. MacOS Darwin 10.12 and higher (MacOS 10.13.x recommended)

*The steps below have been testing in Ubuntu 16.04, 17.10, and 18.04

Clone the eosio and eosio.cdt repositories. I’d recommend keeping them in the home directory (~).

git clone https://github.com/EOSIO/eos.git
git clone https://github.com/EOSIO/eosio.cdt.git

Then compile/install.

cd ~/eos ; ./eosio_build.sh ; sudo ./eosio_install.sh
cd ~/eosio.cdt ; ./build.sh ; sudo ./install.sh

If all goes well, you should see a big EOSIO ascii art image at the end and get folders “eosio” and “eosio.cdt” in your usr/local/. These contain symbolic links to the following software:

misty@mistywest:~$ ls /usr/local/eosio/bin/
cleos eosiocpp eosio-s2wasm keosd
eosio-abigen eosio-launcher eosio-wast2wasm nodeos
misty@mistywest:~$ ls /usr/local/eosio.cdt/bin/
clang eosio-ld llvm-nm llvm-strip
clang++ eosio-pp llvm-objcopy opt
clang-7 llc llvm-objdump wasm2wat
eosio-abigen lld llvm-ranlib wasm-ld
eosio-cc llvm-ar llvm-readelf wat2wasm
eosio-cpp LLVMEosioApply.so llvm-readobj

The software of interest right now is…

nodeos — Block producer, which you will use to run a blockchain on your local node.
cleos — The tool for communicating with the your local chain, or remote chain if you wish.
eosio-cpp — The build system for compiling smart contracts. (Ensure you’re not using eosiocpp).

Getting Started With The Tools

To start, we’re going to run a local node by issuing the command below. The -e option enables block production, even if the chain is stale. The -p followed by an parameter (in this case eosio) specifies id of the block producer that controls this node. The user eosio is the default user that comes configured when you clone the repo.

nodeos -e -p eosio --plugin eosio::chain_api_plugin --plugin \
eosio::history_api_plugin --contracts-console

If this command is successful you should see the console spitting out blocks every 0.5s, congrats — you’re running a local blockchain, go tell all your friends! The blocks should look like this… Keep in mind this is a completely local blockchain, which has no connection to the actual EOS network; here we’re allowed to do whatever we want without repercussion, a perfect testing environment.

2018-10-01T19:59:45.000 thread-0   producer_plugin.cpp:1419      produce_block        ] Produced block 000a11fcbbbc185b... #659964 @ 2018-10-01T19:59:45.000 signed by eosio [trxs: 0, lib: 659963, confirmed: 0]

Now that our node is up, we’re going to start with cleos. First we can use it to get some info about our node.

misty@mistywest:~$ cleos get info
{
“server_version”: “0f6695cb”,
“chain_id”: “cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f”,
“head_block_num”: 664406,
“last_irreversible_block_num”: 664405,
“last_irreversible_block_id”: “000a235500fa72f5094a5353fc3d1eadd334e148f85b4015515ebd57dbb64d1f”,
“head_block_id”: “000a23564b456493b0ececb293a52792b067ee8d548014e97279c9c8bfe399b5”,
“head_block_time”: “2018–10–01T20:54:48.500”,
“head_block_producer”: “eosio”,
“virtual_block_cpu_limit”: 200000000,
“virtual_block_net_limit”: 1048576000,
“block_cpu_limit”: 199900,
“block_net_limit”: 1048576,
“server_version_string”: “v1.3.0”
}

It can be seen that the head_block_producer field is listed as eosio, which makes sense as that was the -p parameter when we launched cleos. Now we wish to use that account to make another account, for now we’re just going to use the default wallet included in the repo. If we’re ever going to transition over to mainnet we need to make sure we create a new wallet with new accounts. If we go ahead and issue…

misty@mistywest:~$ cleos get account eosio
created: 2018-06-01T12:00:00.000
privileged: true
permissions:
owner 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CVactive 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CVmemory:quota: unlimited used: 2.66 KiBnet bandwidth:
used: unlimited
available: unlimited
limit: unlimited
cpu bandwidth:
used: unlimited
available: unlimited
limit: unlimited

We see the public key and other various properties of the eosio account. Memory, or “RAM”, is the allocation of on chain data storage we can use. The current EOS mainnet has 64Gb, it’s possible for developers to purchase this resource to use in their dapps (decentralised applications), once RAM is used — it is not replenished. Net and CPU are similar to ram, although they replenish per unit time. For our localnode we’re allowed to use unlimited RAM, NET and CPU.

Creating A Wallet

Now we’re going to go ahead and make a new wallet named mistywest

misty@mistywest:~$ cleos wallet create -n mistywest --to-console
Creating wallet: mistywest
Save password to use in the future to unlock this wallet.
Without password imported keys will not be retrievable.
"PW5KjM9y3sLAWmHsfEfC9HVdBah22Ejo2suhQvbqzGoBj2awXHyax"

This is the part where most people might get a little confused, the password this command spits out is a password! Not a private key. The way eosio works is, you create a wallet and then import keys into it. So, now we’re going to do just that…

Start by unlocking your wallet. Use ctrl+shift+p to paste your wallet password into the field when you’re prompted to.

misty@mistywest:~$ cleos wallet unlock -n mistywestpassword:
Error 3120007: Already unlocked

We were already unlocked, if your wallet was locked you should see something that says “Wallet unlocked” or something similar. Now that we’re unlocked it’s time to import our key for the eosio account.

misty@mistywest:~$ cleos wallet import -n mistywest \
--private-key 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
imported private key for: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

The number that starts with 5KQ is our private key for the matching publickey EOS6M, which is associated with the eosio account. In order to make an account, you need an existing account. It is for this reason that the private key for matching public key above is all in the public domain. If you were to transition over to mainnet you would never use this key pair! Now that our wallet is unlocked and we’ve imported our keys for the eosio account we can do lots of stuff. I personally want a walker account, so I’m going to make that.

misty@mistywest:~$ cleos create account eosio walker \ EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

And a mistywest account.

misty@mistywest:~$ cleos create account eosio \
mistywest EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

Creating A Contract

Now I want to get started on a new smart contract, so I’m going to make a fresh directory and switch into it. For this example, I want to make a smart contract that distributes tokens to Westies (MistyWest employees) based on their performance in Agario. Then, let’s build a voting system to be used for extremely important company decisions. It’s also possible to weigh people’s votes based on how many tokens they have.

Is that a good application? No, but it’s good enough for demonstration purposes, so let’s proceed.

misty@mistywest:~$ mkdir mw-coin ; cd mw-coin

First, I want to make a token, which is basically a cryptocurrency that lives on another cryptocurrency blockchain. So I’m going to grab the eosio.token contract and move it over.

misty@mistywest:~/mw-coin$ cp ~/eos/contracts/eosio.token/* .
misty@mistywest:~/mw-coin$ ls
CMakeLists.txt eosio.token.abi eosio.token.cpp eosio.token.hpp

Here we have four files

  • eosio.token.abi — abi is short for “application binary interface”, it’s basically a JSON file that knows where the hooks live once we compile our source into webassembly, more on this later.
  • eosio.token.cpp — c++ file, this is where our C++ source lives.
  • eosio.token.hpp — h++ file, normal header file stuff.
  • CMakeLists.txt — Delete this, we’re not going to use CMake right now.

We want to put together a simple Makefile so we can compile this thing and ensure that the compiler is in fact eosio-cpp and not eosiocpp. As I said before, eosiocpp has been deprecated. I’ve commented out the line that adds the compiler command — abigen, as we don’t want automatic abi generation at this time; I’ll get into this later.

# Makefile for an eosio smart contact

CC=eosio-cpp
Contract=mw-coin

all:
@echo "Building"
# $(CC) $(Contract).cpp -o $(Contract).wasm --abigen
$(CC) $(Contract).cpp -o $(Contract).wasm

clean:
rm -f $(Contract).wast
# rm -f *.abi
rm -f $(Contract).wasm

deploy:
cleos set contract mistywest ../$(Contract) -p mistywest@active

I went ahead and changed all the names to mw-coin

misty@mistywest:~/mw-coin$ mv eosio.token.cpp mw-coin.cpp
misty@mistywest:~/mw-coin$ mv eosio.token.hpp mw-coin.hpp
misty@mistywest:~/mw-coin$ mv eosio.token.abi mw-coin.abi

Then in mw-coin.cpp ensure you include mw-coin.hpp instead of the old eosio.token.hpp, after make this change you should be able to compile by just issuing make.

misty@mistywest:~/mw-coin$ make
Building
# eosio-cpp mw-coin.cpp -o mw-coin.wasm — abigen
eosio-cpp mw-coin.cpp -o mw-coin.wasm
misty@mistywest:~/mw-coin$ ls
Makefile mw-coin.abi mw-coin.cpp mw-coin.hpp mw-coin.wasm

Deploying The Contract

EOS contracts are compiled down to WebAssembly, which is a binary style executable that can be run in a browser. It’s typically compiled down from high level languages and can run at near machine code execution speeds. WebAssembly is relatively new and was designed to augment sites running on JavaScript, not replace them.

Our output is mw-coin.wasm, which is the WebAssembly binary that we will deploy. I’m not going to go into the token contract as there are lots of great tutorials online explaining exactly what’s going on. But to get a quick idea of what we’re about to do, take a look at the functions in mw-coin.hpp.

create — Initial creation of the token. This is permissioned so only the deployer may call it.
issue — Function for issuing tokens to users.
transfer — Function that allows a user to transfer tokens to another user.

Now lets deploy…

misty@mistywest:~/mw-coin$ make deploy
cleos set contract mistywest ../mw-coin -p mistywest@active
Reading WASM from ../mw-coin/mw-coin.wasm…
Publishing contract…
executed transaction: e9136a748a9960c0e095336e7ee6d11000794026905f5d65298df8771919013e 8000 bytes 3937 us

Now that our contract lives on the local node it’s time to create and deploy our token. First I’m going to call the create function using cleos. The first argument “mistywest” is the name of the account providing the contract. The next argument “create” is the actual function call, this lives in mw-coin.cpp. The stuff that lives in the square brackets are the arguments for the function “create” and finally the -p mistywest@active means to sign this action as mistywest, this means we’re going to need to have our wallet unlocked! I’ve named our token “MWC — MistyWest Coin” and have declared a max supply of 1000, with decimal precision of 0.xx.

misty@mistywest:~$ cleos push action mistywest create \
‘[“mistywest”, “1000.00 MWC”]’ -p mistywest@active

This issues 10 MWC to Walker, because he’s cool and probably won Agario at least once.

misty@mistywest:~$ cleos push action mistywest issue \
‘[“walker”, “10.00 MWC”, “Hi Walker!”]’ -p mistywest@active

Using cleos it’s now possible to check walker’s balance, which of course is in fact 10 MWC!

misty@mistywest:~$ cleos get currency balance mistywest walker MWC
10.00 MWC

Improving The Contract

So, this is all great if you want a contract that can be used to issue tokens, but I want to build something with slightly more utility. To implement to voting contract, I’ll need three functions. The first being a place to change who’s allowed to vote, this will be permissioned against the mistywest account. Next, I’ll need a function to raise new issues that people can vote on. Finally, I’ll need a function for people to vote with. Opening up our mw-coin.hpp we’re going to add three public members.

// mw-coin.hpp
[[eosio::action]]
void openpoll( int poll_number,
string poll_desc
);
[[eosio::action]]
void addvoter( account_name newvoter );
[[eosio::action]]
void vote( int poll_number,
int desc
);
struct [[eosio::table]] voter_table{
account_name voter;
uint64_t primary_key()const { return voter; }
};
typedef eosio::multi_index<N(votertable), voter_table> votertable;

The “[[eosio::action]]” is a C++ attribute that is used in abi file generation. We’re going to be building our abi files manually so we don’t need them, although it’s good practice to drop them in. The struct near the bottom with the “[[eosio::table]]” attribute is a definition for an EOSIO Multi Index Table. This is used for persistent storage on the blockchain and where we will keep our list of voters. We’re going to use the “voter” as our primary index, of which I will get into later. The typedef allows us to declare this table wherever we wish.

In mw_coin.cpp we’re going to add functions to match the definitions.

void mw::addvoter( account_name newvoter )
{
require_auth ( _self );

votertable voterTable( _self, newvoter );
voterTable.emplace( _self, [&]( auto& s ) {
s.voter = newvoter;
});
}void mw::openpoll( int poll_number, string poll_desc ) {}void mw::vote( int poll_number, int desc) {}

At the bottom we will also put this.

EOSIO_ABI( eosio::mw, (addvoter)(create)(issue)(transfer) )

In the “addvoter” function, we call require_auth ( _self ), which is basically saying that _self ie: mistywest better have signed this function call before calling. If not, the function will return without doing anything else, and then declare the voter table (as mentioned above). The first argument is the “owner” and the second is the “scope”; I will speak more to this lower down.

I use the emplace function to add our function argument “newvoter” into the table. This should make a little more sense after I demonstrate.

Exit out and open up the mw-coin.abi file. This file has four sections:

  1. types — For variable types used that are not defined here. You effectively have to re-typedef stuff here.
  2. structs — Actions and Tables are listed here along with their argument types.
  3. actions — Any actions that are in your contract (functions onchian that people should be able to call).
  4. tables — Any tables that are used. We declared a new one so we have to add it in.

Take some time to look over the current abi file, remembering that it’s for the tokens contract. It’s important to understand what’s going on, as the automatic abi generation process can’t catch everything and sometime screws up. I tried for a while to get away without learning about these but in the end I had to.

We’re going to add some fields; first add the addvoter function to the structs section of the mw-coin.abi.

{
“name”: “addvoter”,
“base”: “”,
“fields”: [
{“name”:”newvoter”, “type”:”account_name”}
]
}

Then add voter_table to the structs section.

{
“name”: “voter_table”,
“base”: “”,
“fields”: [
{“name”:”voter”, “type”:”account_name”}
]
}

Then addvoter to the actions section.

{
“name”: “addvoter”,
“type”: “addvoter”,
“ricardian_contract”: “”
}

Then votertable to the table section.

{
“name”: “votertable”,
“type”: “voter_table”,
“index_type”: “i64”,
“key_names” : [“voter”],
“key_types” : [“uint64”]
}

Redeploying The Contract

Now we have our abi file setup and our code is looking pretty good for a first pass. Let’s compile and deploy by issuing

misty@mistywest:~/mw-coin$ make ; make deploy

The output should be obvious if anything went wrong.

Now that our code is onchain it’s possible to call functions. Let’s use cleos to add our first voter…

misty@mistywest:~$ cleos push action mistywest addvoter \
‘[“walker”]’ -p mistywest@active

Accessing Tables

Now that we have a table with my name in it living somewhere on our blockchain, cleos get table can be used to query tables onchain. The first argument is the owner, in this case mistywest (defined in mw-coin.cpp). The second argument is the the scope, in this case walker (defined in mw-coin.cpp). The third argument is the name of the table. When we execute this we can in fact see that walker is a registered voter.

misty@mistywest:~$ cleos get table mistywest walker votertable
{
“rows”: [{
“voter”: “walker”
}
],
“more”: false
}

Closing Thoughts

To recap on what we learned today, we:

  1. Became familiar with basic eosio commands
  2. Compiled EOSIO from source
  3. Created a wallet and some accounts
  4. Compiled a contract
  5. Entered some data into and eosio Multi-Index database

Stay tuned for the next installment, where we will finish off the code and deploy the entire voting contract. If you have any questions about this tutorial, please contact Walker at walker@mistywest.com

Thanks for reading!

--

--

MistyWest
MistyWest

MistyWest is a leading engineering design consultancy accelerating the world’s transition to a more sustainable future.