Home Compile and Interact with Solidity Smart Contracts with the Python brownie Package and VSCode
Post
Cancel

Compile and Interact with Solidity Smart Contracts with the Python brownie Package and VSCode

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 results
  • contracts/: Contract sources (Solidity and Vyper files)
  • interfaces/: Interface sources
  • reports/: JSON report files for use in the GUI
  • scripts/: Scripts for deployment and interaction
  • tests/: 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.

This post is licensed under CC BY 4.0 by the author.