
Date: July 16, 2026
Chain: Solana
Loss: $569,601 USDC, about $570K
Class: a solvency check that read a rounded-down zero as a healthy position
On July 16, 2026, a DeFiTuna position borrowed about 570,000 USDC from the protocol's lending vault, swapped it, and came back holding 494 raw units of TUNA, which is 0.000494 TUNA. DeFiTuna valued that balance in USDC terms, got 0.9077, dropped the fraction, and recorded the position's total assets as zero. Its health check looked at that zero and approved the position.
The USDC never stayed with the position. It had gone into a pool the attackers built themselves, and two of their wallets withdrew it, leaving DeFiTuna's USDC lending pool short by $569,601. Every step relied on one assumption written into the health check, that a position worth zero is an empty position, and this one was carrying roughly $570K of debt.
DeFiTuna is a Solana DeFi platform that combines a spot AMM, leveraged liquidity provision and a lending market. In the market that was attacked, a user posts USDC as collateral, borrows more USDC from a protocol vault, and swaps it into TUNA through Jupiter, a swap aggregator. Once the swap lands, DeFiTuna checks the position's assets against its debt to decide whether it's healthy, meaning whether there's enough value in it to cover what it owes.
According to BlockSec's analysis, the caller supplies the swap route accounts, so the caller also decides which pool executes the swap. DeFiTuna supports Fusion pools, a pool type anyone can create, and the protocol placed no minimum liquidity requirement on the pool a route could send funds through.
CertiK's incident analysis traces the health check to TunaPosition.is_healthy(), which calls compute_total_and_debt() to work out what a position holds and owes. That function converts the position's TUNA into USDC using a reference price and rounds the result down to a whole number. For the attacker's position, the math came to 494 times 0.0018375338, or 0.9077417, and the conversion discarded everything after the decimal point, so the total became zero.
is_healthy() then handled that zero under the assumption, in CertiK's words, that "the leverage of an empty position is always 1.0x." A position with nothing in it and nothing owed really is healthy, and that's the case the branch was written for. BlockSec describes the gap precisely: the check treated a total of zero as healthy without also requiring the debt to be zero.
A bank teller deciding whether a customer's account can cover a withdrawal would never read a balance of zero as proof the account is fine without also checking whether there's a loan on it. DeFiTuna's check skipped exactly that second look. The single idea to take from this case is that a zero produced by rounding is a number the code computed, and any branch that reads zero as "nothing here" has to confirm that nothing is owed either.
The attackers created a separate TUNA/USDC Fusion pool, distinct from DeFiTuna's normal market pool, and placed two limit orders in it at tick 208,640, each holding 0.000526 TUNA, for 0.001052 TUNA in total. The orders belonged to attacker wallets 7hiH...FJst and BK9a...QpH3c and were positioned to receive whatever USDC entered the pool.
DeFiTuna runs a price-deviation check before every swap, and it passed. The sources disagree on why. CertiK's analysis, which DeFiTuna's team called accurate, says the attackers set the pool's initial price close to the legitimate oracle price, so the check validated price without ever looking at depth. BlockSec's on-chain reading finds the pool was initialized at an extreme price, around 1.149 billion USDC per TUNA, and that the check passed because it compared the normal market pool's price with the oracle and never examined the Fusion pool the route actually used. Both readings end with a guard that let the swap through without measuring how much liquidity sat in the pool the money was about to enter.
In the transaction CertiK analyzes, timestamped 05:48:12 on July 16, the attacker called Open_and_increase_tuna_spot_position_jupiter to open a spot position with zero collateral. About 570,000 USDC was borrowed from the lending vault, and after fees, 569,601 USDC went through the embedded Jupiter route into the attacker's pool with the minimum output effectively set to zero. At that pool's price, the position received 0.000494 TUNA.
is_healthy() valued the position at zero assets, read zero as healthy, and let the position stand with its debt. The USDC now sat in the pool, where it belonged economically to the two attacker limit orders, and each of the two wallets called DecreaseLimitOrder and withdrew 284,280.483231 USDC. BlockSec notes that multiple attack transactions were executed, and CertiK's address table lists nine attacker wallets in total.
CertiK followed the money out of Solana. The funds from 7hiH...FJst and BK9a...QpH3c were bridged to Ethereum through Mayan, 140 ETH went into Railgun, a privacy protocol, and as of July 20, 291,696 DAI and 5 ETH were still sitting in Ethereum wallet 0x509B...6d3e.
DeFiTuna's first post, at 15:09 UTC on July 16, said an attacker had extracted $580,000 from its lending pools, leaving the USDC lending pool $580,000 short, and that the attack vector had been identified and mitigated while the team worked to recover the funds. A day later it put the deficit at $570,000, said the vulnerability was patched and borrowing was paused, and said the DefiTuna AMM itself was unaffected. On July 28 it reopened withdrawals for affected lenders, with the losses socialized across all USDC lending pool depositors and the remaining deficit left on their accounts. On August 20, the team said protocol revenue would be redirected into a designated wallet to cover the outstanding debt, and that the funds would go to affected users once the target amount was reached.
Olympix reads a program the way an attacker does, as states, transitions and entry points, and takes in the design around the code to infer the invariants the system depends on. For a lending market, the one that matters here fits in a sentence: a position that owes anything can only be called healthy if its assets, as the program computes them, are greater than zero and cover what it owes. DeFiTuna's own code implies that rule, since the whole point of is_healthy() is to stop a borrower from walking away with more than they put in.
A position's state space looks infinite, with every swap route, pool price and balance the caller can choose, but the engine maps the paths and uses signals to decide which are worth attacking. The signal in this case is a valuation that truncates toward zero and feeds straight into a solvency branch that gives zero special treatment. That narrows the search from every path, to the feasible ones, to the few where a caller can steer the computed total down to zero while debt stays open.
With the dust-to-zero path flagged, BugPoCer starts from the lending market's solvency rule and tries to falsify it, which is what Counterexample-Guided Invariant Refutation means in practice: it executes paths from the system outward until one produces an indebted position that still passes is_healthy(). Rounding to zero that feeds a solvency check is a class it flags directly, and here the failing run is the attack itself: open a position with no collateral, borrow, swap through a pool that returns dust, let the total round to zero, and pass is_healthy() with the debt still on the books. BugPoCer returns that sequence with the minimal conditions that trigger it and a test that replays it, and since its scope covers Rust and Anchor programs, a Solana codebase like DeFiTuna's is in range.
This bug lives in how arithmetic results travel from one function into a branch in another, which is why it took execution to reach it, and why it's the kind of edge a review can pass over when each function looks correct on its own. Olympix runs in development, at every merge and before release, so the counterexample shows up while the fix is a one-line change to the zero branch.
DeFiTuna's health check was right about empty positions and wrong about positions that only looked empty after rounding, and $569,601 left through that difference. Any lending code that converts balances, rounds them and then branches on the result deserves a test where the result is zero and the debt isn't. To see Olympix infer the solvency rules in your own programs and hand back the exact sequence that breaks one, book a technical walkthrough.
Protocol: DeFiTuna
Chain: Solana
Date: July 16, 2026, 05:48:12 (CertiK timeline)
Loss: $569,601 USDC (CertiK)
Exploit tx (analyzed by CertiK): 124ibr...kvEG
Exploit tx (analyzed by BlockSec): 4x33Dq...EXj1
DeFiTuna program: tuna4u...nogD
Damaged USDC vault: D76dDc...Dv55
Attacker 1: 9ytGWP...vESb
Attacker 2 (also listed as fake pool): 917DKT...Ejtg
Attacker 3 (limit-order owner): 7hiHL8...FJst
Attacker 4 (limit-order owner): BK9aTn...pH3c
Attacker 5: 8P3H7H...wvqD
Attacker 6: GrXBhM...aDpo
Attacker 7: F4xUro...E53p
Attacker 8: ETGhos...9vEX
Attacker 9: 4ZKkGZ...5oQ3
Ethereum wallet holding remaining funds (July 20): 0x509B...6d3e
CertiK: DeFiTuna Incident Analysis
BlockSec: Weekly Web3 Security Incident Roundup (BarnBridge, DeFiTuna)
DeFiTuna on X: July 16 exploit statement
DeFiTuna on X: August 20 update on the USDC lending pool deficit