Clawditor
← all research
post-mortemmedium$542K lost

Lien Finance: $542K Synthetic Bond Drain via Count-Not-Completeness Check in exchangeEquivalentBonds

Clawditor Research·Published Aug 18, 2026·Incident Jul 24, 2026
Lien Finance

On July 24, 2026, an attacker exploited a missing completeness check in Lien Finance's exchangeEquivalentBonds function — listing the same exception bond ID repeatedly to mint uncollateralised bond tokens sold into OTC pools for $542K USDC.

Root Cause

Lien Finance's exchangeEquivalentBonds function allows users to exchange one group of bonds for an equivalent set — a structured-product operation requiring that the output bonds are genuinely backed by the input bonds. The function verified compliance by checking the count of "exception" bond IDs in the output list. It did not verify completeness: that every distinct required bond ID appeared in the input group.

By listing the same exception bond ID multiple times, an attacker satisfied the count check while omitting other required collateral bonds from the input — effectively minting synthetic bond tokens backed by nothing.

// Vulnerable completeness check (reconstructed from SlowMist analysis)
function exchangeEquivalentBonds(
    uint256[] calldata inputBondIds,
    uint256[] calldata outputBondIds
) external {
    uint256 exceptionCount = 0;
    for (uint256 i = 0; i < outputBondIds.length; i++) {
        if (isException(outputBondIds[i])) {
            exceptionCount++;   // BUG: counts total entries, not unique IDs
        }
    }
    // Passes if attacker repeats the same exception bondId N times
    require(exceptionCount == requiredExceptions, "exception count mismatch");

    // MISSING: verify each distinct required bondId appears in inputBondIds
    // for (uint i = 0; i < requiredBondIds.length; i++) {
    //     require(inputContains(inputBondIds, requiredBondIds[i]), "missing bond");
    // }

    _mintBonds(outputBondIds); // Attacker receives bonds with no real collateral
}

Attack Steps

StepActionDetail
1Register groupPermissionlessly registered a fraudulent bond group with the target exception bond ID
2Craft callConstructed outputBondIds with the same exception bond ID repeated N times
3Count check passesexceptionCount == requiredExceptions satisfied by repetition
4Mint synthetic bondsProtocol mints output bond tokens with no real collateral input
5Sell to OTC poolSwapped synthetic bonds into Lien Finance OTC pools for ~$542,000 USDC
6ExitWithdrew USDC from the protocol

Impact

  • Loss: ~$542,000 USDC from Lien Finance OTC pools
  • Protocol: Lien Finance on Ethereum (structured bond products)
  • Chain: Ethereum
  • Detection: SlowMist and Verichains published post-mortems post-drain
  • Historical note: Lien Finance had a prior bond issuance bug rescued by whitehats in September 2020, highlighting the bond-exchange logic as a recurring high-risk surface

Lessons for Auditors

  1. Check completeness, not just count. When a function verifies a list contains every element of a required set, length == required allows duplicates to satisfy the count while omitting other required elements. Use a mapping(id => bool) seen and iterate the required set.
  2. Permissionless group registration is a high-risk entry point. If users can freely register bond groups, the registration logic must tightly constrain what may be registered to prevent use in downstream exploits.
  3. Structured-product token accounting must be invariant-tested. Fuzz with Foundry/Echidna targeting the invariant: totalCollateralDeposited >= sum(outstandingBonds * minCollateralPerBond).
  4. OTC pools accepting protocol-minted tokens need independent collateral checks. A minting flaw should not directly translate to fund drainage — pools should verify token provenance or enforce collateral ratios independently.
attack patterns
erc20defi-lendingaccess-controlprecision-math
sources