• Stars
    star
    144
  • Rank 255,590 (Top 6 %)
  • Language
    Solidity
  • Created about 2 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Building and testing smart contracts with Foundry

Building and testing smart contracts with Foundry

A Complete Introduction to Smart Contract Development with Foundry

The slides for this workshop are available here

Also see Foundry Book, Foundry cheatsheet, and this video tutorial

Things that we'll be covering

  • Testing & assertions
  • Fuzzing
  • Logging
  • Running a local node
  • Deploying a contract to the local node
  • Calling contracts from the CLI
  • Installing and using libraries
  • Configuring remappings
  • Mocking users

In this workshop you'll learn how to build, test, and deploy smart contracts with Solidity using Foundry. We'll start with an overview of the various CLIs and libraries that make up Foundry, what they do, and how to install them.

We'll then spin up a new project, walk through the file structure, and create an example smart contract. From there we'll create and run tests for the contract. Next, we'll run a local test Ethereum network, deploy the contract to the network, and use cast to call end execute transactions to the test network.

We'll then deploy the to Optimism to show how to deploy to a live network. Finally, we'll create an ERC721 contract and learn how to mock users to test out various functions like minting a token, transferring a token, and burning a token.

By the end of this workshop, you should have a good understanding of how Foundry works and how to use it to build, test, and deploy smart contracts.

Prerequisites

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

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 and scripts 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).

  3. Fuzzing - Fast fuzz testing with shrinking of inputs & printing of counter-examples

  4. Flexible debug logging - dappTools-style, using DsTest's emitted logs, or Hardhat-style, using the console.sol contract

  5. Fast CI with the Foundry GitHub action.

  6. Cast - Cast is Foundry's command-line tool for performing Ethereum RPC calls. You can make smart contract calls, send transactions, or retrieve any type of chain data - all from your command-line!

Foundry

Foundry is made up of 3 CLI tools - Forge, Cast, and Anvil.

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.

Anvil is a local Ethereum node, similar to Ganache or Hardhat node.

Building & testing a smart contract with Foundry

Prerequisites

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

VS Code Solidity configuration

If you're using VS Code, consider setting up the following Solidity configurations so that your text editor knows where to find the dependencies and your Solidity code by setting:

  1. Package Default Dependencies Contracts Directory as src
  2. Package Default Dependencies Directory as lib

Solidity configuration

Let's build

If you have any issues with the installation instructions, you can see more detailed instructions here.

To get started, we'll install the latest release:

curl -L https://foundry.paradigm.xyz | bash

This will download foundryup. Then install Foundry by running:

foundryup

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

forge init

The forge CLI will create a few files and folders, including lib, src, script, and test.

The lib directory contains forge-std, a collection of helpful contracts for use with forge and foundry.

The src directory contains a barebones smart contract.

The test directory contains an example test.

The script directory contains an example script.

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

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

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;
  }
}

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

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import 'src/HelloWorld.sol';

contract HelloWorldTest is Test {
    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"
        );
    }
}

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

forge build

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

Tests

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

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

forge test output

There are also utilities for:

truthiness - assertTrue

decimal equality - assertEqDecimal

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

You can view most of the assertions here.

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
    );
}

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

Logging

Foundry also supports logging.

To log out the greeting, we can use log, log_string, or log_named_string:

function test2() public {
    assertEq(hello.version(), 0);
    hello.updateGreeting("Hello World");
    assertEq(hello.version(), 1);
    string memory value = hello.greet();
    emit log(value);
    assertEq(
        hello.greet(),
        "Hello World"
    );
}

To print the logs, we need to run the test script with the -vv flag:

forge test -vv

To learn more about logs and traces, check out the documentation here.

To view all of the supported logging methods, check out the documentation here.

Anvil

You can start the local EVM test network at any time:

anvil

This will start a local network and spin up 10 accounts and private keys and log them out to the console.

Once the network is running, we can use forge to deploy the contract to the network.

