2/5
## Crafting the `ConfigurePool.s.sol` Script for CCIP Token Pool Configuration This lesson guides you through creating a Foundry script, `ConfigurePool.s.sol`, designed to configure your deployed `RebaseTokenPool` contracts for Cross-Chain Interoperability Protocol (CCIP) operations. This script is executed *after* the initial deployment of token pools on both source and destination chains (typically handled by a script like `Deployer.s.sol`). Its primary function is to establish the connection details and rate-limiting parameters, enabling seamless cross-chain token transfers. This process mirrors the configuration logic you might have previously implemented within test environments. ### Core Concepts in Play Before diving into the script, let's review the key concepts involved: 1. **Foundry Scripts:** We'll leverage Foundry's scripting capabilities (`forge script`) to automate contract interactions. These scripts inherit from `forge-std/Script.sol` and use a `run()` function as their entry point. Foundry cheatcodes, such as `vm.startBroadcast()` and `vm.stopBroadcast()`, allow the script to send transactions as if they were initiated by the script runner's address. 2. **CCIP Token Pools:** These specialized smart contracts are central to CCIP. They manage the locking/releasing or burning/minting of tokens during cross-chain transfers. Crucially, they must be configured with information about their counterpart pools on other chains. 3. **Chain Configuration (`applyChainUpdates`):** The `TokenPool` contract exposes an `applyChainUpdates` function, which is the workhorse for our configuration. It accepts two main arguments: * An array of chain selectors to *remove* (this is often an empty array during initial setup). * An array of `TokenPool.ChainUpdate` structs, detailing the chains to *add* or update. 4. **The `ChainUpdate` Struct:** Defined within `TokenPool.sol`, this struct bundles all necessary information to link a local pool to a remote one: * `remoteChainSelector` (uint64): The unique identifier of the target blockchain. * `remotePoolAddresses` (bytes[]): An array of ABI-encoded addresses of the pool(s) on the remote chain. * `remoteTokenAddress` (bytes): The ABI-encoded address of the token contract on the remote chain. * `outboundRateLimiterConfig` (RateLimiter.Config): Configuration for rate-limiting tokens leaving the current pool. * `inboundRateLimiterConfig` (RateLimiter.Config): Configuration for rate-limiting tokens arriving at the current pool. 5. **Rate Limiting (`RateLimiter.Config`):** CCIP employs rate limiting to manage token flow and enhance security. The `RateLimiter.Config` struct, defined in the `RateLimiter.sol` library, specifies: * `isEnabled` (bool): A flag to activate or deactivate rate limiting. * `capacity` (uint128): The maximum token amount the "bucket" can hold. * `rate` (uint128): The rate (tokens per second) at which the bucket refills. 6. **ABI Encoding:** The `ChainUpdate` struct requires `remotePoolAddresses` and `remoteTokenAddress` to be `bytes` or `bytes[]` types, respectively. Therefore, `address` values must be converted to this format using `abi.encode()`. 7. **Separation of Deployment and Configuration:** This lesson follows a common and recommended pattern: contract deployment (creating instances) is handled by one script, while inter-contract state setup (configuration) is managed by a separate script, like the `ConfigurePool.s.sol` we are building. ### Building the `ConfigurePool.s.sol` Script Let's construct the script step-by-step. **1. File Creation and Initial Setup** First, create a new file named `ConfigurePool.s.sol` within your Foundry project's `script/` directory. Add the standard SPDX license identifier and Solidity pragma: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; ``` **2. Importing Necessary Contracts and Libraries** Next, import the required modules: `Script` from `forge-std`, `TokenPool` and its associated structs, and `RateLimiter` for its configuration struct. ```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"; ``` **3. Defining the Script Contract and `run` Function** Define a contract, `ConfigurePoolScript`, that inherits from `Script`. The core logic will reside in the `run` function. This function will accept parameters for all necessary configuration values, providing flexibility. ```solidity contract ConfigurePoolScript is Script { function run( address localPool, uint64 remoteChainSelector, address remotePool, address remoteToken, bool outboundRateLimiterIsEnabled, uint128 outboundRateLimiterCapacity, uint128 outboundRateLimiterRate, bool inboundRateLimiterIsEnabled, uint128 inboundRateLimiterCapacity, uint128 inboundRateLimiterRate ) public { // Configuration logic will go here } } ``` Passing rate limiter settings as parameters, rather than hardcoding, allows for easier adjustments per deployment or chain. **4. Preparing `ChainUpdate` Data within the `run` Function** Inside the `run` function, we'll prepare the data structure needed for the `applyChainUpdates` call. * **Start Transaction Broadcasting:** Begin by telling Foundry to broadcast subsequent state-changing calls as transactions. ```solidity vm.startBroadcast(); ``` * **Prepare ABI-Encoded Addresses:** The `ChainUpdate` struct expects the remote pool and token addresses to be ABI-encoded. ```solidity // Prepare remotePoolAddresses (needs to be bytes[]) bytes[] memory remotePoolAddresses_ = new bytes[](1); remotePoolAddresses_[0] = abi.encode(remotePool); // Prepare remoteTokenAddress (needs to be bytes) bytes memory remoteTokenAddress_ = abi.encode(remoteToken); ``` We use an array of size one for `remotePoolAddresses_` as we are typically configuring a single corresponding pool. * **Create and Populate the `ChainUpdate` Struct:** Instantiate an array for `ChainUpdate` structs (again, typically size one for a single remote chain configuration) and populate it. ```solidity TokenPool.ChainUpdate[] memory chainsToAdd = new TokenPool.ChainUpdate[](1); chainsToAdd[0] = TokenPool.ChainUpdate({ chainSelector: remoteChainSelector, // Renamed from remoteChainSelector for clarity within struct remotePoolAddresses: remotePoolAddresses_, remoteTokenAddress: remoteTokenAddress_, outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: outboundRateLimiterIsEnabled, capacity: outboundRateLimiterCapacity, rate: outboundRateLimiterRate }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: inboundRateLimiterIsEnabled, capacity: inboundRateLimiterCapacity, rate: inboundRateLimiterRate }) }); ``` **5. Calling `applyChainUpdates`** With the `chainsToAdd` data prepared, call `applyChainUpdates` on the `localPool` contract. Cast the `localPool` address to the `TokenPool` interface to access its functions. We'll provide an empty `uint64` array for the `chainsToRemove` parameter, as we are only adding/updating a configuration. ```solidity // Cast localPool address to TokenPool contract type TokenPool(localPool).applyChainUpdates( new uint64[](0), // Chains to remove (empty array) chainsToAdd // Chains to add/update ); ``` * **Stop Transaction Broadcasting:** Conclude the transaction block. ```solidity vm.stopBroadcast(); ``` The complete `run` function will look like this: ```solidity contract ConfigurePoolScript is Script { function run( address localPool, uint64 remoteChainSelector, address remotePool, address remoteToken, bool outboundRateLimiterIsEnabled, uint128 outboundRateLimiterCapacity, uint128 outboundRateLimiterRate, bool inboundRateLimiterIsEnabled, uint128 inboundRateLimiterCapacity, uint128 inboundRateLimiterRate ) public { vm.startBroadcast(); // Prepare remotePoolAddresses (needs to be bytes[]) bytes[] memory remotePoolAddresses_ = new bytes[](1); remotePoolAddresses_[0] = abi.encode(remotePool); // Prepare remoteTokenAddress (needs to be bytes) bytes memory remoteTokenAddress_ = abi.encode(remoteToken); TokenPool.ChainUpdate[] memory chainsToAdd = new TokenPool.ChainUpdate[](1); chainsToAdd[0] = TokenPool.ChainUpdate({ chainSelector: remoteChainSelector, remotePoolAddresses: remotePoolAddresses_, remoteTokenAddress: remoteTokenAddress_, outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: outboundRateLimiterIsEnabled, capacity: outboundRateLimiterCapacity, rate: outboundRateLimiterRate }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: inboundRateLimiterIsEnabled, capacity: inboundRateLimiterCapacity, rate: inboundRateLimiterRate }) }); // Cast localPool address to TokenPool contract type TokenPool(localPool).applyChainUpdates( new uint64[](0), // Chains to remove (empty array) chainsToAdd // Chains to add/update ); vm.stopBroadcast(); } } ``` ### Important Considerations * **Execution Order:** This `ConfigurePool.s.sol` script is intended to be run *after* the `RebaseTokenPool` contracts have been deployed on both the source and destination chains. * **Reciprocal Configuration:** For CCIP to function correctly, configuration must be reciprocal. You will need to run this script (or apply equivalent logic) on *both* participating chains, each pointing to the other as the remote chain with its respective pool and token details. * **Why `abi.encode()`?** The `TokenPool` contract's `applyChainUpdates` function, via the `ChainUpdate` struct, specifically expects remote pool and token addresses in a `bytes` (or `bytes[]`) format, not as raw `address` types. `abi.encode()` performs this necessary conversion. * **Foundry Compilation:** If your project involves complex CCIP contract dependencies or deep call stacks, you might encounter stack too deep errors during compilation. Using the `--via-ir` flag with Foundry (`forge build --via-ir` or `forge script --via-ir ...`) can often resolve these by leveraging the IR-based compilation pipeline. This `ConfigurePool.s.sol` script provides a robust and reusable method for configuring your CCIP token pools after their initial deployment, laying the groundwork for cross-chain token transfers.
An essential tutorial to Crafting the `ConfigurePool.s.sol` Script for CCIP Token Pool Configuration - Master building a Foundry script (`ConfigurePool.s.sol`) to configure deployed `TokenPool` contracts for CCIP operations. You'll learn to use `applyChainUpdates`, prepare `ChainUpdate` structs with rate limiters, and ABI-encode addresses for robust cross-chain setup.
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