The most commonly used language to interact with Ethereum Smart Contracts is JavaScript. However, JavaScript has some very notable flaws (taken from https://www.destroyallsoftware.com/talks/wat check it out if you want a good laugh):
1
2
3
4
5
6
> [] + []
< ''
> [] + {}
< [object Object]
> {} + []
< 0
Python is not perfect but might be a better solution to your problem. Luckily, Ben Hauser created the Python brownie
package that allows you to compile, run, and deploy Solidity and Vyper smart contracts for the Ethereum blockchain.
Prerequisites
Even though you might not be a fan of JavaScript, you need Node.js and the ganache-cli to run a local Ethereum test network. So make sure to install a recent version of node
together with its package manager npm
. Afterward, install the ganache-cli
globally:
1
npm install -g ganache-cli
Setting up Brownie in a Python Virtual Environment
To keep your Python setup “clean,” install brownie
into a virtual environment. So first, make a new directory, enter it and create a virtual Python environment:
1
2
3
mkdir brownie_project
cd brownie_project
python -m venv .venv
Then you have to activate the virtual environment. When you are on Linux or macOS, the following command will activate the virtual environment:
1
source .venv/bin/activate
In the Windows 10/11 PowerShell you have to use this command instead:
1
.\.venv\Scripts\activate
When your virtual environment is active, it’s time to install brownie through pip:
1
python -m pip install eth-brownie
Due to the .venv
directory, your project directory is not empty, and therefore the command to initialize a new brownie project brownie init
will fail. However, with the --force
flag, you can convince brownie
to initialize a project:
1
brownie init --force
Now brownie
has created several files and directories:
build/
: Project data such as compiler artifacts and unit test resultscontracts/
: Contract sources (Solidity and Vyper files)interfaces/
: Interface sourcesreports/
: JSON report files for use in the GUIscripts/
: Scripts for deployment and interactiontests/
: Scripts for testing the project.gitignore
/.gitattribute
: Files for a git repository
The settings for a brownie
project can be changed with the brownie-config.yaml
. For this article, set the Solidity version to 0.6.4
. To do so, created a brownie-config.yaml
file at the root of your brownie
project and add the following content:
1
2
3
compiler:
solc:
version: 0.6.4
Add a Solidity Smart Contract to a Python Brownie Project
You can add your first smart contract now with the brownie
project setup done. For this article, you will use a faucet as an example. A faucet is a smart contract that allows you to withdraw Ether from it and transfer Ether to it. This example is adapted from the excellent book Mastering Ethereum (Affiliate Link):
// Version of Solidity compiler this program was written for
pragma solidity 0.6.4;
// Our first contract is a faucet!
contract Faucet {
// Accept any incoming amount
receive() external payable {}
// Give out ether to anyone who asks
function withdraw(uint withdraw_amount) public {
// Limit withdrawal amount
require(withdraw_amount <= 100000000000000000);
// Send the amount to the address that requested it
msg.sender.transfer(withdraw_amount);
}
fallback() external payable {}
}
There are three functions: receive
, withdraw
, and fallback
. When receive
is called the Ether specified in the transaction is transferred to the contract. fallback
works in the same way as receive
, however, it is called as a default when there is a transfer to the contract without a function specified. Lastly, withdraw
transfers the specified amount of Wei to the address that called the contract if the amount is less than 100000000000000000 Wei = 0.1 Ether.
Create the file contracts/Faucet.sol
and copy the whole code into it.
Compile a Solidity Smart Contract in a Python Brownie Project
To check if the code in the contracts
compiles execute the following command in the root directory of the project:
1
brownie compile
The output of this command should look like this:
1
2
3
4
5
6
7
8
9
10
Brownie v1.17.2 - Python development framework for Ethereum
Compiling contracts...
Solc version: 0.6.4
Optimizer: Enabled Runs: 200
EVM Version: Istanbul
Generating build data...
- Faucet
Project has been compiled. Build artifacts saved at brownie_project/build/contracts
The last line of the output tells you that the build artifacts of the solidity compiler have been placed inside build/artifacts
. In there, you will find a JSON file, Faucet.json
, that holds all the necessary data to deploy and run this smart contract.
Test Deploy a Solidity Smart Contract with Python Brownie
To test and interact with the Faucet smart contract on your local Ethereum network you can either use the brownie
command line interface by running:
1
brownie console
This will open a command line that allows you to run Python code in the same way as the Python interactive mode with the small addition that brownie
started a ganache
instance in the background that you can interact with. Per default, ganache
creates ten test accounts you can use. Here is an example of how to transfer Ether between accounts using brownie
:
1
2
3
4
5
6
7
8
9
10
>>> from brownie import accounts
>>> accounts[0].balance()
100000000000000000000
>>> accounts[1].balance()
100000000000000000000
>>> accounts[0].transfer(to=accounts[1], amount="1 ether")
>>> accounts[0].balance()
99000000000000000000
>>> accounts[1].balance()
101000000000000000000
But for complex programs such as interaction with a contract, brownie's
console is not the right tool. Therefore you can also write Python code and run it with brownie
and let it interact with your local Ethereum network. Place the following code into scripts/faucet.py
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from brownie import accounts, Faucet, web3
def main():
act = accounts[0]
f = Faucet.deploy({'from': act})
print(f"Faucet at: {f.address}")
print(f"Balance: {f.balance()}")
print("")
print(f"Account: {act.address}")
print(f"Balance: {act.balance()}")
print("")
print("Transfer ether to Faucet:")
act.transfer(f.address, "1 ether")
print(f"Faucet at: {f.address}")
print(f"Balance: {f.balance()}")
print("")
print(f"Account: {act.address}")
print(f"Balance: {act.balance()}")
print("")
print("withdraw from Faucet")
f.withdraw(web3.toWei(0.1, "ether"), {'from': act})
print(f"Faucet at: {f.address}")
print(f"Balance: {f.balance()}")
print("")
print(f"Account: {act.address}")
print(f"Balance: {act.balance()}")
print("")
Every contract you place into contracts/
brownie
provides a class you can use to deploy and interact with the contract. In the code example above, the Faucet
class is used to deploy the contract and get its address and balance after deployment. And the best feature is that you can call the smart contract functions as Python object functions. In line 25 the function withdraw
is called to transfer 0.1 Ether from the faucet to the account.
To run this Python code in faucet.py
, enter the following command at the root directory of the project:
1
brownie run faucet.py
You will now see the interaction of the account and the faucet on the command line like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Running 'scripts\faucet.py::main'...
Transaction sent: 0x64575fc4494d5e72be7352ff5af9cd8bc740648fdee7818a08501655f975da77
Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0
Faucet.constructor confirmed Block: 1 Gas used: 95493 (0.80%)
Faucet deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87
Faucet at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87
Balance: 0
Account: 0x66aB6D9362d4F35596279692F0251Db635165871
Balance: 100000000000000000000
Transfer ether to Faucet:
Transaction sent: 0x4724838bb2e89c4585fa0eeffcca701686fa98425e2e12fc5940cb25cdd7cb8a
Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 1
Transaction confirmed Block: 2 Gas used: 21055 (0.18%)
Faucet at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87
Balance: 1000000000000000000
Account: 0x66aB6D9362d4F35596279692F0251Db635165871
Balance: 99000000000000000000
withdraw from Faucet
Transaction sent: 0x1807d3afa2919ba1d300d8cb98d9af4cfe1ca178e6991f998265fed8ca597610
Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 2
Faucet.withdraw confirmed Block: 3 Gas used: 28935 (0.24%)
Faucet at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87
Balance: 900000000000000000
Account: 0x66aB6D9362d4F35596279692F0251Db635165871
Balance: 99100000000000000000
And this is how you test deploy a Solidity smart contract with the Python brownie
package.
Using Python Brownie with VSCode
Everything you have done so far took place on the command line. However, if you write complex smart contracts and Python code, you want to use an IDE. Gladly, VSCode allows you to do that.
To write Solidity code and get syntax highlighting and linting in VSCode install the Solidity Extension by Juan Blanco. After installing the extension, set the Solidity version VSCode should use for checking your contract’s code. To do that, open the command palette with Ctrl+Shift+P or Cmd+Shift+P on macOS and enter settings and choose Preferences: Open Workspace Settings (JSON). In the now open .vscode/settings.json
add the following key-value pair:
1
"solidity.compileUsingRemoteVersion": "v0.6.4+commit.1dca32f3"
You can always return to this to change it to a different Solidity version. Additionally, add the following key-value pair as well to tell VSCode to use the virtual environment you created earlier when executing Python code:
1
"python.terminal.activateEnvironment": true
Your .vscode/settings.json
should look like this now:
1
2
3
4
{
"python.terminal.activateEnvironment": true,
"solidity.compileUsingRemoteVersion": "v0.6.4+commit.1dca32f3"
}
To run the brownie run faucet.py
command from VSCode, you need to add a task. Open the command palette again, enter task, and choose Tasks: Configure Task -> Create tasks.json from file template -> Others this creates the file .vscode/tasks.json
. Remove the default task echo and add a new task such that the file looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version": "2.0.0",
"tasks": [
{
"label": "Run Faucet",
"type": "shell",
"command": ".venv/bin/activate; brownie run faucet.py",
"windows": {
"command": ".\\.venv\\Scripts\\activate; .venv\\Scripts\\brownie.exe run faucet.py"
}
}
]
}
The new task Run Faucet first activates the virtual environment and then executes the brownie run faucet.py
command. To run this task from VSCode open the command palette once again and enter task and choose: Task: Run Task -> Run Faucet -> Continue without scanning the task output this runs the faucet.py
using brownie
in the terminal of VSCode.
You can find all the code of this article also in a GitHub repository and if you have any questions about this article, feel free to join our Discord community to ask them over there.