Coding an Ethereum smart contract

Updated 2019-08-28

Bernard DeffargesBernard Deffarges
8VKxya t20 YXwG74

It seems that Distributed Ledger Technologies (DLT) are a lot used for speculation these days.

We believe their initial promise to move trust from central authorities to automatic and distributed systems still holds.

One of our goals is to use DLT to build a distributed system of trust for Data Analysis especially in Biopharma. We are not yet there and we welcome input. We have tried building smart contracts for Biopharma and for data analysis in clinical studies with Ethereum but we are still in an evaluation phase, we still need to experiment.

You will easily find plenty of information on the Internet to start coding Ethereum distributed application.

Below I'm compiling what I think might be needed to start writing, testing and deploying smart contracts on the EVM.

But first of all if you have not done it yet, you need to get acquainted with the Distributed Ledger Technologies (DLT) concepts and Ethereum. I'm not going to do this introduction here.

My Great Project Token (MGPT)

So, let's start at the beginning and let's assume you want to release a new token for your great project. Let's call it My Great Project Token (MGPT).

Whatever the purpose of your token is, Distributed Ledger Technologies (DLT) like Ethereum are a lot about money! Therefore, more than anywhere else, mistakes, bugs, etc. can cost a lot and everything should be done very carefully.

Avoid the NIH syndrome

And more than ever, we should avoid the Not Invented Here (NIH) Syndrome. Meaning we should use libraries that exist and have been tested. Of course, that also means trust, which has also always to be balanced: how much can we trust some javascript libraries to access a Smart Contract to transfer ethers or tokens? Testing, testing and testing is the sole answer I know.

ERC777

For your token, I recommend to work with standards. There are many reasons for that. For example, all tokens using a same standard share the same interface and therefore the same methods that can be called. If your token is fungible, meaning one token is interchangeable with any other of the same type, then you should use ERC777 which is the most recent version ERC20. ERC777 is fully compatible with ERC20 but fixes a lot of its shortcomings.

Truffle suite to setup your project

You could start writing your ERC777 implementation but here again avoid the NIH syndrome. Whatever your programming skills and your IQ are, writing and testing an ERC777 implementation is a lot of work. Instead use a library (and trust it!). I have spent time looking at libraries and the most advanced and of very high quality to my expertise is openzeppelin. These guys are really doing a great work! So we will use the openzeppelin implementation of ERC777, but how do we start an Ethereum smart contract project.

Javascript everywhere

Well, javascript, nodejs, npm are your first best friends... Make sure you have node and npm installed on your machine.

  node --version
  npm --version

should return the version of both. For this post I'm with node v10.15.3 and npm 6.9.2.

To setup an Ethereum smart contract project we will be using Truffle Suite, another great product that also provides the Ganache local test blockchain.

Truffle suite will provide you a project template to start with for your smart contract implementation. It's very useful. It will setup your project for testing, migrating contracts, etc. so, first install truffle (npm -g for general):

npm install truffle -g

then create a project directory, move to it and initialize your truffle project:

mkdir <my great project>
cd <my great project>
npx truffle init

You are ready to go, you have a smart contract project!

if you are wondering what npx is, it's a version of npm (part of latest npm actually) that solves dependency resolution in a new way.

So your smart contract project looks like a kind of javascript node project and that's what it is. The connection to the blockchain and to Solidity code is done through web3js

you should have a version of web3js embedded in truffle but you can also install it with

  npm install web3

If you really don't like javascript, you can do everything in Java or Scala as well with web3j and web3j-scala.

Even though I'm more like a Scala engineer -I love Scala more than any other programming language-, I have not tested the Scala version.

Truffle configuration

Your Truffle project already provides some interesting features. In truffle-config.js you can specify the information about the distributed ledger you want to access.

You should start with a local development ledger and Ganache is very powerful.

Once everything works on Ganache you can think moving to a test network like ropsten.

A typical configuration to access Ganache would be:

...
 development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
     gas: 6000000,           // Gas sent with each transaction (default: ~6700000)
     gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
    },
...    

Truffle directories

truffle init also created a few useful directories.

