diff --git a/contracts/ccip/ccip_token_pools/README.md b/contracts/ccip/ccip_token_pools/README.md index af140b86..1d91181f 100644 --- a/contracts/ccip/ccip_token_pools/README.md +++ b/contracts/ccip/ccip_token_pools/README.md @@ -6,15 +6,17 @@ CCIP Token Pools are smart contracts that manage the cross-chain transfer of tok ### Key Points -Tokens with dynamic dispatch are supported for both pools, however the deposit and withdraw overrides are not invoked during `lock_or_burn` and `release_or_mint` functions. +Tokens with dynamic dispatch are supported for most pools, however the deposit and withdraw overrides are not invoked during `lock_or_burn` and `release_or_mint` functions. ## Pool Types -There are **3 types** of token pools available on Aptos: +There are **5 types** of token pools available on Aptos: 1. **Lock/Release Token Pool** (`lock_release_token_pool`) 2. **Burn/Mint Token Pool** (`burn_mint_token_pool`) 3. **USDC Token Pool** (`usdc_token_pool`) +4. **Managed Token Pool** (`managed_token_pool`) +5. **Regulated Token Pool** (`regulated_token_pool`) ### 1. Lock/Release Token Pool (`lock_release_token_pool`) @@ -84,6 +86,66 @@ There are **3 types** of token pools available on Aptos: - Specifically for USDC token transfers +### 4. Managed Token Pool (`managed_token_pool`) + +- **Automatically registers with CCIP Token Admin Registry** during deployment +- Designed specifically for tokens deployed with the managed token package +- Uses allowlist-based permission system for secure mint/burn operations +- **Does not support dynamic dispatch** - calls `fungible_asset` module functions directly +- Managed token uses internal refs, not dynamic dispatch mechanisms + +**Operation Modes**: + +- Pool's address must be added to managed token's allowlists before operation +- Pool calls `managed_token::mint()` and `managed_token::burn()` functions directly +- Managed token validates permissions via allowlist checks +- Pool automatically registers with Token Admin Registry during deployment + +**Mechanism**: + +- **Outbound**: Pool calls managed token to burn tokens from total supply +- **Inbound**: Pool calls managed token to mint new tokens to recipient + +**When to Use**: + +- For tokens deployed with the managed token package +- When you need allowlist-based control over token operations +- Multiple protocol integrations while retaining developer control +- **Cannot be used with existing standard fungible assets** + +### 5. Regulated Token Pool (`regulated_token_pool`) + +- **Automatically registers with CCIP Token Admin Registry** during deployment +- Designed specifically for tokens deployed with the regulated token package +- Uses **bridge functions** (`bridge_mint()` and `bridge_burn()`) to bypass dynamic dispatch conflicts +- Requires `BRIDGE_MINTER_OR_BURNER_ROLE` authorization from the regulated token +- Maintains all regulatory controls (freezing, pausing, recovery) + +**Operation Modes**: + +- Pool's store address must have `BRIDGE_MINTER_OR_BURNER_ROLE` on the regulated token +- Pool calls `regulated_token::bridge_burn()` and `bridge_mint()` directly +- Bridge functions use `MintRef`/`BurnRef` directly to avoid fungible_asset store operations +- Resolves error 65564 caused by dynamic dispatch conflicts in standard pools + +**Mechanism**: + +- **Outbound**: Pool calls regulated token to burn tokens via bridge function, bypassing dispatch +- **Inbound**: Pool calls regulated token to mint new tokens to recipient via bridge function + +**When to Use**: + +- For tokens deployed with the regulated token package +- When you need regulatory compliance features (account freezing, pause, recovery) +- Role-based access control with RBAC system +- **Cannot be used with standard fungible assets** + +**Key Difference from Burn/Mint Pool**: + +- Uses specialized bridge functions instead of direct `fungible_asset` operations +- Required because regulated tokens have dynamic dispatch enabled +- Preserves all regulatory controls and security features + ## Deployment Guide ### Prerequisites @@ -136,16 +198,43 @@ token_messenger_minter=,\ deployer= ``` +For **Managed Token Pool**: + +```bash +aptos move deploy-object \ + --package-dir contracts/ccip/ccip_token_pools/managed_token_pool \ + --address-name managed_token_pool \ + --named-addresses managed_token=,\ +managed_token=,\ +ccip=,\ +ccip_token_pool=,\ +mcms=,\ +mcms_register_entrypoints= +``` + +For **Regulated Token Pool**: + +```bash +aptos move deploy-object \ + --package-dir contracts/ccip/ccip_token_pools/regulated_token_pool \ + --address-name regulated_token_pool \ + --named-addresses regulated_token=,\ +ccip=,\ +ccip_token_pool=,\ +mcms=,\ +mcms_register_entrypoints= +``` + ### Initialize Pool **Lock/Release Pool**: ```move // For tokens WITHOUT dynamic dispatch -lock_release_token_pool::initialize(admin_signer, option::none()); +lock_release_token_pool::initialize(admin_signer, option::none(), rebalancer_address); -// For tokens WITH dynamic dispatch -lock_release_token_pool::initialize(admin_signer, option::some(transfer_ref)); +// For tokens WITH dynamic dispatch (must provide transfer_ref) +lock_release_token_pool::initialize(admin_signer, option::some(transfer_ref), rebalancer_address); ``` **Burn/Mint Pool**: @@ -160,6 +249,81 @@ burn_mint_token_pool::initialize(admin_signer, burn_ref, mint_ref); usdc_token_pool::initialize(admin_signer); ``` +**Managed Token Pool**: + +```move +// 1. Pool registers automatically during deployment + +// 2. Token owner proposes administrator +token_admin_registry::propose_administrator( + token_owner, + managed_token::token_metadata(), + administrator_address +); + +// 3. Administrator accepts role +token_admin_registry::accept_admin_role( + administrator_signer, + managed_token::token_metadata() +); + +// 4. Administrator activates the pool +token_admin_registry::set_pool( + administrator_signer, + managed_token::token_metadata(), + @managed_token_pool +); + +// 5. Add pool to token allowlists +let pool_store_address = managed_token_pool::get_store_address(); + +managed_token::apply_allowed_minter_updates( + token_owner, + vector[], // remove + vector[pool_store_address] // add pool as minter +); + +managed_token::apply_allowed_burner_updates( + token_owner, + vector[], // remove + vector[pool_store_address] // add pool as burner +); +``` + +**Regulated Token Pool**: + +```move +// 1. Pool registers automatically during deployment + +// 2. Grant bridge role to pool's store address +let pool_store_address = regulated_token_pool::get_store_address(); +regulated_token::grant_role( + admin_signer, + BRIDGE_MINTER_OR_BURNER_ROLE, + pool_store_address +); + +// 3. Token owner proposes administrator +token_admin_registry::propose_administrator( + token_owner, + regulated_token::token_metadata(), + administrator_address +); + +// 4. Administrator accepts role +token_admin_registry::accept_admin_role( + administrator_signer, + regulated_token::token_metadata() +); + +// 5. Administrator activates the pool +token_admin_registry::set_pool( + administrator_signer, + regulated_token::token_metadata(), + @regulated_token_pool +); +``` + ### Configure Cross-Chain Support ```move @@ -202,11 +366,21 @@ pool::apply_allowlist_updates( ## Token Admin Registry Integration -### Automatic Registration Process +### Updated Registration Process + +The Token Admin Registry manages which pools are authorized for specific tokens. The current flow supports **multiple pools per token** but only **one active pool** at a time. + +**⚠️ Critical Requirement**: `propose_administrator` is the **only function** that adds tokens to `token_configs`. This means: + +- It **must be called** before `accept_admin_role` or `set_pool` can work +- Without this step, the token will not exist in the registry's configuration +- All subsequent admin operations will fail if this step is skipped -The Token Admin Registry manages which pools are authorized for specific tokens. It maintains a **1:1 mapping** of tokens to pools. +### Step-by-Step Registration Flow -**During pool deployment**, each pool automatically registers itself with the Token Admin Registry via the `init_module` function: +#### Step 1: Register Pool + +During pool deployment, each pool automatically registers itself with the Token Admin Registry via the `init_module` function: ```move // Automatic registration during pool deployment (in init_module) @@ -214,43 +388,170 @@ token_admin_registry::register_pool( publisher, pool_module_name, // e.g., b"lock_release_token_pool" token_address, - administrator_address, CallbackProof {} ); ``` -This ensures that: +**This step:** + +- Registers the pool for the token (multiple pools can be registered) +- **Does NOT activate the pool** for use +- **Does NOT add the token to token_configs** + +#### Step 2: Propose Administrator + +**⚠️ Authorization Required**: Only the **token owner** or **CCIP owner** can propose an administrator for the token: + +```move +// Must be called by token owner or CCIP owner +token_admin_registry::propose_administrator( + token_owner, + token_address, + proposed_admin_address +); +``` + +**This step:** + +- **Verifies caller owns the token** or is CCIP owner (security checkpoint) +- Adds the token to `token_configs` (REQUIRED for later operations) +- Sets a pending administrator +- Can only be called if no administrator is already set + +#### Step 3: Accept Admin Role + +The proposed administrator must accept the role: + +```move +// Must be called by the proposed administrator +token_admin_registry::accept_admin_role( + proposed_admin_signer, + token_address +); +``` + +**This step:** + +- Sets the administrator as active +- Clears the pending administrator +- Enables the administrator to manage pools for this token + +#### Step 4: Activate Pool + +The administrator can now activate a specific registered pool: + +```move +// Must be called by the token's administrator +token_admin_registry::set_pool( + admin_signer, + token_address, + pool_address_to_activate +); +``` + +**This step:** -- The token is immediately associated with the new pool -- The specified administrator has control over the pool -- The pool can be called by CCIP for cross-chain operations +- Activates the specified pool for the token +- Replaces any previously active pool +- **Does NOT clean up** the previous pool's resources (old pool remains registered but inactive) + +### Complete Flow Example + +```move +// 1. Pool registers automatically during deployment (in init_module) +// token_admin_registry::register_pool(...) // Called automatically + +// 2. Token owner proposes admin +token_admin_registry::propose_administrator( + token_owner, + managed_token::token_metadata(), + pool_administrator_address +); + +// 3. Proposed admin accepts role +token_admin_registry::accept_admin_role( + pool_administrator, + managed_token::token_metadata() +); + +// 4. Admin activates the pool +token_admin_registry::set_pool( + pool_administrator, + managed_token::token_metadata(), + @managed_token_pool // Pool address to activate +); +``` + +### Multiple Pools Support + +The new system allows multiple pools to be registered for the same token: + +- **Registration**: Multiple pools can register for the same token (no uniqueness check) +- **Activation**: Only one pool can be active at a time via `set_pool` +- **Switching**: Administrators can switch between registered pools using `set_pool` +- **No Automatic Cleanup**: Previous pools remain registered but inactive when a new pool is activated + +**Important Implications:** + +- Inactive registered pools continue to exist and can be queried via `get_pool_local_token` +- Resources from inactive pools are not automatically cleaned up +- Multiple pool objects may exist for the same token simultaneously +- Only the active pool (returned by `get_pool`) should be trusted for operations ### Pool Management Functions -**Check Current Pool**: +**Check Active Pool**: ```move -let pool_address = token_admin_registry::get_pool(token_address); +let active_pool_address = token_admin_registry::get_pool(token_address); ``` -**Check Pool Administrator**: +**Check Pool's Token** (works for ANY registered pool, not just active): ```move -let admin_address = token_admin_registry::get_administrator(token_address); +let local_token = token_admin_registry::get_pool_local_token(pool_address); ``` -**Transfer Pool Admin**: +**Check Token Configuration**: ```move -token_admin_registry::transfer_admin_role(admin_signer, token_address, new_admin); +let (pool_address, admin, pending_admin) = token_admin_registry::get_token_config(token_address); ``` -**Accept Admin Role**: +**Transfer Admin Role**: ```move +// Current admin proposes new admin +token_admin_registry::transfer_admin_role(current_admin, token_address, new_admin); + +// New admin accepts token_admin_registry::accept_admin_role(new_admin_signer, token_address); ``` +### Security Considerations + +⚠️ **Critical Security Issue**: `get_pool_local_token` returns information for ANY registered pool, **even if it's not currently active**. This could allow adversaries to mislead users into believing a pool is active when it's only registered but not in use. + +**Attack Scenario:** + +1. Adversary deploys a malicious pool and registers it for a legitimate token +2. Adversary calls `get_pool_local_token(malicious_pool)` and gets the legitimate token address +3. Users might assume the malicious pool is the active pool for that token +4. Users interact with the malicious pool instead of the legitimate active pool + +**Example of safe verification**: + +```move +// DON'T just trust get_pool_local_token +let local_token = token_admin_registry::get_pool_local_token(some_pool); + +// DO verify the pool is actually active for the token +let active_pool = token_admin_registry::get_pool(local_token); +assert!(active_pool == some_pool, E_POOL_NOT_ACTIVE); +``` + +**Best Practice**: Always verify pool activation status before trusting any pool operations. + ### Pool Unregistration To completely remove a pool from the Token Admin Registry: @@ -262,11 +563,20 @@ token_admin_registry::unregister_pool( ); ``` -**⚠️ Warning**: Unregistering a pool will: +**⚠️ Critical Warning**: `unregister_pool` is **highly destructive** and will: -- Remove the token-to-pool mapping -- Disable cross-chain transfers for that token unless registered with another pool -- Require re-registration to restore functionality +- Remove the pool registration completely +- **Remove the token from `token_configs` entirely** (destroys all token admin configuration) +- Remove the administrator assignment for the token +- Emit a `TokenUnregistered` event +- **Disable ALL pool operations** for that token until completely re-registered + +**Recovery Process**: To restore functionality after unregistration, you must restart the **entire 4-step flow**: + +1. Deploy new pool (auto-registers) +2. `propose_administrator` (re-adds token to `token_configs`) +3. `accept_admin_role` +4. `set_pool` ### Pool Upgrades @@ -319,7 +629,6 @@ To switch pools for a token, you must deploy a new pool contract and use the unr new_pool_signer, pool_module_name, // e.g., b"lock_release_token_pool" token_address, - administrator_address, CallbackProof {} ); ``` @@ -407,6 +716,24 @@ apply_chain_updates(admin, vector[], chain_selectors, remote_pools, remote_token - **Cause**: Token is not associated with any pool in Token Admin Registry - **Solution**: Deploy and register a pool for the token +**"Not allowed minter/burner" (Managed Token Pool)** + +- **Cause**: Pool not added to managed token's allowlists +- **Solution**: Add pool's store address to managed token allowlists: + ```move + managed_token::apply_allowed_minter_updates(token_owner, vector[], vector[pool_store_address]); + managed_token::apply_allowed_burner_updates(token_owner, vector[], vector[pool_store_address]); + ``` + +**"Missing role" or "Unauthorized" (Regulated Token Pool)** + +- **Cause**: Pool's store address doesn't have `BRIDGE_MINTER_OR_BURNER_ROLE` +- **Solution**: Grant the bridge role to pool's store address: + ```move + let pool_store_address = regulated_token_pool::get_store_address(); + regulated_token::grant_role(admin_signer, BRIDGE_MINTER_OR_BURNER_ROLE, pool_store_address); + ``` + ### Diagnostic Commands ```move @@ -418,6 +745,15 @@ pool::get_remote_pools(chain_selector); // Check token admin registry token_admin_registry::get_pool(token_address); token_admin_registry::get_administrator(token_address); + +// For managed token pool specifically +managed_token_pool::get_store_address(); // Get pool's resource account address +managed_token::is_minter_allowed(pool_store_address); // Check minter permission +managed_token::is_burner_allowed(pool_store_address); // Check burner permission + +// For regulated token pool specifically +regulated_token_pool::get_store_address(); // Get pool's resource account address +regulated_token::has_role(pool_store_address, BRIDGE_MINTER_OR_BURNER_ROLE); // Check bridge role ``` ## Migration from Other Chains @@ -432,8 +768,10 @@ When bringing tokens from EVM chains to Aptos: 2. **Choose Pool Strategy**: - Keep original on source chain → Lock/Release - - Burn on source, mint on Aptos → Burn/Mint + - Burn on source, mint on Aptos → Burn/Mint or Managed or Regulated - USDC specifically → USDC Pool + - Regulatory compliance needed → Regulated Token Pool + - Allowlist-based control needed → Managed Token Pool 3. **Handle Custom Logic**: diff --git a/contracts/managed_token/README.md b/contracts/managed_token/README.md new file mode 100644 index 00000000..8450b4be --- /dev/null +++ b/contracts/managed_token/README.md @@ -0,0 +1,327 @@ +# Managed Token + +## Overview + +The **Managed Token** is a secure, flexible fungible asset implementation designed for cross-chain interoperability and multi-protocol integration. Unlike standard fungible assets that transfer mint/burn capabilities to individual contracts, the Managed Token retains control through an allowlist-based permission system. + +## Key Features + +- **Retained Control**: Token developers maintain ownership of mint/burn capabilities +- **Allowlist-Based Security**: Granular permission control for minters and burners +- **CCIP Integration**: Designed to work with CCIP Managed Token Pools +- **Emergency Controls**: Ability to revoke permissions when needed + +## Why Managed Token? + +### The Problem with Standard Burn/Mint Pools + +Traditional CCIP burn/mint token pools require developers to provide mint/burn refs: + +```move +struct BurnMintTokenPoolState { + burn_ref: BurnRef, + mint_ref: MintRef, +} +``` + +**Limitations:** + +- If spare burn/mint refs were not created during token creation, the token cannot work with this pool. + +### The Managed Token Solution + +```move +struct TokenState { + allowed_minters: AllowlistState, // Multiple authorized minters + allowed_burners: AllowlistState, // Multiple authorized burners + token: Object +} +``` + +**Benefits:** + +- Developer keeps mint/burn control via allowlists +- Multiple signers can be authorized +- Can add/remove permissions +- Emergency revocation capabilities + +## Security Model + +### Allowlist-Based Permissions + +The Managed Token uses two separate allowlists: + +1. **Allowed Minters**: Addresses authorized to mint new tokens +2. **Allowed Burners**: Addresses authorized to burn existing tokens + +```move +public entry fun mint(minter: &signer, to: address, amount: u64) { + let minter_addr = signer::address_of(minter); + assert_is_allowed_minter(minter_addr); // ← Allowlist check + // ... mint logic +} + +public entry fun burn(burner: &signer, from: address, amount: u64) { + let burner_addr = signer::address_of(burner); + assert_is_allowed_burner(burner_addr); // ← Allowlist check + // ... burn logic +} +``` + +### Ownership Controls + +- **Token Owner**: Has full control over allowlists and can mint/burn directly +- **Allowlisted Addresses**: Can mint/burn according to their permissions + +## Deployment Guide + +### Prerequisites + +- New token deployment (existing tokens cannot be retrofitted) +- Control over the deployment account +- Understanding of allowlist management + +### Step 1: Deploy Contract + +```bash +aptos move deploy-object \ + --package-dir contracts/ccip/managed_token \ + --address-name managed_token +``` + +### Step 2: Initialize Token + +```move +managed_token::initialize( + publisher, + option::some(1000000000), // max_supply (optional) + string::utf8(b"My Token"), + string::utf8(b"MTK"), + 8, // decimals + string::utf8(b"https://mytoken.com/icon.png"), + string::utf8(b"https://mytoken.com") +); +``` + +### Step 3: Configure Initial Allowlists + +```move +// Add minters +managed_token::apply_allowed_minter_updates( + owner, + vector[], // addresses_to_remove + vector[@ccip_pool, @dex_contract] // addresses_to_add +); + +// Add burners +managed_token::apply_allowed_burner_updates( + owner, + vector[], // addresses_to_remove + vector[@ccip_pool, @lending_protocol] // addresses_to_add +); +``` + +## Integration with CCIP + +### Managed Token Pool Setup + +The Managed Token is designed to work with the **Managed Token Pool** for CCIP cross-chain transfers: + +```move +// 1. Deploy managed token pool (automatically registers with CCIP) + +// 2. Add pool to token allowlists +let pool_address = managed_token_pool::get_store_address(); + +managed_token::apply_allowed_minter_updates( + token_owner, + vector[], + vector[pool_address] +); + +managed_token::apply_allowed_burner_updates( + token_owner, + vector[], + vector[pool_address] +); +``` + +### Cross-Chain Token Flow + +**Outbound (Aptos → Other Chain):** + +1. User initiates CCIP transfer +2. Managed Token Pool calls `managed_token::burn()` +3. Allowlist permits the burn operation +4. Tokens are burned, reducing total supply +5. Message sent to destination chain + +**Inbound (Other Chain → Aptos):** + +1. CCIP message received on Aptos +2. Managed Token Pool calls `managed_token::mint()` +3. Allowlist permits the mint operation +4. New tokens minted to recipient +5. Total supply increases + +## API Reference + +### View Functions + +```move +#[view] +/// Returns the address of the token's metadata object +public fun token_metadata(): address + +#[view] +/// Returns list of addresses allowed to mint +public fun get_allowed_minters(): vector
+ +#[view] +/// Returns list of addresses allowed to burn +public fun get_allowed_burners(): vector
+ +#[view] +/// Check if address can mint +public fun is_minter_allowed(minter: address): bool + +#[view] +/// Check if address can burn +public fun is_burner_allowed(burner: address): bool + +#[view] +/// Returns the current owner +public fun owner(): address +``` + +### Administrative Functions + +```move +/// Update the minter allowlist +public entry fun apply_allowed_minter_updates( + caller: &signer, + minters_to_remove: vector
, + minters_to_add: vector
+) + +/// Update the burner allowlist +public entry fun apply_allowed_burner_updates( + caller: &signer, + burners_to_remove: vector
, + burners_to_add: vector
+) + +/// Transfer ownership to new address +public entry fun transfer_ownership(caller: &signer, to: address) + +/// Accept pending ownership transfer +public entry fun accept_ownership(caller: &signer) +``` + +### Token Operations + +```move +/// Mint tokens to specified address (requires allowlist permission) +public entry fun mint(minter: &signer, to: address, amount: u64) + +/// Burn tokens from specified address (requires allowlist permission) +public entry fun burn(burner: &signer, from: address, amount: u64) +``` + +## Best Practices + +### Security Considerations + +1. **Careful Allowlist Management** + + - Only add trusted contracts to allowlists + - Regularly review authorized addresses + - Remove unused permissions promptly + +2. **Multi-Signature Recommended** + + - Use multi-sig for token ownership + - Require multiple approvals for allowlist changes + - Implement timelock for critical operations + +3. **Emergency Procedures** + - Have a plan to revoke compromised permissions + - Monitor mint/burn activities + +## Limitations + +### New Tokens Only + +- **Cannot retrofit existing tokens** with managed token functionality +- Existing tokens must use Lock/Release token pools for CCIP +- Migration from existing tokens requires new token deployment + +### Allowlist Dependency + +- All mint/burn operations depend on allowlist checks +- Incorrect allowlist configuration can break functionality +- Requires active management of permissions + +### Dynamic Dispatch + +- **Dynamic dispatch is not configurable** for Managed Token as of now +- Custom deposit/withdraw functions cannot be registered +- Standard fungible asset operations only + +## Comparison with Alternatives + +| Feature | Managed Token | Standard FA + Burn/Mint Pool | Lock/Release Pool | +| -------------------------- | ------------------------------ | ----------------------------- | ------------------------------------ | +| **Developer Control** | ✅ Full control via allowlists | ❌ Refs transferred to pool | ✅ Full control retained | +| **Multiple Protocols** | ✅ Via allowlist management | ❌ Single pool only | ✅ Via transfer ref sharing | +| **Supply Model** | 🔥 Burn/Mint (supply changes) | 🔥 Burn/Mint (supply changes) | 🔒 Lock/Release (supply constant) | +| **Existing Token Support** | ❌ New tokens only | ❌ New tokens only | ✅ Can use existing tokens | +| **Emergency Controls** | ✅ Can revoke permissions | ❌ Limited control | ✅ Can revoke transfer ref | +| **Liquidity Management** | ✅ No liquidity required | ✅ No liquidity required | ❌ **Requires constant rebalancing** | +| **Operational Complexity** | ✅ Set-and-forget | ✅ Set-and-forget | ❌ **Active monitoring required** | +| **Capital Efficiency** | ✅ 100% efficient | ✅ 100% efficient | ❌ **Must over-provision liquidity** | +| **Transfer Reliability** | ✅ Always succeeds | ✅ Always succeeds | ❌ **Fails when liquidity depleted** | + +### Lock/Release Pool Challenges + +**Liquidity Fragmentation** + +- Each destination chain needs sufficient locked tokens for outbound transfers +- Popular chains can become liquidity-depleted, blocking transfers + +**Rebalancing Overhead** + +- Requires active monitoring of liquidity levels across all chains +- Manual intervention needed to move liquidity between chains +- Operational costs and complexity increase with chain count + +**Capital Inefficiency** + +- Total locked liquidity must exceed circulating supply significantly +- Much higher capital requirements than burn/mint models +- Unused liquidity generates no yield + +## Troubleshooting + +### Common Issues + +**"Not allowed minter" Error** + +- Check if address is in minter allowlist: `is_minter_allowed(address)` +- Add address to allowlist: `apply_allowed_minter_updates()` + +**"Not allowed burner" Error** + +- Check if address is in burner allowlist: `is_burner_allowed(address)` +- Add address to allowlist: `apply_allowed_burner_updates()` + +**Ownership Issues** + +- Verify current owner: `owner()` +- Complete ownership transfer: `accept_ownership()` + +### Support and Resources + +- **Contract Source**: `contracts/managed_token/sources/managed_token.move` +- **Integration Examples**: `contracts/ccip/ccip_token_pools/managed_token_pool/` +- **Test Cases**: `contracts/managed_token/tests/`