Setting Up a Project with Foundry
A beginner guide to deployments and tests

Hey there! I'm Davide, a passionate developer advocate at Chainstack, the leading suite of blockchain infrastructure services.
I'm dedicated to empowering current and aspiring web3 developers by sharing valuable content and resources.
Within my articles, you'll discover a treasure trove of straightforward projects designed to bolster your understanding of fundamental Python, Solidity, JavaScript, and web3 skills. Whether you're a seasoned developer or just starting out, these projects offer an ideal learning path for honing your abilities in the exciting world of blockchain development.
Feel free to explore my articles, and let's embark on this remarkable journey of mastering web3 together!
Introduction
This guide covers the process of setting up, compiling, deploying, and interacting with smart contracts using Foundry, a powerful development environment for Ethereum smart contracts.
1. Initializing a Project
Creating a New Project
In an empty directory, initialize a new Foundry project:
forge initTo create a new directory with the project:
forge init PROJECT_NAMENote: The
srcdirectory is where the smart contracts are placed.
2. Compiling Contracts
To compile the contracts, run either:
forge buildor
forge compileThe
outdirectory will generate a JSON file containing compilation data, such as ABI.
3. Setting Up a Local Blockchain
Use Anvil to start a local blockchain for testing:
anvilThe local blockchain will run at
127.0.0.1:8545, and you can add it to MetaMask for ease of testing.
4. Deploying Contracts
Deploying Locally or to a Custom RPC
- To deploy smart contracts, use
forge create. Forge defaults to the Anvil local blockchain, but other RPCs can be specified using the--rpc-urlflag.
Deploying locally with Anvil running:
forge create CONTRACT_NAMEDeploying to a custom endpoint:
forge create CONTRACT_NAME --rpc-url YOUR_ENDPOINTThis will likely not work because it needs a private key to deploy.
Error:
Error accessing local wallet. Did you set a private key, mnemonic or keystore?
Run `cast send --help` or `forge create --help` and use the corresponding CLI
flag to set your key via:
--private-key, --mnemonic-path, --aws, --interactive, --trezor or --ledger.
Alternatively, if you're using a local node with unlocked accounts,
use the --unlocked flag and either set the `ETH_FROM` environment variable to the address
of the unlocked account you want to use, or provide the --from flag with the address directly.
Options for Specifying Private Key
Run the create command with the
--interactiveflag for a prompt to add a private key:forge create CONTRACT_NAME --interactiveOr, directly include the private key in the command:
forge create CONTRACT_NAME --private-key YOUR_PRIVATE_KEY
5. Writing Deploy Scripts
Scripts in Foundry are written in Solidity. We'll use a Solidity script to deploy a contract. By convention, script files end with
.s.sol.Example: Deploying
SimpleStorage.sol.
Creating the Deploy Script
Create a file named
deploySimpleStorage.s.solwith the following content:// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {Script} from "forge-std/Script.sol"; import {SimpleStorage} from "../src/SimpleStorage.sol"; contract DeploySimpleStorage is Script { function run() external returns (SimpleStorage) { vm.startBroadcast(); SimpleStorage simpleStorage = new SimpleStorage(); vm.stopBroadcast(); return simpleStorage; } }Deploy the contract using the script:
forge script script/DeploySimpleStorage.s.sol --rpc-url YOUR_RPC --broadcast --private-key YOUR_PRIVATE_KEY
Let's break down its key components and functionalities:
Pragma Directive:
pragma solidity ^0.8.19;: Specifies that the script is compatible with Solidity version 0.8.19 or any newer version of the 0.8 series but not version 0.9 or above.
Imports:
import {Script} from "forge-std/Script.sol";: Imports theScriptclass from theforge-stdlibrary, which is a part of Foundry, a development environment for Ethereum smart contracts.import {SimpleStorage} from "../src/SimpleStorage.sol";: Imports theSimpleStoragecontract, presumably a custom contract located in thesrcdirectory.
Contract Declaration:
contract DeploySimpleStorage is Script: Defines a new contract namedDeploySimpleStoragethat inherits from theScriptclass. This setup is typical for deployment scripts in Foundry.
Function Definition:
function run() external returns (SimpleStorage): Therunfunction is the main entry point for the deployment script. It's markedexternalas it's intended to be called externally, and it returns an instance ofSimpleStorage.
Deployment Process:
vm.startBroadcast();: Initiates a transaction broadcast. Thevmobject is a special component in Foundry, providing various functionalities related to the Ethereum Virtual Machine (EVM).SimpleStorage simpleStorage = new SimpleStorage();: Instantiates theSimpleStoragecontract.vm.stopBroadcast();: Ends the transaction broadcast.
Return Statement:
return simpleStorage;: Returns the deployed instance ofSimpleStorage.
This script is a typical example of a deployment script used in the Foundry environment for deploying Ethereum smart contracts. It's concise and follows the pattern of starting a broadcast, deploying the contract, and stopping it. The SimpleStorage contract, which is not detailed here, would contain the actual business logic or data storage mechanisms.
6. Interacting with Contracts Using Cast
Sending Transactions
To send transactions, use
cast send:cast send ADDRESS FUNCTION_SIG PARAMSExample:
cast send 0x5FbDB2315678afecb367f032d93F642f64180aa3 "store(uint256)" 3333 --rpc-url $RPC_URL --private-key $PRIVATE_KEY
Reading from Contracts
Use
cast callfor reading view functions:cast call ADDRESS FUNCTION_SIGNATUREExample:
cast call 0x5FbDB2315678afecb367f032d93F642f64180aa3 "retrieve()"
You can use cast for conversions, for example, hex to dec:
cast --to-base 0x0000000000000000000000000000000000000000000000000000000000000d05 dec
Or use the Chainstack EVM Swiss Knife.
7. Managing Dependencies
Installing Smart Contract Dependencies
Use the following command to install dependencies from a repository:
forge install smartcontractkit/chainlink-brownie-contracts --no-commitDependencies are added to the
libdirectory.
Remapping Dependencies
Add remappings in the
foundry.tomlfile for syntax convenience:remappings = ['@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/']
chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/']
8. Writing and Running Tests
Example Test Contract
- Tests in Foundry are also written in Solidity. Here's an example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {Test, console} from "forge-std/Test.sol";
contract FundMeTest is Test {
uint256 number = 33;
function setUp() external {
number = 3333;
}
function testDemo() public {
console.log("The saved number is", number);
assertEq(number, 3333);
}
}
Run tests with:
forge test -vvThe
-vvflag outputs detailed logs for better insight.
Let's break down its components:
License and Solidity Version Declaration:
// SPDX-License-Identifier: MIT: This is a comment specifying the license under which this file is released, in this case, the MIT License.pragma solidity ^0.8.18;: This line specifies the compiler version. The file is compatible with Solidity version 0.8.18 and above within the 0.8.x range.
Imports:
import {Test, console} from "forge-std/Test.sol";: This line imports two elements from the Forge standard library (forge-std):Test: A base contract that provides testing functionalities.console: A utility to log output to the console. This is particularly useful for debugging and tracking variable values during test execution.
Test Contract Declaration:
contract FundMeTest is Test {: This line declares a new contractFundMeTestwhich inherits from theTestcontract. In the context of Forge, this meansFundMeTestis a test suite.
State Variable:
uint256 number = 33;: A state variablenumberof typeuint256(unsigned integer of 256 bits) is declared and initialized to 33. This variable is used to demonstrate state manipulation and assertion in the test.
Setup Function:
function setUp() external { number = 3333; }: ThesetUp()function is a special function in the Forge framework that runs before each test function. It's used for initializing or resetting the state. Here, it sets thenumbervariable to 3333.
Test Function:
function testDemo() public { ... }: This is the actual test function. In Forge, any function with a name starting withtestis considered a test case.console.log("The saved number is", number);: This line logs the value ofnumberto the console, which is useful for debugging or verifying the test state.assertEq(number, 3333);: This is an assertion statement provided by theTestcontract. It checks whether the value ofnumberis equal to 3333. If the assertion fails (i.e., ifnumberis not 3333), the test will fail.
9. Testing on a Fork
Run tests on a forked network by adding an RPC URL:
forge test -vvv --fork-url $SEPOLIA_RPC
10. Coverage Analysis
Use
forge coverageto analyze how much of your contracts are tested:forge coverage --fork-url $SEPOLIA_RPCIt will display a nice table:
[⠢] Compiling...
[⠢] Compiling 26 files with 0.8.20
[⠆] Solc 0.8.20 finished in 4.07s
Compiler run successful!
Analysing contracts...
Running tests...
| File | % Lines | % Statements | % Branches | % Funcs |
|---------------------------|---------------|---------------|---------------|--------------|
| script/DeployFundme.s.sol | 0.00% (0/3) | 0.00% (0/3) | 100.00% (0/0) | 0.00% (0/1) |
| src/FundMe.sol | 16.67% (2/12) | 23.53% (4/17) | 0.00% (0/4) | 25.00% (1/4) |
| src/PriceConverter.sol | 0.00% (0/6) | 0.00% (0/11) | 100.00% (0/0) | 0.00% (0/2) |
| Total | 9.52% (2/21) | 12.90% (4/31) | 0.00% (0/4) | 14.29% (1/7) |




