The Digitalist Team
January 16, 2023

Automating the testing and deployment of Solidity smart contracts

Have you ever wondered what tools are required for working with smart contracts? Several questions arise. Are there Integrated Development Environments and frameworks that are specially designed for contracts? And what about testing and deployment? How do developers test their contracts? How do they deliver them?

This post offers a practical guide on how to conduct testing, deploy, and interact with Solidity smart contracts in brownie, a Python-based framework designed specifically for smart contract development. With brownie, you can run concise deployment scripts, write tests in PyTest and interact with live contracts, too.

NOTE: It is assumed that the reader has some basic terminal skills, as well as some experience in Python and Solidity Smart Contracts. For anyone who is unfamiliar with these technologies, you will find some learning material at the end of the article.

Problems with Solidity tools

When I wanted to deploy my first Solidity smart contract, I came across the Remix IDE. With full Solidity support, the ability to test and deploy contracts to local and public blockchains, and having a built-in debugger, the Remix IDE is rightfully regarded as one of the best Solidity IDEs.

After my first experiments with this new tool, though, I realized that there is a learning curve to getting efficient at it. I found it cumbersome to work with external blockchain providers and manually to interact with the deployed contract. The Remix console uses JavaScript, which I am not fond of.

These impressions made me ask:

  1. How can I develop a contract from scratch, in a test-driven manner?
  2. What is the leanest way to deploy these contracts and interact with them?
  3. How can I automate operations tasks relating to contracts and integrate them into (existing) Continuous Integration/Continuous Delivery processes?

Brownie

While searching for answers, I found 3 comparable frameworks, which offer most of the features I need. Truffle is the most mature of the three and certainly has the most features. Hardhat is a more modern tool, which focuses on debugging. Both of these tools are written in JavaScript. Brownie is also a decent alternative written in Python.

I opted for brownie, because it has a Command Line Interface, an interactive console, it uses pytest for testing and it makes deployments with different network and account configurations easy. Another consideration was that Python is my scripting language of choice. This allowed me to use familiar tools to solve new problems.

Source code and Developer environment

I set up a git repository on codeberg.org, a platform which helps you get started with the framework. It guides you through setting up the environment you need to be able to follow this article and contains most of the source code that it mentions.

The IDE I am using is VSCode with the Solidity plugin installed. The plugin has many features, such as syntax highlighting, linting and even contract compilation. For this last aspect and for everything else, I use brownie.

You may skip the practical part and read the article as it is. But if you want to follow the practical guide, you may clone the repo now and follow its README.md until your development environment is set up.

git clone https://codeberg.org/tardigrde/mndwrk-brownie-blogpost.git

Lottery smart contract

The contract being worked with is a dead-simple Lottery application. Users can enter the lottery by paying a certain amount of ether to the contract. The owner of the contract can decide when they pick a winner. The lottery can be played repeatedly.

Find the Lottery contract in the attached git repository. It is based on jspruance's' block-explorer-tutorials/Lottery.sol, with some added functionality and clarifying comments.

Brownie console

Let us now dive into the fun part by trying out the brownie console. It is a REPL, similar to Python's, that helps you quickly interact with local or non-local chains. It is the fastest way to familiarize yourself with the brownie API, as it is the best place to make quick experiments to test new ideas or 3rd-party contracts.

After activating the Python environment, invoke the console using:


$ brownie console

NOTE: You can actually start the console on different kinds of local and public, so-called live chains. Network and corresponding account management is outside the scope of this article. If you want to deploy contracts on live chains, refer to the docs.

When the console is started, brownie starts and connects to a ganache instance, which is basically a local snapshot of the Ethereum blockchain. It also provides 10 pre-created accounts which you can use for testing your contracts.


>>> from brownie import accounts
>>> len(accounts)
10

We will use the first account as the owner of the contract.

We may import the Lottery class, which provides a Pythonic API for the contract.


>>> from brownie import Lottery

Then we can deploy the Lottery contract by calling the class' built-in deploy method with constructor parameters. Lottery has no such parameters, but we must pass at least a config dictionary. The from key's value tells brownie which account we will deploy the contract with.


>>> lottery = Lottery.deploy({"from": owner})
Transaction sent: 0x04d5a2db4f8ca20fb0279a0d8f74b3a6e45c8630e4758654304e8995a05160b6  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0  
Lottery.constructor confirmed   Block: 1  Gas used: 427318 (3.56%)  
Lottery deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87

