0/5
## Setting Up Local Fork Tests for Chainlink CCIP with Foundry Developing and testing cross-chain applications introduces complexities beyond single-chain development. Simulating interactions between different blockchain environments is crucial before deploying to live testnets or mainnet. This lesson guides you through setting up a powerful local testing environment using Foundry's fork testing capabilities combined with Chainlink Local to specifically test Chainlink Cross-Chain Interoperability Protocol (CCIP) interactions. ### Understanding Foundry Fork Testing Fork testing in Foundry allows you to create a local testing environment that mirrors the state of a live blockchain (like Ethereum Sepolia, Arbitrum Sepolia, or even mainnet) at a specific block number or its latest state. Foundry effectively downloads the necessary state data, enabling your tests to run against a realistic replica of the target network. **Why use fork testing?** 1. **Realistic Integration:** Test how your contracts interact with already deployed protocols (e.g., DEXs, lending platforms) on the target chain. 2. **Deployment Simulation:** Verify your deployment scripts behave as expected in an environment closely resembling the live network. 3. **Cross-Chain Simulation:** As demonstrated here, fork multiple chains (e.g., Sepolia and Arbitrum Sepolia) to test interactions *between* them locally, especially when combined with tools like Chainlink Local. 4. **Security Analysis:** Recreate historical states to analyze past exploits or test contract behavior under specific conditions. Foundry manages forking primarily through VM cheatcodes within your Solidity test files. ### Essential Foundry Forking Cheatcodes Foundry provides several cheatcodes via its `vm` instance (accessible in tests inheriting `forge-std/Test.sol`) to manage blockchain forks: * **`vm.createFork(string calldata rpcUrlOrAlias, uint256 blockNumber)` / `vm.createFork(string calldata rpcUrlOrAlias)`:** * Creates a *new* fork based on the specified RPC endpoint (either a full URL or an alias defined in `foundry.toml`). * Optionally accepts a `blockNumber` to fork from; otherwise, it forks from the latest block. * Returns a `uint256` identifier (the `forkId`) for this newly created fork. * **Crucially, this cheatcode *does not* switch the current execution context to the new fork.** * **`vm.createSelectFork(string calldata rpcUrlOrAlias, uint256 blockNumber)` / `vm.createSelectFork(string calldata rpcUrlOrAlias)`:** * Functions similarly to `vm.createFork` in creating a new fork environment. * **Additionally, it immediately switches the EVM execution context to this newly created fork.** Subsequent contract calls and cheatcodes within the current scope will operate on this fork. * Also returns the `uint256` fork ID. * **`vm.selectFork(uint256 forkId)`:** * Switches the active EVM execution context to a previously created fork, identified by the `forkId` returned from `vm.createFork` or `vm.createSelectFork`. This is essential for performing actions sequentially on different forked chains within the same test function. * **`vm.makePersistent(address account)`:** * Marks a specific contract address (and its associated code and storage) as persistent across *all* forks created during the test run. * This is vital for shared infrastructure or simulator contracts (like the Chainlink Local simulator) that need to be accessible and maintain their state regardless of which fork (`selectFork`) is currently active. ### Configuring Foundry for Forking (`foundry.toml`) To use convenient aliases instead of full RPC URLs in your cheatcode calls, you need to define them in your `foundry.toml` configuration file: ```toml [profile.default] # ... other settings like src, out, libs ... # Define RPC endpoint aliases [rpc_endpoints] sepolia-eth = "YOUR_SEPOLIA_RPC_URL" # Replace with your actual Sepolia RPC URL arb-sepolia = "YOUR_ARB_SEPOLIA_RPC_URL" # Replace with your actual Arbitrum Sepolia RPC URL # Add remapping for Chainlink Local after installation [remappings] # ... other remappings ... '@chainlink-local/=lib/chainlink-local/' ``` **Important:** Replace the placeholder URLs (`YOUR_SEPOLIA_RPC_URL`, `YOUR_ARB_SEPOLIA_RPC_URL`) with valid endpoint URLs obtained from an RPC provider (e.g., Alchemy, Infura). You'll also need to install Chainlink Local using `forge install --no-commit chainlink/chainlink-local@<COMMIT_HASH>` (refer to Chainlink documentation for the recommended commit hash) and ensure the `@chainlink-local/` remapping is added as shown above. ### Integrating Chainlink Local for CCIP Simulation While Foundry's forking creates realistic *single-chain* environments, it doesn't inherently simulate the *cross-chain communication* layer provided by protocols like Chainlink CCIP. This is where Chainlink Local comes in. **Chainlink Local** is an installable package providing local simulations of Chainlink services. For CCIP testing in a forked Foundry environment, we use the `CCIPLocalSimulatorFork` contract. * **Purpose:** This contract simulates the necessary Chainlink CCIP components (Routers, RMN) locally, enabling you to test the flow of cross-chain messages between your forked environments without deploying to live testnets. * **`CCIPLocalSimulatorFork`:** This is the core contract you deploy within your Foundry test setup. * **`getNetworkDetails(uint64 chainSelector)`:** A key function on the simulator contract. You call this function, passing the destination chain's selector ID, to retrieve a struct containing the simulated CCIP configuration for that chain (e.g., the simulated Router address, LINK token address). The `CCIPLocalSimulatorFork` contract needs to be accessible from *both* (or all) forks you create in your test. This is achieved by deploying it once (typically on the initially selected fork) and then making its address persistent using `vm.makePersistent`. ### Setting Up the Test File (`test/CrossChain.t.sol`) Let's structure a basic test contract to set up a cross-chain testing environment between Sepolia and Arbitrum Sepolia using fork testing and Chainlink Local. 1. **Imports:** Include necessary Foundry test utilities, your project contracts, interfaces, and the Chainlink Local simulator. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Test, console} from "forge-std/Test.sol"; // Import your project contracts (replace with actual paths/names) // import {MyCrossChainContract} from "../src/MyCrossChainContract.sol"; // ... other necessary imports // Import the Chainlink Local Simulator for forked environments import {CCIPLocalSimulatorFork} from "@chainlink-local/src/ccip/CCIPLocalSimulatorFork.sol"; import {Register} from "@chainlink-local/src/ccip/Register.sol"; // For NetworkDetails struct ``` 2. **Contract Definition and State Variables:** Define the test contract inheriting from `Test` and declare state variables to hold fork IDs and the simulator instance. ```solidity contract CrossChainForkTest is Test { // Fork IDs uint256 sepoliaForkId; uint256 arbSepoliaForkId; // Chainlink Local Simulator instance CCIPLocalSimulatorFork cciplocalSimulatorFork; // Chain Selectors (example values - use actual selectors) uint64 sepoliaChainSelector = 16015286601757825753; // Example Sepolia selector uint64 arbSepoliaChainSelector = 3478487238524512106; // Example Arb Sepolia selector // Addresses for contracts deployed on each fork (will be set later) // address sepoliaContractAddress; // address arbSepoliaContractAddress; // Setup function executed before each test function setUp() public { // Implementation follows } // Your test functions will go here // function test_sendMessageFromSepoliaToArbitrum() public { ... } } ``` 3. **`setUp()` Function Implementation:** This is where the core environment setup happens. ```solidity function setUp() public { // 1. Create and select the first fork (Sepolia) // Uses the 'sepolia-eth' alias from foundry.toml // Forks from the latest block by default sepoliaForkId = vm.createSelectFork("sepolia-eth"); // The EVM context is now set to the Sepolia fork // 2. Create the second fork (Arbitrum Sepolia) but DO NOT select it yet // Uses the 'arb-sepolia' alias from foundry.toml arbSepoliaForkId = vm.createFork("arb-sepolia"); // The EVM context remains on the Sepolia fork // 3. Deploy the Chainlink Local Simulator ON the currently active fork (Sepolia) cciplocalSimulatorFork = new CCIPLocalSimulatorFork(); // 4. Make the simulator contract address persistent across ALL forks // This is CRITICAL so it's accessible when we switch to arbSepoliaForkId vm.makePersistent(address(cciplocalSimulatorFork)); // Optional: Deploy your contracts on the Sepolia fork now // vm.selectFork(sepoliaForkId); // Ensure Sepolia is active (already is) // Register.NetworkDetails memory sepoliaDetails = cciplocalSimulatorFork.getNetworkDetails(sepoliaChainSelector); // MyCrossChainContract sepoliaContract = new MyCrossChainContract(sepoliaDetails.router, sepoliaDetails.link); // sepoliaContractAddress = address(sepoliaContract); // vm.makePersistent(sepoliaContractAddress); // Make persistent if needed by other fork tests // Optional: Deploy contracts on the Arbitrum Sepolia fork // vm.selectFork(arbSepoliaForkId); // Switch context to Arbitrum Sepolia // Register.NetworkDetails memory arbDetails = cciplocalSimulatorFork.getNetworkDetails(arbSepoliaChainSelector); // MyCrossChainContract arbContract = new MyCrossChainContract(arbDetails.router, arbDetails.link); // arbSepoliaContractAddress = address(arbContract); // vm.makePersistent(arbSepoliaContractAddress); // Make persistent if needed // Reset context back to Sepolia if needed for the start of tests, or leave on Arb Sepolia // vm.selectFork(sepoliaForkId); } ``` With this `setUp` function, each test case (`test_...`) will start with two forks created (Sepolia and Arbitrum Sepolia) and a persistent `CCIPLocalSimulatorFork` instance ready to provide simulated network details for both chains. Inside your test functions, you can use `vm.selectFork(forkId)` to switch between the chains, deploy contracts, interact with them, and use the simulator to test CCIP message sending and receiving logic locally. Remember to consult the Foundry Book and Chainlink documentation for more detailed information on specific cheatcodes, Chainlink Local setup, and advanced usage patterns.
A comprehensive introduction to Setting Up Local Fork Tests for Chainlink CCIP with Foundry - Master Foundry's fork testing cheatcodes to replicate multiple blockchain states locally for cross-chain development. Integrate Chainlink Local's simulator to test CCIP interactions realistically between these forked environments.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at EigenLayer
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on April 4, 2025
Solidity Developer
Advanced FoundryDuration: 36min
Duration: 3h 06min
Duration: 5h 02min
Duration: 6h 02min
Duration: 2h 47min
Duration: 1h 23min
Duration: 4h 28min
Duration: 1h 19min
Duration: 1h 10min
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at EigenLayer
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on April 4, 2025
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar