Ethereum contracts deployedTransactions may use real assets. Primary-market minting and instant redemption are KYC-gated; deployment does not mean every product or entry path is open. Review the legal and risk disclosures.

Cantina Managed review of the supply gateway

External review. This review was conducted by a party other than Forest Road. What the engagement did and did not cover is set out under Method and in the report itself, the limits of a review bear on what its findings are worth, and should be read alongside them. It does not by itself authorize a production launch. It reviews the source at the baseline below, which is not necessarily identical to the code deployed on any network. Forest Road Vault is live on Ethereum mainnet; its Solana curator vault remains on devnet and BSC has its own deployment status. An open finding applies only to the product, version and deployment state named by this review. Each review's scope bounds what its clean result is worth. Nothing here is a securities-law representation; token characterization is a matter for counsel.

Scope
DELIBERATELY NARROW, and the scope matters more than the result: TWO FILES ONLY, contracts/src/MintRedeemController.sol and contracts/src/libraries/Roles.sol. That is the USDfr supply gateway and the role constants. It is NOT a review of the vault, queue, waterfall, cascade, oracle, bridge, curator module or governance. A clean result across two files is not a clean result across the protocol, and anyone citing this report should cite its scope in the same sentence.
Method
Commercial engagement with Cantina Managed, conducted 17-20 August 2026 by Valerian Callens (Lead Security Researcher) and Jiri123 (Security Researcher). The first review of this protocol by a NAMED EXTERNAL SECURITY FIRM and the first by human researchers under a commercial engagement; every earlier round on this register was internal or AI-assisted. Cantina's own disclaimer records that the assessment is bound to the specific commit reviewed and is not a replacement for continuous security measures.
Reviewed baseline
71c285ec

Findings and remediation history

  • 3.1.5InformationalRemediated

    redeem(uint256, uint256) has no deadline, contradicting ADR-0034 W

    ADR-0034 W requires the redeem functions to take both a minimum out and a deadline. The one-argument form supplies the par floor internally so it settles at par or reverts and delay cannot make it worse. The two-argument form bounds price but NOT TIME, which is the free option W describes: a caller who names a floor and is not included for an hour lets a searcher hold the transaction until the ratio decays to that floor. Cantina's recommendation was documentary rather than a code change - amend W to record which form is canonical and point integrators at the three-argument form - and that is what was done, in commit ab844dc.

  • 3.1.4InformationalAccepted

    Direct redemption does not consume the ADR-0033 exit interlock

    THE SUBSTANTIVE ONE OF THE FIVE. ADR-0033 section 5 states that junior and senior exits share one interlock so neither cohort can escape while the other stays exposed. CuratorModule.withdrawFirstLoss and RedemptionQueue.closeEpoch both read reserveLossExitsLocked; MintRedeemController._redeem does not, so direct redemption stays open while both other exits are shut. Most locked states are already covered elsewhere, since an under-backed book and a latched deficit both price sub par and a live shortfall reverts in _requireCustodiedReserve. THE STATE THAT LEAVES PAR REACHABLE IS AN ACTIVE RESERVE-LOSS ARM BEFORE ANY PHYSICAL SHORTFALL. Cantina also noted the RedemptionQueue comment calling the queue the sole senior exit, which is what section 5 was written against. Accepted as a known limitation of mainnet v1, not remediated in this release.

  • 3.1.3InformationalAccepted

    previewRedeem() can publish a redeem price that would not settle

    previewRedeem is meant to publish the executable redemption price but uses checks not aligned with redeem(). It verifies that USDfr and the controller are unpaused but NOT the ReserveManager, so it can return a full quote while releaseUSDC is blocked and every redemption reverts. It also passes a zero draw into _quoteRedeem, so below par it publishes the gross marked price while redeem() settles at the junior drawn price. A quoting defect rather than a settlement one, and no funds are at risk, but an integrator surfacing this number to users would show a price that cannot be obtained.

  • 3.1.1InformationalAccepted

    Oversized redemption input panics before balance validation

    _quoteRedeem calculates usdfrIn + drawn before checking the caller's USDfr balance, so a max-sized input with a nonzero draw overflows and reverts with a panic instead of returning the intended token or controller error. Reachable only on the under-backed path. No funds are at risk because no caller can hold or burn such an amount; the cost is a confusing failure mode rather than a loss.

  • 3.1.2InformationalAccepted

    Invalid immutable module wiring requires an upgrade to recover

    initialize() checks only that the usdfr, compliance and reserves addresses are nonzero. A wrong or codeless address would leave the controller unusable and the wiring could not be corrected without an upgrade. Limited to deployment misconfiguration, and the recommended validation is already in place: post-deploy checks assert controller.modules() against the hash-bound deployment manifest and confirm code through the ERC-1967 implementation slot. The wiring is kept immutable deliberately; the residual is a deployment-time risk already mitigated by process.