Let's stop here to inspect the output. We can see both the transaction hash and the contract address. Feel free to inspect these fields in the console.


lottery.address
lottery.tx
lottery.tx.status

Meanwhile, we can call public view functions to get some metadata about the lottery.


>>> lottery.getPlayers()
()
>>> lottery.getBalance()
0
>>> lottery.cost() # in WEI
10000000000000000

Now let's enter the lottery with the second account in the accounts list.


>>> lottery.enter({"from": accounts[1], "value": ".01 ether"})
# ...hidden output...

Note the new value key (unlucky naming) passed in the config dictionary. What it means is 0.01 ether was included in the transaction, which is the participation cost.

Let's call those view functions again.


>>> lottery.getPlayers()
("0x33A4622B82D4c04a53e170c638B944ce27cffce3")
>>> lottery.getBalance()
10000000000000000
>>> assert lottery.getPlayers()[0] == accounts[1]
>>>

We got the first passing assert statement, which is an invitation to write a unit test. You may also leave the console now.

Testing

Brownie uses pytest under the hood. You can write your usual unit tests like so:


def test_enter():
	lottery = Lottery.deploy({"from": accounts[0]})    
	assert len(lottery.getPlayers()) == 0    
	lottery.enter({"from": accounts[1], "value": ".01 ether"})
	assert len(lottery.getPlayers()) == 1    
	assert lottery.getPlayers()[0] == accounts[1]

Put this into a pytest file e.g. ./tests/test_lottery.py (see conventions for test discovery), and call:


$ brownie test

The tests can be extremely helpful for implementing new features, while avoiding regression issues. They give confidence that the contract we or a 3rd wrote works as expected.

Write tests until you are happy with the coverage and results, then commence with deployment.

Running scripts

You can write scripts to deploy contracts and interact with them in brownie. Consider the simplest form of the deployment script under ./scripts/lotter.py:


from brownie import accounts, Lottery

def main():
	Lottery.deploy({"from": accounts[0]})

You can execute the function called main with brownie run, like so:


$ brownie run scripts/lottery.py

The local ganache instance is torn down after the script concludes. If you want to interact with the deployed contract, you have to write extra code after deployment, or run the script on live chains. We will go with the former.

Lottery simulation

In this last section, we will conduct a lottery simulation. The code we wrote during the development and testing phase can be reused here.

The following script deploys the lottery, enters with all the 10 pre-created test accounts, and finally announces a winner.


def main():
	# deploy the contract    
  lottery = Lottery.deploy({"from": accounts[0]})‍
  
  # enter with test accounts    
  for i in range(10):        
  	lottery.enter(            
    	{"from": accounts[i], "value": ".01 ether"}      
      	)‍
        
    print(f"Players: {lottery.getPlayers()}")   
    print(f"Lottery balance: {lottery.getBalance()}")‍
    
    # pick winner    
    lottery.pickWinner()
    
    print(f"The winner of the first lottery is: {lottery.getWinnerByLotteryId(1)}")
    

Running this with brownie run scripts/simulate_lottery.py produces a lot of output on the terminal. I encourage you to analyze it.

Conclusion

In this article, I attempted to demonstrate how you can use Solidity frameworks, like brownie, to make contract development easier.

Although brownie is a relatively new tool too, I have found I can leverage my Python and pytest know-how to help flatten the learning curve. It also allows me to integrate tests and scripts related to smart contracts in already existing projects.

I hope reading this article inspires you to try out brownie or its alternatives. At the very least, I hope it gives you a glimpse into how smart contract development works.

Sources

Brownie docs

Solidity docs

pytest docs

Remix IDE docs

Framework comparisons:

Web3: Truffle VS Hardhat VS Embark VS Brownie

Write a Simple Smart Contract in Truffle, Hardhat and Brownie

Learning material

Learn Shell - Free Interactive Shell Tutorial

Learn Python - Free Interactive Python Tutorial

CryptoZombies - Free Solidity Tutorial & Ethereum Blockchain Programming Course

Levente Csőke
by
Csőke Levente
DevOps / Backend engineer

Levente Csőke has 4 years of experience in multi-tier web application development. Currently, he works as a DevOps/Backend engineer by day and experiments with web3 technologies by night. He started working with Solidity a year ago and immediately fell in love with the concept of Smart Contracts. Recently he has also discovered that he enjoys writing articles about things he learned.

