Foundry Forge Tutorial: How to Compile, Test, and Deploy Solidity Smart Contracts
TL;DR: Forge is a powerful tool for Solidity smart contract development and testing within the Foundry framework. It offers features like fast compilation, flexible testing with fuzzing and property-based testing, support for multiple networks. Forge is designed to streamline the entire smart contract development lifecycle from coding to deployment in EVM blockchain networks.
Table of Contents
Foundry is a key tool for working with smart contracts in Solidity and EVM networks. Foundry provides a set of tools to facilitate the development, testing, and deployment of smart contracts. One of these tools is Forge.
Forge is the core tool of Foundry for Solidity smart contract development and testing. It provides a powerful and flexible environment for writing, compiling, testing, and deploying smart contracts. Forge is designed to be fast and efficient, allowing developers to iterate quickly on their code and test it thoroughly.
In this post, we will explore some examples of how to use Forge in real-world scenarios to compile, test, deploy and verify smart contracts on EVM networks.
Compilation
One of the first steps in smart contract development is to compile your Solidity code. Forge provides a fast and efficient compiler that supports multiple versions of Solidity and allows you to configure the compilation process with various options.
If you want to copy the commands, just click on the command box and it will be copied to your clipboard.
Basic compilation
The simplest scenario is to run Forge with the default configuration. Forge auto-detects the required Solidity version from your contracts' pragma statements and downloads the compiler automatically. You can do this by running the following command in your terminal:
forge build
In case you want to specify a particular Solidity version or configure the compiler settings, you can do so in the foundry.toml file or by using CLI flags. For example, to specify a different compiler version, you can add the following to your foundry.toml:
[profile.default]
solc_version = "0.8.28"
Or in the case you want to specify a range of versions, you can do it like this:
[profile.default]
solc_version = "0.8.0" < "0.9.0"
Optimize compilation
In some scenarios it is necessary to optimize the compilation of your smart contracts, especially when you are preparing for deployment on a live network. This action reduces the size of the compiled bytecode and can lower the gas cost of deploying and executing your contracts. Forge allows you to enable optimization and configure the number of runs to balance between deployment cost and runtime efficiency.
To enable optimization, you can add the following to your foundry.toml:
[profile.default]
optimizer = true
optimizer_runs = 200
Higher optimizer runs can lead to more efficient bytecode for contracts that will be executed frequently, while lower runs may be better for contracts that are deployed once and not called often.
If you want the high optimization level, you can use --via-ir flag. In the foundry.toml file, you can set it like this:
[profile.default]
optimizer = true
via_ir = true
Furthermore, Foundry allows you to create different running profiles to have different configurations for different scenarios. For example, you can have a profile for testing, another for deployment, another for optimization, etc.
Here is an example of how to create different running profiles in foundry.toml:
[profile.default]
optimizer = true
optimizer_runs = 200
via_ir = true
[profile.dev]
optimizer = false
via_ir = false
[profile.production]
optimizer = true
optimizer_runs = 200
via_ir = true
To compile and run any command in the different profiles, you can set the value of the environment variable FOUNDRY_PROFILE:
FOUNDRY_PROFILE=production forge build
FOUNDRY_PROFILE=production forge test
When you are running a smart contract compilation, you should be aware of the smart contract size limit in EVM networks, which is 24KB. If your compiled bytecode exceeds this limit, you will not be able to deploy your contract on the network. In such cases, you may need to optimize your code or split it into smaller contracts to fit within the size limit. To check the size of your compiled contracts, you can use the following command:
forge build --sizes
This command will display the size of each compiled contract, allowing you to identify any contracts that may exceed the size limit and need optimization or refactoring.
Forge cache
By default, each time we compile the smart contracts in Foundry, all the contracts which have been modified are recompiled. This is a very useful feature, as it allows us to save time and resources.
However, in some scenarios it is necessary to force the recompilation of all the contracts, even if they have not been modified. This can be done by using the --force flag:
forge build --force
Testing
One of the most common operations we can do with Forge is to test our smart contracts. Forge provides a powerful and flexible environment for testing smart contracts.
To run all the tests in your project, you can use the following command:
forge test
There are multiple flags you can use with the forge test command to customize the test execution. Some of the most common ones are:
forge test --match-contract [contract_name]
forge test --match-test [test_name]
forge test --match-contract [contract_name] --match-test [test_name]
The flags --match-contract and --match-test allow you to specify the contract and test names you want to run. For example, if you have a contract named MyContract and a test named test_myFunction, you can run the test by using the following command:
forge test --match-contract MyContract --match-test test_myFunction
Another useful flag is --vvvv, which allows you to increase the verbosity of the test output. This can be very helpful for debugging and understanding the test execution flow.
As you increase the verbosity level, you will see more detailed information about the test execution, including the values of variables, the execution flow, and any errors that occur.
The max level of verbosity is 5.
When you are working with tests, it is very important to know the part of your code you are covering with your tests. You can use the following command which generates a coverage report that shows which lines of code are covered by your tests and which are not.
forge coverage
Scripting
Forge also allows you to write and execute scripts to automate various tasks related to smart contract development and deployment. This can be very useful for tasks such as deploying contracts, interacting with deployed contracts, or performing complex operations that require multiple steps.
To simulate a script execution, you can use the following command:
forge script [script_path]
If you want to execute the script on a live network, you can use the --broadcast flag and specify the network with the --rpc-url flag:
forge script [script_path] --broadcast --rpc-url [network_rpc_url]
If you are interested in learning more about scripting with Forge, you can check this post where we explain in detail how to write and execute scripts with Forge.
Formatting
Forge also provides a powerful code formatter that allows you to format your Solidity code according to a consistent style. This can help improve the readability and maintainability of your code.
To format your code with Forge, you can use the following command:
forge fmt
The formatting configuration should be customized in the foundry.toml file. You can specify various options such as indentation, line length, and more.
If you want to see the available options, you can check the official documentation at
Formatter official docs
Frequently Asked Questions
How do I configure different Solidity versions?
You can specify a single version or a range in the foundry.toml file under the solc_version parameter. Forge automatically detects and downloads the correct compiler.
How do I enable optimization for my contracts?
You can enable optimization in the foundry.toml file by setting optimizer = true and configuring optimizer_runs for the desired level of optimization.
What is the purpose of the --via-ir flag?
The --via-ir flag enables the use of the intermediate representation (IR) in the Solidity compiler, which can lead to more efficient bytecode and better optimization results.
What is the purpose of the --force flag?
By default, Forge only recompiles modified files. The --force flag bypasses the cache and forces a full recompilation of all smart contracts in the project.
How can I see more details when a test fails?
You can increase the verbosity of the test output using the -v flags. Specifically, -vvvv shows traces for failing tests, and -vvvvv shows traces for all tests, which is essential for debugging.
Can I run a single test function?
Yes, you can use forge test --match-test [test_name] to run a specific test function. You can also combine it with --match-contract to target a specific test contract.
How do I deploy contracts using Forge?
The recommended way is using forge script with the --broadcast flag. This allows you to simulate the deployment first and then broadcast the transactions to the network.
Extra resources
If you want to learn more about Forge and all the scenarios it supports, you can check the official documentation at Forge official docs
Furthermore, if you want to learn more about other Foundry tools, you can check the Foundry Cast tutorial for smart contract interaction, where we explain how to use Cast, the command-line tool from Foundry to interact with Ethereum nodes and smart contracts or if you are interested in the local Ethereum node from Foundry, you can check the Foundry Anvil tutorial for local Ethereum node, where we explain how to use Anvil, the local Ethereum node from Foundry for testing and development.