# Limitations
Source: https://docs.chain.link/solutions/cross-chain-vault-adapter/reference/limitations

> For the complete documentation index, see [llms.txt](/llms.txt).

Use this page to check whether your vault and tokens fit the Cross-Chain Vault Adapter as shipped, and to find the conditions that turn requests into failed messages or leave tokens stuck. The hub chain is the chain where the vault and the adapter are deployed, and a source chain is any other chain that users send deposits and redemptions from. The vault stays the product and CCIP stays the bridge, so the limits below sit in the adapter, the path that users on other chains take to reach the vault, and in its CCIP settings. For a request that succeeds, see [How it works](/solutions/cross-chain-vault-adapter/overview/how-it-works). For how tokens come back after a failure, see [Failures and recovery](/solutions/cross-chain-vault-adapter/overview/failures-and-recovery).

## Vault and token compatibility

The adapter works with a standard synchronous, single-asset [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) vault. Which tokens need CCIP support depends on the flow you want to offer; see [Requirements](/solutions/cross-chain-vault-adapter#requirements). If your vault's asset is not CCIP-enabled, users can still send CCIP-enabled shares from another chain and receive the redeemed asset on the hub chain, where the asset stays local. Deposits from another chain and redemptions that return the asset to the source chain require a CCIP-enabled asset.

The cases below need a fork of the adapter or are not supported.

| Case                                                                                                | What happens                                                                                                                                                                                                                                                                                                                                        | What to do                                                                                                                |
| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Asynchronous (request-based) vault                                                                  | The adapter calls `deposit` and `redeem` and needs the output in the same transaction. A vault that queues requests does not return shares or assets in that call, so the message fails.                                                                                                                                                            | Fork the adapter.                                                                                                         |
| Multi-asset vault                                                                                   | The adapter routes by comparing the bridged token with the vault's single `asset()`. Any other token reverts `InvalidTargetToken`, and the message fails.                                                                                                                                                                                           | Fork the adapter.                                                                                                         |
| Target that is not ERC-4626                                                                         | The adapter calls `asset()`, `deposit`, and `redeem` on the payload's target. If the target does not implement them, the call reverts and the message fails.                                                                                                                                                                                        | Fork the adapter.                                                                                                         |
| Vault that restricts depositors, or applies per-account limits or cooldowns                         | The adapter is the depositor, the initial share owner, and the redeemer, so the vault sees one account for every user. Per-account caps apply to all users combined. Allowlisting the adapter in the vault opens the vault to any sender on a configured source chain.                                                                              | Fork the adapter and add your own sender or beneficiary checks.                                                           |
| Fee-on-transfer, rebasing, or other non-standard ERC-20 asset or share token                        | The adapter assumes a transfer moves exactly the stated amount. Adapter fee accounting and `minimumOut` checks use nominal amounts, so they drift from real balances.                                                                                                                                                                               | Use standard ERC-20 tokens only.                                                                                          |
| Share token that is not a [CCIP cross-chain token (CCT)](/ccip/concepts/cross-chain-token/overview) | Deposits that ask to return shares to the source chain fail, because the return leg cannot bridge shares, and the whole message becomes a failed message. Users cannot move shares to another chain, so redeeming from another chain is not possible. Deposits with local delivery still work, because they need only the asset to be CCIP-enabled. | Register the share token as a CCT on both chains, typically Lock & Release on the hub chain and Burn & Mint on the other. |
| Hub chain without the Shanghai upgrade                                                              | The audited bytecode uses the `PUSH0` opcode, which only Shanghai-or-later chains support.                                                                                                                                                                                                                                                          | Deploy only on chains that support `PUSH0`. Recompiling for an older EVM version changes the audited bytecode.            |

To register the share token as a CCT, follow the [Lock & Mint tutorial](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry). Only the vault may mint shares, which is why the hub-chain side typically uses Lock & Release.

## Fees and accounting

The adapter fee is set with `setAssetFee` or `setAssetFees` and stored in `assetFees`. For how the adapter charges it, including why a fee change affects messages in flight and why a fee at or above the amount fails the message, see [Fees](/solutions/cross-chain-vault-adapter/overview/how-it-works#fees). The accounting limits below come on top of that.

- **Vaults that share an asset share fee state.** Redeem-return fees are keyed by the asset, so every vault on one adapter with that asset uses the same redeem-return fee for a destination. `collectedFees` is keyed by the asset too, so their fees pool in one balance. Deposit-return fees are keyed by each vault's share token and stay separate.
- **Only accounted fees can be withdrawn.** [`withdrawFee`](/solutions/cross-chain-vault-adapter/reference/adapter-contract#withdrawfee) moves at most `collectedFees[asset]`. The adapter has no general ERC-20 sweep.
- **Adapter fees do not pay CCIP fees.** The adapter pays each return leg's CCIP fee from its own native gas token balance. Collected adapter fees stay in the asset. Fund the adapter with the native gas token separately. If the balance is below the router's fee, the return reverts `InsufficientNativeBalance` and the whole message fails.
- **`minimumOut` checks nominal amounts.** It compares against the amount the vault returns from `deposit` or `redeem`, after the adapter fee, not against balance changes. See [Slippage protection](/solutions/cross-chain-vault-adapter/overview/how-it-works#slippage-protection).

## Operational limits

| Limit                      | What it means                                                                                                                                                                                                                                                                                                            |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| No pause                   | Turning processing off does not stop CCIP delivery. See [What counts as a failure](/solutions/cross-chain-vault-adapter/overview/failures-and-recovery#what-counts-as-a-failure).                                                                                                                                        |
| No retry                   | The adapter never reprocesses a failed message. Re-enabling a vault, chain, or processing switch does not help messages that already failed.                                                                                                                                                                             |
| No automatic refund        | Someone must call [`refundFailedMessage`](/solutions/cross-chain-vault-adapter/reference/adapter-contract#refundfailedmessage) and pay its CCIP fee, or the local refund address must call [`recoverFailedMessageLocally`](/solutions/cross-chain-vault-adapter/reference/adapter-contract#recoverfailedmessagelocally). |
| No sender allowlist        | Any sender on a configured source chain can deposit into or redeem from allowlisted vaults. The adapter checks only the source chain and the target.                                                                                                                                                                     |
| One token, one vault       | A message must carry exactly one token, or it fails with `InvalidTokenCount`. One adapter can allowlist several vaults, and the payload's target picks one per message.                                                                                                                                                  |
| Global processing switches | `depositsEnabled` and `redeemsEnabled` apply to every vault on the adapter.                                                                                                                                                                                                                                              |
| Two destinations only      | Output goes to a beneficiary on the hub chain or back to the chain the message came from. The adapter cannot route output to a third chain.                                                                                                                                                                              |
| Native gas token only      | The adapter pays CCIP fees for return legs in the hub chain's native gas token, never in LINK. Cross-chain refunds are paid by the caller.                                                                                                                                                                               |
| Token-only returns         | Return legs carry no data and a gas limit of 0, so a beneficiary contract on the source chain receives tokens without a callback.                                                                                                                                                                                        |

## Configuration risks

The first four risks concern CCIP 2.0 lane settings. [CCIP 2.0 lanes](/solutions/cross-chain-vault-adapter/overview/how-it-works#ccip-20-lanes) explains each setting.

- **V3 return format on a CCIP 1.6 lane.** The router rejects every return leg and cross-chain refund to that chain. Set `GENERIC_EXTRA_ARGS_V3_BASIC` with [`setEvmReturnLaneFormat`](/solutions/cross-chain-vault-adapter/reference/adapter-contract#setevmreturnlaneformat) only for CCIP 2.0 lanes; correcting the format unblocks later refunds.
- **Return finality the token pool does not allow.** Return legs and refunds of that token revert. Check the pool's allowed finality before you call [`setEvmReturnRequestedFinality`](/solutions/cross-chain-vault-adapter/reference/adapter-contract#setevmreturnrequestedfinality).
- **Inbound finality or Cross-Chain Verifier (CCV) settings that do not match senders.** CCIP rejects the message before the adapter runs, so the adapter's recovery functions do not apply. Fix [`setInboundFinality`](/solutions/cross-chain-vault-adapter/reference/adapter-contract#setinboundfinality) or [`setCCVsConfig`](/solutions/cross-chain-vault-adapter/reference/adapter-contract#setccvsconfig), then use [manual execution](/ccip/concepts/manual-execution).
- **CCIP 2.0 settings left at their defaults.** The factory applies none of these settings and does not fund the adapter. Until the admin sets them, return legs use `GenericExtraArgsV2`, return finality cannot be requested, the adapter rejects faster-than-finality messages, and an unfunded adapter fails return legs with `InsufficientNativeBalance`. See [Deploy the adapter](/solutions/cross-chain-vault-adapter/guides/deploy-the-adapter#fund-the-adapter).
- **The CCIP Router is fixed at deployment.** `ROUTER` is immutable, so moving to a different CCIP Router means deploying and configuring a new adapter. Failed messages on the old adapter stay there until someone refunds or recovers them from it.
- **Refunds use the chain family recorded at failure time.** If the chain type was misconfigured when a message failed, a later `setChainType` fix does not change how that message is refunded, so use local recovery. Setting a chain to `NONE` after a failure does not block its refund.
- **`recoverNative` takes the whole native gas token balance.** [`recoverNative`](/solutions/cross-chain-vault-adapter/reference/adapter-contract#recovernative) has no amount parameter. Afterward, every return leg fails with `InsufficientNativeBalance` until the adapter is funded again.
- **Role changes take effect in one step.** The adapter uses OpenZeppelin [AccessControlEnumerable](https://docs.openzeppelin.com/contracts/5.x/api/access#AccessControlEnumerable), so `grantRole`, `revokeRole`, and `renounceRole` need no acceptance step. If the last `DEFAULT_ADMIN_ROLE` holder renounces, no one can change admin-controlled configuration or grant roles again. Fee setters and collectors keep only their own functions. Grant the new admin before you revoke the old one.
- **The factory and the constructor grant roles differently.** Deployed directly, the constructor gives `defaultAdmin` all three roles, in addition to granting `feeSetter` and `feeCollector` their own. Deployed through the factory, each role goes only to its configured address, and the factory renounces its own roles. With separate addresses, a factory-deployed admin cannot set or withdraw adapter fees until it grants itself those roles. See [Role handoff](/solutions/cross-chain-vault-adapter/reference/factory-contract#role-handoff).
- **Changes apply to messages in flight.** Disabling a chain, a vault, or a processing switch fails every message that arrives afterward, including messages sent before the change. A fee change likewise applies to messages sent before it.

## When funds can get stuck

In these scenarios, tokens are lost or stranded, or one of the two recovery paths is gone. The payload checklist in [What a user sends](/solutions/cross-chain-vault-adapter/overview/how-it-works#what-a-user-sends) prevents the first two.

| Scenario                                                           | Why                                                                                                                                                                                                                                                                                                 | Prevention                                                                                                                         |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| No local refund address, and a cross-chain refund is not possible  | Local recovery needs `localRefundAddress` in the payload when the message is sent. It cannot be added later, and a payload that is not exactly 128 bytes leaves it zero. A cross-chain refund fails if, for example, the router no longer supports the return lane or the token has no pool for it. | Always set `localRefundAddress` to an address the user controls on the hub chain.                                                  |
| Malformed payload                                                  | A hand-built payload can make the message fail inside CCIP with no recovery path, as described in [Failures with no recovery path](/solutions/cross-chain-vault-adapter/overview/failures-and-recovery#failures-with-no-recovery-path).                                                             | Build the payload with a standard ABI encoder, such as Solidity `abi.encode`, ethers, viem, or cast.                               |
| Refund recipient is a contract that cannot use the returned tokens | A cross-chain refund always goes to the original `message.sender` on the source chain, not to the beneficiary or the local refund address.                                                                                                                                                          | Send from an account or contract that can move refunded tokens, or set `localRefundAddress`.                                       |
| Tokens sent to the adapter outside a normal request                | An ERC-20 transfer straight to the adapter, or a CCIP message with empty data and a gas limit of 0, delivers tokens without calling `ccipReceive`. The adapter records nothing, `withdrawFee` withdraws only accounted fees, and there is no ERC-20 sweep.                                          | Send tokens only in CCIP messages with a 128-byte payload and a non-zero gas limit. `recoverNative` recovers the native gas token. |
| Admin role renounced while configuration is broken                 | No one can fix settings such as a wrong return lane format, so cross-chain refunds that depend on them stay blocked. Only local recovery remains, and only for messages that set a local refund address.                                                                                            | Verify the configuration with test messages before any admin change, and keep at least one `DEFAULT_ADMIN_ROLE` holder.            |

## Customizing the adapter

If your vault does not fit, fork the adapter rather than modifying the vault. See [Use it as-is or customize it](/solutions/cross-chain-vault-adapter#use-it-as-is-or-customize-it) for when to fork.

- **There are no hooks to override.** `processMessage` and `_processTarget` are not `virtual`. Edit them in your copy of [`CrossChainERC4626Adapter.sol`](https://github.com/smartcontractkit/cross-chain-vault-adapters/blob/main/src/ccip/CrossChainERC4626Adapter.sol).
- **Keep the safety checks.** Keep the router check in `ccipReceive`, the self-call that isolates `processMessage` in a `try`/`catch`, failed-message storage with both recovery paths, and the beneficiary address checks. The failure handler, the `catch` path in `ccipReceive`, must never revert on any input, or tokens can be stranded as in the malformed payload case above.
- **Keep the compiler settings.** The audited build uses solc 0.8.24, `via_ir`, `optimizer_runs = 1`, and `evm_version = cancun`, as pinned in [`foundry.toml`](https://github.com/smartcontractkit/cross-chain-vault-adapters/blob/main/foundry.toml).
- **A fork is not the audited code.** The audit covers the contracts in `src/ccip` as published. Any change to the source or the compiler settings changes the bytecode, so get your fork its own security review before you deploy it.

## Versions covered

This page describes the following versions. The contracts in `src/ccip` are audited.

| Component                         | Version                                                                                                 |
| --------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `CrossChainERC4626Adapter`        | 1.0.0 (`typeAndVersion` is `CrossChainERC4626Adapter 1.0.0`)                                            |
| `CrossChainERC4626AdapterFactory` | 1.0.0 (`typeAndVersion` is `CrossChainERC4626AdapterFactory 1.0.0`)                                     |
| `@chainlink/contracts-ccip`       | 2.0.0                                                                                                   |
| `@chainlink/contracts`            | 1.5.0 (`ITypeAndVersion` only)                                                                          |
| OpenZeppelin Contracts            | 5.0.2 (`AccessControlEnumerable`, `IERC4626`, `ReentrancyGuard`) and 4.8.3 (`IERC20`, `SafeERC20`)      |
| Compiler                          | solc 0.8.24, `via_ir`, `optimizer_runs = 1`, `evm_version = cancun` (requires `PUSH0`)                  |
| CCIP lanes                        | CCIP 1.6 lanes (`GenericExtraArgsV2` return legs) and CCIP 2.0 lanes (`GenericExtraArgsV3` return legs) |
| Hub chains                        | EVM chains with the Shanghai upgrade or later                                                           |

For exact signatures, events, and errors, continue to [Adapter contract](/solutions/cross-chain-vault-adapter/reference/adapter-contract) and [Factory contract](/solutions/cross-chain-vault-adapter/reference/factory-contract).