LinkedIn

January 16, 2023

Automating the testing and deployment of Solidity smart contracts

Have you ever wondered what tools are required for working with smart contracts? Several questions arise. Are there Integrated Development Environments and frameworks that are specially designed for contracts? And what about testing and deployment? How do developers test their contracts? How do they deliver them?

This post offers a practical guide on how to conduct testing, deploy, and interact with Solidity smart contracts in brownie, a Python-based framework designed specifically for smart contract development. With brownie, you can run concise deployment scripts, write tests in PyTest and interact with live contracts, too.

NOTE: It is assumed that the reader has some basic terminal skills, as well as some experience in Python and Solidity Smart Contracts. For anyone who is unfamiliar with these technologies, you will find some learning material at the end of the article.

Problems with Solidity tools

When I wanted to deploy my first Solidity smart contract, I came across the Remix IDE. With full Solidity support, the ability to test and deploy contracts to local and public blockchains, and having a built-in debugger, the Remix IDE is rightfully regarded as one of the best Solidity IDEs.

After my first experiments with this new tool, though, I realized that there is a learning curve to getting efficient at it. I found it cumbersome to work with external blockchain providers and manually to interact with the deployed contract. The Remix console uses JavaScript, which I am not fond of.

These impressions made me ask:

  1. How can I develop a contract from scratch, in a test-driven manner?
  2. What is the leanest way to deploy these contracts and interact with them?
  3. How can I automate operations tasks relating to contracts and integrate them into (existing) Continuous Integration/Continuous Delivery processes?

Brownie

While searching for answers, I found 3 comparable frameworks, which offer most of the features I need. Truffle is the most mature of the three and certainly has the most features. Hardhat is a more modern tool, which focuses on debugging. Both of these tools are written in JavaScript. Brownie is also a decent alternative written in Python.

I opted for brownie, because it has a Command Line Interface, an interactive console, it uses pytest for testing and it makes deployments with different network and account configurations easy. Another consideration was that Python is my scripting language of choice. This allowed me to use familiar tools to solve new problems.

Source code and Developer environment

I set up a git repository on codeberg.org, a platform which helps you get started with the framework. It guides you through setting up the environment you need to be able to follow this article and contains most of the source code that it mentions.

The IDE I am using is VSCode with the Solidity plugin installed. The plugin has many features, such as syntax highlighting, linting and even contract compilation. For this last aspect and for everything else, I use brownie.

You may skip the practical part and read the article as it is. But if you want to follow the practical guide, you may clone the repo now and follow its README.md until your development environment is set up.

git clone https://codeberg.org/tardigrde/mndwrk-brownie-blogpost.git

Lottery smart contract

The contract being worked with is a dead-simple Lottery application. Users can enter the lottery by paying a certain amount of ether to the contract. The owner of the contract can decide when they pick a winner. The lottery can be played repeatedly.

Find the Lottery contract in the attached git repository. It is based on jspruance's' block-explorer-tutorials/Lottery.sol, with some added functionality and clarifying comments.

Brownie console

Let us now dive into the fun part by trying out the brownie console. It is a REPL, similar to Python's, that helps you quickly interact with local or non-local chains. It is the fastest way to familiarize yourself with the brownie API, as it is the best place to make quick experiments to test new ideas or 3rd-party contracts.

After activating the Python environment, invoke the console using:


$ brownie console

NOTE: You can actually start the console on different kinds of local and public, so-called live chains. Network and corresponding account management is outside the scope of this article. If you want to deploy contracts on live chains, refer to the docs.

When the console is started, brownie starts and connects to a ganache instance, which is basically a local snapshot of the Ethereum blockchain. It also provides 10 pre-created accounts which you can use for testing your contracts.


>>> from brownie import accounts
>>> len(accounts)
10

We will use the first account as the owner of the contract.

We may import the Lottery class, which provides a Pythonic API for the contract.


>>> from brownie import Lottery

Then we can deploy the Lottery contract by calling the class' built-in deploy method with constructor parameters. Lottery has no such parameters, but we must pass at least a config dictionary. The from key's value tells brownie which account we will deploy the contract with.


