/ BLOCKCHAIN, ETHEREUM, TESTING, FRAMEWORK, TRUFFLE

Starting with Ethereum - Industrialization

This is the 5th post in the Starting Ethereum focus series.Other posts include:

  1. Starting with Ethereum - Setup
  2. Starting with Ethereum - Writing a contract
  3. Starting with Ethereum - Deploying and running a contract
  4. Starting with Ethereum - Smart contracts
  5. Starting with Ethereum - Industrialization (this post)

Last week, we finally developed a contract providing some value in the form of a rough-around-the-edges voting application.

There’s one huge issue, though: there are no tests! As a professional developer, this is just not acceptable. Also, there’s no framework to ease greenfield developments, no automated deployment process, etc. To sum it up, Ethereum comes with no tools aimed at state-of-the-art software development out-of-the-box.

Since this is a huge issue, there’s third-party tooling available in the form of the Truffle framework.

  1. Built-in smart contract compilation, linking, deployment and binary management
  2. Automated contract testing for rapid development
  3. Scriptable deployment & migrations framework
  4. Network management for deploying to both public & private networks
  5. Access to hundreds of external packages
  6. Interactive console for direct contract communication
  7. External script runner that executes scripts within a Truffle environment
  8. Built for speed
— Truffle website
http://truffleframework.com/

In this post, we’ll focus on testing and deployment.

Setup

Some steps are required before proper starting.

Installing Truffle

Truffle comes into the form of a npm package.

npm install -g truffle

Too bad if you’re allergic to JavaScript, because the rest of the post contains a lot of it.

Initializing a project

  1. Create a dedicated folder for the project
  2. Inside it, initialize the Truffle template
mkdir referendum
cd referendum
truffle init

Once executed, the following structure will be present at the project folder root:

Folder/file Content

contracts

Smart contracts, written in Solidity. Add your own contracts there.

After initialization, there will one Migration.sol file. Don’t remove it as it will be used during deployment.

migrations

Deployment scripts, written in JavaScript

test

Test files, written either in Solidity or in JavaScript. Add your own tests there.

truffle.js

Truffle configuration file for advanced usage.

Testing

Truffle allows to test contracts both through Solidity and JavaScript files. From a 10,000 feet view, Solidity tests are akin to integration tests while JavaScript are more like end-to-end tests.

Both need to be targeted at a contract deployed on an Ethereum network.

Creating Solidity tests

I tried to first write my tests in Solidity, because I’m not a JavaScript fan…​ and felt the pain:

  • It requires contracts and test methods to match a pattern - like with JUnit before v4.
  • There’s no mocking.
  • And testing for exceptions requires a huge hack.

At that point, I was fed up and ran back to JavaScript pretty fast.

Creating JavaScript tests

Truffle doesn’t reinvent the wheel: under the cover, it reuses existing frameworks, Mocha for testing and Chai for assertions.

There are a few requirements for tests. They should:

  • be placed in the test folder - that’s pretty obvious
  • have a standard .js extension
  • follow the standard Mocha test structure, replacing the describe() function with the custom contract() function

Here’s an example of such a test for the Referendum contract :

'use strict';

let VotingToken = artifacts.require('VotingToken');               (1)
let ReferendumContract = artifacts.require('ReferendumContract'); (1)

contract('ReferendumContract', () => {

  const totalSupply = 16;
  const votesCount = totalSupply - 1;

  let token;
  let contract;

  it('should be properly initialized', async () => {
    token = await VotingToken.new('DUMMY', votesCount);                           (2)
    let question = 'Why?';
    contract = await ReferendumContract.new(question, votesCount, token.address); (2)
    assert.equal(await contract.getQuestion.call(), question); (3)
    assert.equal(await contract.isOpen.call(), true);          (3)
  });
});

Interesting bits in the above snippet are the following:

1 artifacts.require() tells which deployed contracts the script requires.
2 new() deploys such a contract
3 When reading data, using call() instead of the real method doesn’t cost any Ether. This should be preferred over calling the "real" underlying method

The above snippet leverages syntax and features from ECMAScript 6 (2015!), despite all available online samples written with older versions. In particular, it takes advantage of async/await to make asynchronous code more readable by preventing Callback Hell.

