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?
- 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
- Project setup
- How to install dependencies in Foundry (EX. OpenZeppelin)
- Integrating Foundry with VSCode
- Writing the contract and test cases using Foundry
- Understanding Traces in Foundry
- Generating Gas report using Foundry
- 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?embedable=true
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
Once the project is created, use commands to check if everything is working correctly.
cd foundry-demo && forge build
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
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 pop up.
To fix this error, Run this command.
forge remappings > remappings.txt
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/
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;
}
}
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;
}
test
: Functions prefixed withĀtest
Ā are run as a test case.
function testNumberIs42() public {
assertEq(testNumber, 42);
}
testFail
: The inverse of the test prefix ā if the function does not revert, the test fails.
function testNumberIs42() public {
assertEq(testNumber, 42);
}
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));
}
}
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.
More details on the Traces:Ā https://book.getfoundry.sh/forge/traces
Generating Gas Report Using Foundry
To generate a gas report, useĀ ā gas-reportĀ with the test command.
forge test --gas-report
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 constructorprivate-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
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
Complete code:Ā GitHub
š” Follow me on Twitter for more awesome stuff like thisĀ @pateldeep_eth Linkedin
Also published here