>>> lottery = Lottery.deploy({"from": owner})
Transaction sent: 0x04d5a2db4f8ca20fb0279a0d8f74b3a6e45c8630e4758654304e8995a05160b6  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0  
Lottery.constructor confirmed   Block: 1  Gas used: 427318 (3.56%)  
Lottery deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87

Let's stop here to inspect the output. We can see both the transaction hash and the contract address. Feel free to inspect these fields in the console.


lottery.address
lottery.tx
lottery.tx.status

Meanwhile, we can call public view functions to get some metadata about the lottery.


>>> lottery.getPlayers()
()
>>> lottery.getBalance()
0
>>> lottery.cost() # in WEI
10000000000000000

Now let's enter the lottery with the second account in the accounts list.


>>> lottery.enter({"from": accounts[1], "value": ".01 ether"})
# ...hidden output...

Note the new value key (unlucky naming) passed in the config dictionary. What it means is 0.01 ether was included in the transaction, which is the participation cost.

Let's call those view functions again.


>>> lottery.getPlayers()
("0x33A4622B82D4c04a53e170c638B944ce27cffce3")
>>> lottery.getBalance()
10000000000000000
>>> assert lottery.getPlayers()[0] == accounts[1]
>>>

We got the first passing assert statement, which is an invitation to write a unit test. You may also leave the console now.

Testing

Brownie uses pytest under the hood. You can write your usual unit tests like so:


def test_enter():
	lottery = Lottery.deploy({"from": accounts[0]})    
	assert len(lottery.getPlayers()) == 0    
	lottery.enter({"from": accounts[1], "value": ".01 ether"})
	assert len(lottery.getPlayers()) == 1    
	assert lottery.getPlayers()[0] == accounts[1]

Put this into a pytest file e.g. ./tests/test_lottery.py (see conventions for test discovery), and call:


$ brownie test

The tests can be extremely helpful for implementing new features, while avoiding regression issues. They give confidence that the contract we or a 3rd wrote works as expected.

Write tests until you are happy with the coverage and results, then commence with deployment.

Running scripts

You can write scripts to deploy contracts and interact with them in brownie. Consider the simplest form of the deployment script under ./scripts/lotter.py:


from brownie import accounts, Lottery

def main():
	Lottery.deploy({"from": accounts[0]})

You can execute the function called main with brownie run, like so:


$ brownie run scripts/lottery.py

The local ganache instance is torn down after the script concludes. If you want to interact with the deployed contract, you have to write extra code after deployment, or run the script on live chains. We will go with the former.

Lottery simulation

In this last section, we will conduct a lottery simulation. The code we wrote during the development and testing phase can be reused here.

The following script deploys the lottery, enters with all the 10 pre-created test accounts, and finally announces a winner.


def main():
	# deploy the contract    
  lottery = Lottery.deploy({"from": accounts[0]})‍
  
  # enter with test accounts    
  for i in range(10):        
  	lottery.enter(            
    	{"from": accounts[i], "value": ".01 ether"}      
      	)‍
        
    print(f"Players: {lottery.getPlayers()}")   
    print(f"Lottery balance: {lottery.getBalance()}")‍
    
    # pick winner    
    lottery.pickWinner()
    
    print(f"The winner of the first lottery is: {lottery.getWinnerByLotteryId(1)}")
    

Running this with brownie run scripts/simulate_lottery.py produces a lot of output on the terminal. I encourage you to analyze it.

Conclusion

In this article, I attempted to demonstrate how you can use Solidity frameworks, like brownie, to make contract development easier.

Although brownie is a relatively new tool too, I have found I can leverage my Python and pytest know-how to help flatten the learning curve. It also allows me to integrate tests and scripts related to smart contracts in already existing projects.

I hope reading this article inspires you to try out brownie or its alternatives. At the very least, I hope it gives you a glimpse into how smart contract development works.

Sources

Brownie docs

Solidity docs

pytest docs

Remix IDE docs

Framework comparisons:

Web3: Truffle VS Hardhat VS Embark VS Brownie

Write a Simple Smart Contract in Truffle, Hardhat and Brownie

Learning material

Learn Shell - Free Interactive Shell Tutorial

Learn Python - Free Interactive Python Tutorial

CryptoZombies - Free Solidity Tutorial & Ethereum Blockchain Programming Course

Levente Csőke
Csőke Levente
DevOps / Backend engineer

Related Services

No items found.

Tags

No items found.

get in touch.

Tell us about your goals!
Contact Sales
The Digitalist Team
January 16, 2023

