Brownie
Setup eth-brownie
Let us build a simple web3 application using Brownie and Ganache for simple storage application which stores any given value.
create a folder for project and then open vs code in that directory.
mkdir brownie_simple_storage
code brownie_simple_storage
(Recommended) Install brownie with pipx
for virtual environment.
Just go to the terminal and run the following:
python3 -m pip install --user pipx
python3 -m pipx ensurepath
And then install brownie.
pipx install eth-brownie
Check if installed correctly using
brownie --version
Setup Ganache
Install ganache from it’s website and spawn a local blockchain for development purposes.
Create a Project
Required steps:
- Create folder structure
- Write a contract
- Compile the contract
- Write deployment script
Run brownie init
to create a series of folders for project. It may create the following folders:
+ build
+ contracts (saves all built contracts)
+ deployments (automates all deployments)
+ interfaces
+ contracts (all contracts are written here)
+ interfaces (makes it super easy to interact with other apps)
+ reports (basic reports)
+ scripts (automation scripts)
+ tests (tests all contracts)
+ .gitattributes
+ .gitignore
Then write your contract in the contracts
folder in main level.
contracts/SimpleStorage.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.9.0;
contract SimpleStorage {
uint256 favoriteNumber;
// This is a comment!
struct People {
uint256 favoriteNumber;
string name;
}
People[] public people;
mapping(string => uint256) public nameToFavoriteNumber;
function store(uint256 _favoriteNumber) public {
favoriteNumber = _favoriteNumber;
}
function retrieve() public view returns (uint256){
return favoriteNumber;
}
function addPerson(string memory _name, uint256 _favoriteNumber) public {
people.push(People(_favoriteNumber, _name));
nameToFavoriteNumber[_name] = _favoriteNumber;
}
}
and then run
brownie compile
You may see json
file for the contracts in the contracts dir.
Deployment Script
goto scripts
folder and write a simple deployment script.
In
web3.py
version we did everything manually which are automated for us.
- Compilation
- Dumped to a file
- ABI & Bytecode
- Spawing & stopping ganache
- Get private & public addresses
- deploy simple storage
scripts/deploy.py
def deploy_simple_storage():
...
def main():
deploy_simple_storage()
and run brownie run scripts/deploy.py
brownie independantly create a ganache server and turns it down after use. Everything is made simple for us.
Here, we need to add our private account (which will be encrypted by brownie). Go to terminal and run:
brownie accounts new {name-of-account}
It will ask to enter your private key (securely), export it from metamask and paste it in the terminal input section.
Now, we will have a new account natively integrated with brownie. See it by running
brownie accounts list
Remove any account by running:
brownie accounts delete {my-account-name-2}
Now, just access the account def deploy_simple_storage
by using the following code. Note that it will ask for private key to match the password and by following this approach we are not exposing our keys in github history / internet.
scripts/deploy.py
from brownie import accounts
def deploy_simple_storage():
account = accounts.load("my-account-name")
print("safely encrypted:", account)
# the above line will pront the public key, not private.
# if you want to randomly use any account, you may
# instead use
account = accounts[0]
print("randomly generated:", account)
def main():
deploy_simple_storage()
And then run brownie run scripts/deploy.py
.
Another way to do this (less securely) is to use .env
files and mapping the file using brwonie-config.yaml
in main directory. If done so, we just need to use inside brwonie-config.yaml
:
dotenv: .env
inside .env
export PRIVATE_KEY=0xhasbdjjuwfoiQDOIWCFOIACS324
from brownie import accounts
def deploy_simple_storage():
account = accounts.add(os.getenv("PRIVATE_KEY))
Alternatively by adding couple of lines in the brwonie-config.yaml
file, we can do the same thing more explicitly.
dotenv: .env
wallets:
from_key: ${PRIVATE_KEY}
from brownie import config
from brownie import accounts
def deploy_simple_storage():
account = accounts.add(config["wallets"]["from_key"])
Deploy
Brownie is pretty intelligent, just import the written contract by name and then deploy it using .deploy("from": account)
. Now, use the deploy.py
script to do so:
from brownie import config, accounts, SimpleStorage
def deploy_simple_storage():
# grab account address
account = accounts[0]
# transaction needs from account
simple_storage = SimpleStorage.deploy("from": account)
# call does not need from account
stored_value = simple_storage.retrieve()
print(stored_value)
# transaction
transaction = simple_storage.store(15, {"from":account})
transaction.wait(1) # 1 is number of blocks
# check updates
stored_value = simple_storage.retrieve()
print(stored_value)
There are two types of functions - either call or transaction. View functions are call functions (eg.
retrieve
) that do not change the state of blockchain and just shows number.
Tests
Goto the tests
directory and create test_simple_storage.py
. Good tests are essential so let us write some tests for our contract. A test can be divided into one of these categories
- Arrange
- Act
- Assert
tests/test_simple_storage.py
from brownie import accounts, SimpleStorage
def test_deploy():
# arrange
account = accounts[0]
# act
simple_storage = SimpleStorage.deploy({"from": account})
starting_value = simple_storage.retrieve()
expected_value = 0
# assert
assert starting_value == expected_value
def test_updating_storage():
# arrange
account = accounts[0]
simple_storage = SimpleStorage.deploy({"from": account})
# act
expected_value = 15
transaction = simple_storage.store(expected_value, {"from":account})
# assert
assert expected_value == simple_storage.retrieve()
And then run
brownie test
to run all tests. Otherwise run brownie test -k test_updating_storage
for running specific test. If you want to debug by spawning a terminal when test case fails, use:
brownie test --pdb