GitHub Repo:- https://github.com/PradhumnaPancholi/Figbot
Hey everyone! A little while ago, I was learning Dapp Tools as it has fantastic tools for developing and auditing smart contracts. And although I loved the experience, I soon learned that it is in the clandestine development stage. This means that casual/individual users can not depend on maintainers for support and updates.
Then I stumbled upon Foundry. It has everything that Dapp Tools offers apart from built-in symbolic execution (which is not a problem for me as I use Manticore by Trail of Bits ). And this is auditing related hence not a hindrance in smart contract development by any stretch of the imagination.
After working with Foundry for a bit, I enjoyed the experience and wanted to share that with others. Hence, this article.
This article will go through the benefits of Foundry, the installation process, developing an NFT (because everyone is interested in that), testing the contract, and deploying it with Figment Datahub.
Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Foundry is made up of three components:
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.
There are many smart contract development tools like Truffle, Hardhat, and Brownie. But one of my primary reasons for looking into Dapp Tools in the first place was native Solidity tests. Writing smart contracts is not hard when switching between frameworks like Hardhat and Brownie. And they are incredible tools with plugins, but one needs to be well versed in JavaScript/TypeScript and Python to perform testing.
Foundry allows us to write our tests natively in Solidity. This saves a lot of time onboarding new developers and makes the process smoother. In my experience of helping people navigate their way into smart contracts development, I have learned that the best and most efficient way for junior developers to engage with DAO/community-maintained projects is by writing tests and learning about the code-base itself. I remember Scupy Trooples once mentioned that they used the same approach while developing Alchemix Finance on Bankless.
In addition to that, built-in fuzzing, cheat codes, Cast, and Anvil make it a solid suite for testing smart contracts. There will be more detailed articles on those components coming soon. [Easy to integrate static analyzer]
Let’s dive in and build an NFT project now.
If you are on Mac or Linux, all you need to do is run two commands:
curl -L https://foundry.paradigm.xyz | bash
foundryup
Make sure to close the terminal before running foundryup
.
And Voila! You are all done.
For Windows, you need to have Rust installed and then :
cargo install --git https://github.com/foundry-rs/foundry --locked
For this article, we will be creating a simple NFT project called Figbots.
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?
Installing libraries with Foundry is slightly different than Hardhat and Brownie. We don’t have npm or pip packages. We install libraries directly from the Source (GitHub repo) in Foundry.
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 asset to IPFS using Pinata. 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.
Setting state variable:
MAX_SUPPLY
to 100COST
to 0.69 etherTOKEN_URI
to CID, we receive from PinataUsing Counter for token id:
using Counters for Counters.Counter;
Counters.Counter private tokenIds;
ERC721 constructor:
constructor() ERC721(“Figbot”, “FBT”) {}
Mint function:
msg.value
is greater than COST
tokenIds.current()
is greater or equal to MAX_SUPPLY
_safeMint
and _setTokenURI
Withdraw function:
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); }
TotalSupply function:
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 Foundry cheat codes (I love them!) to manage account states to fit our test scenario.
We will be testing for the following scenarios:
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.
We will be using three cheatcodes in our tests :
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.
And Voila!!! we have a passed test.
Now that we have written a simple test, let’s write one with cheatcodes. The primary function of our contract.
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.
Here we will test a function that only the “owner” can successfully perform. For this test, we will :
withdrawFunds
function ( if successful, it should make the owner’s balance 0.69 ether)
Now that we have tested our contract, it is time to deploy it. We need private keys to a wallet (with some Rinkeby test ETH) and an RPC URL. For our RPC URL, we will use Figment DataHu.
Figment DataHub provides us with infrastructure to develop on Web 3. It supports multiple chains like Ethereum, Celo, Solana, Terra, etc.
You can get your RPC URL for Rinkeby from under the “Protocols” tab.
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>
Once we have the environment variables, we are all set to deploy
forge create Figbot --rpc-url=$FIG_RINKEBY_URL --private-key=$PVT_KEY
We are almost done here. So far, we have written, tested, and deployed a smart contract with Foundry and Figment DataHub. But we are not entirely done just yet. We are now going to verify our contract. We will need to set up our Etherscan API key for that.
export ETHERSCAN_API=<Your Etherscan API Key>
And now we can verify our smart contract.
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.