1/5
## Configuring CCIP Token Pools with a Forge Script This lesson details the creation of a Forge script (`ConfigurePool.s.sol`) using Solidity. The primary purpose of this script is to configure Chainlink CCIP (Cross-Chain Interoperability Protocol) Token Pools *after* they have been deployed. Specifically, it sets up the necessary connection parameters and rate limiting configurations between a token pool on the current (local) blockchain and its corresponding pool on a remote chain. It's crucial to understand that this script must be executed on **both** the source and destination chains, each time providing the details of the *other* chain. ### Initial Setup and Boilerplate First, create a new file named `ConfigurePool.s.sol` within the `script` directory of your Forge project. Begin with the standard Solidity boilerplate: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; ``` Next, import the necessary contracts and libraries: ```solidity import {Script} from "forge-std/Script.sol"; import {TokenPool} from "@ccip/contracts/src/v0.8/ccip/pools/TokenPool.sol"; import {RateLimiter} from "@ccip/contracts/src/v0.8/ccip/libraries/RateLimiter.sol"; ``` * `Script`: The base contract for Forge scripts from the Forge Standard Library. * `TokenPool`: The CCIP Token Pool contract interface, allowing interaction with deployed pools. * `RateLimiter`: The library containing the `Config` struct used for defining rate limits. ### Contract Definition Define the script contract, inheriting from the imported `Script` contract: ```solidity contract ConfigurePoolScript is Script { // run function will be defined here } ``` ### The `run` Function: Core Configuration Logic The main logic resides within the `run` function. This function is designed to be flexible, accepting all necessary configuration details as parameters rather than hardcoding them. ```solidity function run( address localPool, // Address of the Token Pool on the *current* chain uint64 remoteChainSelector, // CCIP Chain Selector ID of the *remote* chain address remotePool, // Address of the Token Pool on the *remote* chain address remoteToken, // Address of the underlying Token on the *remote* chain bool outboundRateLimiterIsEnabled, // Flag to enable/disable outbound rate limit uint128 outboundRateLimiterCapacity, // Token bucket capacity for outbound transfers uint128 outboundRateLimiterRate, // Refill rate (tokens/sec) for outbound transfers bool inboundRateLimiterIsEnabled, // Flag to enable/disable inbound rate limit uint128 inboundRateLimiterCapacity, // Token bucket capacity for inbound transfers uint128 inboundRateLimiterRate // Refill rate (tokens/sec) for inbound transfers ) public { // Implementation follows } ``` **Implementation Steps:** 1. **Start Broadcast:** Begin a transaction broadcast using Forge's `vm.startBroadcast()` cheatcode. This ensures that all subsequent state-changing calls within the script are sent as a single transaction from the script runner's address. ```solidity vm.startBroadcast(); ``` 2. **Prepare `ChainUpdate` Data:** The configuration is applied by calling the `applyChainUpdates` function on the local `TokenPool`. This function expects an array of `TokenPool.ChainUpdate` structs. We need to construct this array. * **Encode Remote Addresses:** The `TokenPool.ChainUpdate` struct requires the remote pool address(es) as `bytes[]` and the remote token address as `bytes`. Therefore, the `address` parameters (`remotePool`, `remoteToken`) must be ABI-encoded. ```solidity // Prepare remote pool address array (bytes[]) - supports multiple pools, but we use one bytes[] memory remotePoolAddresses = new bytes[](1); remotePoolAddresses[0] = abi.encode(remotePool); // The remoteToken address will be encoded directly during struct creation ``` * **Create Rate Limiter Config Structs:** Instantiate `RateLimiter.Config` structs for both outbound and inbound limits using the function parameters. These will be nested inside the `ChainUpdate` struct. * **Create `ChainUpdate` Array:** Initialize an array of `TokenPool.ChainUpdate` with a size of 1, as we are configuring a single remote chain connection per script execution. ```solidity TokenPool.ChainUpdate[] memory chainsToAdd = new TokenPool.ChainUpdate[](1); ``` * **Populate the `ChainUpdate` Struct:** Fill the first element of the `chainsToAdd` array with all the configuration details. Note the use of named parameters for clarity and the correct struct field names (`outboundRateLimiterConfig`, `inboundRateLimiterConfig`). ```solidity chainsToAdd[0] = TokenPool.ChainUpdate({ remoteChainSelector: remoteChainSelector, remotePoolAddresses: remotePoolAddresses, // The encoded bytes array remoteTokenAddress: abi.encode(remoteToken), // ABI-encoded remote token address outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: outboundRateLimiterIsEnabled, capacity: outboundRateLimiterCapacity, rate: outboundRateLimiterRate }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: inboundRateLimiterIsEnabled, capacity: inboundRateLimiterCapacity, rate: inboundRateLimiterRate }) }); ``` 3. **Apply Updates to the Pool:** Call the `applyChainUpdates` function on the target `localPool`. Cast the `localPool` address to the `TokenPool` contract type to access its functions. The function takes two arguments: * An array of remote chain selectors (`uint64[]`) to *remove* (we pass an empty array as we are only adding). * The `chainsToAdd` array containing the configuration for the remote chain. ```solidity // Cast localPool address to TokenPool type to call its function // Pass an empty uint64 array for chains to remove TokenPool(localPool).applyChainUpdates(new uint64[](0), chainsToAdd); ``` 4. **Stop Broadcast:** End the transaction broadcast using `vm.stopBroadcast()`. ```solidity vm.stopBroadcast(); ``` ### Compilation and Common Issues When compiling the script using `forge build`, you might encounter some issues that were addressed during the original video lesson: * **Stack Too Deep Error:** Complex interactions, especially within a larger project with intricate tests, can lead to this error. Using the `--via-ir` flag (`forge build --via-ir`) enables the Yul intermediate representation compiler pipeline, which can often resolve these depth issues. * **Type Mismatch in `applyChainUpdates`:** The first argument of `applyChainUpdates` expects `uint64[]` (an array of chain selectors to remove). Ensure you pass `new uint64[](0)` and not an empty array of a different type (like `address[]`). * **Named Argument Mismatch:** When populating the `ChainUpdate` struct, ensure the names used match the actual field names in the struct definition (e.g., use `outboundRateLimiterConfig`, not `outboundRateLimiter`). * **Typos:** Standard Solidity typos (e.g., `uiont64` instead of `uint64`) will cause compilation errors and need correction. After addressing these potential issues, the script should compile successfully. ### Key Concepts Recap * **Forge Scripts:** Automating contract interactions using Solidity files in the `script/` directory. * **Broadcasting (`vm.startBroadcast`/`vm.stopBroadcast`):** Grouping multiple contract calls into a single transaction sent by the script runner. * **CCIP Token Pool Configuration:** The necessity of explicitly linking token pools across different chains by providing addresses, chain selectors, and rate limits. * **`TokenPool.ChainUpdate` Struct:** The data structure used to pass remote chain configuration updates to a token pool. * **`RateLimiter.Config` Struct:** Defines the parameters (capacity, rate) for CCIP's token bucket rate limiting mechanism. * **ABI Encoding:** Converting data types like `address` into `bytes` or `bytes[]` as required by function arguments or struct fields. * **Parameterization:** Passing configuration values as function arguments for script flexibility and reusability. * **Type Casting:** Explicitly treating an `address` as a specific contract type (`TokenPool(localPool)`) to call its functions. * **ViaIR Pipeline:** An alternative compilation pipeline in Forge (`--via-ir`) used to handle complex contracts that might exceed default compiler limits. This configuration script is a prerequisite for enabling cross-chain token transfers via CCIP. Once executed on both participating chains (each pointing to the other), the token pools will be aware of each other and ready for bridging operations.
A detailed guide to Configuring CCIP Token Pools with a Forge Script - Learn to write a Solidity Forge script (`ConfigurePool.s.sol`) for post-deployment Chainlink CCIP Token Pool setup. Configure inter-chain connections and crucial rate-limiting parameters by running the script on both chains.
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