This is an introductory article to Brownie, a smart contract development and testing framework for Solidity and Vyper, especially with Python developers in mind.
We first start with the installation process and then create a project with a simple smart contract. We will look at how to interact with the smart contract on a local blockchain using the built-in console. Finally, we will look at how to run a unit test.
What is Brownie?
Brownie is a popular smart contract development and testing framework for the Ethereum Virtual Machine, supporting Solidity and Vyper as the smart contract languages.
It is Python-based, meaning that it uses various Python libraries, such as web3.py and pytest, and uses Python to write scripts. It also has a built-in console similar to the Python interpreter to interact with smart contracts.
All in all, if you are a Python developer and a fan of Pythonic style, you will probably feel more comfortable using Brownie than other JavaScript-based frameworks, such as Truffle or Hardhat.
How to Install Brownie?
The Brownie documentation recommends to use pipx
to install Brownie. So, letβs install pipx
first unless you already have it on your machine.
$ python3 -m pip install --user pipx $ python3 -m pipx ensurepath
Then, install Brownie.
$ pipx install eth-brownie installed package eth-brownie 1.17.1, Python 3.9.1 These apps are now globally available - brownie done! β¨ π β¨
We can check that Brownie has been installed successfully by running the brownie command:
$ brownie --version Brownie v1.17.1 - Python development framework for Ethereum
How to use Brownie
In this section, we will look at Brownieβs basic functionality, such as:
- Create a project from scratch
- Use the console to interact with the smart contract
- Run unit tests
How to create a Brownie project
To create a project, run the command brownie init in an empty directory. Brownie has a template system called Brownie mixes, which we can use to create a project for specific purposes, such as ERC-20 token, NFT, etc. But in this article, we will start from an empty project and create a very simple smart contract so that we can understand the basic functionality better.
In the following example, we create a new directory called brownie_test
in the home directory and run brownie init
inside the new directory.
[~]$ mkdir brownie_test [~]$ cd brownie_test [~/brownie_test]$ brownie init Brownie v1.17.1 - Python development framework for Ethereum SUCCESS: A new Brownie project has been initialized at /Users/mikio/brownie_test
The command creates the default directory structure, as shown below.
[~/brownie_test]$ tree . . βββ build β βββ contracts β βββ deployments β βββ interfaces βββ contracts βββ interfaces βββ reports βββ scripts βββ tests
How to compile a smart contract
First, we need a smart contract. Letβs take an example from the Solidity documentation. Save this smart contract in the contracts directory as storage.sol
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.16 <0.9.0; contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public view returns (uint) { return storedData; } }
We can run the brownie compile command to compile the smart contract.
[~/brownie_test]$ brownie compile Brownie v1.17.1 - Python development framework for Ethereum Compiling contracts... Solc version: 0.8.10 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - SimpleStorage Project has been compiled. Build artifacts saved at /Users/mikio/brownie_test/build/contracts
Please note the name of the smart contract (SimpleStorage) because we will need it in the next section.
Brownie automatically compiles smart contracts (if there are any changes) when starting the console or running tests, so we donβt usually need to run the brownie compile command unless we want to compile the code manually.
How to use the Brownie console
Brownie offers the built-in console to interact with the local blockchain and smart contracts, like executing Python code using the Python interpreter.
Letβs start the console by running the brownie console command.
[~/brownie_test]$ brownie console Brownie v1.17.1 - Python development framework for Ethereum BrownieTestProject is the active project. Launching 'ganache-cli --port 8545 --gasLimit 12000000 --accounts 10 --hardfork istanbul --mnemonic brownie'... Brownie environment is ready. >>>
If you are a Python developer, the prompt >>>
should be familiar to you.
In the command output, we can also see that it automatically starts a local blockchain (Ethereum simulator) using Ganache CLI. It creates 10 test accounts by default, which we can access via the object accounts.
>>> accounts [<Account '0x66aB6D9362d4F35596279692F0251Db635165871'>, <Account '0x33A4622B82D4c04a53e170c638B944ce27cffce3'>, <Account '0x0063046686E46Dc6F15918b61AE2B121458534a5'>, <Account '0x21b42413bA931038f35e7A5224FaDb065d297Ba3'>, <Account '0x46C0a5326E643E4f71D3149d50B48216e174Ae84'>, <Account '0x807c47A89F720fe4Ee9b8343c286Fc886f43191b'>, <Account '0x844ec86426F076647A5362706a04570A5965473B'>, <Account '0x23BB2Bb6c340D4C91cAa478EdF6593fC5c4a6d4B'>, <Account '0xA868bC7c1AF08B8831795FAC946025557369F69C'>, <Account '0x1CEE82EEd89Bd5Be5bf2507a92a755dcF1D8e8dc'>]
We can access each account just like a Python list. For example, the first account is accounts[0]
, the second account is accounts[1]
, etc.
>>> accounts[0] <Account '0x66aB6D9362d4F35596279692F0251Db635165871'> >>> accounts[1] <Account '0x33A4622B82D4c04a53e170c638B944ce27cffce3'>
We can see the Ether balance of each account by using the method balance() as shown below.
>>> accounts[0].balance() 100000000000000000000 >>> web3.fromWei(accounts[0].balance(), 'ether') Decimal('100')
We can access the smart contract we compiled in the previous section by the smart contract name (SimpleStorage
).
>>> SimpleStorage []
But to use this smart contract, we first need to deploy it using the deploy method. The following example uses the first account (accounts[0]
) to deploy the smart contract.
>>> simple_storage = SimpleStorage.deploy({'from': accounts[0]}) Transaction sent: 0xce078c4b3d092d945a56a9b3991e6c9a3a9b712e6ab88dafbcbfe03f1f714f99 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 SimpleStorage.constructor confirmed Block: 1 Gas used: 90539 (0.75%) SimpleStorage deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87
The deploy method returns a Contract
object.
>>> simple_storage <SimpleStorage Contract '0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87'>
We can now run the functions in the smart contract. For example, letβs call the function get()
to check the current storedData
value.
>>> simple_storage.get() 0
Then, we can send a transaction to execute the function set()
to update the storedData
value, for example, to 5.
>>> tx = simple_storage.set(5) Transaction sent: 0x74b1553a551c617d9c631fe4c92e0144faeaa8df426a38ab2980ac02231a26af Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 1 SimpleStorage.set confirmed Block: 2 Gas used: 41394 (0.34%)
The return value is a Transaction
object, and we can find more details using the method info()
.
>>> tx.info() Transaction was Mined --------------------- Tx Hash: 0x74b1553a551c617d9c631fe4c92e0144faeaa8df426a38ab2980ac02231a26af From: 0x66aB6D9362d4F35596279692F0251Db635165871 To: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 Value: 0 Function: SimpleStorage.set Block: 2 Gas Used: 41394 / 12000000 (0.3%)
We can check the storedData
value by calling the function get()
again.
>>> simple_storage.get() 5
We can exit the console by running quit()
, just like the Python interpreter.
>>> quit() Terminating local RPC client...
How to run tests in Brownie
Brownie uses pytest to make unit tests more accessible. Letβs create a simple test file test_storage.py
in the directory tests
.
# File: test_storage.py # def test_storage(SimpleStoage, accounts): simple_storage = SimpleStorage.deploy({'from': accounts[0]}) assert simple_storage.get() == 0 simple_storage.set(5) assert simple_storage.get() == 5
The test file is a pytest
file, which means the usual pytest
conventions apply, such as:
- The test file names start with
test_
- The test function names start with
test_
. - Assertions are done by the
assert
statement
Brownie automatically creates a fixture for our smart contract (SimpleStorage
) and the account object (accounts). So, we can use them by specifying the fixture names (SimpleStorage
, accounts) in the function arguments.
If youβre not familiar with pytest
, you might find the following articles helpful:
Then, we deploy the contract and execute the functions, as we did on the Brownie console in the previous section. In this test, we first assert that the storedData
value is 0. Then, after setting the value to 5 by executing the function set(5)
, assert that the value has been changed to 5.
Now, go back to the console and run the command brownie test
.
[~/brownie_test]$ brownie test Brownie v1.17.1 - Python development framework for Ethereum ==================== test session starts ==================== platform darwin -- Python 3.9.1, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 rootdir: /Users/mikio/brownie_test plugins: eth-brownie-1.17.1, web3-5.24.0, hypothesis-6.24.0, xdist-1.34.0, forked-1.3.0 collected 1 item Launching 'ganache-cli --port 8545 --gasLimit 12000000 --accounts 10 --hardfork istanbul --mnemonic brownie'... tests/test_storage.py . [100%] ===================== 1 passed in 2.21s ===================== Terminating local RPC client...
We can see the pytest
output, which shows that the test has passed. Like pytest
, using the -v
option adds more information to the output.
[~/brownie_test]$ brownie test -v Brownie v1.17.1 - Python development framework for Ethereum ==================== test session starts ==================== platform darwin -- Python 3.9.1, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- /Users/mikio/.local/pipx/venvs/eth-brownie/bin/python cachedir: .pytest_cache hypothesis profile 'brownie-verbose' -> verbosity=2, deadline=None, max_examples=50, stateful_step_count=10, report_multiple_bugs=False, database=DirectoryBasedExampleDatabase(PosixPath('/Users/mikio/.brownie/hypothesis')) rootdir: /Users/mikio/brownie_test plugins: eth-brownie-1.17.1, web3-5.24.0, hypothesis-6.24.0, xdist-1.34.0, forked-1.3.0 collected 1 item Launching 'ganache-cli --port 8545 --gasLimit 12000000 --accounts 10 --hardfork istanbul --mnemonic brownie'... tests/test_storage.py::test_storage PASSED [100%] ===================== 1 passed in 2.33s ===================== Terminating local RPC client...
Summary
In this article, we looked at the basics of Brownie, a popular Python-based smart contract development and testing framework for Solidity and Vyper.
We first looked at how to install Brownie and then created a new project from scratch. We then looked at how to interact with the smart contract on the local blockchain using the console. Finally, we leant how to run unit tests.
The next step would be to deploy the smart contract to a public testnet, but it will be covered in a future article.
I hope this article has been helpful to you. You can find more information about Brownie in the Brownie documentation.
Learn Solidity Course
Solidity is the programming language of the future.
It gives you the rare and sought-after superpower to program against the “Internet Computer”, i.e., against decentralized Blockchains such as Ethereum, Binance Smart Chain, Ethereum Classic, Tron, and Avalanche – to mention just a few Blockchain infrastructures that support Solidity.
In particular, Solidity allows you to create smart contracts, i.e., pieces of code that automatically execute on specific conditions in a completely decentralized environment. For example, smart contracts empower you to create your own decentralized autonomous organizations (DAOs) that run on Blockchains without being subject to centralized control.
NFTs, DeFi, DAOs, and Blockchain-based games are all based on smart contracts.
This course is a simple, low-friction introduction to creating your first smart contract using the Remix IDE on the Ethereum testnet – without fluff, significant upfront costs to purchase ETH, or unnecessary complexity.