To do so, update the name of script/Counter.s.sol to script/HelloWorld.s.sol

Next, update the script in script/HelloWorld.s.sol:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import { HelloWorld } from 'src/HelloWorld.sol';

contract ContractScript is Script {
    function setUp() public {}

    function run() public {
        vm.broadcast();
        new HelloWorld("Hello from Foundry!");
    }
}

Next, set the PRIVATE_KEY variable by using one of the private keys given to you by Anvil:

export PRIVATE_KEY=<your-private-key>

To deploy, run this script:

forge script script/HelloWorld.s.sol:ContractScript --fork-url http://localhost:8545 \
--private-key $PRIVATE_KEY --broadcast

Once the contract is deployed, the contract address will be logged out to your terminal.

Set the CONTRACT_ADDRESS variable in your terminal:

export CONTRACT_ADDRESS=<your-contract-address>

We can then use cast to interact with it.

For read operations, we can use cast call:

cast call $CONTRACT_ADDRESS "greet()(string)"

For transactions, we can use cast send, passing in a private key and any arguments:

cast send $CONTRACT_ADDRESS "updateGreeting(string)" "My new greeting" --private-key $PRIVATE_KEY

To test that the greeting has been updated, run the call command again:

cast call $CONTRACT_ADDRESS "greet()(string)"

Installing packages

You can install packages using the forge install command.

To try this out, let's install OpenZeppelin Contracts, then we'll use them to create an ERC721 token:

You may need to add and commit any changes to your code to git in order to run the install script.

forge install OpenZeppelin/openzeppelin-contracts

Next, create a file named remappings.txt in the root of the project and add the following configuration:

@openzeppelin/=lib/openzeppelin-contracts/

This will allow us to easily import with the following syntax:

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

You can view all of the automatically inferred remappings for the project by running the following command:

forge remappings

ERC721 contract

With OpenZeppelin Contracts installed, let's create a new file named ERC721.sol in the src directory and add the following code:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract DevconPanda is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() ERC721("DevconPanda", "DCP") {}

    function mint(address user, string memory tokenURI) public returns (uint256) {
        uint256 newItemId = _tokenIds.current();
        _mint(user, newItemId);
        _setTokenURI(newItemId, tokenURI);

        _tokenIds.increment();
        return newItemId;
    }
}

Next, let's write the test.

In the test directory, create a new file named ERC721.t.sol and add the following code:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import 'src/ERC721.sol';

contract ERC721Test is Test {
    DevconPanda devconPanda;
    address noah = address(0x1);
    address sofia = address(0x2);

    function setUp() public {
      devconPanda = new DevconPanda();
    }

    function testMint() public {
      devconPanda.mint(noah, "testhash");
      address owner_of = devconPanda.ownerOf(0);
      assertEq(noah, owner_of);
    }
}

The testMint function will test that we have minted a token, and that the owner of that token belongs to the expected owner.

To try it out, run the test command:

forge test

As you can see, this command will run every test in the entire project.

If you'd like to only test a certain contract, you can run this command:

forge test --match-contract ERC721

Mocking another user

Next let's look at the prank cheatcode.

prank sets msg.sender to the specified address for the next call. "The next call" includes static calls as well, but not calls to the cheat code address.

This will allow us to mock, or simulate, whatever user we'd like to simulate in our test.

You can also use startPrank which will set msg.sender for all subsequent calls until stopPrank is called.

Let's try using prank to transfer a token to another user.

Because we will be simulating the token owner, we should be able to transfer the token. If we were not simulating the token owner, the test should fail (feel free to give it a shot!).

To test this out, add the following function to the test:

function testTransfer() public {
  devconPanda.mint(noah, "testhash");
  vm.startPrank(noah);
  devconPanda.safeTransferFrom(noah, sofia, 0);

  address ownerOf = devconPanda.ownerOf(0);
  assertEq(sofia, ownerOf);
}

Finally, let's check the balance of a user's address:

