Advisories

Minting vault shares for nothing

By Will·
Affected
IndexedEx / Down To Finance - Uniswap v3 & v4 SE vaults
CVSS

Summary

The Uniswap v3 and v4 StandardExchange (SE) vaults in cyotee/indexedex (Down To Finance) credit a pretransferred deposit by comparing a stored reserve figure R against a live on-chain position valuation deployed. R is snapshotted at the pool price of the vault's previous operation; deployed is recomputed from the current pool tick on every call. Because the two are measured at different prices, moving the bound pool price inflates the "unbooked surplus" the vault believes it holds.

An attacker can then call exchangeIn(..., pretransferred=true), deliver zero tokens, and receive vault shares against a deposit that never happened. Those shares redeem for real backing - so the attacker drains honest depositors. Permissionless, repeatable, and flash-loanable.

The defect

function _secureTokenTransfer(IERC20 tokenIn, uint256 amountIn, bool pretransferred)
    internal returns (uint256 actualIn)
{
    uint256 R  = MultiAssetBasicVaultRepo._reserveOfToken(address(tokenIn)); // STORED, stale
    uint256 B0 = tokenIn.balanceOf(address(this));
    if (!pretransferred) {
        tokenIn.safeTransferFrom(msg.sender, address(this), amountIn);
        return tokenIn.balanceOf(address(this)) - B0;                        // real pull delta (safe)
    }
    uint256 deployed   = _deployedFaceOf(address(tokenIn));  // LIVE, from current slot0 tick
    uint256 faceBooked = R > deployed ? R - deployed : 0;
    uint256 U          = B0 > faceBooked ? B0 - faceBooked : 0;
    if (amountIn > U) revert ISecurePullErrors.TransferDeltaInsufficient(amountIn, U);
    return amountIn;  // credits amountIn as "received" with NO in-call transfer verified
}

The pretransferred branch trusts U = balance − (R − deployed) as "surplus already sitting in the vault." But R and deployed are priced at different pool states, so R − deployed is not a conservative baseline. Nudge the pool price and U reports free tokens that were never sent.

Why the earlier fix didn't hold

This is a defect *in the fix for a previously remediated issue.* An earlier audit replaced an absolute-balance pull with this reserve-delta form. The reserve-delta baseline R − deployed is not price-consistent, so the "delta prevents over-credit" guarantee does not actually hold. It's the same failure pattern we keep finding: the patch closed the reported instance, not the class.

What we proved

Working PoCs against both synthetic and real forked deployed bytecode. One run mints shares worth 14.62 token0 for zero input, then redeems them for real vault backing. The v3 and v4 SE adapters share the shape; the DETF diamond core itself is clean - the bug lives in the external-protocol StandardExchange adapters.

Status

Reported as APEX-2026-001 with the PoC and a live testnet exploit transaction. Draft held here pending coordinated disclosure and remediation - the class was still unfixed in the public repo at last check.

Disclosure & proof

Six days ago we privately reported this to the team behind IndexedEx / Down To Finance (app.downto.finance) on Robinhood Chain. It's still unpatched and we haven't heard back, so with a mainnet deployment looking imminent, we decided it should be public before the contract ever holds user funds.

To prove it without going anywhere near real money, we deployed our own independent, byte-for-byte copy of the protocol on Robinhood's testnet, fully separate from any developer contracts or funds. We armed it with one honest deposit, let an ordinary market trade move the pool price, and that alone opened the phantom-deposit path. The attacker wallet held 0 tokens before and after, spent nothing, and was still minted 3.029254145383447796 vault shares against a deposit it never made - then redeemed them for 3.8655 real tokens.

To be clear, this did not happen on DTF's deployed vaults. Their own testnet vaults (seUsdeWeth, seRichWeth, seUsdgWeth, seUsdgUsde) are currently unarmed, with no deployed pool position, so they aren't exploitable in their current state. But our demo shows a single ordinary deposit is enough to arm a vault - no special call needed. If this code ships to Robinhood mainnet as-is, it carries the same risk against real funds.

This came out of Project Apex, our agentic security research program - the same multi-agent methodology behind Vector, our AI-driven red-team suite. This bug surfaced through a 12-agent hunt-and-verify pass over IndexedEx's 200k+ lines of Solidity, one of five candidate findings that survived full on-chain proof. We have other disclosures in flight under the same program, two of them widely-used in-memory databases - more on those once they're responsibly handled.

This isn't meant to FUD. DTF is a novel product, and with novelty comes novel vulnerabilities. Our only interest is protecting the public and flagging that the project is not in a mainnet-deployable state, because it poses a real risk to any assets it would hold right now. Our resources are open to future versions of this contract, and to anyone else building where real money is on the line.

The defect in _secureTokenTransfer: a stored, stale reserve snapshot is checked against a live pool-price value. The gap between them becomes free credit.
The defect in _secureTokenTransfer: a stored, stale reserve snapshot is checked against a live pool-price value. The gap between them becomes free credit.
3.029254145383447796 vault shares minted from nothing and sent straight to the attacker.
3.029254145383447796 vault shares minted from nothing and sent straight to the attacker.
Same transaction: the attacker sent 0 ETH and zero tokens in, yet the vault minted shares anyway.
Same transaction: the attacker sent 0 ETH and zero tokens in, yet the vault minted shares anyway.

Read the full thread · View the exploit transaction