This is the first review of this protocol by a named external security firm, and the first conducted by human researchers under a commercial engagement rather than AI-assisted internally. Every earlier round on this register was either an internal review or an AI-assisted one.

It is also, deliberately, the narrowest review on this register. Read the scope before the result.

Scope, which matters more than the headline

Two files at commit 71c285ec: contracts/src/MintRedeemController.sol and contracts/src/libraries/Roles.sol. That is the USDfr supply gateway and the role constants. It is not a review of the vault, the queue, the waterfall, the cascade, the oracle, the bridge, the curator module or governance.

A clean result across two files is not a clean result across the protocol. Anyone citing this report should cite its scope in the same sentence.

Result

Five findings, all Informational. No Critical, High, Medium or Low. One fixed, four acknowledged.

The Cantina Managed team statement reads: "No significant issues were identified during the assessment, and the protocol is expected to operate as intended."

The five findings

Fixed. redeem(uint256, uint256) has no deadline, contradicting ADR-0034 W. The one-argument form settles at par or reverts, so delay cannot make it worse, but the two-argument form bounds price and not time. That is a free option: a caller who names a floor and is not included for an hour lets a searcher hold the transaction until the ratio decays to that floor. The remedy was documentary rather than a code change, and it is the correct one. ADR-0034 W now records which form is canonical and points integrators at the three-argument form.

Acknowledged, and open.

Oversized redemption input panics before balance validation. _quoteRedeem computes usdfrIn + drawn before checking the caller's balance, so a max-sized input with a live junior draw overflows rather than returning a controller error. Reachable only on the under-backed path, and no funds are at risk because no caller can hold such an amount.

Invalid immutable module wiring requires an upgrade to recover. initialize checks only that the module addresses are non-zero, so a wrong or codeless address would leave the controller unusable with no path back except an upgrade. Limited to deployment misconfiguration, and the recommended validation is already in place: post-deploy checks assert controller.modules() against the hash-bound manifest and confirm code through the ERC-1967 implementation slot.

previewRedeem() can publish a redeem price that would not settle. It checks the USDfr and controller pauses but not the ReserveManager pause, so it can quote a full price while every redemption reverts. It also passes a zero draw, so below par it publishes the gross marked price while redeem() settles at the junior drawn price. A quoting defect, not a settlement one.

Direct redemption does not consume the ADR-0033 exit interlock. ADR-0033 §5 says junior and senior exits share one interlock so neither cohort can escape while the other stays exposed. CuratorModule.withdrawFirstLoss and RedemptionQueue.closeEpoch honour it; _redeem does not. Most locked states are covered elsewhere, because an under-backed book and a latched deficit both price sub-par and a live shortfall reverts. The state that leaves par reachable is an active reserve-loss arm before any physical shortfall. Accepted as a known limitation of mainnet v1.

What Cantina recorded as trust assumptions

The report devotes more space to trust assumptions than to findings, and that section is the more useful read. It states plainly that attesters are the primary trust boundary for off-chain facts, that once the oracle accepts the required signatures the protocol cannot independently verify the underlying event, and that security therefore assumes no dishonest or compromised set of current attester keys can meet the threshold.

It also records, so that it is not misreported as a safety guarantee, that senior capital is impairable and direct redemption may settle below par after available junior protection is applied, and that preventing every senior loss is not a protocol invariant.

How to read this alongside the rest of the register

This review does not supersede the mainnet-v1 full audit of 16 August. That one covered the deployed protocol against live mainnet state and its findings, including the open DV entries, remain the substantive picture. This one examined two files in depth and found nothing above informational in them.

The full report is published at github.com/Forest-Road-Company/forest-road-vault under audit-reports/.

Earlier roundFull audit of the live mainnet deployment→