The attack was built on an unusual combination of the protocol's technical features. The attackers created a practically empty TUNA/USDC liquidity pool, after which they routed borrowed USDC funds into it through the Jupiter swap aggregator.
Because the swap yielded a negligibly small amount of TUNA tokens, DeFiTuna's asset valuation system, owing to a rounding quirk, valued the position at zero. The protocol then mistakenly deemed such a position financially sound, allowing the attackers to bypass the solvency check and withdraw the borrowed funds through liquidity positions under their control.
The attackers' addresses:
- 9ytGWP8tCRF1keREJ5VHqBpSuM9MZYwm3oFQQa1SvESb
- 917DKTphW3rhBG5gsJpwKsNGisNV2dx74uUFd8HBEjtg
- 7hiHL8AgDuLNVDQLfN3GHdLAEeCN1F7uz6nSANRvFJst
- BK9aTnKfPNnnj45Me5ACrky2vexzUrZHRzr4BjmQpH3c
- 8P3H7Hy98LWhw5QhuXdoigVdAzPCYRTYjdtQQKhGwvqD
- GrXBhM6Ty6YxubFp8zubtJF12WXmKj1dhMyakRPeaDpo
- F4xUroPaHro4gb2JAqa3e93E7DdZytRQJ4L2cHT5E53p
- ETGhosPrFApbiiKXDpxrhBz7J2MdTA3dxnL2Nkio9vEX
- 4ZKkGZuoXqgSHbmsJXUib3dgn5WNhzbPEXrxuMYJ5oQ3
- 917DKTphW3rhBG5gsJpwKsNGisNV2dx74uUFd8HBEjtg
The compromised address:
- D76dDcSU5HnAGqVEZCDLyGgLpTp4xZuqeZyVDtUdDv55
How the attack unfolded: from an empty pool to the withdrawal of funds
The initial token price was set as close as possible to the value shown by the official price oracle. Thanks to this, the DeFiTuna protocol successfully passed the built-in price-deviation check and detected nothing suspicious.
The attackers then placed two of their own limit positions in this pool at an anomalously high price mark, tick 208,640, adding only 0.001052 TUNA (in two equal parts of 0.000526 TUNA each).
After this, the protocol's Open_and_increase_tuna_spot_position_jupiter function was called. Analysis of the instruction data showed that the attacker opened a trading position without any collateral of their own.
Nearly 570 thousand USDC, borrowed from the liquidity pool (net of the fee), was automatically routed through the built-in Jupiter router. In the 47-byte swap instruction, the route the funds were to take had been specified in advance.
In effect, the entire amount — 569,601 USDC — was exchanged for TUNA tokens through the low-liquidity pool prepared in advance. Since the only available orders in this pool were the positions created by the attackers at the tick 208640 level, the swap went precisely through them.
Taking into account the 0.1% fee, the final result of the swap was:
After rounding, the protocol received only 0.000494 TUNA.
Where the error occurred
Having received such an insignificant amount of tokens, DeFiTuna began calculating the position's value using the pre-swap price, according to the price oracle's data. In the function is_healthy() → compute_total_and_debt(), the value was computed as follows:
At the next stage, the to_num() function converted this number into an integer value. However, in such a conversion the fractional part is completely discarded. As a result:
This is precisely where the critical error arose. After rounding, the system decided that the position's total value was zero. The protocol then applied a built-in rule:
Based on this logic, the system mistakenly concluded that the position was completely safe and met the solvency requirements. In reality, all the USDC funds already economically belonged to the two limit orders created in advance by the attackers. After successfully bypassing the check, both addresses called the DecreaseLimitOrder function and withdrew 284,280.483231 USDC each.
Why DeFiTuna's protection failed
The problem lay in the solvency-check mechanism TunaPosition.is_healthy(). The compute_total_and_debt() function calculated the value of assets using a reference price and then rounded the result down using the to_num() function. The attackers deliberately constructed an extremely unbalanced liquidity pool.
After the borrowed USDC was routed into this pool through an arbitrary swap route, so little TUNA came back that the position's value, after rounding, became zero. It was precisely this that made it possible to completely bypass the solvency check and withdraw the borrowed funds.
Where the stolen $570 thousand went
After the attack, the stolen assets were distributed across several intermediate wallets and then consolidated at two main addresses.
After this, the funds were transferred from the Solana network to Ethereum using the Mayan cross-chain bridge. Once on the Ethereum network, 140 ETH were sent to the Railgun confidential-transfer service from the address: 0x6a2e244af2c17CFc64e8ca0EA4335318EdF20B0B.
Meanwhile, about 300K DAI, at the time this material was prepared, continued to sit in the wallet: 0x509B9D094A6C26D716aaC131E8aDee5B16B86d3e.
