DEV Community

Franco Victorio
Franco Victorio

Posted on

Debugging transactions in Ethereum. Part 2: Truffle and Buidler

In the previous post we looked at some ways to debug transactions that were mined in a testnet or in the mainnet, using etherscan and eth-cli. In this post we'll move to transactions that we execute in "local" blockchains, like ganache-cli and the Buidler EVM.

I'm going to use the Truffle MetaCoin box for the examples, so if you want to follow along you can start by running npx truffle unbox metacoin in an empty directory. You'll also have to edit the truffle-config.js file, uncomment it, and change the port of the development network from 7545 to 8545.

Truffle debug

If your project is built using Truffle, you can use its debugger to inspect a transaction step by step. This is a command line tool similar to gdb or pdb; if you've used something like that in the past, the experience is very similar.

In our example, if we start ganache-cli in another terminal and then run npx truffle test, we'll see a bunch of transactions being executed. Copy the hash of the last transaction and run:

npx truffle compile # this might be necessary if the build/ directory isn't there
npx truffle debug 0x3e3299b90e92efeccfe806f7b67e4683fb2c6ad2e114cbbe4b1308e4402ce090
Enter fullscreen mode Exit fullscreen mode

(Your hash could be different.)

And you'll get something like this:

Truffle debugger 1

Here you can do things like seting a breakpoint with b, stepping over to the next line with o or evaluating an expression with :. Let's step over twice and inspect the value of the amount parameter with :amount:

Truffle debugger 2

Check the docs to learn more about this feature.

Debugging tests

Another tool you can use is the global debug() function in your tests. Go to test/metacoin.js and replace this line

-const balance = await metaCoinInstance.getBalance.call(accounts[0]);
+const balance = await debug(metaCoinInstance.getBalance.call(accounts[0]));
Enter fullscreen mode Exit fullscreen mode

and then run your tests with npx truffle test --debug (notice the extra flag). Your tests will be interrupted when that method is called and you'll be able to inspect it:

Truffle debugger 3

This is great not only because you don't need to get the hash of some transaction, but also because you can use it for plain calls that don't even send a transaction.

Debugging non-local transactions

What about transactions in a testnet or in the mainnet? As far as I know, truffle debug can be used against any node that supports the debug_traceTransaction JSON-RPC method. And (again, AFAIK; here I am in the "I don't really know what I'm talking about" territory) besides local blockchains, this method is supported mainly by geth when syncing in archive mode. I haven't tested it because this requires syncing more than 2 TB of data.

Are there any other alternatives? Not that I'm aware of. Infura doesn't support debug_traceTransaction calls. I also tried using ganache --fork but this didn't work either (it actually kind of worked once? I think it might if the transaction is recent enough).

Anyway, if anyone knows how to do this, please let me know in the comments.

Buidler

Buidler is a task runner for smart contract projects that comes with a lot of useful tools. You can use it on its own, or you can complement a truffle project with it, which is what we're going to do here. First, install it:

yarn add --dev @nomiclabs/buidler
Enter fullscreen mode Exit fullscreen mode

and then run npx buidler and select the "Create an empty buidler.config.js" option.

console.log

One of the things that come with Buidler is the Buidler EVM, that lets you do things like logging stuff inside your contracts or having stack traces that work across solidity and javascript. Let's start with the console.

First, open the contracts/MetaCoin.sol file and add these two lines:

 pragma solidity >=0.4.25 <0.7.0;

 import "./ConvertLib.sol";
+import "@nomiclabs/buidler/console.sol";

...

    function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
+       console.log(msg.sender, receiver, amount);
        if (balances[msg.sender] < amount) return false;
Enter fullscreen mode Exit fullscreen mode

Next, kill ganache-cli (if it's still running) and execute npx buidler node, which starts a node that uses the Buidler EVM.

Now just run npx truffle test and you'll be able to see the console messages:

Buidler 1

You'll notice that here we are logging the same information that is emitted in the Transfer event, so what's the point? Well, first of all, events are not emitted for failed transactions, but the console works just fine. And second, you can also use logs for method calls that don't send transactions.

Stack traces

The Buidler EVM also supports stack traces that look like this:

Buidler 2

As you can see, here we get both the line of the contract that failed, and the line of the javascript test that triggered the transaction!

Getting this to work is a little more involved, but worth it. First, install the truffle and web3 buidler plugins:

yarn add --dev @nomiclabs/buidler-truffle5 "@nomiclabs/buidler-web3@^1.2.0"
Enter fullscreen mode Exit fullscreen mode

Then open buidler.config.js and add

usePlugin("@nomiclabs/buidler-truffle5")
Enter fullscreen mode Exit fullscreen mode

to the beginning of the file.

Then create a file called test/truffle-fixture.js:

const MetaCoin = artifacts.require("MetaCoin")
const ConvertLib = artifacts.require("ConvertLib")

module.exports = async () => {
  const convertLib = await ConvertLib.new()
  ConvertLib.setAsDeployed(convertLib)
  MetaCoin.link(convertLib)
  const metacoin = await MetaCoin.new()
  MetaCoin.setAsDeployed(metacoin)
}
Enter fullscreen mode Exit fullscreen mode

This is just some glue code we need so that our contracts are deployed before running our tests with buidler (so far we've been running them with truffle).

You'll also need to remove the solidity test that comes with the MetaCoin box:

rm test/TestMetaCoin.sol
Enter fullscreen mode Exit fullscreen mode

This is necessary because buidler doesn't support Solidity tests yet.

Now everything is ready: run npx buidler test to run your tests in the Buidler EVM. Of course, tests still pass so we won't get a stack trace. Let's modify our contract to trigger an error. Open contracts/MetaCoin.sol and add this line:

  ...

    function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
+       require(amount <= 5);
    ...
Enter fullscreen mode Exit fullscreen mode

and run npx buidler test again. You'll get a stack trace like the one we saw at the beginning.


Both Truffle and Buidler offer some nice functionalities to help make sense of what's happening inside your contracts. The MetaCoin contract is really simple, so this might seem overkill, but you've surely run into issues where you wished you'd had a debugger, a logger or a stack trace. Or, even better, all three of them!

Top comments (3)

Collapse
 
gorgos profile image
Markus Waas

The revert messages in Solidity (+support in Truffle) definitely helped a lot, but a stack-trace can still be pretty useful a lot of times. Same with the console.log vs. events. It is really annoying not seeing events for reverted transactions. I have never tried actually debugging a transaction directly with Truffle, thanks for the tip.

Collapse
 
seemcat profile image
Maricris Bonzo

Hi! Currently diving into debugging dapps. This article offered great insights, so thank you! Wondering what you think are the differences between debuggers and stack tracers? When would you use one over the other? Or should we be using both? Or are they the same?

Collapse
 
cmalfesi profile image
Cristian Malfesi

Thanks for this article. It's very useful information!