Building and Testing Smart Contracts with Foundry by Paradigm

Nader Dabit - Dec 20 '21 - - Dev Community

This post was originally published on Mirror

One of the things I really like about Paradigm is that they seem to be very focused on helping builders and developers, and are not afraid to get their hands dirty with code (people like Anish Agnihotri and Georgios Konstantopoulos are some of the best engineers in web3 or maybe anywhere). They also share an enormous amount of some of the highest quality blockchain / web3 / crypto related content in existence. They definitely don't seem like the the typical VC firm.

They recently created and open sourced Foundry, a new Solidity development environment.

Since it came out I've been wanting to try it out, and finally had the chance to this week.

In this post I want to give you a quick rundown of what I learned and how to get started with it.

Foundry Overview

Paradigm's description of Foundry is that Foundry is a portable, fast and modular toolkit for Ethereum application development.

It fits into the stack the same way that Hardhat, Truffle, and Dapp Tools do.

The main differences / selling points of Foundry are:

1. It allows you to write your tests in Solidity instead of JavaScript.

They make a great case about why writing tests in Solidity VS JavaScript is better, and they are spot on with most of their points.

There is just a lot less boilerplate and a lot less mental overhead. Once you write a few tests in Solidity you feel the difference.

2. It's fast.

Foundry is written in Rust and it is fast. They've documented a few benchmarks here, but it's hard to do it justice until you use it (especially after using an alternative).

Foundry is made up of 2 CLI tools - forge and cast.

Forge is the Ethereum development and testing framework.

Cast is a CLI that allows you to interact with EVM smart contracts, send transactions, and read data from the network.

Drawbacks

While Foundry is fantastic for hardcore smart contract development, as an avid Hardhat user and enthusiast, I have to also outline some of the tradeoffs.

At the time of this writing I feel like Hardhat wins for full stack developers because it offers better tooling for switching between and deploying to various networks.

Hardhat also has a robust plugin ecosystem that allows you to extend the project with a lot of additional functionality.

Finally, Hardhat is fairly mature at this point and just works, whereas Foundry is still building out its base feature set.

Now that we've had an overview of Foundry, let's look at how to use it to build and test a smart contract.

Building & testing a smart contract with Foundry

To install Foundry, you must first have Rust installed on your machine.

To get started, we'll install forge

cargo install --git https://github.com/gakonst/foundry --bin forge --locked
Enter fullscreen mode Exit fullscreen mode

Next, in an empty directory, we can use the init command to initialize a new project:

forge init
Enter fullscreen mode Exit fullscreen mode

The forge CLI will create two directories, lib and src.

The lib directory contains the testing contract (lib/ds-test/src/test.sol) as well as a demo test contract implementing some various tests (lib/ds-test/demo/demo.sol).

The src directory contains a barebones smart contract and test.

Let's create a basic smart contract to test out. Rename Contract.sol to HelloWorld.sol and update it with the following:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

contract HelloWorld {
  string private greeting;
  uint public version = 0;

  constructor (string memory _greeting) {
    greeting = _greeting;
  }

  function greet() public view returns(string memory) {
    return greeting;
  }

  function updateGreeting(string memory _greeting) public {
    version += 1;
    greeting = _greeting;
  }
}
Enter fullscreen mode Exit fullscreen mode

Next, we can run a build and compile the ABIs:

forge build
Enter fullscreen mode Exit fullscreen mode

This should create an out directory containing the ABIs for both the main contract as well as the test.

Next, let's update the name of test/Contract.t.sol to test/HelloWorld.t.sol and add the following code:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import "ds-test/test.sol";
import 'src/HelloWorld.sol';

contract HelloWorldTest is DSTest {
    HelloWorld hello;
    function setUp() public {
      hello = new HelloWorld("Foundry is fast!");
    }

    function test1() public {
        assertEq(
            hello.greet(),
            "Foundry is fast!"
        );
    }

    function test2() public {
        assertEq(hello.version(), 0);
        hello.updateGreeting("Hello World");
        assertEq(hello.version(), 1);
        assertEq(
            hello.greet(),
            "Hello World"
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Forge comes built in with some really great testing features like assertions and gas cost snapshots.

In our test we've asserted equality using the assertEq utility.

To run the test, we can run:

forge test
Enter fullscreen mode Exit fullscreen mode

When the test is run, we'll see output for not only the success of the test, but also the gas cost:

Forge test

There are also utilities for:

truthiness - assertTrue

decimal equality - assertEqDecimal

greater than, less than - assertGt, assertGe, assertLt, assertLe

You can view the assertions in the testing contract at lib/ds-test/src/test.sol. These are all of the available functions that we can use in our tests.

Fuzzing

Foundry also supports fuzzing.

This allows us to define function parameter types and the testing framework will populate these values at runtime.

If it does find an input that causes the test to fail, it will return it so you can create a regression test.

For instance, we can update the test2 function to receive a function argument, and use the value in our test without ever having to define what it is:

function test2(string memory _greeting) public {
    assertEq(hello.version(), 0);
    hello.updateGreeting(_greeting);
    assertEq(hello.version(), 1);
    assertEq(
        hello.greet(),
        _greeting
    );
}
Enter fullscreen mode Exit fullscreen mode

Now when we run the test, Foundry will automatically populate the _greeting variable when the test is run.

Conclusion

Foundry is a welcome addition to the web3 stack, bringing improved tooling and performance for smart testing and development.

I'm excited to see this project mature and already would recommend developers looking to quickly build and test non-trivial smart contracts to try it out today.

Huge shout out and thank you to Paradigm for their work building developer tooling like Foundry, it is much appreciated.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .