Author: Alexey Kutsenko 👨💻
Brownie is a Python-based smart contract development and testing framework focused on the Ethereum Virtual Machine.
Features:
- Full support for Solidity and Vyper
- Contracts are tested using pytest
- A console is available for quick interaction with contracts
- Supports ethPM packages
- Property and state-based testing using the hypothesis library
- Built-in MythX security analysis tool for contracts
In this overview, we will look at how to initialize a project, the project structure, learn which library is used for writing tests, and how to debug during code testing.
Important! Basic knowledge of web3.py is required to work with Brownie.
Before initializing a project, it must be installed according to the documentation.
Important! For the framework to work properly without errors, you need to install:
- Python3 version 3.6 or higher, python3-dev
- ganache-cli - tested with version 6.12.2
It should be mentioned that for the Brownie graphical interface to work, make sure the Tkinter library is installed. This can be done with the command python -m tkinter
. If for some reason it is not installed, you can read how to do it here.
To initialize a new project, use brownie init
.
You can also use ready-made templates.
The command to initialize a project from templates is brownie bake nameOfRepo
.
contracts/: The contracts themselves (code and libraries).
interfaces/: Contract interfaces.
scripts/: Python scripts.
tests/: Project tests.
build/: Information obtained as a result of building and deploying is stored here.
reports/: Reports in JSON format.
There is also a configuration file brownie-config.yaml — it can specify compiler options, data for connecting to a node, or testing parameters. Configuration options can be found here.
Main brownie commands:
init
- initializes a new projectbake
- initializes a project from a templatepm
- installs and manages external packagescompile
- compiles contractsconsole
- launches a console for interaction with the necessary network (local test environment or connection to mainnet/testnets)test
- runs all tests in the tests/ folderrun
- executes a script in the scripts/ folderaccounts
- allows managing accounts from which transactions will occurnetworks
- allows viewing, adding/removing networksgui
- launches a graphical interface that allows viewing test coverage and security reports, as well as contract opcodesanalyze
- script for finding vulnerabilities in smart contracts through the MythX API tool
Let's discuss in more detail how the pm
and gui
commands work:
- Brownie allows you to install other projects as packages, offering advantages like:
- Easy import and development of code ideas written by others.
- Reducing code duplication between projects.
- Writing unit tests that check interactions between your project and another project.
You can use only GitHub repositories and ethpm.
For more detailed information on how to install, manage packages and import them into tests, you can read here.
Brownie includes a graphical interface for viewing data on test coverage and analyzing the compiled bytecode of your contracts.
Important! If you have problems loading the gui, you probably haven't installed Tkinter.
After entering the command brownie gui
, a graphical interface will launch where you can select a contract. Then, tabs for all contracts and libraries imported into your contract will appear. In addition, on the right column, there will be opcodes and program counters.
Additionally, you can highlight sections of the code and view corresponding opcodes.
To generate a test coverage report, you need to call the command brownie test --coverage
.
Afterwards, in the graphical interface, you can select the report (Report) and specify the mode - branches or statements.
Below is an image showing how test coverage appeared in our graphical interface:
You can read more in detail here.
To run tests, use the command brownie test
.
When writing tests, it's important to focus on key functions:
- Fixtures (a function applied to one or several test functions, called before each test execution)
- Markers (a decorator applied to a test function. For example, you can specify that a test should run only when launched in a certain network)
- Parametrizing Tests (essentially the same as the marker mentioned above, but it allows specifying function arguments as an array. You can set several sets of arguments, and the function will be called multiple times with different arguments)
It's also worth noting that you can install a package using the pm
command, after which it can be imported via fixtures and deploy a specific contract. This way, you can test the interaction of your project with other projects.
Property-based testing is a powerful tool for identifying edge cases and detecting erroneous assumptions in your code.
The main concept of property-based testing is that instead of writing a test for one scenario, you write tests that describe a range of scenarios, and then let your computer explore the possibilities for you, instead of writing each one manually.
The process consists of steps:
- Choose a function in your smart contract you want to test.
- Specify a range of input data for this function that should always produce the same result.
- Call the function with random data from your specification.
- Conduct a test result check.
Using this method, each test runs many times with different random data. If a case is found where the test fails, an attempt is made to find the simplest possible case that still causes the problem. This example is saved in a database and repeated in each subsequent test to ensure that once the problem is fixed, it stays fixed.
This way, you can test necessary functions with a range of arguments, find vulnerabilities, and fix them. You can read more about setting up such tests here.
Stateful testing is an advanced property-based testing method used for testing complex systems. It uses Hypothesis.
Hypothesis is a library for Python, used for automatic "property-based testing". Unlike the traditional approach to testing, where tests are manually written for specific input data and expected results, Hypothesis automatically generates test cases.
In Hypothesis, you define "properties" that your code should satisfy, and the library attempts to "disprove" these properties by generating various input data. If the library finds input data that leads to a property violation, it reports this, providing an example that causes the error. This allows developers to discover and fix potential problems and edge cases in their code that they might not have suspected when manually writing tests.
Hypothesis is particularly useful for testing functions with a wide range of possible inputs and finding rare but critical cases that can lead to program failures or errors.
Important! This feature is still in development and is considered experimental.
You can learn more here.
Brownie integrates the MythX analysis tool, which allows for automated security checks of your project.
MythX offers both free and paid services.
First, you need to register at MythX and obtain an API key. After that, you need to specify this key using the command export MYTHX_API_KEY=YourToken
or through the flag brownie analyze --api-key=<string>
.
Next, to start the scanning, you can use the built-in command brownie analyze
.
To view the result in the graphical interface immediately after scanning, use the command brownie analyze --gui
.
More details on how scanning works are explained here.
-
Brownie supports the popular Hardhat framework.
To use it, install it using the command
npm install --save-dev hardhat
.Now, when we want to use the local hardhat network, we can use the command
--network hardhat
. For example, launch the console withbrownie console --network hardhat
. -
Brownie supports the very fast local network Anvil, which is a tool of the Foundry framework. See how to install it here. After installation, we can use this network
--network anvil
.