Automating the testing and deployment of Solidity smart contracts

Have you ever wondered what tools are required for working with smart contracts? Several questions arise. Are there Integrated Development Environments and frameworks that are specially designed for contracts? And what about testing and deployment? How do developers test their contracts? How do they deliver them?

This post offers a practical guide on how to conduct testing, deploy, and interact with Solidity smart contracts in brownie, a Python-based framework designed specifically for smart contract development. With brownie, you can run concise deployment scripts, write tests in PyTest and interact with live contracts, too.

NOTE: It is assumed that the reader has some basic terminal skills, as well as some experience in Python and Solidity Smart Contracts. For anyone who is unfamiliar with these technologies, you will find some learning material at the end of the article.

Problems with Solidity tools

When I wanted to deploy my first Solidity smart contract, I came across the Remix IDE. With full Solidity support, the ability to test and deploy contracts to local and public blockchains, and having a built-in debugger, the Remix IDE is rightfully regarded as one of the best Solidity IDEs.

After my first experiments with this new tool, though, I realized that there is a learning curve to getting efficient at it. I found it cumbersome to work with external blockchain providers and manually to interact with the deployed contract. The Remix console uses JavaScript, which I am not fond of.

These impressions made me ask:

  1. How can I develop a contract from scratch, in a test-driven manner?
  2. What is the leanest way to deploy these contracts and interact with them?
  3. How can I automate operations tasks relating to contracts and integrate them into (existing) Continuous Integration/Continuous Delivery processes?

Brownie

While searching for answers, I found 3 comparable frameworks, which offer most of the features I need. Truffle is the most mature of the three and certainly has the most features. Hardhat is a more modern tool, which focuses on debugging. Both of these tools are written in JavaScript. Brownie is also a decent alternative written in Python.

I opted for brownie, because it has a Command Line Interface, an interactive console, it uses pytest for testing and it makes deployments with different network and account configurations easy. Another consideration was that Python is my scripting language of choice. This allowed me to use familiar tools to solve new problems.

Source code and Developer environment

I set up a git repository on codeberg.org, a platform which helps you get started with the framework. It guides you through setting up the environment you need to be able to follow this article and contains most of the source code that it mentions.

The IDE I am using is VSCode with the Solidity plugin installed. The plugin has many features, such as syntax highlighting, linting and even contract compilation. For this last aspect and for everything else, I use brownie.

You may skip the practical part and read the article as it is. But if you want to follow the practical guide, you may clone the repo now and follow its README.md until your development environment is set up.

git clone https://codeberg.org/tardigrde/mndwrk-brownie-blogpost.git

Lottery smart contract

The contract being worked with is a dead-simple Lottery application. Users can enter the lottery by paying a certain amount of ether to the contract. The owner of the contract can decide when they pick a winner. The lottery can be played repeatedly.

Find the Lottery contract in the attached git repository. It is based on jspruance's' block-explorer-tutorials/Lottery.sol, with some added functionality and clarifying comments.

Brownie console

Let us now dive into the fun part by trying out the brownie console. It is a REPL, similar to Python's, that helps you quickly interact with local or non-local chains. It is the fastest way to familiarize yourself with the brownie API, as it is the best place to make quick experiments to test new ideas or 3rd-party contracts.

After activating the Python environment, invoke the console using:


$ brownie console

NOTE: You can actually start the console on different kinds of local and public, so-called live chains. Network and corresponding account management is outside the scope of this article. If you want to deploy contracts on live chains, refer to the docs.

When the console is started, brownie starts and connects to a ganache instance, which is basically a local snapshot of the Ethereum blockchain. It also provides 10 pre-created accounts which you can use for testing your contracts.


>>> from brownie import accounts
>>> len(accounts)
10

We will use the first account as the owner of the contract.

We may import the Lottery class, which provides a Pythonic API for the contract.


>>> from brownie import Lottery

Then we can deploy the Lottery contract by calling the class' built-in deploy method with constructor parameters. Lottery has no such parameters, but we must pass at least a config dictionary. The from key's value tells brownie which account we will deploy the contract with.


>>> lottery = Lottery.deploy({"from": owner})
Transaction sent: 0x04d5a2db4f8ca20fb0279a0d8f74b3a6e45c8630e4758654304e8995a05160b6  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0  
Lottery.constructor confirmed   Block: 1  Gas used: 427318 (3.56%)  
Lottery deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87

