visit
Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Today’s focus is going to be on Forge. But I will be posting in-depth articles on Caste and Anvil in the upcoming weeks.
curl -L //foundry.paradigm.xyz | bash
foundryup
Make sure to close the terminal before running foundryup
.
cargo install --git //github.com/foundry-rs/foundry --locked
Start by creating a directory called “Figbots.” And run forge init
once you are inside the directory. This command will create a foundry project for you with git
initialized.
Let’s take a quick look at the folder structure. You have three primary folders, namely src, lib, and test. Very much self-explanatory here, you write your contracts in src
, tests in test
, and lib
contains all the libraries you installed, e.g., OpenZeppelin. In addition to that, you get foundry.toml
which contains all the configurations just like hardhat.config.js
and brownie-config.yaml
if you have used those frameworks. Another sweet thing is .github, where you can write your Github Actions. I find it really helpful for tests when working in a team.
Let’s start building! We will create a simple NFT called Figbot with a limited supply, cost (for minting), and withdrawal. With this approach, we can cover edges for different tests. First of all, rename Contract.sol
and test/Contract.t.sol
to Figbot.sol
and Figbot.t.sol
respectively. Now, we can not write smart contracts without Openzeppelin, can we?
forge install Openzeppelin/openzeppelin-contracts
Now we can import the ERC721URIStorage.sol extension to create our NFT. To check that everything is alright, we can run the command forge build
, and it will compile our project. The compiler will yell at you if there is something wrong. Otherwise, you will get a successful compile.
Just like any other package manager, Forge allows you to use forge install <lib>,
forge remove <lib>
, and forge update <lib>
to manage your dependencies.
We will be using three contracts from the Openzeppelin. Counters, ERC721URIStorage, and Ownable. Time to upload our to IPFS using . We use the Ownable contract to set deploying address owner
and have access to onlyOwner
modifier to allow only the owner to withdraw funds. Counters
to help us with token id(s) and ERC721URIStorage
to keep the NFT contract simple.
MAX_SUPPLY
to 100COST
to 0.69 etherTOKEN_URI
to CID, we receive from Pinatausing Counters for Counters.Counter;
Counters.Counter private tokenIds;
constructor() ERC721(“Figbot”, “FBT”) {}
msg.value
is greater than COST
tokenIds.current()
is greater or equal to MAX_SUPPLY
_safeMint
and _setTokenURI
function withdrawFunds() external onlyOwner { uint256 balance = address(this).balance; require(balance > 0, "No ether left to withdraw"); (bool success, ) = (msg.sender).call{value: balance}(""); require(success, "Withdrawal Failed"); emit Withdraw(msg.sender, balance); }
function totalSupply() public view returns (uint256) { return _tokenIds.current(); }
As we all know, testing our smart contracts is really important. In this section, we will be writing some tests to get a solid understanding of forge test
and get used to writing tests in native solidity. We will be three (I love them!) to manage account states to fit our test scenario.
As we can have complex logic in our smart contracts. And they are expected to behave differently depending on the state, the account used to invoke, time, etc. To deal with such scenarios, we can use cheatcodes to manage the state of the blockchain. We can use these cheatcodes using vm
instance, which is a part of Foundry’s Test
library.
startPrank
: Sets msg.sender
for all subsequent calls until stopPrank
is called.
stopPrank
:
Stops an active prank started by startPrank
, resetting msg.sender
and tx.origin
to the values before startPrank
was called.
deal
: Sets the balance of an address provided address to the given balance.
Foundry comes with a built-in testing library. We start by importing this test library, our contract (the one we want to test), defining the test, setting variables, and setUp
function.
pragma solidity ^0.8.13;
import"forge-std/Test.sol";
import "../src/Figbot.sol";
contract FigbotTest is Test {
Figbot figbot;
address owner = address(0x1223);
address alice = address(0x1889);
address bob = address(0x1778);
function setUp() public {
vm.startPrank(owner);
figbot = new Figbot();
vm.stopPrank();
}
}
For state variables, we create a variable figbot
of type Figbot
. This is also the place where I like to define user accounts. In Foundry, you can describe an address by using the syntax address(0x1243)
. you can use any four alphanumeric characters for this. I have created the accounts named owner, Alice, and bob, respectively.
Now our setUp
function. This is a requirement for writing tests in Foundry. This is where we do all the deployments and things of that nature. I used the cheatcode startPrank
to switch the user to the “owner.” By default, Foundry uses a specific address to deploy test contracts. But that makes it harder to test functions with special privileges like withdrawFunds
. Hence, we switch to the “owner” account for this deployment.
Starting with a simple assertion test to learn Foundry convention. By convention, all the test functions must have the prefix test
. And we use assertEq
to test if two values are equal.
We call our MaxSupply
function and test if the result value is 100, as we described in our contract. And we use forge test
to run our tests.
balanceOf
Alice is 1
We have another testing function used for tests that we expect to fail. The prefix used for such a test is testFail
. We will test if the mint
function reverts if the caller has insufficient funds.
balanceOf
Bob is 1
Because mint didn’t go through, the balance of Bob is not going to be 1. Hence, it will fail, which is exactly what we are used testFail
for. So when you run forge test
, it will pass.
withdrawFunds
function ( if successful, it should make the owner’s balance 0.69 ether)
Open your terminal to enter both of these things as environment variables.
export FIG_RINKEBY_URL=<Your RPC endpoint>
export PVT_KEY=<Your wallets private key>
forge create Figbot --rpc-url=$FIG_RINKEBY_URL --private-key=$PVT_KEY
export ETHERSCAN_API=<Your Etherscan API Key>
forge verify-contract --chain-id <Chain-Id> --num-of-optimizations 200 --compiler-version <Compiler Version> src/<Contract File>:<Contract> $ETHERSCAN_API
Congratulations! Now you can write, test, and deploy smart contracts using Foundry. I hope you enjoyed and learned from this article. I indeed enjoyed writing this. Feel free to let me know your thoughts about it.