function testBalance() public {
  devconPanda.mint(sofia, "testhash");
  devconPanda.mint(sofia, "testhash");
  devconPanda.mint(sofia, "testhash");

  uint balance = devconPanda.balanceOf(sofia);
  assertEq(balance, 3);
}

Test coverage

You can check for test coverage by running the coverage command:

forge coverage

To debug in more details what has not been covered, use the debug report:

forge coverage --report debug

To learn more about what you can do with test coverage, check out this blog post or run forge coverage --help

Conclusion

To learn more, check out the Foundry Book, Foundry cheatsheet, and this video tutorial

More Repositories

1

awesome-aws-amplify

Curated list of AWS Amplify Resources
1,782
star
2

polygon-ethereum-nextjs-marketplace

A full stack digital marketplace running on Ethereum with Polygon & Next.js
JavaScript
1,303
star
3

full-stack-ethereum

Building full stack apps with Solidity, Ethers.js, Hardhat, and The Graph
TypeScript
802
star
4

react-native-ai

Full stack framework for building cross-platform mobile AI apps
TypeScript
794
star
5

semantic-search-nextjs-pinecone-langchain-chatgpt

Embeds text files into vectors, stores them on Pinecone, and enables semantic search using GPT3 and Langchain in a Next.js UI
TypeScript
739
star
6

awesome-aws-appsync

Curated list of AWS AppSync Resources
625
star
7

gpt-travel-advisor

reference architecture for building a travel application with GPT3
TypeScript
543
star
8

foundry-cheatsheet

522
star
9

complete-guide-to-full-stack-solana-development

Code examples for the blog post titled The Complete Guide to Full Stack Solana Development with React, Anchor, Rust, and Phantom
JavaScript
474
star
10

dynamodb-documentclient-cheat-sheet

DynamoDB JavaScript DocumentClient cheat sheet
443
star
11

full-stack-web3

A full stack web3 on-chain blog and CMS
JavaScript
420
star
12

next.js-amplify-workshop

AWS Amplify Next.js workshop
JavaScript
360
star
13

gatsby-auth-starter-aws-amplify

Starter Project with Authentication with Gatsby & AWS Amplify
JavaScript
320
star
14

gpt-fine-tuning-with-nodejs

GPT Fine-Tuning using Node.js - an easy to use starter project
JavaScript
250
star
15

full-stack-serverless-code

Code examples for my book Full Stack Serverless with O'Reilly Publications
JavaScript
244
star
16

openai-functions-god-app

TypeScript
240
star
17

heard

React Native Enterprise Social Messaging App
JavaScript
236
star
18

aws-appsync-react-workshop

Building real-time offline-ready Applications with React, GraphQL & AWS AppSync
JavaScript
228
star
19

nextjs-chatgpt-plugin-starter

ChatGPT plugin starter project using Next.js
TypeScript
209
star
20

micro-frontend-example

Building Micro Frontends with React, Vue, and Single-spa
JavaScript
207
star
21

amplify-photo-sharing-workshop

Building full-stack cloud apps with AWS Amplify and React
JavaScript
197
star
22

chicken-tikka-masala-recipe

Nader's chicken tikka masala recipe
PHP
193
star
23

decentralized-identity-example

An authentication system built with Ceramic & self.id
JavaScript
190
star
24

aws-amplify-workshop-react

Building Serverless React Applications with AWS Amplify
172
star
25

building-a-subgraph-workshop

In this workshop you'll learn how to build an NFT Subgraph using any smart contract or smart contracts.
TypeScript
165
star
26

graphql-recipes

A list of GraphQL recipes that, when used with the Amplify CLI, will deploy an entire AWS AppSync GraphQL backend.
158
star
27

next.js-cdk-amplify-workshop

Full stack serverless workshop with Next.js, CDK, and AWS Amplify
JavaScript
157
star
28

supabase-next.js

Full stack app built with Supabase and Next.js
JavaScript
152
star
29

titter

Decentralized Twitter prototype built with Polygon, GraphQL, Next.js, Ceramic, Arweave, and Bundlr
JavaScript
152
star
30