Let's stop here to inspect the output. We can see both the transaction hash and the contract address. Feel free to inspect these fields in the console.


lottery.address
lottery.tx
lottery.tx.status

Meanwhile, we can call public view functions to get some metadata about the lottery.


>>> lottery.getPlayers()
()
>>> lottery.getBalance()
0
>>> lottery.cost() # in WEI
10000000000000000

Now let's enter the lottery with the second account in the accounts list.


>>> lottery.enter({"from": accounts[1], "value": ".01 ether"})
# ...hidden output...

Note the new value key (unlucky naming) passed in the config dictionary. What it means is 0.01 ether was included in the transaction, which is the participation cost.

Let's call those view functions again.


>>> lottery.getPlayers()
("0x33A4622B82D4c04a53e170c638B944ce27cffce3")
>>> lottery.getBalance()
10000000000000000
>>> assert lottery.getPlayers()[0] == accounts[1]
>>>

We got the first passing assert statement, which is an invitation to write a unit test. You may also leave the console now.

Testing

Brownie uses pytest under the hood. You can write your usual unit tests like so:


def test_enter():
	lottery = Lottery.deploy({"from": accounts[0]})    
	assert len(lottery.getPlayers()) == 0    
	lottery.enter({"from": accounts[1], "value": ".01 ether"})
	assert len(lottery.getPlayers()) == 1    
	assert lottery.getPlayers()[0] == accounts[1]

Put this into a pytest file e.g. ./tests/test_lottery.py (see conventions for test discovery), and call:


$ brownie test

The tests can be extremely helpful for implementing new features, while avoiding regression issues. They give confidence that the contract we or a 3rd wrote works as expected.

Write tests until you are happy with the coverage and results, then commence with deployment.

Running scripts

You can write scripts to deploy contracts and interact with them in brownie. Consider the simplest form of the deployment script under ./scripts/lotter.py:


from brownie import accounts, Lottery

def main():
	Lottery.deploy({"from": accounts[0]})

You can execute the function called main with brownie run, like so:


$ brownie run scripts/lottery.py

The local ganache instance is torn down after the script concludes. If you want to interact with the deployed contract, you have to write extra code after deployment, or run the script on live chains. We will go with the former.

Lottery simulation

In this last section, we will conduct a lottery simulation. The code we wrote during the development and testing phase can be reused here.

The following script deploys the lottery, enters with all the 10 pre-created test accounts, and finally announces a winner.


def main():
	# deploy the contract    
  lottery = Lottery.deploy({"from": accounts[0]})‍
  
  # enter with test accounts    
  for i in range(10):        
  	lottery.enter(            
    	{"from": accounts[i], "value": ".01 ether"}      
      	)‍
        
    print(f"Players: {lottery.getPlayers()}")   
    print(f"Lottery balance: {lottery.getBalance()}")‍
    
    # pick winner    
    lottery.pickWinner()
    
    print(f"The winner of the first lottery is: {lottery.getWinnerByLotteryId(1)}")
    

Running this with brownie run scripts/simulate_lottery.py produces a lot of output on the terminal. I encourage you to analyze it.

Conclusion

In this article, I attempted to demonstrate how you can use Solidity frameworks, like brownie, to make contract development easier.

Although brownie is a relatively new tool too, I have found I can leverage my Python and pytest know-how to help flatten the learning curve. It also allows me to integrate tests and scripts related to smart contracts in already existing projects.

I hope reading this article inspires you to try out brownie or its alternatives. At the very least, I hope it gives you a glimpse into how smart contract development works.

Sources

Brownie docs

Solidity docs

pytest docs

Remix IDE docs

Framework comparisons:

Web3: Truffle VS Hardhat VS Embark VS Brownie

Write a Simple Smart Contract in Truffle, Hardhat and Brownie

Learning material

Learn Shell - Free Interactive Shell Tutorial

Learn Python - Free Interactive Python Tutorial

CryptoZombies - Free Solidity Tutorial & Ethereum Blockchain Programming Course

Levente Csőke
by
Csőke Levente
DevOps / Backend engineer

Levente Csőke has 4 years of experience in multi-tier web application development. Currently, he works as a DevOps/Backend engineer by day and experiments with web3 technologies by night. He started working with Solidity a year ago and immediately fell in love with the concept of Smart Contracts. Recently he has also discovered that he enjoys writing articles about things he learned.

