Foundry is a smart contract development toolkit for Ethereum application development written in Rust. It is an alternative technology to Ethereum development tools like Truffle and Hardhat.
Before we enter Foundry, smart contracts are self-executing digital contracts that execute predefined rules and run automatically on the blockchain. You can check out our blog post on smart contracts to learn more about smart contracts.
Foundry has the following four major components:
- Forge: Forge is a smart contract testing framework that ships with Foundry. Forge tests, builds, and deploys your smart contracts.
- 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!
- Anvil: Anvil is a local testnet node shipped with Foundry. You can use it to test your contracts from frontends or interact with RPC. This is similar to Truffle’s ganache
- Chisel: Chisel is an advanced Solidity REPL shipped with Foundry. It can be used to quickly test the behavior of Solidity snippets on a local or forked network.
FOUNDRY INSTALLATION
Installing Foundry is done using the Foundry installer Foundryup.
To get started with the installation, open any terminal of your choice and run the following command:
curl -L https://foundry.paradigm.xyz | bash |
After a successful installation, we should see results like this:
Next, we would follow the command to open another terminal tab and run this command:
foundryup |
If correctly done, we should see results like this:
SETTING UP A PROJECT WITH FOUNDRY
Now that Foundry is installed, we will go ahead to initialize a new project with it using the command below:
forge init foundry-hello-world |
Note that foundry-hello-world is the name of the project
Once the project is created, we can navigate into the directory and check the generated project by running the following command:
Cd foundry-hello-world |
In order to list all files and directories in the current directory, including hidden ones, and display them in the long format, we would run the following command:
Ls -al |
We should see results like this:
Now we can go ahead to build the project by running this command:
Forge build |
Once build is successful, you should see results like this:
INSTALLING DEPENDENCIES
By default, Forge handles dependencies through git submodules, allowing compatibility with any GitHub repository containing 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 the command for installing dependencies and Openzeppelin is the dependency,
The command above pulls the openZeppelin-contracts library, stages the .gitmodules file in git and makes a commit with the message “Installed openzeppelin-contracts”.
If done correctly, we should see results like this:
PROJECT LAYOUT
Forge is flexible on how you structure your project. By default, the structure looks like this:
You can configure Foundry’s behavior using foundry.toml.
- Remappings are specified in remappings.txt.
- The default directory for contracts is src/.
- The default test directory is test/, where any contract with a function that starts with test is considered a test.
- Dependencies are stored as git submodules in lib/.
You can configure where Forge looks for dependencies and contracts using the –lib-paths and –contracts flags, respectively. Alternatively, you can configure it in foundry.toml.
Combined with remappings, this gives you the flexibility needed to support the project structure of other toolchains such as Hardhat and Truffle.
Note, if you are using VScode like I am, you might encounter errors while importing dependencies, like this
To solve this error, the solution will require remapping
To do this 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/ erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ forge-std/=lib/forge-std/src/ openzeppelin-contracts/=lib/openzeppelin-contracts/ openzeppelin/=lib/openzeppelin-contracts/contracts/ |
WRITING SMART-CONTRACTS
Next, we will create a Simple token contract file named SimpleToken.sol
And replace it with Counter.sol in our foundry-hello-world project
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import “openzeppelin/contracts/token/ERC20/ERC20.sol”; contract SimpleToken is ERC20 { constructor(uint256 initialSupply) ERC20(“Simple Token”, “STK”) { _mint(msg.sender, initialSupply * (10 ** uint256(decimals()))); } } |
TESTING THE SIMPLE TOKEN CONTRACT WITH FOUNDRY
Tests are written in solidity which minimizes context switching.
To test the token smart-contract above, we have to rename the test file from Counter.t.sol to SimpleToken.t.sol
This code below is the test contract
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Test, console2} from “forge-std/Test.sol”; import {SimpleToken} from “../src/SimpleToken.sol”; contract SimpleTokenTest is Test { SimpleToken public token; function setUp() public { token = new SimpleToken(1000000); } function test_InitialSupply() public { assertEq(token.name(), “Simple Token”); assertEq(token.symbol(), “STK”); assertEq(token.totalSupply(), 1000000 * 10 ** 18); uint256 ownerBalance = token.balanceOf(address(this)); assertEq(ownerBalance, 1000000 * 10 ** 18); } } |
After writing the code test, write the code for the script named SimpleToken.s.sol
With this code
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Script, console2} from “forge-std/Script.sol”; contract SimpleTokenScript is Script { function setUp() public {} function run() public { vm.broadcast(); } } |
After this, run forge test in your terminal, if run correctly you should see results like this
GENERATING GAS REPORT
To generate gas reports for our contracts, well use the –gas-report flag with the forge test command like this:
Forge-test –gas-report |
If run correctly, we should see results like this:
DEPLOYING SMART CONTRACTS WITH FOUNDRY
Forge can deploy smart contracts to a given network with the forge create command.
Forge can deploy only one contract at a time.
To deploy a contract, you must provide an RPC URL (env: ETH_RPC_URL) and the account’s private key that will deploy the contract.
Now, we’ll deploy our SimpleToken contract to a network using the command
forge create –rpc-url <your_rpc_url> –private-key <your_private_key> src/SimpleToken.sol:SimpleToken
rpc-url is the RPC on the network you want to deploy to, private-key is your deployer wallet private key.
Solidity files may contain multiple contracts. :SimpleToken above specifies which contract to deploy from the src/SimpleToken.sol file.
Use the –constructor-args flag to pass arguments to the constructor.
To run the complete command for deploying the SimpleToken Contract to Binance smart chain testnet, we will run this command in our terminal:
forge create –rpc-url https://data-seed-prebsc-2-s2.binance.org:8545 –private-key <Your-private-Key> src/SimpleToken.sol:SimpleToken –constructor-args “Simple Token” “STK” 18 1000000000000000000000 |
If run correctly, we should see results like this:
Optionally, we can also verify contracts if you have your API key using the –verify <Etherscan-api-key> flag.
CONCLUSION
Foundry is a very convenient tool for deploying smart contracts. You can read and practice more on foundry by accessing their documentation.