Skip to content
This repository has been archived by the owner on Oct 10, 2024. It is now read-only.

tokencard/ethertest

Repository files navigation

Ethertest

This is a set of tools that will help you test smart contracts using Golang.

Ethertest will help you run repeatable and isolated tests, assert code coverage of your Solidity, record traces of contract execution and estimate Gas usage of external methods.

Core of Ethertest is based on Geth, making execution EVM code as close to running on a Geth node as possible.

Overview

Ethertest consists of three main components TestRig, TestBackend and Account:

TestRig

TestRig is factory of new in-memory block chain instances (TestBackend). TestRig records all code coverage and tracing information for all TestBackend instances it has created, so it is best kept as a singleton in the test package.

TestBackend

TestBackend is an in-memory blockchain your tests can interact with. Ideally you should create one TestBackend per test, making each test isolated from side effects of other tests.

TestBackend implements github.com/ethereum/go-ethereum/accounts/abi/bind/ContractBackend interface, so it can be used by abigen generated contract bindings. In addition it will give you access to the current blockchain account balances, transaction receipts, and option to either commit (mine one block) or roll back (go one block back in time).

After the test is done, Close() method should be executed on TestBackend. This will free allocated caches and stop go routines.

Account

Account encapsules private/public key for an Ethereum address. Every time NewAccount() function is called, a new random private/public key is created.

Account has functions to get the account address, get balance of the account address, transfer funds to another address and create github.com/ethereum/go-ethereum/accounts/abi/bind.TransactOpts needed to call transaction methods on contract bindings.

Code Coverage

To enable code coverage, TestRig needs AST and srcmap and bin generated by solidity compiler in a file called combined-json. Following command will generate combined-json file:

  solc --optimize --overwrite --bin --abi --combined-json bin-runtime,srcmap-runtime,ast,srcmap,bin -o <build_path> <your_contract>.sol

Every contract that need code coverage has to be registered with the TestRig:

  tr.AddCoverageForContracts("<path to combined.json>", "<path to the solidity source file>")

After all tests have finished, code coverage can be asserted with:

  testRig.ExpectMinimumCoverage("<sol file name>:<contract name>", <expected coverage percent as float64>)

If the coverage of the contract is lower than expected, the method will print a coloured source of the contract (green for executed, red for not executed) and panic with a message stating expected and current code coverage.

Genesis Account Allocation

When a new TestBackend is created all accounts have 0 ETH, making the whole blockchain unusable. This can be changed by adding genesis account allocation to TestRig before creating the TestBackend:

  testRig.AddGenesisAccountAllocation(<ether address>, <amount in WEI (*big.Int)>)

Genesis allocation is memorized in the TestRig, so every creation of a new TestBackend will contain all added allocations.

Gas Usage

After all have finished, gas usage of the contracts can be printed by calling PrintGasUsage method of TestRig:

  testRig.PrintGasUsage(os.Stdout)

Output will be similar to:

Gas Usage for "test.sol:Test"
+------------------+-------+-------+-------+
|  FUNCTION NAME   |  MIN  |  MED  |  MAX  |
+------------------+-------+-------+-------+
| setValue(string) | 33109 | 33109 | 33109 |
+------------------+-------+-------+-------+

Where MIN, MED and MAX are minimum, median (50 percentile), and maximum gas spent calling each function in a transaction context.

LastExecuted

When a transaction fails, it is sometimes useful to find out what was the last line of code executed. Method LastExecuted() on the TestRig will return a string containing file name, line number and the appropriate source code snippet.