supabase-nextjs-auth

Example project implementing authentication, authorization, and routing with Next.js and Supabase
JavaScript
151
star
31

react-native-in-action

React Native in Action, written for Manning Publications
146
star
32

prompt-engineering-for-javascript-developers

Notes summarized from ChatGPT Prompt Engineering for Developers by DeepLearning.ai
145
star
33

write-with-me

Real-time Collaborative Markdown Editor
JavaScript
144
star
34

aws-amplify-workshop-react-native

Building Cloud-enabled Mobile Applications with React Native & AWS Amplify
JavaScript
139
star
35

lens-protocol-frontend

Example of a basic front end built on Lens Protocol
JavaScript
136
star
36

cdk-graphql-backend

A real-time GraphQL API deployed with CDK using AWS AppSync, AWS Lambda, and DynamoDB
TypeScript
131
star
37

create-new-cli

Create your own CLI using a series of simple commands.
JavaScript
128
star
38

perma

Perma is a web3 prototype of permanent video storage and viewing using Next.js, Arweave, and Bundlr.
JavaScript
120
star
39

appsync-graphql-real-time-canvas

Collaborative real-time canvas built with GraphQL, AWS AppSync, & React Canvas Draw
JavaScript
119
star
40

full-stack-ethereum-marketplace-workshop

Build a Full Stack Marketplace on Ethereum with React, Solidity, Hardhat, and Ethers.js
JavaScript
118
star
41

amplify-auth-demo

Demo of OAuth + Username / Password authentication in AWS Amplify
JavaScript
111
star
42

hype-beats

Real-time Collaborative Beatbox with React & GraphQL
JavaScript
111
star
43

next.js-authentication-aws

This project deploys a Next.js project to AWS with comprehensive authentication enabled
JavaScript
104
star
44

this-or-that

This or that - Real-time atomic voting app built with AWS Amplify
CSS
98
star
45

react-native-deep-linking

Deep Linking set up in a React Native App
Objective-C
96
star
46

beginning-webpack

This repository goes along with the medium post titled "Beginner's guide to Webpack"
JavaScript
95
star
47

react-native-bootcamp

React Native Bootcamp Materials for TylerMcginnis.com
94
star
48

react-native-mobx-list-app

React Native + Mobx List Application
JavaScript
91
star
49

appsync-auth-and-unauth

How to allow both authenticated & unauthenticated access to an API
JavaScript
91
star
50

aws-amplify-workshop-web

Building web applications with React & AWS Amplify
JavaScript
90
star
51

full-stack-ethereum-workshop

Building full stack dapps on the EVM with Hardhat, React, and Ethers.js
HTML
89
star
52

sign-in-with-ethereum-authentication-flow

Example implementation of how to implement Sign In with Ethereum
JavaScript
85
star
53

react-notes

React notes tutorial
JavaScript
84
star
54

vue-graphql-appsync

Vue example using GraphQL with AWS AppSync
JavaScript
81
star
55

lens-pwa

Lens PWA
TypeScript
80
star
56

amplify-datastore-example

Example of basic app using Amplify DataStore
JavaScript
78
star
57

custom-nft-subgraph-workshop

78
star
58

lens-shadcn

Example application combining Lens Protocol, WalletConnect, Next.js, and ShadCN
TypeScript
78
star
59

amplify-with-cdk

An example project showing how to mix CDK with AWS Amplify
TypeScript
77
star
60

react-aws-live-streaming

This project shows how to implement a live-streaming platform using AWS and React
JavaScript
73
star
61

react-native-navigation-v2

Up and running with React Native Navigation - V2 - by Wix
JavaScript
73
star
62

bored-ape-yacht-club-api-and-subgraph

Graph Protocol Subgraph / API for querying Bored Ape Yacht Club NFT data with full text search
TypeScript
70
star
63

react-appsync-graphql-recipe-app

Example application using React + AWS AppSync + GraphQL
JavaScript
70
star
64