LinkedIn

January 16, 2023

Automating the testing and deployment of Solidity smart contracts

Have you ever wondered what tools are required for working with smart contracts? Several questions arise. Are there Integrated Development Environments and frameworks that are specially designed for contracts? And what about testing and deployment? How do developers test their contracts? How do they deliver them?

This post offers a practical guide on how to conduct testing, deploy, and interact with Solidity smart contracts in brownie, a Python-based framework designed specifically for smart contract development. With brownie, you can run concise deployment scripts, write tests in PyTest and interact with live contracts, too.

NOTE: It is assumed that the reader has some basic terminal skills, as well as some experience in Python and Solidity Smart Contracts. For anyone who is unfamiliar with these technologies, you will find some learning material at the end of the article.

Problems with Solidity tools

When I wanted to deploy my first Solidity smart contract, I came across the Remix IDE. With full Solidity support, the ability to test and deploy contracts to local and public blockchains, and having a built-in debugger, the Remix IDE is rightfully regarded as one of the best Solidity IDEs.

After my first experiments with this new tool, though, I realized that there is a learning curve to getting efficient at it. I found it cumbersome to work with external blockchain providers and manually to interact with the deployed contract. The Remix console uses JavaScript, which I am not fond of.

These impressions made me ask:

  1. How can I develop a contract from scratch, in a test-driven manner?
  2. What is the leanest way to deploy these contracts and interact with them?
  3. How can I automate operations tasks relating to contracts and integrate them into (existing) Continuous Integration/Continuous Delivery processes?

Brownie

While searching for answers, I found 3 comparable frameworks, which offer most of the features I need. Truffle is the most mature of the three and certainly has the most features. Hardhat is a more modern tool, which focuses on debugging. Both of these tools are written in JavaScript. Brownie is also a decent alternative written in Python.

I opted for brownie, because it has a Command Line Interface, an interactive console, it uses pytest for testing and it makes deployments with different network and account configurations easy. Another consideration was that Python is my scripting language of choice. This allowed me to use familiar tools to solve new problems.

Source code and Developer environment

I set up a git repository on codeberg.org, a platform which helps you get started with the framework. It guides you through setting up the environment you need to be able to follow this article and contains most of the source code that it mentions.

The IDE I am using is VSCode with the Solidity plugin installed. The plugin has many features, such as syntax highlighting, linting and even contract compilation. For this last aspect and for everything else, I use brownie.

You may skip the practical part and read the article as it is. But if you want to follow the practical guide, you may clone the repo now and follow its README.md until your development environment is set up.

git clone https://codeberg.org/tardigrde/mndwrk-brownie-blogpost.git

Lottery smart contract

The contract being worked with is a dead-simple Lottery application. Users can enter the lottery by paying a certain amount of ether to the contract. The owner of the contract can decide when they pick a winner. The lottery can be played repeatedly.

Find the Lottery contract in the attached git repository. It is based on jspruance's' block-explorer-tutorials/Lottery.sol, with some added functionality and clarifying comments.

Brownie console

Let us now dive into the fun part by trying out the brownie console. It is a REPL, similar to Python's, that helps you quickly interact with local or non-local chains. It is the fastest way to familiarize yourself with the brownie API, as it is the best place to make quick experiments to test new ideas or 3rd-party contracts.

After activating the Python environment, invoke the console using:


$ brownie console

NOTE: You can actually start the console on different kinds of local and public, so-called live chains. Network and corresponding account management is outside the scope of this article. If you want to deploy contracts on live chains, refer to the docs.

When the console is started, brownie starts and connects to a ganache instance, which is basically a local snapshot of the Ethereum blockchain. It also provides 10 pre-created accounts which you can use for testing your contracts.


>>> from brownie import accounts
>>> len(accounts)
10

We will use the first account as the owner of the contract.

We may import the Lottery class, which provides a Pythonic API for the contract.


>>> from brownie import Lottery

Then we can deploy the Lottery contract by calling the class' built-in deploy method with constructor parameters. Lottery has no such parameters, but we must pass at least a config dictionary. The from key's value tells brownie which account we will deploy the contract with.


