Zircuit Finance - Circle CCTP Adapter

QuantstampCode version: not recorded (see the report)

Quantstamp reviewed Zircuit (Zircuit Finance - Circle CCTP Adapter) on 29 Apr 2026: 4 findings, 0 critical; 3 marked fixed, 1 acknowledged.

No open critical findings
Open the reportOpen Zircuit’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings4

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High000
Medium000
Low013
Low`removePeer` Emits Zeroed Domain Instead of the Actual Removed Values, Breaking Audit Trail ReconstructionFixed

`CircleCCTPAdapter` maintains a bijection between chain identifiers and Circle CCTP domains through the `chainIdToConfig` and `domainToChainId` mappings, and signals every lifecycle change on a single event, `CCTPPeerSet(uint256 indexed chainId, uint32 indexed domain, address indexed adapter)`. Off-chain indexers, monitoring tooling, and any downstream subgraph rely on this event to mirror peer state. On additions the event carries the real domain and adapter values; on removals the current implementation emits `CCTPPeerSet(chainId, 0, address(0))` to encode "cleared". However, `0` is the canonical Circle CCTP v2 domain identifier for Ethereum mainnet — a legitimate value that may be registered as a peer at any time. Emitting `0` in the `indexed domain` topic as a removal sentinel is indistinguishable on-chain from an event describing a peer whose domain is Ethereum. Indexers that filter on `domain = 0` to track Ethereum-related peer changes will falsely match every `removePeer` emission, and indexers that reconstruct history by pairing set with remove will lose the original domain of the peer being removed because the topic overwrites it with a value that looks like an Ethereum registration. This could cause off-chain systems to misclassify peer removals as Ethereum peer registrations, corrupt historical peer state reconstructions, and complicate observability once Ethereum is legitimately registered. There is no on-chain fund impact, but the ambiguity is avoidable and undermines the otherwise clean event-based audit trail the contract provides.

LowNo ERC-20 Token Recovery FunctionFixed

`CircleCCTPAdapter` has no function to recover ERC20 tokens that land in the contract outside of the normal `receiveFunds` flow. In practice, USDC cannot get stuck via the protocol path — `messageTransmitter.receiveMessage()` and the subsequent transfer to the router are atomic, so any failure rolls back the mint entirely. However, if USDC or any other ERC20 is sent directly to this contract address by mistake (e.g. a user error, a failed airdrop, or dust from an unrelated interaction), it is permanently unrecoverable. Every other adapter in this package ( `OFTBridgeAdapter`, `ProtoCCTPGatewayAdapter`, `ArbitrumAdapterL1`, and `ArbitrumAdapterL2`) has a `RESCUE_ROLE`-gated recovery mechanism.

LowReceive-Side Validation Skips Circle CCTP Message HeaderFixed

`CircleCCTPAdapter.receiveFunds()` authenticates an inbound CCTP v2 message by forwarding it to `messageTransmitter.receiveMessage()` and then hands it to `_parseReceivedMessage()`. The parser reads fields from the 228-byte `BurnMessageV2` body, `mintRecipient`, `amount`, `feeExecuted`, `messageSender`, and the trailing `hookData`, and compares each against state maintained by the adapter (the `domainToChainId` mapping, the stored peer adapter for the resolved source chain, and `address(this)`). These three body-level comparisons are the adapter's entire authorization model for inbound messages. The parsed `destAddress` and `extraData` are then passed to `router.handleReceivedFunds`, which gates the final destination through `Factory.isAuthorizedBridgeAddress()`. However, Circle's `MessageTransmitterV2.sendMessage()` is a permissionless entry point that produces valid, attestable messages whose body is entirely attacker-controlled bytes. The adapter never inspects the 148-byte message header, in particular the `sender` field (which for a genuine burn message equals the source-chain `TokenMessengerV2`) and the `recipient` field (which equals the destination-chain `TokenMessengerV2`). An attacker on a registered peer chain can therefore craft a message whose body is byte-compatible with `_parseReceivedMessage()` (correct `mintRecipient`, `messageSender` equal to the registered peer adapter, any chosen `amount`, any `feeExecuted`, any `destAddress` encoded in the hook data) have Circle attest it, and submit it via `receiveFunds` on the adapter. Circle dispatches the hook payload to an attacker-chosen no-op handler, so no USDC is minted to the adapter, yet the body-level checks all pass because the attacker wrote the bytes the parser expects. This could allow any USDC residing in the adapter — operational dust, accidental transfers, or balances introduced by a future refactor — to be routed to a vault of the attacker's choosing through `router.handleReceivedFunds()`. In the current integration, the destination is constrained to addresses that `Factory.isAuthorizedBridgeAddress()` has whitelisted, so the funds end up in a Zircuit-authorized vault rather than an attacker-controlled wallet; the attacker gains nothing and the worst case is a bounded shift of residual balance between authorized cluster vaults. The residual business impact is therefore informational, but the adapter has no defense-in-depth for future integrations that may permit non-cluster destinations or for scenarios in which the adapter accrues a non-trivial balance.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Part of Zircuit’s security record

Zircuit
Audits4 audits by 1 firmLast audit Sep 2026, by Quantstamp.
Critical findingsNo open critical findingsAcross 4 reports.
Open Zircuit’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us