react-amplify-appsync-files-s3

An example project showing how to upload and download public and private images in GraphQL using AppSync and S3
JavaScript
70
star
65

react-strict-dom-example

JavaScript
69
star
66

full-stack-react-native-appsync-workshop

Building Full Stack GraphQL Applications with React Native & AWS AppSync
JavaScript
67
star
67

speakerchat

SpeakerChat - Real-time Event Q&A Platform with Markdown Support
JavaScript
66
star
68

react-p2p-messaging

A real-time peer-to-peer messaging app built with React & Gun.js
JavaScript
65
star
69

graphql-suspense

Lightweight component that allows you to interact with a GraphQL API using React Suspense
JavaScript
65
star
70

nuxt-supabase-full-multi-user-blog

Build a mult-user blogging app with Supabase and Nuxt.js
Vue
65
star
71

archive-forever

HTML
63
star
72

next.js-tailwind-authentication

A Next.js authentication starter built with Tailwind and AWS Amplify
JavaScript
63
star
73

near-subgraph-workshop

Building a NEAR NFT API with The Graph
60
star
74

react-authentication-in-depth

Example of User Authentication using React with React Router and AWS Amplify
JavaScript
60
star
75

graphql-api-cdk-serverless-postgres

TypeScript
59
star
76

curious-cases-of-graphql

Code and examples from my talk - Curious Cases of GraphQL
57
star
77

gasless-transactions-example

Example of Gasless Transactions with Biconomy
JavaScript
57
star
78

react-chatbots

Building Chatbots with React, Amazon Lex, AWS Lambda, & AWS Amplify
JavaScript
57
star
79

xmtp-chat-app-nextjs

Real-time encrypted chat, built with XMTP and Next.js
TypeScript
57
star
80

react-native-lens-example

Example app built with React Native Lens UI Kit
JavaScript
55
star
81

lens-protocol-workshop

Introduction to web3 social media with Next.js and Lens Protocol
TypeScript
54
star
82

zora-nextjs-app

Example Full Stack App built with Next.js, Zora, Tailwind, and The Graph
TypeScript
54
star
83

draw-together

TypeScript
53
star
84

terminal-portfolio

TypeScript
51
star
85

arweave-workshop

JavaScript
50
star
86

lens-gated-publications

Example application implementing gated Lens posts, encryption, and decryption
JavaScript
49
star
87

graphql-search

Implementing Search in GraphQL using AWS AppSync & React Apollo
JavaScript
49
star
88

basic-amplify-storage-example

A basic example app showing how to add storage with Amazon S3
JavaScript
49
star
89

production-ready-vue-authentication

How to implement a real user authentication flow in Vue with Vue Router & AWS Amplify.
Vue
48
star
90

build-an-authenticated-api-with-cdk

Workshop - Build an authenticated CDK back end
TypeScript
48
star
91

real-time-image-tracking

Real-time image tracking with React, GraphQL, and AWS AppSync
JavaScript
48
star
92

appsync-lambda-ai

Demo of using a GraphQL resolver to hit a lambda function, then hit a few AI services, and return the response.
JavaScript
47
star
93

appsync-react-native-with-user-authorization

End to end React Native + AWS AppSync GraphQL application with queries, mutations, subscriptions, & user authentication & authorization
JavaScript
47
star
94

openzeppelin-nft-api

Building NFT APIs with OpenZeppelin and The Graph
46
star
95

cryptocoven-api

Cryptocoven Graph API
TypeScript
46
star
96

transilator

Text translation and synthesization Chrome plugin
JavaScript
46
star
97

xp-mobile-account-abstraction

TypeScript
45
star
98

react-native-navigator-experimental-redux

React Native Navigator Experimental with Redux
JavaScript
45
star
99

next.js-amplify-datastore

An example app using Amplify DataStore with Next.js for static site generation, pre-rendering, and SSR
JavaScript
45
star
100

full-stack-warp-arweave

JavaScript
44
star