Reproduced Exploit
Term Structure: withdrawAssets subtracts the withdrawal twice and locks the rest
When the vault already holds the assets, withdrawAssets subtracts amount and then subtracts amount again at the bottom of the function. A 50 FT withdrawal cuts totalFt by 100 while only 50 tokens leave. The remaining shares then price a withdrawal that underflows on the second subtraction, so 150 F…
Loss
150 FT stuck after a 200 FT deposit and a 50 FT withdrawal
Chain
Ethereum
Category
logic
Date
Feb 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 54895-the-totalft-and-accretingprincipal-are-updated-incorrectly-i. Standalone Foundry PoC and full write-up: 54895-the-totalft-and-accretingprincipal-are-updated-incorrectly-i_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/state-update · vuln/dos/frozen-funds
Key info#
| Field | Value |
|---|---|
| Loss | 150 FT stuck after a 200 FT deposit and a 50 FT withdrawal |
| Vulnerable contract | Vault.withdrawAssets |
| Attacker EOA | 0x5489500000000000000000000000000000000001 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · block 0 · 2025-02 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | _totalFt and _accretingPrincipal are decremented twice when cash covers the withdrawal |
TL;DR#
When the vault already holds the assets, withdrawAssets subtracts amount and then subtracts amount again at the bottom of the function. A 50 FT withdrawal cuts totalFt by 100 while only 50 tokens leave. The remaining shares then price a withdrawal that underflows on the second subtraction, so 150 FT cannot be withdrawn. The fixed function subtracts once and returns the full 200.
Background#
Cantina's February 2025 Term Structure competition, reported by Nyksx. The contest log showed a 1e8 withdrawal moving totalFt by 2e8. The team removed the trailing decrements in PRs 4 and 5.
The vulnerable code#
if (assetBalance >= amount) {
token.safeTransfer(recipient, amount);
_totalFt -= amount;
_accretingPrincipal -= amount;
}
// @> VULN: the withdrawal was already subtracted above, so totalFt falls by twice the tokens that left
_totalFt -= amount;
_accretingPrincipal -= amount;
Root cause#
The sufficient-balance branch updates the totals, and the lines that were meant for the other branch still run afterward.
Preconditions#
- The vault's token balance is at least the withdrawal. This run deposits 200 and withdraws 50.
- A later withdrawal uses the understated
totalFtas the share price.
Attack walkthrough#
- Deposit 200 FT.
totalFtand the share supply are 200. - Withdraw 50 shares. The transfer sends 50. Both pairs of subtractions run, so
totalFtis 100 and the cash left is 150. - Withdraw the remaining 150 shares. The payout is 100, and the second subtraction underflows.
- The call reverts. 150 FT stays in the vault. The depositor holds 50.
- The fixed vault subtracts once. Withdrawing 200 returns 200 and leaves
totalFtat 0.
Diagrams#
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 54895-the-totalft-and-accretingprincipal-are-updated-incorrectly-i_exp (evm-hack-registry mirror).
- AuditVault finding: 54895-the-totalft-and-accretingprincipal-are-updated-incorrectly-i.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.