1/5
## Mastering Local CCIP Testing with Foundry and Chainlink Local This lesson will guide you through setting up and utilizing Foundry's powerful fork testing capabilities in tandem with Chainlink Local. Our goal is to create a robust local environment for testing Chainlink Cross-Chain Interoperability Protocol (CCIP) interactions, specifically focusing on how to simulate message passing between two different blockchain forks without the need for constant deployments to live testnets. We'll be preparing to test CCIP-enabled smart contracts, using a RebaseToken example that communicates across local forks of Sepolia and Arbitrum Sepolia. ## Understanding Fork Testing in Smart Contract Development Fork testing is a sophisticated technique that allows you to create a local copy, or "fork," of an actual blockchain's state at a specific block number or its latest state. This local instance includes all on-chain data and deployed contracts, enabling your tests to run against realistic conditions and interact with existing protocols. Foundry, a popular toolkit for Ethereum smart contract development, offers two primary methods for implementing fork testing: 1. **Forking Mode (CLI):** You can run your entire test suite against a single forked network using the `forge test --fork-url <your_rpc_url>` command-line flag. This approach applies the fork to all tests executed in that run. 2. **Forking Cheatcodes (In-Test):** Foundry provides VM cheatcodes that allow you to create, select, and manage multiple blockchain forks directly *within* your Solidity test scripts. This method offers granular control and is the one we'll be focusing on for testing cross-chain interactions. Fork testing is invaluable for several scenarios: * **Realistic Interaction Testing:** Test how your contracts behave when deployed on a live network by interacting with its actual state. * **Event Analysis and Debugging:** Analyze past on-chain events or security incidents by forking the chain state *before* the event and replaying transactions. * **Integration Testing:** Test interactions with existing, live protocols. For example, you can call functions on a deployed Uniswap contract from within your local test environment. ## Essential Foundry Cheatcodes for Managing Forks To effectively manage multiple blockchain environments within our tests, we'll utilize several key Foundry cheatcodes: * **`vm.createFork(string calldata urlOrAlias)`:** This cheatcode creates a new fork from the specified RPC URL or an alias defined in your `foundry.toml` configuration file. It returns a `uint256 forkId`, a unique identifier for this fork instance. Importantly, `createFork` does *not* automatically switch the test execution context to the newly created fork. * **`vm.createSelectFork(string calldata urlOrAlias)`:** Similar to `createFork`, this cheatcode also creates a new fork. However, it *immediately selects* this new fork, making it the active environment for subsequent VM calls within the test. It also returns the `forkId`. This is particularly useful for setting up the initial fork you intend to work with. * **`vm.selectFork(uint256 forkId)`:** This cheatcode switches the active execution context to a previously created fork, identified by its `forkId`. This allows your tests to seamlessly transition between different blockchain environments, such as moving from a Sepolia fork to an Arbitrum Sepolia fork. * **`vm.makePersistent(address account)`:** This crucial cheatcode makes the state (both code and storage) of a specific smart contract address persistent across *all* active forks created within the test run. This is vital for ensuring that certain contracts, like our Chainlink Local simulator, are accessible and maintain their state consistently across the different forked environments. ## Introducing Chainlink Local: Simulating CCIP Locally Chainlink Local is an installable package provided by Chainlink that empowers developers to run various Chainlink services—including Data Feeds, VRF, and, most importantly for us, CCIP—directly within their local development environments such as Foundry, Hardhat, or Remix. In this lesson, Chainlink Local's primary role is to simulate the CCIP message relay mechanism between our locally running Sepolia fork and our Arbitrum Sepolia fork. When a CCIP message is "sent" on one fork using a simulated router provided by Chainlink Local, the package handles the underlying process to make that message available for execution on the other fork, all within our isolated test environment. The key component from Chainlink Local that we'll interact with is the `CCIPLocalSimulatorFork` contract. This contract needs to be deployed during our test setup. Our test scripts will then interact with this `CCIPLocalSimulatorFork` instance to obtain network-specific details (like router addresses) for each fork and to manage the simulated message flow. The synergy is clear: Foundry's fork testing cheatcodes create the isolated local blockchain environments (our Sepolia and Arbitrum Sepolia forks). Chainlink Local, through `CCIPLocalSimulatorFork` and the `vm.makePersistent` cheatcode, provides the essential bridge *between* these local forks. This setup simulates the CCIP network layer, allowing us to perform end-to-end testing of cross-chain interactions entirely within our Foundry test suite. ## Setting Up Your Foundry Project for Forked CCIP Tests Before writing our tests, we need to configure our Foundry project and install Chainlink Local. **1. Configure `foundry.toml`:** To use aliases with forking cheatcodes, you must define them in your `foundry.toml` file, typically under the `[profile.default]` section, using the `rpc_endpoints` key. You will need valid RPC URLs for the networks you intend to fork (e.g., Sepolia, Arbitrum Sepolia). Services like Alchemy or Infura provide such URLs. Here's an example configuration snippet: ```toml [profile.default] # ... other settings like src, out, libs rpc_endpoints = { sepolia = "YOUR_SEPOLIA_RPC_URL_HERE", arb-sepolia = "YOUR_ARB_SEPOLIA_RPC_URL_HERE" } # ... remappings # Ensure you replace YOUR_SEPOLIA_RPC_URL_HERE and YOUR_ARB_SEPOLIA_RPC_URL_HERE # with your actual RPC provider URLs. ``` The keys used here (`sepolia`, `arb-sepolia`) will serve as the aliases in our `vm.createFork` and `vm.createSelectFork` cheatcode calls. **2. Install Chainlink Local:** Use Foundry's `forge install` command to add Chainlink Local to your project. It's often recommended to specify a particular commit hash for stability, which can be found in the official Chainlink Local documentation. The command used in the reference video was: `forge install smartcontractkit/chainlink-local@cd3bfb8c42716cfb791174314eba2c8d178551b9 --no-commit` The `--no-commit` flag prevents Foundry from automatically committing the submodule update if your project uses Git. *Note: Always refer to the official Chainlink Local documentation for the latest recommended installation instructions and commit hash, as this may change.* **3. Add Remappings:** After installing Chainlink Local, you need to inform Foundry where to locate its contracts. This is done by adding a remapping either in your `remappings.txt` file or directly in `foundry.toml`. Add the following remapping: ``` @chainlink-local/=lib/chainlink-local/ ``` This tells the Solidity compiler that any import starting with `@chainlink-local/` should be resolved from the `lib/chainlink-local/` directory. ## Implementing the Initial Test Setup in Solidity With our project configured, let's create the initial structure for our cross-chain test file, typically located in the `test/` directory (e.g., `test/CrossChain.t.sol`). **1. Imports:** Begin by importing necessary libraries and contracts: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; import { Test, console } from "forge-std/Test.sol"; // Import your project-specific contracts // import { RebaseToken } from "../src/RebaseToken.sol"; // import { RebaseTokenPool } from "../src/RebaseTokenPool.sol"; // import { Vault } from "../src/Vault.sol"; // import { IRebaseToken } from "../src/interfaces/IRebaseToken.sol"; // Import the Chainlink Local simulator import {CCIPLocalSimulatorFork} from "@chainlink-local/src/ccip/CCIPLocalSimulatorFork.sol"; ``` *(Ensure you uncomment and adjust paths for your project-specific contracts as needed.)* **2. Contract Definition:** Define your test contract, inheriting from Foundry's `Test` contract: ```solidity contract CrossChainTest is Test { // ... test logic will go here } ``` **3. State Variables:** Declare state variables to store the identifiers for our forks and the instance of the CCIP simulator: ```solidity contract CrossChainTest is Test { uint256 sepoliaFork; uint256 arbSepoliaFork; CCIPLocalSimulatorFork ccipLocalSimulatorFork; // ... } ``` **4. The `setUp()` Function:** The `setUp()` function in Foundry tests is executed before each individual test function (those prefixed with `test...`). This ensures a clean, consistent state for every test case. Here's how we'll configure our forks and deploy the simulator: ```solidity function setUp() public { // 1. Create and select the initial (source) fork (Sepolia) // This uses the "sepolia" alias defined in foundry.toml sepoliaFork = vm.createSelectFork("sepolia"); // 2. Create the destination fork (Arbitrum Sepolia) but don't select it yet // This uses the "arb-sepolia" alias defined in foundry.toml arbSepoliaFork = vm.createFork("arb-sepolia"); // 3. Deploy the CCIP Local Simulator contract ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); // 4. Make the simulator's address persistent across all active forks // This is crucial so both the Sepolia and Arbitrum Sepolia forks // can interact with the *same* instance of the simulator. vm.makePersistent(address(ccipLocalSimulatorFork)); } ``` This `setUp` function performs the foundational steps for our cross-chain tests: * It establishes our primary working environment on a fork of Sepolia. * It creates a secondary environment based on Arbitrum Sepolia. * It deploys the `CCIPLocalSimulatorFork` contract. * Critically, it uses `vm.makePersistent` to ensure that this single instance of `CCIPLocalSimulatorFork` is accessible with the same address and state on both the Sepolia and Arbitrum Sepolia forks. This shared simulator is what will enable us to test message passing between them. ## Key Considerations and Best Practices As you work with fork testing and Chainlink Local, keep these points in mind: * **RPC URL Requirement:** You *must* provide your own valid RPC URLs in `foundry.toml` for the networks you intend to fork. Without these, Foundry cannot create the forked environments. * **Aliases in `foundry.toml`:** The aliases you define (e.g., `sepolia`, `arb-sepolia`) are directly used as string arguments in the `vm.createFork` and `vm.createSelectFork` cheatcodes, making your test code cleaner and configuration more manageable. * **The Importance of `vm.makePersistent`:** When using Chainlink Local (or any shared contract) with multiple forks in a single test run, `vm.makePersistent` is essential. It guarantees that all forked environments interact with the *exact same instance* of the specified contract, maintaining state consistency which is vital for simulating cross-chain interactions. * **Chainlink Local Versioning:** While using a specific commit hash for installing Chainlink Local (as shown in the installation command) can ensure stability and compatibility with tutorials, always consult the latest official Chainlink documentation for the recommended version or installation method. Software evolves, and documentation will reflect the most current best practices. * **`setUp()` Behavior:** Remember that the `setUp()` function runs before *every* individual test function within your test contract. This means each test starts with freshly created forks and a newly deployed (but persistent across those fresh forks) `CCIPLocalSimulatorFork` instance. This provides excellent test isolation. * **ZK Sync Limitations (at time of video):** It was noted that Foundry cheatcodes, including forking and `prank`, might not work as expected on ZK Sync environments at the time of the original video. This is why networks like Sepolia and Arbitrum Sepolia were chosen for the CCIP demonstration. Always verify current compatibility for specific L2s or sidechains. By following these setup steps and understanding these core concepts, you are now well-equipped to start writing detailed fork tests for your CCIP-enabled smart contracts, simulating complex cross-chain interactions locally and efficiently.
A developer's guide to Mastering Local CCIP Testing with Foundry and Chainlink Local - Set up Foundry to manage multiple local blockchain forks like Sepolia and Arbitrum Sepolia using RPC aliases and cheatcodes such as `vm.createSelectFork` and `vm.makePersistent`. You'll then bridge these forks by deploying Chainlink Local's `CCIPLocalSimulatorFork`, enabling seamless local testing of CCIP message relay.
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)
Web3 engineer, educator, and Cyfrin co-founder. Patrick's smart contract development and security courses have helped hundreds of thousands of engineers kickstarting their careers into web3.
Guest lecturers:
Last updated on May 20, 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)
Web3 engineer, educator, and Cyfrin co-founder. Patrick's smart contract development and security courses have helped hundreds of thousands of engineers kickstarting their careers into web3.
Guest lecturers:
Last updated on May 20, 2025