Control blockchain like a boss

We’ve seen it quite a few times now: some blockchain project decides to raise funds for development and writes a smart contract to do it. Somebody makes a small mistake (or many, many mistakes) and — whoops! — contract gets hacked and funds are lost.

It’s becoming increasingly clear: just like in the earliest days of programming, the blockchain paradigm shift requires extensive testing before any code goes into production. This is because there is lots of monetary value attached to the programs we write, and most of the time that value isn’t even ours (or our employer’s).

It is therefore imperative that we write automatic tests that check our code. Doing so helps us ensure the quality of the software that we develop. Here at Neufund, developing and and actively maintaining a comprehensive test suit is one of our top priorities. This is especially true for the distributed app (dApp) we’ve created for raising funds during our Initial Capital Building Mechanism (ICBM).

There are multiple levels of tests that you can write, but in this article I am going to focus on end to end (E2E) tests. They’re designed to test an entire system in configuration close to the production environment, emulating how an end user would interact with it. It’s important to note that we are going to test not only the frontend app but smart contracts running on the Ethereum blockchain as well. To detect regressions, we run E2E tests on every commit using TravisCI.

Smart contracts preparation

To power our ICBM we have developed a whole set of smart contracts. Their behavior is checked by an extensive test suite, but to run E2E we needed to introduce some changes.

Breaching trustless trust

One of the selling points of using smart contracts to power your company is that your users don’t have to put trust in your good intentions. Instead, they trust in the source code of the program running on the blockchain. Everything is transparent. Of course, we did the same at Neufund (we have good intentions too! ;) ). The result is a system that gives you a lot of guarantees and handles your funds in a trustless manner. This is great for our users, but makes it almost impossible to write E2E tests. The reason is that we need a simple way to control the whole system to test different scenarios. To accomplish this task, we needed to modify the source code and breach these guarantees. This is of course done only during tests.

Dockerization

Docker makes it super easy to run processes with complex dependencies. In our case, we’re going to create a docker image with testrpc and compiled contracts ready to start with a container. Dockerfile is not particularly exciting, so I won’t paste it here. The only noteworthy thing is a startup script that makes sure that testrpc gets PID 1 and won’t exit too early:

#!/usr/bin/env bash

set -e

cd “$(dirname “$0”)”

cd ..

wait_for_testrpc() {

./scripts/wait-for-it.sh localhost:8545 -t 5

yarn deploy:fast

}

wait_for_testrpc&

exec ./scripts/testrpc.sh

We run testrpc in the deterministic mode so we can quickly grab a contract address later.

Writing tests

These days, there are plenty of different approaches to writing E2E tests. You can use one of the drivers for Selenium to control a variety of browsers, or just pick one of the headless browsers which are easier to work with most of the time. We decided to use the shiny new headless Chrome and Puppeteer to control it. It’s super simple to setup and works with minimal configuration on CI servers like Travis.

E2E tests are asynchronous by nature so prepare to work with promises — a lot. Gladly, newer versions of Node have native support for await/async syntax, which makes working with async code a much nicer experience. Especially Typescript (which we already wrote about) makes working with promises a breeze — you will get an error if you don’t await on promise.

Page object pattern

Page object pattern is an approach where you put all interaction with your page into one place. This helps to reduce significantly the amount of code in a test case itself and makes it more readable as well. You just say “go to signup page”, “click OK button”, “input text to this field” instead of “select button with id=’accept-btn’”, “click it”, etc. This allows us to write code like this:

const homepage = await HomePage.create(puppeteerInstance); await homepage.beforeIcoDetails.waitFor(); expect(await homepage.countdownDays.text()).to.be.eq("00");

expect(await homepage.countdownHours.text()).to.be.eq("00");

expect(await homepage.countdownMinutes.text()).to.be.eq("50");

expect(await homepage.countdownSeconds.text()).to.be.eq("00");

Web3 injection

Unfortunately, it’s not possible to install or control plugins like Metamask in headless Chrome — we needed to figure out a workaround.

We built our dApp in such a way that it detects a Web3 instance injected by the user, but uses it only to get information about available accounts and issue transactions to the blockchain. The rest is done by Web3 connected directly to our infrastructure.

In tests, we needed to inject our very own Web3 instance and make sure that it has access to a unlocked user account with some Ether already there. We managed to inject a custom library by using Puppeteer’s evaluate function just after the page load event:



eval(web3Raw);

(window as any).web3 = new (window as any).Web3(

new (window as any)

.Web3.providers.HttpProvider("

);

}, web3Raw as any); await page.evaluate((web3Raw: any) => {eval(web3Raw);(window as any).web3 = new (window as any).Web3(new (window as any).Web3.providers.HttpProvider(" https://localhost:9090/node "));}, web3Raw as any);

This is one of the rare instances where Typescript doesn’t actually provide any benefits, since the body of that function is going to be serialized and evaluated in totally different environment — inside headless Chrome.

Another problem is that Puppeteer’s type information is not accurate. That’s why we need all these ugly any casts. The good news, however, is that TS 2.6 is gonna ship with //@ts-ignore comments as a way to silence any type problems.

Time traveling

Okay, maybe we are not going to literally travel in time, but Chrome will do it for us! Our dApp shows a lot of counters and generally speaking is very “time sensitive”: it changes its layout based on whether it runs before or after the ICBM starting date.

In working on this, I was wondering whether there was a feasible way to change the browser time to test different scenarios. It turns out that there is! We can use the lolex library (a subset of sinonjs) to control how timers work inside a browser. We can for example freeze time to make some assertions with confidence etc. The only tricky thing here is to make sure that it loads BEFORE your application code. It’s basically the same approach as injecting Web3 — just use evaluate function.