Getting Started With Brownie (Part 3)

Basic Brownie functionality

Ben Hauser
Better Programming

--

Photo by André François McKenzie on Unsplash

Now that we’ve installed Brownie and created a project, it’s time to get acquainted with some of the core functionality.

If you haven’t yet, start by opening the console:

brownie console

Brownie will launch Ganache and create some objects to help you interact with your project. Once it’s ready, you are given a command prompt:

For the newcomers to Python, it’s time to level up! Let’s take a look at two very useful built-in functions:

  • dir displays a list of objects within the namespace or a list of members of an object. If using Python feels like visiting a foreign country to you, then think of dir as your roadmap.
  • help accepts any object as an argument and displays documentation for the object. Continuing with our previous analogy, if dir is your roadmap, thenhelp is your guidebook.

With these two functions and a healthy dose of curiosity, you can pretty much figure out how to do anything without reaching for the documentation. (But please do open the Brownie documentation. I put a lot of work into it.)

Transferring Ether

Let’s start with a simple transaction. Because we’re using Ganache, there are already several unlocked accounts available to us that are funded with 100 ether. Objects to interact with these accounts can be found in a container called accounts:

Let’s start with a simple transfer of one ether from accounts[0] to accounts[1]. Enter the following command:

accounts[0].transfer(accounts[1], "1 ether")

Instead of "1 ether" you could type 1e18 or even 1000000000000000000, but I find the string easier to read and Brownie will happily convert it.

Ganache mines the transaction immediately, and the result is outputted to the console:

See that last line? <Transaction ...>? That’s an object representing the transaction that you just made. Because it wasn’t assigned to a variable, it was printed to the console. But don’t worry, even though you didn’t assign it all is not lost! Transactions are also added to a container called history:

The TransactionReceipt object contains many attributes and methods for inspecting a transaction. It is one of the most powerful components of Brownie and probably deserves its own article. We’ll come back to it soon.

Working With Contracts

The token-mix template includes one deployable contract, named Token. Each contract has an object that can be used for deployment, like so:

Token.deploy("TST", "Test Token", 18, 1e24, {'from': accounts[0]})

While typing the command, you’ll notice that Brownie offers hints as to the required values. How neat is that?

The final value is known as a transaction dictionary. This is where you specify information such as the sender, gas price, and gas limit. Brownie uses sensible defaults where possible — the only required field is from.

Broadcast the transaction, and behold: A newly deployed contract is born.

The call returns a Contract object, which is used to interact with the deployment. What’s that? You didn’t assign it again?! No biggie. The object that you used to deploy is also a container that holds the deployments.

Assign the deployed Contract object to a variable, and then let’s have a look at it with our good friend dir:

All of the standard ERC20 methods are available as callable members within the object. Start by checking the total supply and a balance:

As expected, all the tokens were initially assigned to the address we used to deploy the contract. Let’s try sending some of those tokens, and this time be sure to assign the returned TransactionReceipt:

tx = contract.transfer(accounts[1], 1e18, {'from': accounts[0]})

Working With Transactions

Now it’s time to take a look at that transaction. We’ll start with a quick call to dir(tx). You can see just how much information is available:

It’s a long list, and most of the attributes are fairly self-explanatory. For now, call tx.info() to get an expanded human-readable description of the transaction.

Where the TransactionReceipt object really shines is when things don’t go according to plan. Type the following command:

tx = contract.transfer(accounts[3], 1e18, {'from': accounts[2]})

Oh no, our transaction failed!

You might already know what went wrong, but we’ll investigate anyway. Start with the following command:

tx.error()

Brownie outputs a chunk of source code where the error occurred:

Okay, we failed a SafeMath underflow check. Good to know, but we could use some context. Try this command next:

tx.traceback()

This time we’re shown a Python-style traceback highlighting each frame leading up to the revert:

And voila! The underflow was because msg.sender had an insufficient balance. This example is quite simple, but when you find yourself building the next DeFi gem and you’re jumping between a dozen contracts in a single transaction, those tracebacks are gonna leave you feeling pretty zen.

Next Up

That’s it for now! Soon we’ll release a Part Four where we discuss unit testing.

--

--