Unit testing Solidity contracts on Ethereum with Go

Smart contracts on the Ethereum blockchain can be written in the Solidity language. There are several tools and frameworks available for testing these smart contracts. This article demonstrates how to unit test these contracts in a typesafe manner without sacrificing concurrency.

My friend recently introduced me to writing smart contracts for decentralized applications (or dapps for short) using the Solidity programming language for the Ethereum blockchain. He mentioned to me a project he had been developing with the Truffle framework. It’s a very popular tool from what I’ve been hearing and seeing, and it is complemented by a confectionary of related tools with delightful names like Ganache and Drizzle. Except that once I started taking a real bite into them, they didn’t leave a very pleasant aftertaste. Other testing frameworks like Embark, dapple, and Populus also exist in different library ecosystems. But all of them are either written in Python or JavaScript.

I come from a Java/Scala background and more recently have been developing a lot of services written in the Go programming language for software at Tesla. So typesafety and concurrency mean a lot to me. And for those reasons, Python and JavaScript are among my least favorite programming languages for writing and maintaining scalable code. I spent some time, a few months prior to writing this piece, skimming through the source repositories of blockchain implementations like Bitcoin, LiteCoin, Ethereum, Ripple, and Monero. And of all the code I really looked at, the go-ethereum implementation stood out like a shining star to me. The code was easy to read, simple to build, and well-documented. It is effectively the leading Ethereum implementation. Many of the examples I found online for testing Solidity contracts were based on the web3.js toolkit for the Ethereum JSON-RPC specification, which was great for getting started with geth console but I wanted something simpler. And I wanted something that interfaced in-process directly with geth as opposed to something wrapped in various layers of sugary fluff or something that required me to test contracts against a live node.

Blockchain Simulator in go-ethereum

I went searching for Solidity unit testing libraries written in Go and was pleased to find a wiki page on Go bindings for Ethereum contracts. I was especially thrilled to discover in this write-up that go-ethereum ships with a blockchain simulator. Let me just repeat that: the Go implementation of Ethereum ships with a blockchain simulator! “That’s pretty bad ass,” I thought to myself.

But as I followed the examples in the wiki line-by-line, I was slightly dismayed to encounter code snippets that were incompatible with the version of go-ethereum source (master@d2fe83d) that I had cloned from Github. Now feel free to skip to the next section if my details of troubleshooting the wiki examples bore you, just know that I eventually got them working :) But for the more curious reader, the specific code that broke with my build was:

sim := backends.NewSimulatedBackend(
core.GenesisAccount{
Address: auth.From,
Balance: big.NewInt(10000000000),
})

which results in the compilation error:

cannot use core.GenesisAccount literal (type core.GenesisAccount) as type core.GenesisAlloc in argument to backends.NewSimulatedBackend

Gotta love compilers, right? Ok, so the issue with this build is that genesis handling was refactored in geth 1.6.0 and the article above from @karalabe was authored just a few months before that release. The type signature of the NewSimulatedBackend constructor method now looks like this:

type GenesisAlloc map[common.Address]GenesisAccount
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend

Which means the correct way to create a simulated Ethereum blockchain as of 1.6.0 is something along the lines of:

gAlloc := map[common.Address]core.GenesisAccount{
auth.From: {Balance: big.NewInt(10000000000)},
}
sim := backends.NewSimulatedBackend(gAlloc)

Gotta love type inference. Just imagine trying to figure any of this out in Ruby, Python, or JavaScript. Not that it can’t be done, it just wouldn’t be pretty. Alright, so that gets things working with the bindings provided in the wiki page linked above which got me started with this package. Now let’s actually write some tests with a simpler, more up-to-date example.

Unit Test: Hello World

First off, I’d encourage every software developer to learn some Go. I was reluctant a few years ago to learn yet-another-language especially after having invested a lot in Java and Scala, and back then I initially rubbed it off as just a fad ordained by Google. But the more I’ve been reading and writing Go, the more I become impressed with the quality of software I find written in Go, the complexity of successful Go projects (e.g. Docker, Kubernetes, etc.), the community of Go developers, its adoption rate, the diversity of its libraries, and the simplicity of its use. We will download, install, and build the following dependencies as a prerequisite to our Hello World smart contract:

