Coding an Ethereum smart contract
Updated 2019-08-28
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:
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
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:
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:
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
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.