Paradigm

Announcing Foundry v0.2.0

Mar 29, 2022 | Georgios Konstantopoulos

Contents

What is Foundry?

Foundry is a portable, fast, and modular toolkit for Ethereum application development, written in Rust. Our goal with Foundry is to create the best developer experience for building secure smart contracts on EVM chains. Most importantly: Foundry is built by Solidity developers for Solidity developers.

We announced Foundry v0.1.0 in December 2021 as a first step towards improving the Ethereum development experience. This initial version centered on four key features:

  1. Letting developers write tests in Solidity to minimize context switching;
  2. Making tests and the compilation pipeline REALLY fast to keep the developer's feedback loop tight;
  3. Using intuitive patterns for expanding test coverage, debugging, and testing against a live network's state; and,
  4. Being easy to install and distribute across platforms.

The results since December have convinced us that Foundry solves some major development pain points. On Solmate for instance, it took down testing time from 393s to 2.8s, a 140x improvement. Developers from top protocols like Optimism and MakerDAO are using Foundry as a standalone framework, or will introduce it incrementally in their codebases to improve their build and test process.

Now we're excited to announce Foundry v0.2.0, a major step forward for the toolkit.

What's new?

TL;DR: we shipped a lot of code over the last three months.

More than 400 pull requests and over 90 contributors later, we are proud to present the results:

  1. Faster Compilation and Testing: Forge is now much faster at building your code and running your tests, 5-340x times faster than every alternative in the market.
  2. New Debugging & Gas Optimization features: New features help you can gain insights on your code's internal execution paths, including call tracing, an interactive debugger, gas reports, and more.
  3. New Testing Features: New cheatcodes for EVM state overrides and state-assisted fuzzing have been implemented to explore more code paths and increase confidence in your code's security.
  4. Easy Installation: Foundry can now be installed out of the box on all platforms in seconds. No Cargo, no NPM, <15MB. This is the best UX for installing a new Ethereum development tool we've ever had.
  5. Foundry Configuration File: We introduced foundry.toml so that you can specify project-specific settings without needing to write custom scripts for passing CLI parameters.

We also now have the Foundry Book, our official documentation! While it's still at its early stages, it shows how to set up a Foundry project, write unit & fuzz tests, use cheatcodes, fork from a live network, and more.

In this post we'll focus on the new features that were added to Forge, Foundry's testing framework. Cast, the Swiss Army knife for interacting with a live network, will be covered in a separate post.

If all this sounds exciting, let's dive in.

Blazing-fast compilation & testing

Foundry devs and users are obsessed with speed. We want our code to compile fast, and the tests to run even faster. As a result, we've been carefully optimizing every part of the compilation and testing pipeline. But first, let's see how fast it really is.

Compilation Benchmarks

We tested the time it takes to build a project with various caching scenarios. We chose to use openzeppelin-contracts and Uniswap/v3-core as our benchmark repositories. The following scenarios were checked:

  1. No caching involved: We'd expect most of the time to be dominated by solc execution.
  2. Some Caching: We compile the project, modify a file "deep" in the dependency graph, and compile again. We expect this to be somewhat faster but not a lot, given that most of the project is getting recompiled.
  3. A lot of caching: Same as above, but we modify a file at the edge of the dependency graph, which will cause more cache hits, so we expect it to be faster.
  4. Full cache: No file is touched, the equivalent of running the same compilation command 2 times in a row.

We provide the following graphs which plot the time it took to compile for each scenario (lower is better):

Takeaway: Forge compilation is consistently faster by a factor of 1.7-11.3x, depending on the amount of caching involved.

The above results used the same compiler settings but did not attempt to modify Hardhat's settings from the defaults. It is possible that performance can be improved by tuning the settings (e.g. disable plugins or Typescript typechecks) but we chose to benchmark against the default settings since that is what most users experience.

Testing Benchmarks

We then proceeded to benchmark performance against other frameworks.

We started by comparing performance with dapp, Forge's "parent" which recently passed on the torch to Foundry. Thank you DappTools!