If this is your first time using Go, you can download and install any of the latest distributions from the golang “Getting Started” page. The examples I prepared below work with Go 1.9.2. Once you have Go installed, clone the go-ethereum source code and checkout the latest tag (which as of this writing is v1.8.7) so we can build off the most recent stable release:

$ cd $GOPATH/src/github.com/ethereum
$ git clone https://github.com/ethereum/go-ethereum.git
$ cd go-ethereum
$ git checkout v1.8.7

Next we want to install the tools necessary to compile our Solidity files. Most folks already working with Solidity will be familiar with web services like Remix or the SolC API for compiling and generating Ethereum bindings from *.sol files. For writing our unit tests we will want to compile and generate these bindings locally. To do that we will install the abigen tool that ships with go-ethereum from its source folder:

$ go install ./cmd/abigen

and also the solc tool which you can do following the instructions on solidity.readthedocs.io — though if you are developing on Mac OS X, rather than NPM, I recommend installing the Solidity Compiler using Homebrew:

$ brew tap ethereum/ethereum
$ brew install solidity

Now in a working directory of your choosing, let’s write our “Hello World” smart contract to a file called helloworld.sol:

pragma solidity ^0.4.23;
contract helloworld {
    function say() public pure returns (string) {
return 'hello etherworld';
}
}

Next we will generate the Go bindings for this simple contract using the command:

$ abigen --sol helloworld.sol --pkg main --out helloworld.go

The abigen tool will not only generate native Go bindings for your Solidity contract, it will invoke the solc command to produce an application binary interface (ABI) string (labelled as the constant HelloworldABI in helloworld.go) as well as the bytecode (labelled HelloworldBin) that will run in our simulated Ethereum virtual machine. I’ve attached the gist to the output file that I code-generated with the command above for reference, but I suggest trying the codegen yourself to verify that your tooling is setup properly on your local development machine. With this Go binding in hand, we can now write a test for our Say() unit that simply expects the output string "hello etherworld" from this function:

Now let’s go ahead and run our newly minted smart contract unit test using:

$ go test -v helloworld*.go
=== RUN   TestRunHelloworldSuite
=== RUN TestRunHelloworldSuite/TestSay
--- PASS: TestRunHelloworldSuite (0.00s)
--- PASS: TestRunHelloworldSuite/TestSay (0.00s)
PASS
ok command-line-arguments 0.041s

and voila! all our smart contract unit tests pass without having to run or configure a private network, or having to download and install a sugary add-on.

In the test code above, I’m using a Go package called testify that I’ve found really helpful for organizing large collections of similar unit tests into suites — for instance, each contract could have a suite for which tests are written. All of the test state for reuse between units is encapsulated in HelloworldTestSuite and is then wired up in SetupTest to be configured with a blockchain simulator. First we create a private key with crypto.GenerateKey() that will be used to populate a signer function for authorizing transactions in our simulated Ethereum backend. The function bind.NewKeyedTransactor returns a *TransactOpts that points to an address from which we can make transactions. We will use this address to create a genesis block with an account allocated with funds and then create our simulated blockchain with this account via the refactored genesis handling mechanism described earlier. Then, we deploy our “hello world” contract with DeployHelloworld where s.auth points to all the authorization data needed to make transactions from the generated account and s.sim points to a fully mocked blockchain backed by an in-memory database. The transaction to deploy this contract remains in a “pending” state until we s.sim.Commit() it to the blockchain, which makes the contract code available at the given address. Finally, we can invoke and test the output of our s.helloworld.Say function which can be fine-tuned with non-nil contract caller options.

And there we have it: a smart contract written in Solidity tested in Go without ever needing to fire up a node or connect to a network.


Acknowledgements: Special thanks to my friend Jonathan Viray for always serving up fresh thoughts.

Like what you read? Give Nathan Murthy a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.