Bulloak
Generate tests based on the Branching Tree Technique.
Install / Use
/learn @alexfertel/BulloakREADME
bulloak
A Solidity test generator based on the Branching Tree Technique.
<!-- prettier-ignore -->[!WARNING] Note that
bulloakis still0.*.*, so breaking changes may occur at any time. If you must depend onbulloak, we recommend pinning to a specific version, i.e.,=0.y.z.
Installation
cargo install bulloak
VSCode
The following VSCode extensions are not essential but they are recommended for a better user experience:
- Solidity Inspector -
syntax highlighting for
.treefiles - Ascii Tree Generator: convenient way to generate ASCII trees
Usage
bulloak implements two commands:
bulloak scaffoldbulloak check
Scaffold Solidity Files
Say you have a foo.tree file with the following contents:
FooTest
└── When stuff is called // Comments are supported.
└── When a condition is met
└── It should revert.
└── Because we shouldn't allow it.
You can use bulloak scaffold to generate a Solidity contract containing
modifiers and tests that match the spec described in foo.tree. The following
will be printed to stdout:
// $ bulloak scaffold foo.tree
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.0;
contract FooTest {
modifier whenStuffIsCalled() {
_;
}
function test_RevertWhen_AConditionIsMet() external whenStuffIsCalled {
// It should revert.
// Because we shouldn't allow it.
}
}
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:
$ 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
behavior with the -f flag. This will force bulloak to overwrite the contents
of the file.
$ bulloak scaffold -wf ./**/*.tree
Note all tests are showing as passing when their body is empty. To prevent this,
you can use the -S (or --vm-skip) option to add a vm.skip(true); at the
beginning of each test function. This option will also add an import for
forge-std's Test.sol and all test contracts will inherit from it.
You can skip emitting the modifier definitions by passing the -m (or
--skip-modifiers) flag. Functions will still reference these modifiers in
their signatures; only the modifier definitions themselves are omitted. This is
useful together with bulloak check -m (which suppresses missing‑modifier
violations). If you use -m alone, the scaffolded file will not compile until
you provide the modifier definitions (or re-run without -m).
To normalize the generated comments, pass -F (or --format-descriptions).
When enabled, bulloak capitalizes the first letter of each branch description
and ensures it ends with a dot, so you don't need to touch the .tree file to
get consistent sentence casing in the scaffolded test bodies.
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:
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:
pragma solidity 0.8.0;
contract HashPairTest {
function test_ShouldNeverRevert() external {
// It should never revert.
}
function test_WhenFirstArgIsSmallerThanSecondArg() external {
// It should match the result of `keccak256(abi.encodePacked(a,b))`.
}
}
This Solidity file is missing the tests for the branch
When first arg is bigger than second arg, which would be reported after
running bulloak check tests/scaffold/basic.tree, like so:
warn: function "test_WhenFirstArgIsBiggerThanSecondArg" is missing in .sol
+ fix: run `bulloak check --fix tests/scaffold/basic.tree`
--> tests/scaffold/basic.tree:5
warn: 1 check failed (run `bulloak check --fix <.tree files>` to apply 1 fix)
As you can see in the above message, bulloak can fix the issue automatically.
If we run the command with the --stdout flag, the output is:
--> tests/scaffold/basic.t.sol
pragma solidity 0.8.0;
contract HashPairTest {
function test_ShouldNeverRevert() external {
// It should never revert.
}
function test_WhenFirstArgIsSmallerThanSecondArg() external {
// It should match the result of `keccak256(abi.encodePacked(a,b))`.
}
function test_WhenFirstArgIsBiggerThanSecondArg() external {
// It should match the result of `keccak256(abi.encodePacked(b,a))`.
}
}
<--
success: 1 issue fixed.
Running the command without the --stdout flag will overwrite the contents of
the solidity file with the fixes applied. Note that not all issues can be
automatically fixed, and bulloak's output will reflect that.
warn: 13 checks failed (run `bulloak check --fix <.tree files>` to apply 11 fixes)
You can skip checking that the modifiers are present by passing the -m (or
--skip-modifiers) option. This way, bulloak will not warn when a modifier is
missing from the generated file.
Use the same --format-descriptions flag when running bulloak check if you
rely on the normalized comments. This keeps the structural matcher aligned with
what bulloak scaffold --format-descriptions produces.
Rules
The following rules are currently implemented:
- A Solidity file matching the spec file must exist and be readable.
- The spec and the Solidity file match if the difference between their names
is only
.treeand.t.sol.
- The spec and the Solidity file match if the difference between their names
is only
- There is a contract in the Solidity file and its name matches the root node of the spec.
- Every construct, as it would be generated by
bulloak scaffold, is present in the Solidity file. - The order of every construct, as it would be generated by
bulloak scaffold, matches the spec order.- Any valid Solidity construct is allowed and only constructs that would be
generated by
bulloak scaffoldare checked. This means that any number of extra functions, modifiers, etc. can be added to the file.
- Any valid Solidity construct is allowed and only constructs that would be
generated by
- Condition titles may repeat anywhere in a tree.
bulloakreuses a single modifier definition per unique condition title and applies it wherever referenced. - Top‑level actions (leaves directly under the root) must have unique titles.
bulloakcannot disambiguate these deterministically, so duplicates are reported as semantic errors.
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:
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
bulloak error: unexpected `when` 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
- Condition:
when/givenbranches of a tree. - Action:
itbranches of a tree. - Action Description: Children of an action.
Spec
Each tree file should describe at least one function under test. Trees follow
these rules:
- Single tree per file: the root can be just the contract name (e.g., FooTest).
- Multiple trees in the same file: each root must be
Contract::function, using::as a separator, and all roots must share the same contract name (e.g.,Foo::hashPair,Foo::min). bulloakexpects you to use├and└characters to denote branches.- If a branch starts with either
whenorgiven, it is a condition.whenandgivenare interchangeable.
- If a branch starts with
it, it is an action.- Any child branch an action has is called an action description.
- Keywords are case-insensitive:
itis the same asItandIT. - Anything starting with a
//is a comme
Related Skills
gh-issues
346.8kFetch GitHub issues, spawn sub-agents to implement fixes and open PRs, then monitor and address PR review comments. Usage: /gh-issues [owner/repo] [--label bug] [--limit 5] [--milestone v1.0] [--assignee @me] [--fork user/repo] [--watch] [--interval 5] [--reviews-only] [--cron] [--dry-run] [--model glm-5] [--notify-channel -1002381931352]
node-connect
346.8kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
107.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Writing Hookify Rules
107.6kThis skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