Repository Forge DappTools Speedup
maple-finance/loans 800ms 268s 335x
Rari-Capital/solmate 2.8s 394s 140x
reflexer-labs/geb 0.4s 23s 57.5x
Rari-Capital/vaults 0.28s 6.5s 23x

(Note that in the above benchmarks compilation was always skipped.)

Then, we ported some tests from @uniswap/v3-periphery to use Forge, so that we could compare against Hardhat. Deploying Uniswap and running the "exact input" tests took 10s on Hardhat, and 600ms on Forge, a 16x improvement.

Finally, we wanted to test performance when forking against a live network. So we used Convex Finance's shutdown functionality as a benchmark to compare not only against other frameworks, but against hosted services like Blocknative and Tenderly as well.

Framework Remote RPC Local RPC Cached
Blocknative N/A N/A 3.529s
Dapptools 52m 17.447s 17m 34.869s 3m 25.896s
Ganache 10m 5.384s 1m 2.275s 22.662s
Hardhat 8m 26.483s 35.145s 7.531s
Foundry 6m 59.875s 13.610s 0.537s
Tenderly N/A N/A 17.805s

Check the repository for more context on how the measurements were made.

Takeaway: Forge is the fastest EVM test runner that exists, even beating hosted services in transaction simulation speeds.

How is this all possible? That is a topic for another post dear reader, as each improvement we did deserves its own post.

Debugging & Gas Optimization UX Improvements

As a reminder, at the December launch we already supported Hardhat's console.sol-style logging, and Dapptools' emit log-based logging which allow you to print values along your contract's execution. We also supported showing the gas cost of each test.

That, however, was not enough! We cannot expect a developer to annotate all their code with logs to figure out what is happening. They also want more granularity on the gas cost of each individual call, instead of the entire test cost.

Call Traces

To provide more runtime introspection, we introduced call traces, which provide a structured output of every external call made by any contract, along with its arguments and return value.

This is the output you'd get when running forge test -vvvv:

Notice how successful calls are colored in green, reverts are red, and cheatcode calls are blue! More details on how calltraces work can be found in the book.

Interactive Debugger

However, call traces do not give you insight on the opcode by opcode execution of your code, which you may want to view when you are doing low level optimizations or trying to debug a test and need to inspect the state of the EVM's stack and memory.

To solve that, we provide an interactive debugger as part of forge test --debug and forge run. Here's how it looks like (you can navigate with keyboard buttons shown at the bottom of the screen):

Gas Reports

Most Ethereum developers are familiar with the gas table produced by eth-gas-reporter. We also ported that to work with Foundry. This is how forge test --gas-report looks like:

New Testing Features

We all know that smart contract security is hard. We want to make that easier, by providing functionality so that developers can test every path in their code.

New Cheatcodes

Foundry and DappTools allows overriding the state of the EVM, with a method called "cheatcodes". For example, you can use the roll cheatcode to set the block number, the store cheatcode to set an arbitrary storage slot to any value, prank to impersonate an arbitrary address, among others. This is quite powerful!

We have introduced a variety of new cheatcodes so that developers can have even more control over their tests' state. See all supported cheatcodes below:

For more information on how to use each cheat code, please refer to the Cheatcodes Reference, and Foundry's cheatcode unit tests.

Fuzzer Improvements

Foundry not only lets you write blazing fast configurable unit tests, it provides great UX for writing property-based tests, which we call fuzz tests.

We made two big improvements to the fuzzer:

  1. Biasing edges for unsigned integers: Previously we were picking numbers at random. Now, we check all the number boundaries, as well as values in the neighborhood of the boundary (e.g. 0, 1, 2, UINT256_MAX, UINT256_MAX - 1 etc.)
  2. State dictionary-assisted fuzzer: Every output, state modification, address, balance, and log produced during execution is now added to the fuzzer's dictionary. This allows trying out values which otherwise would never get hit if chosen randomly.

