DEV Community

Cover image for How to develop and deploy smart contracts with Foundry & Openzeppelin
Deep
Deep

Posted on

How to develop and deploy smart contracts with Foundry & Openzeppelin

Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.

Foundry is made up of three components:

Forge: Ethereum testing framework (like Truffle, Hardhat, and DappTools).

Cast: CLI for interacting with EVM smart contracts, sending transactions, and getting chain data.

Anvil: local Ethereum node, similar to Ganache or Hardhat Network.

Why should we use it?

https://miro.medium.com/max/700/0*SpttNRShCalW2K5p.jpg

  • It’s speedy, no more wasted time while running tests.
  • It allows you to write tests in solidity which minimizes context switching.
  • Many testing features like fuzzing, console.log, and cheat codes give you more power and flexibility.

Topics we are going to cover in this Article

  1. Project setup
  2. How to install dependencies in Foundry (EX. OpenZeppelin)
  3. Integrating Foundry with VSCode
  4. Writing the contract and test cases using Foundry
  5. Understanding Traces in Foundry
  6. Generating Gas report using Foundry
  7. Deploying the contract using Foundry

Installation

Installation of Foundry is well explained in the foundry book, check out the instructions here

https://book.getfoundry.sh/getting-started/installation

Setting up the project

once, the foundry is installed we can create a new project using

forge init foundry-demo // forge-demo is name of the project
Enter fullscreen mode Exit fullscreen mode

once the project is created, use commands to check if everything is working correctly

cd foundry-demo && forge build
Enter fullscreen mode Exit fullscreen mode

Installing dependencies

Forge manages dependencies using git submodules by default, which means that it works with any GitHub repository that contains smart contracts.

To use OpenZeppelin we need to install it as a dependency in our project, to-do so use the command

forge install OpenZeppelin/openzeppelin-contracts
// forge install is command which is used for installing  dependencies
// <https://github.com/OpenZeppelin/openzeppelin-contracts>
// use {{username}}/{{repo_name}} from the github url
Enter fullscreen mode Exit fullscreen mode

Integrate Foundry with VSCode

After installing OpenZeppelin as a dependency try importing something from it in the contract (your contract is there under the /src directory), If you are using VSCode an error will popup

Image description

To fix this error, Run this command

forge remappings > remappings.txt
Enter fullscreen mode Exit fullscreen mode

What this command does is it creates a remappings.txt file inside the root directory of the project

At this moment the content in the file might look like this,

ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
Enter fullscreen mode Exit fullscreen mode

For more details visit here https://book.getfoundry.sh/config/vscode

Writing the contract

Rename the file src/Counter.sol → src/FDemo.sol, the code for our ERC721 smart contract is as below

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

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

contract FDemo is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenId;

    constructor() ERC721("FDemo", "FD") {}

    function mint(string memory tokenUri) external returns (uint256) {
        uint256 newTokenId = _tokenId.current();

        _mint(msg.sender, newTokenId);
        _setTokenURI(newTokenId, tokenUri);

        _tokenId.increment();

        return newTokenId;
    }
}
Enter fullscreen mode Exit fullscreen mode

Testing the contract using Foundry

Let's start by renaming the test file to match the name of our contract Counter.t.sol → FDemo.t.sol

Forge uses the following keywords in tests:

  • setUp: An optional function invoked before each test case is run
function setUp() public {
    testNumber = 42;
}
Enter fullscreen mode Exit fullscreen mode
  • test: Functions prefixed with test are run as a test case
function testNumberIs42() public {
  assertEq(testNumber, 42);
}
Enter fullscreen mode Exit fullscreen mode
  • testFail: The inverse of the test prefix — if the function does not revert, the test fails
function testNumberIs42() public {
  assertEq(testNumber, 42);
}
Enter fullscreen mode Exit fullscreen mode

So, Right now we only have one method mint, so we will be writing a test case for this method

and this is going to be a pretty simple one

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

import "forge-std/Test.sol";
import "../src/FDemo.sol";

contract FoundryDemoTest is Test {
    FDemo instance;

    function setUp() public { 
        instance = new FDemo();
    }

    function testMint() public { 
        string memory dummyTokenUri = "ipfs://metadata_url";
        uint256 tokenId = instance.mint(dummyTokenUri);

        assertEq(dummyTokenUri, instance.tokenURI(tokenId));
    }

}
Enter fullscreen mode Exit fullscreen mode

Now, to run this test we can use the command forge test

If we want to explore more details/ events/ flow about the test cases, we can use Traces To enable Traces while running test cases use -vvv or -vvvv

The attached screenshot shows the result of test cases with and without traces

Image description

More details on the Traces: https://book.getfoundry.sh/forge/traces

Generating Gas report using Foundry

to generate gas report use — gas-report with the test command

forge test --gas-report
Enter fullscreen mode Exit fullscreen mode

Image description

More details on the Gas Report here: https://book.getfoundry.sh/forge/gas-reports

Deploying and verifying contracts with Foundry

Forge can deploy smart contracts to a given network with the forge create command.

Some options we can use with forge create while deploying the contract

  • —rpc-url : Rpc URL of the network on which we want to deploy our contract (in our case we will be using the RPC URL of polygon mumbai testnet)
  • constructor-args : Pass arguments to the constructor
  • private-key : Private key of deployers wallet

We can optionally pass --verify && --etherscan-api-key if we want to verify our contract

$ forge create --rpc-url <your_rpc_url> \\
    --constructor-args "ForgeUSD" "FUSD" 18 1000000000000000000000 \\
    --private-key <your_private_key> src/MyToken.sol:MyToken \\
    --etherscan-api-key <your_etherscan_api_key> \\
    --verify
Enter fullscreen mode Exit fullscreen mode

Let's deploy, now

forge create --rpc-url <https://rpc.ankr.com/polygon_mumbai>
--private-key <your_private_key>  src/FDemo.sol:FDemo
--etherscan-api-key <your_etherscan_api_key>
--verify
Enter fullscreen mode Exit fullscreen mode

Image description

Complete code: GitHub

💡 Follow me on Twitter for more awesome stuff like this @pateldeep_eth
Linkedin

Top comments (0)