contracts should contain your Solidity code. I'm not going to write anything about Solidity code here.

build will contain the build artifacts.

migrations contains the javascript code to migrate the smart contracts. It reminds me of database migration scripts like you find in Ruby on Rails or tools like mybatis.

And indeed, at the end, a distributed ledger is a database which needs migrations!

test contains your testing code whether it's written in javascript or solidity. Truffle test will try to execute all files ending with .js or .sol

I mentioned openzeppelin, so let's add it to our project:

  npm install @openzeppelin/contracts

Now you can import the openzeppelin directely into your code.

MGPT Solidity smart contract

Do you need to be a fluent Solidity programmer to write Smart contracts? It depends.

Of course, if you want to do complex things you better become an expert but you can write your first MGPT ERC777 smart contract quite easily.

Go to the contracts folder and create a new soldity file, called for example mgpt.sol

pragma solidity ^0.5.0;
 
import "@openzeppelin/contracts/token/ERC777/ERC777.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
 
/**
 * @title My Great Product Token
 * The most powerful token in the Universe
 */
contract MyGreatProductToken is ERC777 {
    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply)
 
    ERC777(name, symbol, new address[](0))
 
    public{
        _mint(msg.sender, msg.sender, initialSupply, "", "");
    }
}

Once again, we are not teaching Solidity here. But interesting enough we can see how we import the openzeppelin smart contract token that we have installed with npm, interesting connection of Javascript to Solidity.

As the blockchain is an immutable data structure, storage space must be used wisely, therefore the use of low level types like uint256.

Our MGPT ERC777 code looks really simple, it basically inherits all features from its parent class code in Openzeppelin with the class extension construct in Solidity is ERC777. Solidity is an object oriented language that implements multiple inheritance.

Truffle migrate

The previous code just specifies our MGPT smart contract, but it does not instantiate an object on the Distributed ledger. Truffle migrate will do it.

Accessing the distributed ledger is done asynchronously from javascript. Therefore you can write your code either chaining .then() code or using the async and await code style. Hereafter, I will use the latest one but that is a matter of taste, up to you to use what you like better.

So in the migration you need to provide a method that gets three arguments (deployer, network, accounts) and that can use deployer to deploy smart contracts.

But first you need to install openzeppelin-test-helpers which gives you helpers like a local ERC1820Registry which you will need during development of ERC777 contracts.

Once again run npm install:

npm install --save-dev openzeppelin-test-helpers chai

So to depoy our MGPT, here is how 2_deploy_contracts.js will look like:

require('openzeppelin-test-helpers/configure')({ web3 });
const { singletons, BN } = require('openzeppelin-test-helpers');
 
const MGPT = artifacts.require("MyGreatProductToken");
 
module.exports = async function(deployer, network, accounts) {
    console.log(`network name: ${network}`);
    if (network === 'development') {
        // In a test environment an ERC777 token requires deploying an ERC1820 registry
        await singletons.ERC1820Registry(accounts[0]);
    }
 
    const tokenName ="My Great Product Token";
    const tokenSymbol = "MGPT";
    // 100 million tokens, it seems like for ERC777 tokenbits are expected
    const tokenInitSupply = new BN("100 000 000 000 000 000 000 000 000");
 
    await deployer.deploy(
            MGPT,
            tokenName,
            tokenSymbol,
            tokenInitSupply);
};

At the project level you can now run your migration with:

truffle migrate 

That should deploy your smart contract to your local Ganache, but you need to make sure Ganache is running. If it's not, make sure you have installed it as well, otherwise run

npm install -g ganache-cli

You can use the UI version and you will see the transactions, the contract creations, the spendings, etc.

The Ganache development networks starts automatically with 10 accounts, each one having 100 ETH. The accounts are available in the accounts array. If you don't specify it otherwise, account[0] is used as sender account. Make always sure it is what you want.

A working migration should eventually produce something like the following:

