5/5
## Automating Token and Pool Deployment for Chainlink CCIP with Foundry This lesson guides you through creating a Foundry script (`Deployer.s.sol`) to streamline the deployment of a custom `RebaseToken` and its associated `RebaseTokenPool`. More importantly, we'll cover the essential configuration steps required by Chainlink CCIP (Cross-Chain Interoperability Protocol) to enable this token for Burn & Mint cross-chain transfers, specifically within the CCIP local development environment. Our primary objective is to automate the entire setup process, from contract deployment to CCIP registration, using a single, executable Foundry script. ## Understanding the Core Components Before diving into the script, let's familiarize ourselves with the key smart contracts involved: * **`RebaseToken.sol`:** This is your custom ERC20 token. While its internal rebasing logic isn't the focus here, it's crucial that it inherits from OpenZeppelin's `ERC20` and `Ownable` contracts. It also implements an `AccessControl` mechanism, defining a `MINT_AND_BURN_ROLE` which is essential for CCIP operations. * **`RebaseTokenPool.sol`:** This contract is specifically designed for CCIP. It inherits from the standard CCIP `TokenPool` contract and manages the locking/burning of `RebaseToken` on the source chain and the releasing/minting on the destination chain during cross-chain transfers. * **`Deployer.s.sol`:** This is the Foundry script we will build. Inheriting from Foundry's `Script` base contract, it will contain the logic to deploy the `RebaseToken` and `RebaseTokenPool`, and then perform the necessary CCIP configurations. * **CCIP Contracts (from `@chainlink-local` and `@ccip/contracts`):** * **`CCIPLocalSimulatorFork`:** Provided by Chainlink, this contract gives us access to addresses and configurations specific to the local CCIP simulation environment. It's vital for local development and testing. * **`Register`:** Part of the local simulator, used by `CCIPLocalSimulatorFork` to provide network details. * **`RegistryModuleOwnerCustom`:** A CCIP contract used to register the token administrator (an Externally Owned Account - EOA in this case) for your custom token. * **`TokenAdminRegistry`:** This CCIP contract is where token administrators manage their token configurations, accept their admin roles, and critically, link their tokens to their corresponding token pools. * **`IERC20`:** The standard ERC20 token interface, used for type compatibility. ## Key Concepts: Foundry Scripts and CCIP Burn & Mint Two core concepts underpin this lesson: 1. **Foundry Scripts:** Foundry allows you to write deployment and interaction logic in Solidity itself using "scripts." These scripts inherit from `Script` and can utilize Foundry's "cheat codes" – special functions provided by the Foundry Virtual Machine (VM) to interact with the blockchain environment, simulate transactions, and manage state. * **`vm.startBroadcast()` / `vm.stopBroadcast()`:** These are crucial cheat codes. Any contract calls made between `vm.startBroadcast()` and `vm.stopBroadcast()` are executed as actual transactions on the target blockchain network (or, in our case, the local simulator). 2. **Chainlink CCIP (Burn & Mint Mechanism):** CCIP enables secure cross-chain token transfers and messaging. We're focusing on the "Burn & Mint" token transfer mechanism. In this model: * When tokens are sent from a source chain, they are "burned" (removed from circulation) on that chain by the token pool. * An equivalent amount of tokens is then "minted" (created) on the destination chain by the corresponding token pool, and delivered to the recipient. * **Token Pool:** Each token enabled for CCIP Burn & Mint requires a dedicated Token Pool contract. This pool is authorized to mint and burn the token and interacts with the CCIP routers to facilitate cross-chain transfers. ## Crafting the `Deployer.s.sol` Script Let's build the `Deployer.s.sol` script step-by-step. This script will deploy our `RebaseToken`, its `RebaseTokenPool`, and configure them for CCIP. **1. Imports:** First, we import all necessary contracts. This includes our custom contracts, Foundry's `Script`, CCIP contracts from the local simulator and CCIP packages, and the standard `IERC20` interface. ```solidity import {Script} from "forge-std/Script.sol"; import {RebaseToken} from "../src/RebaseToken.sol"; import {RebaseTokenPool} from "../src/RebaseTokenPool.sol"; // Assuming Vault and IRebaseToken might be part of a larger system, // but not directly used in this specific deployment script for token & pool. // import {Vault} from "../src/Vault.sol"; // import {IRebaseToken} from "../src/interfaces/IRebaseToken.sol"; import {CCIPLocalSimulatorFork, Register} from "@chainlink-local/src/ccip/CCIPLocalSimulatorFork.sol"; import {IERC20} from "@ccip/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; import {RegistryModuleOwnerCustom} from "@ccip/contracts/src/v0.8/ccip/RegistryModuleOwnerCustom.sol"; import {TokenAdminRegistry} from "@ccip/contracts/src/v0.8/ccip/TokenAdminRegistry.sol"; ``` **2. Define the Script Contract:** Our script contract, let's name it `TokenAndPoolDeployer`, inherits from `Script`. ```solidity contract TokenAndPoolDeployer is Script { // Script logic will go here } ``` **3. The `run` Function:** The main logic resides in the `run()` function. It's public and will return instances of our deployed `RebaseToken` and `RebaseTokenPool`. ```solidity function run() public returns (RebaseToken token, RebaseTokenPool pool) { // Deployment and configuration logic } ``` **4. Instantiate CCIP Local Simulator and Get Network Details:** Inside `run()`, we first instantiate `CCIPLocalSimulatorFork`. We then use it to get network-specific details (like CCIP contract addresses) for the chain our script is running on (`block.chainid`). ```solidity CCIPLocalSimulatorFork ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); // The Register.NetworkDetails struct holds crucial addresses for CCIP components. // It must be stored in memory. Register.NetworkDetails memory networkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); ``` **5. Start Broadcast:** Now, we signal to Foundry that subsequent calls should be treated as on-chain transactions. ```solidity vm.startBroadcast(); ``` **6. Deploy `RebaseToken`:** We deploy our `RebaseToken`. Its constructor `constructor() ERC20("Rebase Token", "RBT") Ownable(msg.sender) {}` sets the name, symbol, and owner (to `msg.sender`, which is the deployer account executing the script) and doesn't require arguments passed from the script itself. ```solidity token = new RebaseToken(); ``` **7. Deploy `RebaseTokenPool`:** Next, we deploy the `RebaseTokenPool`. This constructor *does* require arguments: * The address of the `RebaseToken` it will manage (cast to `IERC20`). * An empty array for the allowlist (meaning all addresses are allowed by default, or a more specific allowlist isn't being configured here). * The RMN (Risk Management Network) Proxy address, obtained from `networkDetails`. * The CCIP Router address, also obtained from `networkDetails`. ```solidity pool = new RebaseTokenPool( IERC20(address(token)), // The deployed token address new address[](0), // Empty allowlist networkDetails.rmnProxyAddress, // RMN Proxy address from simulator networkDetails.routerAddress // Router address from simulator ); ``` **8. Grant Mint/Burn Role to Pool:** For the `RebaseTokenPool` to burn tokens on the source chain and mint them on the destination, it needs permission. We grant the `MINT_AND_BURN_ROLE` (defined in `RebaseToken`) to the deployed pool's address. ```solidity token.grantMintAndBurnRole(address(pool)); ``` **9. Perform CCIP Configuration Steps:** This is a critical sequence to register our token with the CCIP system: * **Register Admin:** We inform the CCIP `RegistryModuleOwnerCustom` contract that the deployer of this script (the owner of the token) will be the administrator for this `RebaseToken`. ```solidity RegistryModuleOwnerCustom(networkDetails.registryModuleOwnerCustomAddress) .registerAdminViaOwner(address(token)); ``` * **Accept Admin Role:** The designated admin (our deployer account) must then formally accept this administrative role for the token within the `TokenAdminRegistry`. ```solidity TokenAdminRegistry(networkDetails.tokenAdminRegistryAddress) .acceptAdminRole(address(token)); ``` * **Set Pool:** Finally, we link our `RebaseToken` to its dedicated `RebaseTokenPool` in the `TokenAdminRegistry`. This tells CCIP which pool contract is responsible for managing our specific token. ```solidity TokenAdminRegistry(networkDetails.tokenAdminRegistryAddress) .setPool(address(token), address(pool)); ``` **10. Stop Broadcast:** We conclude the sequence of on-chain transactions. ```solidity vm.stopBroadcast(); ``` **11. Return Values:** The `run` function will implicitly return the `token` and `pool` instances we created, as defined in its signature. This completes the core logic of our `Deployer.s.sol` script. ## Essential CCIP Configuration Explained To enable a custom token for CCIP Burn & Mint transfers, several configuration steps are mandatory after deploying the token and its pool. Our script automates these: 1. **Granting Token Permissions to the Pool:** The `RebaseTokenPool` needs the authority to call `mint` and `burn` functions on the `RebaseToken`. This is achieved by granting it the `MINT_AND_BURN_ROLE` (or an equivalent permissioning mechanism) within the `RebaseToken` contract. 2. **Registering a Token Administrator:** CCIP needs to know who is authorized to manage the token's settings within the CCIP ecosystem. We use `RegistryModuleOwnerCustom.registerAdminViaOwner(address(token))` to declare the token's owner (our deployer EOA) as its CCIP administrator. 3. **Accepting the Administrator Role:** The designated administrator must then explicitly accept this role. This is done by calling `TokenAdminRegistry.acceptAdminRole(address(token))`. 4. **Linking Token to Pool:** The final step is to associate the `RebaseToken` with its `RebaseTokenPool` in the `TokenAdminRegistry`. The call `TokenAdminRegistry.setPool(address(token), address(pool))` establishes this crucial link, informing CCIP which pool contract handles the cross-chain operations for this specific token. These steps ensure that your token is correctly registered and configured within the Chainlink CCIP framework for Burn & Mint operations. ## Best Practices and Troubleshooting When working with Foundry scripts and CCIP configurations, keep these points in mind: * **Custom Tokens:** You can adapt this process for your own custom ERC20 tokens. The key is that your token must have mint and burn functions that can be exclusively called by its associated CCIP token pool. The `BurnMintERC677` token often cited in Chainlink documentation is one example, but any compliant token (like our `RebaseToken` with `MINT_AND_BURN_ROLE`) will work. * **Verify Constructor Arguments:** Always double-check the constructor arguments required by the contracts you are deploying (e.g., `RebaseTokenPool`). Mismatched or missing arguments are common sources of deployment failures. * **CCIP Addresses via `CCIPLocalSimulatorFork`:** In a local development environment, rely on `CCIPLocalSimulatorFork.getNetworkDetails()` to obtain the correct addresses for CCIP components like the Router, RMN Proxy, and various registries. These addresses are specific to the simulator. * **`memory` Keyword for Structs:** When a function returns a struct, like `getNetworkDetails()` returning `Register.NetworkDetails`, you typically need to declare the variable that will store this struct with the `memory` data location keyword in Solidity (e.g., `Register.NetworkDetails memory networkDetails;`). * **Type Casting:** Be mindful of type casting. You'll frequently need to cast contract instance variables to `address` (e.g., `address(token)`) when calling functions that expect an address. Conversely, you might cast an address to an interface type (e.g., `IERC20(address(token))`) when a function expects an interface. * **`forge build` is Your Friend:** Regularly run `forge build` during development. The Solidity compiler is excellent at catching typos, incorrect variable names, missing data location keywords, and other syntax or type errors. For example, common errors encountered during development might include: * **Data Location Error:** Forgetting the `memory` keyword for `networkDetails`. * **Member Not Found Error:** A typo in a struct field name (e.g., `rmProxyAddress` instead of the correct `rmnProxyAddress`). Addressing these compiler errors early will save you significant debugging time. By following these guidelines and understanding the deployment and configuration flow, you can effectively use Foundry to manage your CCIP-enabled tokens in a local development environment. Refer to the official Chainlink CCIP documentation for further details, especially the guides on enabling tokens for Burn & Mint and using Foundry for EOA-based registration.
A streamlined deployment guide to Automating Token and Pool Deployment for Chainlink CCIP with Foundry - Learn to craft a `Deployer.s.sol` Foundry script for deploying a custom `RebaseToken` and its `RebaseTokenPool`. You'll also automate the essential Chainlink CCIP configurations to enable Burn & Mint token transfers in a local development environment.
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