>>> lottery = Lottery.deploy({"from": owner})
Transaction sent: 0x04d5a2db4f8ca20fb0279a0d8f74b3a6e45c8630e4758654304e8995a05160b6  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 0  
Lottery.constructor confirmed   Block: 1  Gas used: 427318 (3.56%)  
Lottery deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87

Let's stop here to inspect the output. We can see both the transaction hash and the contract address. Feel free to inspect these fields in the console.


lottery.address
lottery.tx
lottery.tx.status

Meanwhile, we can call public view functions to get some metadata about the lottery.


>>> lottery.getPlayers()
()
>>> lottery.getBalance()
0
>>> lottery.cost() # in WEI
10000000000000000

Now let's enter the lottery with the second account in the accounts list.


>>> lottery.enter({"from": accounts[1], "value": ".01 ether"})
# ...hidden output...

Note the new value key (unlucky naming) passed in the config dictionary. What it means is 0.01 ether was included in the transaction, which is the participation cost.

Let's call those view functions again.


>>> lottery.getPlayers()
("0x33A4622B82D4c04a53e170c638B944ce27cffce3")
>>> lottery.getBalance()
10000000000000000
>>> assert lottery.getPlayers()[0] == accounts[1]
>>>

We got the first passing assert statement, which is an invitation to write a unit test. You may also leave the console now.

Testing

Brownie uses pytest under the hood. You can write your usual unit tests like so:


def test_enter():
	lottery = Lottery.deploy({"from": accounts[0]})    
	assert len(lottery.getPlayers()) == 0    
	lottery.enter({"from": accounts[1], "value": ".01 ether"})
	assert len(lottery.getPlayers()) == 1    
	assert lottery.getPlayers()[0] == accounts[1]

Put this into a pytest file e.g. ./tests/test_lottery.py (see conventions for test discovery), and call:


$ brownie test

The tests can be extremely helpful for implementing new features, while avoiding regression issues. They give confidence that the contract we or a 3rd wrote works as expected.

Write tests until you are happy with the coverage and results, then commence with deployment.

Running scripts

You can write scripts to deploy contracts and interact with them in brownie. Consider the simplest form of the deployment script under ./scripts/lotter.py:


from brownie import accounts, Lottery

def main():
	Lottery.deploy({"from": accounts[0]})

You can execute the function called main with brownie run, like so:


$ brownie run scripts/lottery.py

The local ganache instance is torn down after the script concludes. If you want to interact with the deployed contract, you have to write extra code after deployment, or run the script on live chains. We will go with the former.

Lottery simulation

In this last section, we will conduct a lottery simulation. The code we wrote during the development and testing phase can be reused here.

The following script deploys the lottery, enters with all the 10 pre-created test accounts, and finally announces a winner.


def main():
	# deploy the contract    
  lottery = Lottery.deploy({"from": accounts[0]})‍
  
  # enter with test accounts    
  for i in range(10):        
  	lottery.enter(            
    	{"from": accounts[i], "value": ".01 ether"}      
      	)‍
        
    print(f"Players: {lottery.getPlayers()}")   
    print(f"Lottery balance: {lottery.getBalance()}")‍
    
    # pick winner    
    lottery.pickWinner()
    
    print(f"The winner of the first lottery is: {lottery.getWinnerByLotteryId(1)}")
    

Running this with brownie run scripts/simulate_lottery.py produces a lot of output on the terminal. I encourage you to analyze it.

Conclusion

In this article, I attempted to demonstrate how you can use Solidity frameworks, like brownie, to make contract development easier.

Although brownie is a relatively new tool too, I have found I can leverage my Python and pytest know-how to help flatten the learning curve. It also allows me to integrate tests and scripts related to smart contracts in already existing projects.

I hope reading this article inspires you to try out brownie or its alternatives. At the very least, I hope it gives you a glimpse into how smart contract development works.

Sources

Brownie docs

Solidity docs

pytest docs

Remix IDE docs

Framework comparisons:

Web3: Truffle VS Hardhat VS Embark VS Brownie

Write a Simple Smart Contract in Truffle, Hardhat and Brownie

Learning material

Learn Shell - Free Interactive Shell Tutorial

Learn Python - Free Interactive Python Tutorial

CryptoZombies - Free Solidity Tutorial & Ethereum Blockchain Programming Course

Levente Csőke
Csőke Levente
DevOps / Backend engineer

Related Services

No items found.

Tags

No items found.

Become a Mndwrk-er

Work, learn, and have fun together
Create your account