It’s advised to use those features, as it makes the code so much more readable.

Executing tests

The easiest way to start executing tests is the following:

truffle develop  (1)
test             (2)
1 Creates a brand new Ethereum network and fires up the Truffle development console
2 Launch all tests situated in the test folder

Initializing a new blockchain should give out the following output:

Truffle Develop started at http://localhost:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Private Keys:
(0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
(1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
(2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
(3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
(4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
(5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
(6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
(7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
(8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
(9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

This is the result from passing the tests:

Using network 'develop'.

  Contract: ReferendumContract
    ✓ should be properly initialized (128ms)
    ✓ should have no votes upfront
    ✓ should return proper results and burn voting token when casting votes (61ms)
    ✓ should close vote when all votes have been cast (61ms)

  Contract: VotingToken
    ✓ should have 5 tokens initialized in the first account (46ms)
    ✓ transfer should decrease owner account balance without impact on total supply (74ms)
    ✓ burn should check if owner balance has enough tokens
    ✓ burn should decrease total supply and decrease owner account balance (43ms)

Alternatively, one can use truffle console to connect to an existing Ethereum network. In that case, an existing Ethereum client should be running and accepting RPC connections:

geth --rinkeby --syncmode fast --cache 1024 --ipcpath ~/Library/Ethereum/geth.ipc --rpc
truffle console
test

Remember to first unlock your account in a separate process:

geth attach
personal.unlockAccount(personal.listAccounts[0], 'password', 10000);

Client binding should be configured in the development network in the truffle.js file:

truffle.js
module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*"
    }
  }
};

Deploying

Deployment is handled by the scripts located in the migrations folder.

Such scripts are numbered. They are meant to be run only once on a specific blockchain, so that next run won’t execute them again (unless specifically configured to do that). This is implemented on the blockchain itself, through the Migrations.sol contract, created during project initialization. There’s a script named 1_initial_migration.js, configured to deploy it, as the first deployment.

The deployment script for the previous VotingToken and ReferendumContract looks like:

var referendum = artifacts.require("ReferendumContract"); (1)
var token = artifacts.require("VotingToken");             (1)

module.exports = (deployer) => { (2)
  deployer.deploy(token, "DUMMY", 5).then(() => {  (3)
    deployer.deploy(referendum, "Are you ok?", 6, token.address);  (4)
  });
};
1 As for testing, this tells which contracts will be required
2 The documentation states: "All migrations must export a function via the module.exports syntax. The deployer object is your main interface for staging deployment tasks."
3 deployer.deploy() obviously deploys the contract. It accepts as the first argument the contract object returned by the artifacts.require(), and as following parameters the contract constructor arguments.
4 The referendum contract requires the token contract. Hence, deployment of the former is chained through a callback using the later’s address field.

To launch those scripts, execute the following command:

migrate

This produces the following output:

Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xe596d8eae73239195fa509bd6d271643f3077c23ff0d89b6cfdbd10c2ee990a4
  Migrations: 0x0984883bb7f65b3ab11e84c10bf2cc97a8583fb1
Saving successful migration to network...
  ... 0xa7302dd463b1f05aea48bcf4beeb267ce73b64cd365981a163af16a567e2748f
Saving artifacts...
Running migration: 2_referendum_migration.js
  Deploying VotingToken...
  ... 0xd759398b331029053fa333d71eaa818e74b4614b0050ccca52801233fc59076b
  VotingToken: 0x4bc1ab343c69944d2aba0e7fa3c77ac7d4ed4577
Saving successful migration to network...
  Deploying ReferendumContract...
  ... 0x86bd810a3b5f8c0fd2b582e1b49ab9bc5bffe8e47eb3776cb5838d91e2b480ce
  ... 0xf0fb9ba91b8f1da15af837efc2287d2df6101b0d6562f049ac9d0a5261abe667
Saving artifacts...
  ReferendumContract: 0x3e94dfb44f1e121ed5c0b883b3ea473981d3e014
One should be already connected to an Ethereum network, either through truffle development or truffle console.

Conclusion

At this point, you should be able to test and deploy your contracts in an industrialized way.

It’s now up to you to start your own journey on the Ethereum blockchain.

The complete source code for this post can be found on Github.
Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Starting with Ethereum - Industrialization
Share this