2_deploy_contracts.js
=====================
network name: development

   Deploying 'MyGreatProductToken'
   -------------------------------
   > transaction hash:    0x4c234f62651dac53592d78c4379fc9c2c09f5b13376e8fe32c101f341b13cf06
   > Blocks: 0            Seconds: 0
   > contract address:    0x71F6A8e2fd472CA50e5e21FAC3C2B1E2c7B8AdC8
   > block number:        31
   > block timestamp:     1567008841
   > account:             0xE88A6e34c4F6ba36E201f72d15De49761d5560Bb
   > balance:             171.61128132
   > gas used:            3355839
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.06711678 ETH

   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.06711678 ETH

Summary
=======
> Total deployments:   1
> Final cost:          0.06711678 ETH

If you want to redeploy with migrate, use

truffle migrate --reset

Truffle console

You can use the Truffle console to interact with your contracts. Run

truffle console

And then try something like:

MyGreatProductToken.deployed().then(instance => {token = instance;})

Here we use the .then() syntax... It should return undefined. But eventually the token variable will be set, which you can test with the following:

token.symbol()

which now should return MGPT

If you get something else or an exception, first of all, make sure the name of your smart contract is right.

Of course Ganache or any network has to run to be able to interact with it from Truffle.

Remember that you can always specify your target network while using truffle commands with --network <name>, default is development.

Testing, testing, testing...

Testing is the most important step when developing DLT Dapps. We are probably never doing enough testing.

Truffle creates a folder where to put your tests and it's up to you to decide whether you do your tests in Javascript of Solidity or both. For now, I have chosen Javascript.

Testing is a good subject for Domain Specific Languages (DSL) but it also ends up to be a matter of taste which syntax everyone prefers:
foo.should.be.a('string'); or expect(foo).to.be.a('string'); or assert.typeOf(foo, 'string');

Should, Expect or Assert, what do like better?

Chai, which we installed previously can do all the three.

Again you have either the method chaining .then(..) option or the async and await combination.

To be consistent I will remain with await.

Here is the code for the testing of the token in javascript:

const MGPT = artifacts.require("MyGreatProductToken");
 
contract("MyGreatProductToken", function(accounts) {
 
   let mgpt;
   let tokenAddress;
 
   const delay = ms => new Promise(res => setTimeout(res, ms));
 
   before('setup contract for each test', async function () {
        mgpt = await MGPT.deployed();
        tokenAddress = await mgpt.address;
        await delay(3000);
    });
 
    it('token information ', async function() {
        const decimals = await mgpt.decimals();
        const symbol = await mgpt.symbol();
        const name = await mgpt.name();
        assert.equal(symbol, 'MGPT');
        assert.equal(decimals, 18);
        assert.equal(name, 'My Great Product Token');
    });
});

Some more useful tools

Debugging a failed transaction

Let's say you have deployed your Smart contract on a test network like ropsten.

You start interacting with it with your test code and suddenly you get something like the following on etherscan Etherscan-revert

Well, but what did really happen?

There are probably many ways to find out but here an approach I like.

Clone the eth-reveal project from github.

Install the project npm install,

Yes, yet another a node, javascript project!

Now run npx eth-reveal -n ropsten -h <transaction hash

like: npx eth-reveal -n ropsten -h 0x90b2e796690a60897534532d9d9fb7724fbf1402ca67aea5680250b07c584aa5

it will return the error message in more details, in our case:

 ....
Status: failure
ES error: Reverted
Revert reason: Pausable: paused

Yes, indeed our contract has been paused therefore the transaction was reverted!

Getting the ABI of a contract

If you want to programmatically interact with a contract you will need its ABI.

The reason is that your code needs to know the methods signatures to be able to call them.

To generate the ABI of your contracts, clone the truffle-export-abi project and then run something like that:

truffle-export-abi -d <path_to_your_contracts_folder> -o <target_path_to_your_abi_json_file.json> -v

That will generate a full ABI that you can later on import in your code.

Annoyances

Mixing languages, libraries and platforms can be very powerful and fun but can also quickly become a nightmare.

Big Numbers and libraries to work with them from Javascript to Solidity is an example.

In some cases scientific notation (like "10000e18") will work, in some other cases it won't. And the error message is not going to help you very much. I'm still not satisfied with the options offered to deal with BNs. If you have suggestions they are welcome.

This post is already long enough, I will stop here.