Brownie Turns 1.0.0!

Have you ever struggled with BigNumber or an unresolved promise? You’re not alone—and it doesn’t have to be this way.

Eleven months ago I was having such a good time with JavaScript that I was inspired to a write a small Python program to test a contract I was working on. It was a meant to be short detour on the way to building something greater. 1400+ commits later that little detour has transformed into a major focus of my life. And today I’m very excited to announce that Brownie is officially out of beta!

If you haven’t heard — Brownie is a Python based framework for developing and testing Ethereum smart contracts. Why Python, you ask? Because Python is a language designed to get things done. The simple, uncluttered syntax and clean design makes it easy to write code that just works. We’re here to test and debug smart contracts after all, not struggle to debug our tests!

Brownie takes a cue from Python and makes testing, debugging, and deployment of smart contracts just work. It abstracts away all the heavy lifting, offering you the path-of-least-resistance to complete your task at hand with minimal code and no headaches.

Want to try it and see? Brownie is available via pip:

pip install eth-brownie

Now, lets look at a few of the features that make it stand out.

Human Readable Traces

When a transaction reverts, Brownie provides a Python-esque traceback that shows exactly where the revert happened:

You can also access a detailed call trace that shows every jump, both internal and external, that occurred during the transaction:

Developer Revert Comments

Each revert string adds a minimum 20,000 gas to your contract deployment cost, and increases the cost for a function to execute. Including a revert string for every require and revert statement is often impractical and sometimes simply not possible due to the block gas limit.

For this reason, Brownie allows you to include revert strings as source code comments that are not included in the bytecode but still accessible when executing your tests! In this way you can write tests that target a specific require or revert statement without increasing gas costs.

Add a comment after a revert or require statement that begins with // dev: and Brownie will perform the substitution:

An example of a transaction that invokes the require statement in the code shown above:

Unit Testing with Pytest

(Satisfied with Mocha? I invite you to read this article.)

Pytest is a mature and full-featured test framework. It lets you write small tests with minimal code, but also scales well for large projects. It’s also highly extendable, allowing custom plugins to integrate deeply to bend it to their use case. Brownie is one such plugin! Brownie’s testing capabilities are worthy of their own article, so for now we’ll just take a quick look some of what’s possible.

Brownie provides fixtures to simplify isolation between modules as well as individual tests. When combined with your own custom fixtures, you can easily create specific base conditions for all tests within a module. For example, look at the following setup fixtures:

The function scoped isolate fixture ensures that the local RPC client is reverted between every test, effectively isolating them. The module-scoped token fixture deploys a Token contract and then yields an object for interacting with it. And here lies the magic: because of Pytest’s fixture scoping rules the transaction to deploy Token is only broadcast once, and each time the RPC reverts the EVM state it returns to immediately after Token was deployed.

With just two simple fixtures, we have defined a common setup for every test and ensured they are properly isolated. How neat is that?

Now let’s look at some tests:

There are a few things here worth mentioning:

The first assertion in the second test would fail if the tests were not properly isolated

Brownie accepts ether amounts given as human readable strings; when testing, 1000000000000000000 == “1 ether”

Catching revert strings is as easy as applying the pytest.reverts context manager

Branch Coverage Evaluation via Opcode Tracing

Brownie calculates code coverage through trace analysis. It associates opcodes to the source code and then analyzes transaction traces to determine which branches and statements were executed when your tests were run. The results are displayed via a simple GUI:

Sample branch coverage report, as displayed by the Brownie GUI

I’ll be releasing another article soon that explores exactly how coverage is calculated.

Static Analysis via MythX

Brownie is integrated with MythX, allowing seamless static analysis of your project. Upon completion the results are visible within the GUI as well as from the MythX dashboard. Running it is as easy as typing:

brownie analyze

This will send the compiled build artifacts for analysis. By default no login is required and the analysis is executed as a trial user. To access more detailed information you can register for free on the MythX website and pass your login data via environment variables or command line arguments.

Multiple Deployments, Multiple Active Projects

Need to deploy multiple instances of the same contract? Simply call the contract’s deploy function a second time. Each contract in your project is represented as a container, and with each new deployment another object is added to that container.

What about working with multiple projects at the same time? Just call project.load and you’ll be returned a new Project object that holds all the contracts for a project. You can also open the same project more than once! Testing interactions between two large project has never been so easy.

Multiple Compiler Versions

Want to use multiple versions of Solidity in your project? No problem! Simply leave the version setting as null within the configuration file, and Brownie will determine which version to use based on the pragma statements. If no compatible version is found for a contract, Brownie installs the most recent compatible release. It also maintains it’s own solc installations, so there’s no chance for a conflict with another installed program.

Native Integers

Brownie uses a language that includes native integers. It’s finally possible to accurately represent the wei value of 1 ether without relying on a third party library. What a glorious time to be alive!

Sorry, I couldn’t resist. Making fun of JavaScript never gets old for me.

What’s Next for Brownie

Brownie is still under active development! Here’s a preview of a couple things we’re working on that we hope to release in the near-to-mid term:

Full support for Vyper — all the functionality offered for Solidity contracts will work for Vyper, including coverage analysis! Create projects containing contracts in both languages, Brownie won’t mind.

ethPM integration — we’re going deep on this one and hoping to set a new standard for functionality and ease of use. ethPM is an incredible project, and I dream of a future where its use within the Ethereum ecosystem is so ubiquitous that we all laugh at how we used to upload our smart contracts to NPM.

How Can I Get Involved?

Brownie is a fully open source project with a small but growing community of developers. We invite you to join us on Gitter, or check out the list of open issues to see where you can contribute. We welcome all issues and pull requests :)

If you want to try out Brownie for your project, the Quickstart section of the documentation is a great place to start. You are always welcome to ask for assistance on Gitter, or reach out to me directly via email or Telegram. I would love to hear from you.