The result? A fuzzer that can find even the most bespoke edge cases very fast. We invite you to try it out, and in the future will be benchmarking it against other industry fuzzers.

Easy Installation

The most important phase when attracting a new user, is the installation phase.

Previously, installing Foundry could only be done from source and required having Rust installed. That's not good enough! So we fixed it in two ways:

  1. A nightly GitHub Action that cuts a release with any changes that happened in the last 24 hours and publishes it under the Releases page on Github.
  2. An updater script that downloads the latest nightly version and installs it under ~/.foundry/bin, called foundryup.

Installing Foundry now is as simple as seen below:

Congrats! Foundry's forge and cast are now available for you to use!

Notably, this works across all platforms, requires no extra tools installed like NPM or Cargo. We believe this is a game changing UX for onboarding on a new tool.

For more information on how foundryup works, check the docs.

Configuration File

When you create a new project via forge init, a foundry.toml file is touched at your project's root which defines basic parameters of your project like where are your contracts, your output artifacts and your libraries.

Foundry.toml is very powerful! It lets you configure every aspect of your project, as seen below, instead of you having to pass them each time as a CLI parameter.

Perhaps the most exciting feature of foundry.toml is "profiles", which let you run different "bundles" of settings depending on a single CLI parameter. As an example, when testing locally you may want to have the default fuzzer runs, whereas on CI you'd want 100000 runs. You'd do this by calling FOUNDRY_PROFILE=ci forge test on CI, which would use the fuzzer runs from the ci section of the above foundry.toml.

Read more about how to configure your foundry.toml in the docs.

What's next?

Foundry is still in the early stages of development. While our results so far are encouraging, we find there's multiple areas for improvement:

  1. Performance: We can improve the EVM execution and Solidity compilation speed with optimizations at the VM layer and by fine tuning the compiler.
  2. Stability: We intend to improve testing in the codebase and do less breaking changes over time, so that developers can feel like they can trust Foundry for its soundness.
  3. New features: We have a lot of ideas in the pipeline: A new testnet node, Solidity deployments, code coverage, auto-formatting, auto-generating docs from natspec, parameterized & invariant tests, flamegraphs, more languages support and more.

What should I do?

A few options:

  • Start a new project using Foundry! Check out the docs and templates!
  • Port some of your existing project's tests to Foundry! Other have done it already on both Hardhat and Brownie!
  • Get involved in Foundry development! There are Good First Issues on Github, and as you see from the above roadmap, there is a lot of work ahead.

If none of the above make sense for you, tell us what's missing for you to make the leap, and we'll add it!

Why is Paradigm doing all this?

First and foremost, we are building Foundry because we needed better tools for internal development than were available publicly. We are proud to be builders and want our tooling to be working for us, not against us. Because we use Foundry for all our projects at Paradigm, we keep a tight feedback loop around what we are building.

Second, by building Foundry, we can help projects in Paradigm’s portfolio ship faster and more safely. Our research and engineering teams spend considerable time with developers in our portfolio, but their time is, unfortunately, finite. By shipping a high-quality development framework, we can scale our efforts and help more teams.

Finally, we hope Foundry can be a contribution of enduring value to Ethereum and the ecosystem that has gathered around it. We encourage everyone building developer tooling to dig into the engineering innovations we have implemented in Foundry and integrate them into their services so more people can benefit from them.

Acknowledgments

We are proud to have >90 contributors to Foundry. I'd like to highlight the people that have helped us the most in this:

Finally, we’re hiring both internally at Paradigm and across the portfolio, or reach out to me at georgios@paradigm.xyz.

Written by:

Disclaimer: This post is for general information purposes only. It does not constitute investment advice or a recommendation or solicitation to buy or sell any investment and should not be used in the evaluation of the merits of making any investment decision. It should not be relied upon for accounting, legal or tax advice or investment recommendations. This post reflects the current opinions of the authors and is not made on behalf of Paradigm or its affiliates and does not necessarily reflect the opinions of Paradigm, its affiliates or individuals associated with Paradigm. The opinions reflected herein are subject to change without being updated.