
Date: July 24, 2026
Chain: Ethereum
Loss: 542,144.63 USDC
Class: bond exchange that counted exception entries instead of checking each bond
Lien Finance's last post on X went out in May 2021. Its contracts kept running anyway, and one wallet that had approved Lien's OTC pool to spend its USDC still held more than half a million dollars of it. At 01:26:47 UTC on July 24, 2026, a single transaction pulled 542,144.63 USDC out of that wallet in exchange for bond tokens the attacker had minted moments earlier, with no collateral behind them.
The bond tokens came from BondMakerCollateralizedEth, the Lien contract that turns ETH collateral into sets of bond tokens and lets holders swap one equivalent set for another. Its exchangeEquivalentBonds function let a caller list bonds to leave out of the swap, and it checked that list by counting matches instead of confirming each listed bond on both sides. According to Verichains, a repeated entry in that list passed the count while a bond that should have been burned was skipped, so the contract minted bond tokens with no collateral destroyed behind them.
Lien was a 2020 protocol for building option-like products out of ETH. Its BondMaker contracts take ETH as collateral and split it into a group of bond tokens whose payoffs, each a polyline over the ETH price, add up to exactly the collateral at maturity. Anyone can register a new bond type and a new bond group, and the contract checks only that the payoffs add up. The contracts the attacker used, BondMakerCollateralizedEth at 0xDA6F...BEf0 and the GeneralizedDotc OTC pool at 0x656e...eF18, were deployed by the same address on January 26, 2021.
GeneralizedDotc is where bonds meet real money. Liquidity providers approve it to spend their stablecoins, and it quotes a USDC price for bond tokens from their registered payoff and an oracle price for ETH. Revoke.cash, which tracks exposed approvals, describes Lien as abandoned, with its contracts still live and unmaintained, which left those approvals as standing offers to buy any bond the pricing logic accepted.
From the verified BondMakerCollateralizedEth source on Blockscout, this is the first half of exchangeEquivalentBonds, the part that burns the input bonds:
For each bond in the input group, the inner loop walks the caller's exceptionBonds list. Every entry that matches the bond marks it as excluded, so it isn't burned, and adds one to exceptionCount. Bonds that match nothing get burned. The require then demands that the number of matches equal the length of the exception list, which the comment says is meant to prove every excluded bond really is in the group. The output half runs the same loop over the output group, subtracting one per match and minting every bond that isn't excluded, and finishes by requiring exceptionCount to be back at zero.
Those checks confirm that the numbers balance. They never confirm that each exception entry is a distinct bond, or that the bonds excluded on one side are the same bonds excluded on the other. Verichains reports that the attacker repeated one exception bond ID so the counts came out right while a bond that should have been part of the burn was left out, and the function went on to mint bond tokens that no burned collateral backed.
The lesson here is that a count is a summary, and a summary can be satisfied by the wrong set of items. Picture a coat check that verifies you're returning as many tickets as coats you're collecting, without reading the numbers on the tickets. The rule exchangeEquivalentBonds depended on was that every bond token minted is matched by collateral burned, and its guard checked arithmetic that a crafted list could make true.
Blockscout shows the attacker's wallet, 0x0D7d...808a, deploying an orchestration contract at 0xe74d...062e in block 25599302 and calling it four times in the same block, at 01:26:47 UTC. Per Verichains, the setup registered a bond group with a crafted payoff, which anyone could do, and then used exchangeEquivalentBonds to mint bond tokens without the matching burn.
The exploit transaction's token transfers show what happened next. The contract minted 69,685.66 tokens of a bond called Immortal Option 20261016 0 and 449.80 tokens of a bond called LBT 20261016 4000, and sold them to the liquidity provider's wallet 0xA961...4d80 through GeneralizedDotc in three trades: 69,684.25 Immortal Option tokens for 532,070.03 USDC, another 1.41 for 74.60 USDC, and all 449.80 LBT tokens for 10,000 USDC. Verichains attributes the prices to GeneralizedDotc's bond pricing, which valued the tokens from their registered payoff and the ETH oracle price without checking whether real collateral stood behind them. The 542,144.63 USDC went straight from the contract to the attacker's wallet in the same transaction.
At 01:28:23 UTC the attacker's wallet ran a batched call that routed the whole 542,144.63 USDC through a swap router into WETH across several pools, and at 01:28:59 UTC it sent 287.66 ETH to 0xcb2b...87C8. Revoke.cash reports that Lien made no public statement and that no funds were reported frozen or returned. As of October 20, 2026, Lien's X account still showed nothing after 2021.
This isn't the first time an equivalence check in Lien's BondMaker design drew attention. Verichains and Revoke.cash both note that in September 2020, a white-hat group including samczsun rescued about $10 million in ETH from an earlier BondMaker contract after finding a different flaw in its equivalence logic.
Olympix reads a contract the way an attacker does, as states, transitions and entry points, and it takes in the context around the code, including the comments and design that say what a function is supposed to guarantee. In BondMakerCollateralizedEth, the state that matters is the supply of each bond token against the collateral behind its group, and the entry points that matter are every function that mints or burns a bond.
Olympix's static analysis surfaces the signal in exchangeEquivalentBonds: a caller-supplied array used to decide which bonds skip the burn and mint logic, validated by a running count rather than by membership of each distinct entry, guarding a path that mints tokens. From that signal, the engine narrows from every path through the bond maker, to the feasible ones a caller can reach with a bond group they registered themselves, to the paths where the mint loop runs on bonds the burn loop didn't, which is where the invariant lives: exchanging one bond group for another must never create bond tokens without destroying equivalent ones.
BugPoCer works from that invariant outward through Counterexample-Guided Invariant Refutation, executing paths until one breaks it. Here the counterexample is a short sequence: register a bond group, call exchangeEquivalentBonds with an exception list that repeats one ID, and end with more bond supply than the burned collateral supports. BugPoCer returns that sequence of calls and state changes, the minimal conditions that trigger it, and a generated Forge test that replays it, which is the July 24 mint produced in development rather than on mainnet.
Because the engine runs automatically in development, at every merge and before release, that test would have sat beside the exchange function from the commit that introduced it. For a protocol whose contracts outlive its team, the check has to happen before deployment, since nobody is left to run it later.
Lien's bond maker did its arithmetic correctly for five years, and the flaw was in what that arithmetic stood in for. The approvals that paid for it had been granted to a protocol that had gone quiet, and the contracts kept honoring them. For builders, any check that compares counts where it should compare identities deserves a test with duplicates in it, and any contract that can mint deserves a test that the mint is always backed. To see how Olympix tests the guarantees your mint and burn paths depend on and hands you a runnable counterexample where one breaks, book a technical walkthrough.
Protocol: Lien Finance (BondMakerCollateralizedEth and GeneralizedDotc)
Chain: Ethereum
Date: July 24, 2026, 01:26:47 UTC
Loss: 542,144.63 USDC
Attacker EOA: 0x0D7d...808a
Attack contract: 0xe74d...062e
Vulnerable contract (BondMakerCollateralizedEth): 0xDA6F...BEf0
OTC pool (GeneralizedDotc): 0x656e...eF18
Victim liquidity provider: 0xA961...4d80
Bond token (Immortal Option 20261016 0): 0x814A...7B49
Bond token (LBT 20261016 4000): 0x793e...25A5
Attack contract deployment tx: 0xcb26...a024
Exploit tx: 0xb96d...e0e7
Block: 25599302
USDC swap tx: 0x5505...9adb
ETH outflow tx (287.66 ETH): 0x5568...1db2
Verichains: Lien Finance Hack Analysis