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…

Feb 2025•Ethereum•logic•3 min read

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

evm-hack-analyzer

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.

Loading fork state…

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-registry mirror.


Vulnerability classes: vuln/logic/state-update · vuln/dos/frozen-funds

Key info#

FieldValue
Loss150 FT stuck after a 200 FT deposit and a 50 FT withdrawal
Vulnerable contractVault.withdrawAssets
Attacker EOA0x5489500000000000000000000000000000000001
Attack contractExploit
Attack txExploit.run()
Chain / block / dateEthereum model · block 0 · 2025-02
Compilersolc 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#

SOLIDITY
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 totalFt as the share price.

Attack walkthrough#

  1. Deposit 200 FT. totalFt and the share supply are 200.
  2. Withdraw 50 shares. The transfer sends 50. Both pairs of subtractions run, so totalFt is 100 and the cash left is 150.
  3. Withdraw the remaining 150 shares. The payout is 100, and the second subtraction underflows.
  4. The call reverts. 150 FT stays in the vault. The depositor holds 50.
  5. The fixed vault subtracts once. Withdrawing 200 returns 200 and leaves totalFt at 0.

Diagrams#

flowchart TD A["Deposit 200 FT"] --> B["Withdraw 50 and transfer 50"] B --> C["VULN subtracts 50 again"] C --> D["totalFt is 100 while 150 FT remains"] D --> E["The rest of the shares underflow"] E --> F["150 FT stays locked in the vault"]

Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

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.