bulloak

A simple, fast, and easy-to-use Solidity test generator based on the Branching Tree Technique.

[!WARNING] bulloak is still 0.*.*, so breaking changes may occur at any time. If you must depend on bulloak, we recommend pinning to a specific version, i.e., =0.y.z.

Installation

bash cargo install bulloak

Usage

bulloak implements two commands:

Scaffold Solidity Files

Say you have a foo.tree file with the following contents:

tree FooTest └── When stuff called └── It should revert.

You can use bulloak scaffold to generate a solidity contract containing modifiers and tests that match the spec described in foo.tree. This will be printed to stdout.

```terminal $ bulloak scaffold foo.tree pragma solidity 0.8.0;

contract FooTest { modifier whenStuffCalled() { _; }

function testRevertWhenStuffCalled() external whenStuffCalled { // It should revert. } } ```

You can use the -w option to write the generated contracts to the file system. Say we have a bunch of .tree files in the current working directory. If we run the following:

text $ bulloak scaffold -w ./**/*.tree

bulloak will create a .t.sol file per .tree file and write the generated contents to it.

If a .t.sol file's title matches a .tree in the same directory, then bulloak will skip writing to that file. However, you may override this behaviour with the -f flag. This will force bulloak to overwrite the contents of the file.

text $ bulloak scaffold -wf ./**/*.tree

Check That Your Code And Spec Match

You can use bulloak check to make sure that your solidity files match your spec. For example, any missing tests will be reported to you.

Say you have the following spec:

tree HashPairTest ├── It should never revert. ├── When first arg is smaller than second arg │ └── It should match the result of `keccak256(abi.encodePacked(a,b))`. └── When first arg is bigger than second arg └── It should match the result of `keccak256(abi.encodePacked(b,a))`.

And a matching solidity file:

```solidity pragma solidity 0.8.0;

contract HashPairTest { function test_ShouldNeverRevert() external { // It should never revert. }

modifier whenFirstArgIsSmallerThanSecondArg() { _; }

function test_WhenFirstArgIsSmallerThanSecondArg() external whenFirstArgIsSmallerThanSecondArg { // It should match the result of keccak256(abi.encodePacked(a,b)). } } ```

This solidity file is missing the tests for When first arg is bigger than second arg, which would be reported after running bulloak check, like so:

text Codegen not found: Couldn't find a corresponding element for "whenFirstArgIsBiggerThanSecondArg" in the solidity file. Codegen not found: Couldn't find a corresponding element for "test_WhenFirstArgIsBiggerThanSecondArg" in the solidity file.

Rules

The following rules are currently implemented:

Compiler Errors

Another feature of bulloak is reporting errors in your input trees.

For example, say you have a buggy foo.tree file, which is missing a character. Running bulloak scaffold foo.tree would report the error like this:

``text ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• bulloak error: unexpectedwhen` keyword

── when the id references a null stream ^^^^

--- (line 2, column 4) --- file: foo.tree ```

Trees

bulloak scaffold scaffolds solidity test files based on .tree specifications that follow the Branching Tree Technique.

Currently, there is on-going discussion on how to handle different edge-cases to better empower the solidity community. This section is a description of the current implementation of the compiler.

Terminology

Spec

Each tree file should describe a function under test. Trees follow these rules:

Take the following solidity function:

solidity function hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { return a < b ? hash(a, b) : hash(b, a); } A reasonable spec for the above function would be: tree HashPairTest ├── It should never revert. ├── When first arg is smaller than second arg │ └── It should match the result of `keccak256(abi.encodePacked(a,b))`. └── When first arg is bigger than second arg └── It should match the result of `keccak256(abi.encodePacked(b,a))`.

There is a top-level action which would generate a test to check the function invariant that it should never revert.

Then, we have the two possible preconditions: a < b and a >= b. Both branches end in an action that will make bulloak scaffold generate the respective test.

Note the following things:

Output

There are a few things to keep in mind about the scaffolded solidity test:

Contributing

Please refer to CONTRIBUTING.md.

License

This project is licensed under either of: