TokenController contract provides token configuration management for CCTP, including mapping local tokens to remote tokens across domains and setting per-message burn limits.
Contract: src/roles/TokenController.sol
Key Concepts
- Token Pairs: Links between local tokens and their corresponding remote domain tokens
- Burn Limits: Maximum amount of each token that can be burned per message
- Token Controller Role: Authorized address that manages token configuration
State Variables
burnLimitsPerMessage
Value: Maximum burn amount per message (in token’s smallest unit) Usage: A burn limit of 0 means the token is not supported for burning.
remoteTokensToLocalTokens
keccak256(abi.encodePacked(remoteDomain, remoteToken))Value: Local token address Usage: Enables validation and minting when receiving messages from remote domains.
_tokenController
Functions
linkTokenPair
localToken: Address of the local tokenremoteDomain: Domain ID of the remote chainremoteToken: Address of the token on the remote domain (as bytes32)
- Caller must be the token controller
- Remote token must not already be linked to a local token
TokenPairLinked(address localToken, uint32 remoteDomain, bytes32 remoteToken)
Source: TokenController.sol:126
Note:
- A remote token can only map to one local token
- Multiple remote tokens can map to the same local token
- Linking does not enable deposits (must also call
setMaxBurnAmountPerMessage)
unlinkTokenPair
localToken: Address of the local tokenremoteDomain: Domain ID of the remote chainremoteToken: Address of the token on the remote domain (as bytes32)
- Caller must be the token controller
- Token pair must currently be linked
TokenPairUnlinked(address localToken, uint32 remoteDomain, bytes32 remoteToken)
Source: TokenController.sol:157
Note: Unlinking does not disable burning the local token (must separately call setMaxBurnAmountPerMessage with 0)
setMaxBurnAmountPerMessage
localToken: Address of the local tokenburnLimitPerMessage: Maximum burn amount per message (0 to disable burning)
- Caller must be the token controller
SetBurnLimitPerMessage(address indexed token, uint256 burnLimitPerMessage)
Source: TokenController.sol:186
Important:
- Burns exceeding this limit will revert
- Mints do not respect this limit
- Reducing the limit does not affect previously burned tokens (they can still be minted)
- Setting to 0 disables burning for that token
tokenController
Internal Functions
_setTokenController
newTokenController: Address of the new token controller
- New token controller must be non-zero address
SetTokenController(address tokenController)
Source: TokenController.sol:202
_getLocalToken
remoteDomain: Domain ID of the remote chainremoteToken: Address of the token on the remote domain (as bytes32)
_hashRemoteDomainAndToken
remoteDomain: Domain ID of the remote chainremoteToken: Address of the token on the remote domain (as bytes32)
Modifiers
onlyTokenController
onlyWithinBurnLimit
token: Address of the token to burnamount: Amount to burn
- “Burn token not supported” if burn limit is 0
- “Burn amount exceeds per tx limit” if amount exceeds limit
Events
TokenPairLinked
localToken: Local token addressremoteDomain: Remote domain IDremoteToken: Remote token address as bytes32
TokenPairUnlinked
SetBurnLimitPerMessage
token: Local token addressburnLimitPerMessage: New burn limit per message
SetTokenController
tokenController: New token controller address
Usage Examples
Setting Up a New Token
Updating Configuration
Checking Configuration
Security Considerations
- Token controller should be a trusted address (e.g., multisig or DAO)
- Burn limits should be set based on liquidity and risk assessment
- A token pair can only be linked once (prevents conflicting mappings)
- Minting is not limited by burn limits (asymmetric by design)
- Setting burn limit to 0 effectively disables deposits for that token
- Unlinking a token pair does not automatically disable burning
- Remote tokens are stored as bytes32 to support non-EVM chains
- Multiple remote tokens can map to the same local token (useful for wrapped tokens)
Integration with TokenMinter
TheTokenMinter contract inherits from TokenController and uses these functions in:
- depositForBurn(): Checks burn limit via
onlyWithinBurnLimitmodifier - handleReceiveMessage(): Uses
_getLocalToken()to determine which token to mint - Administrative setup: Owner uses inherited functions to configure supported tokens
Remote Token Address Format
Remote tokens are represented asbytes32 to support both EVM and non-EVM chains: