Truebit Purchase Bonding-Curve Overflow / Zero-Cost Mint ETH Drain (2026-01-08)
- Name: Truebit Purchase bonding-curve overflow / zero-cost mint ETH drain
- Date: 2026-01-08
- Protocol: Truebit
Purchasecontract / TRU token buyback path - Chain: Ethereum
- Category: unchecked arithmetic / bonding-curve pricing failure / legacy-contract exploit / reserve drain
- Loss: about
8,535 ETH, roughly$26.2M-$26.6Mat the time - Summary: Truebit’s long-lived Ethereum
Purchasecontract let users buyTRUagainst an ETH reserve and redeemTRUback into that reserve. Public reverse-engineering showed the buy-side pricing path still relied on legacy unchecked arithmetic in a decompiled_getPurchasePrice()routine. By asking to buy an absurdly large amount ofTRU, the attacker made the final addition of two huge numerator terms in the purchase-cost calculation overflow and wrap to a tiny value, sogetPurchasePrice()andbuyTRU()accepted near-zero or zero ETH for enormous mints. The economically decisive failure sat in that private pricing helper itself: higher-level mint logic treated_getPurchasePrice()as the canonical proof that a giant TRU purchase still deserved reserve-backed issuance. The sell-side path did not share that failure: it still redeemed linearly against the real ETH reserve. The attacker therefore inverted Truebit’s intended spread, minting hugeTRUbalances almost for free and immediately burning them back into real ETH until the reserve was drained. - Trigger / entry point:
- On
2026-01-08, the attacker targeted Truebit’s EthereumPurchasecontract at0x764c64b2a09b09acb100b80d8c505aa6a0302ef2. - The relevant implementation source was not publicly verified on Etherscan, so early analysis relied on decompilation and trace review rather than a published repo or audit trail.
- Public reverse-engineering by BlockSec, Cantina, and others converged on the same core issue: the buy-side price quote from
getPurchasePrice()/buyTRU()could be made to underflow economically because a large arithmetic term wrapped under legacy unchecked math. - The attacker deployed exploit contract
0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8and executed the drain in one main transaction.
- On
- Root cause:
- The decisive failure was not a flash-loan trick or oracle manipulation. It was unchecked arithmetic inside the protocol’s own solvency-critical pricing function.
- Decompilation of the buy path showed that most multiplications were routed through helper math, but the final addition combining the two large numerator terms in
_getPurchasePrice()remained unchecked. - Under pre-
0.8-style arithmetic semantics, sufficiently large requested purchase amounts wrapped that term modulo2^256, collapsing the quoted cost for huge buys to zero or near zero. - That made
_getPurchasePrice()a lying reserve-boundary helper:buyTRU()still treated the returned quote as sufficient proof that the requested issuance was paid for, even though the arithmetic had stopped certifying any economically real cost. - The sell path remained economically live, so Truebit’s reserve logic stopped enforcing the intended invariant
mint cost should always exceed or at least track the ETH redeemable from the newly minted TRU. - The protocol therefore advertised fake solvency on the buy side while honoring real reserve withdrawals on the sell side.
- Exploit path:
- The attacker called
getPurchasePrice()with an attacker-chosen massiveTRUamount until the purchase-cost calculation wrapped and returned a trivial quote. - They called
buyTRU()with that same amount and supplied little or no ETH, minting an enormous block of newTRU. - They approved the
Purchasecontract to spend the freshly mintedTRU. - They immediately called
sellTRU(), which still used the live reserve-based redemption logic and paid out real ETH. - They repeated the buy-then-sell cycle several times inside one transaction, each time extracting more ETH from the reserve than they had paid into the purchase path.
- By the end of the main exploit transaction, roughly
8,535 ETHhad left the contract and only a small residue remained.
- The attacker called
- What the attacker needed to be true:
- The buy-side pricing contract had to still rely on legacy unchecked arithmetic for at least one large intermediate term.
- The sell-side redemption path had to remain usable and funded by a real ETH reserve.
- The contract needed no meaningful per-transaction cap, mint throttle, or reserve-drain circuit breaker that would stop repeated same-transaction buy/sell cycling.
- The pricing bug had to be reachable with attacker-controlled input size alone; no privileged role or offchain dependency was required.
- The old contract had to remain live and sufficiently funded despite years of ecosystem drift and absent public source verification.
- Technical anatomy worth remembering:
- BlockSec’s decompilation showed
buyTRU()andgetPurchasePrice()both depended on a private pricing helper that computed the ETH cost for a requestedTRUamount. - In BlockSec’s decompiled notation, the dangerous step was the unchecked
v12 + v9addition, wherev12captured100 * amount**2 * reserveandv9captured200 * totalSupply * amount * reserve. For extremeamountvalues, that sum wrapped underuint256arithmetic and the final integer division returned0or another economically nonsensical low quote. - Cantina’s reconstruction is useful here because it makes the trust boundary obvious: the protocol carefully routed multiplication and subtraction through helper math, but still let one raw
v12 + v9addition decide whether a giant mint was paid for. - BlockSec highlighted a first attacker-selected amount around
240,442,509.453545333947284131 TRUthat priced to0 ETHafter wraparound and truncation, then redeemed for about5,105 ETHon the first sell leg alone. - Because
sellTRU()redeemed against actual reserve balance rather than the broken purchase quote, the attacker could mint hugeTRUsupply almost for free and then turn it back into real ETH. - Public analysis described roughly five mint-and-burn rounds in the main transaction, with the first round alone cashing out about
5,105 ETH. - This was not just
old Solidity had overflow risk; the economically important lesson is that pricing helpers are reserve-boundary proof objects once mint logic starts treating their output as sufficient payment truth, and asymmetric buy/sell curves become reserve-drain primitives when only one side of the spread keeps honoring reality.
- BlockSec’s decompilation showed
- Representative transactions and addresses:
- Main victim contract:
0x764c64b2a09b09acb100b80d8c505aa6a0302ef2 - Main exploit transaction:
0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014 - Exploit contract:
0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8 - Attacker EOA highlighted in public traces:
0x6c8ec8f14be7c01672d31cfa5f2cefeab2562b50 - BlockSec explorer trace for the main transaction:
https://app.blocksec.com/explorer/tx/eth/0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014
- Main victim contract:
- Response / mitigation:
- Truebit publicly acknowledged the incident and warned users not to interact with the affected contract.
- The exploit nevertheless completed before intervention because the reserve drain happened inside one transaction.
- Public postmortems focused on the latent-risk lesson: old, lightly maintained, or unverified contracts remain live attack surfaces even after years without incident.
- The incident also renewed scrutiny on legacy Ethereum code that predates modern overflow protections, continuous review, and the ecosystem’s current testing expectations.
Pattern tags
- issuance-redemption-solvency-asymmetry
buyTRU()treated a wrapped bonding-curve quote as sufficient proof of paid-for issuance even after the final large-term addition in_getPurchasePrice()had stopped representing any economically real cost- The exploit was a clean
broken buy side, still-honest sell sideround trip: enormousTRUbalances could be minted under arithmetic fiction and immediately burned back into the live ETH reserve under ordinary redemption logic - The reserve drain depended on legacy-contract liveness as much as on bad math: a long-lived, source-unverified contract with meaningful ETH inventory still exposed a public issue-and-redeem loop with no meaningful outflow brakes
Why the exploit worked
- The decisive lie lived inside the protocol’s own buy-side proof object. Public reverse-engineering by BlockSec and Cantina converged on the same point: the attacker did not need an oracle, flash-loan distortion, or cross-protocol side effect. They only needed
_getPurchasePrice()to stop certifying what a giantTRUmint should actually cost in ETH. - The technical failure was especially sharp because it happened late in the computation, not at the obvious
formula existslayer. Most of the decompiled math still routed through helper operations, but the final composed addition of two huge numerator terms (v12 + v9in BlockSec’s notation) remained unchecked under legacyuint256semantics. For sufficiently largeamount, that sum wrapped modulo2^256, so the final integer division returned0or another absurdly small quote. - Once that quote existed,
buyTRU()still treated it as canonical payment truth. Truebit therefore broke the issuance side of the balance sheet before the attacker ever touched the redemption side. The protocol itself had published the false statementthis mint is paid for. - The sell path then monetized the fiction cleanly because it still honored real reserve reality.
sellTRU()redeemed against actual ETH inventory rather than the broken issuance quote, so one same-state round trip turned arithmetic overflow directly into reserve withdrawal. - The age of the contract also mattered. This was not a brand-new deployment with obvious monitoring or kill-switch assumptions; it was a long-lived reserve-holding legacy surface. The exploit worked because unchecked math, public reachability, and meaningful dormant reserves all survived together.
Detection and audit pivots
- Treat every private quote helper on a reserve-backed issuance path as solvency code, not as convenience math. If
buy,mint, oradd liquiditycan proceed whenever that helper returns a number, the helper is already part of the custody boundary. - Inspect the final composed arithmetic, not only the sub-helpers. Truebit is a strong reminder that a curve can look well-structured until the last unchecked addition, subtraction, or scaling step collapses the economic meaning of the whole quote.
- Differential-test
issue -> redeem immediatelyacross extreme input ranges on the same state snapshot. The reviewer question is notdoes the buy quote look monotonic?butcan the resulting claim be sold back for more reserve value than just entered? - Treat dormant, legacy, or source-unverified reserve contracts as live attack surface until they are emptied or deauthorized.
No one uses it anymoreis not a control if the contract still holds reserves and exposes public issue / redeem verbs. - Add reserve-outflow and issuance caps sized for arithmetic failure, not just ordinary usage. Once the buy-side quote is wrong, the next useful question is how much ETH or other reserve inventory can still leave before operators can react.
Failure-prevention checklist
- Keep checked arithmetic enabled across the entire reserve-critical quote path, including the final composed addition, subtraction, scaling, and division steps rather than only the obvious helper multiplications.
- Bound attacker-supplied purchase amounts before they enter large-term curve math, and reject inputs whose intermediate terms would exceed the formula’s reviewed domain.
- Prove a same-state round-trip invariant for the live contract: a
buyTRU(amount) -> sellTRU(amount)cycle should never return more ETH than just entered once explicit fees and tolerated rounding are applied. - Treat quote helpers as custody code. If
getPurchasePrice()is sufficient proof for minting, review it with the same threat model as reserve-withdrawal logic rather than as informational math. - Add reserve-outflow brakes sized for
pricing helper is wrong right now: per-transaction issuance caps, reserve-drain ceilings, and scoped pause rails should all fire before one misquote can empty the treasury. - Decommission legacy reserve surfaces explicitly. Empty reserves, revoke issuance authority, disable public buy / sell entrypoints, and do not rely on low usage, absent front-end links, or source-verification gaps as substitutes for deauthorization.
- Replay extreme-input tests against old contracts that still custody funds. Long-lived reserve rails need modern fuzzing and invariant checks even if they predate current tooling or active product ownership.
Audit prompts
- Which exact arithmetic term is the last economically decisive composition step in the buy-side quote, and does it still run under checked semantics at maximum reachable input sizes?
- Under one state snapshot, can any reachable
buy -> sellround trip ever extract more ETH than entered without outside market movement? - If the contract quote became wrong by 10x or 10,000x for one block, what independent cap, pause, or reserve-release ceiling would stop treasury extinction?
- Are any reserve-holding legacy contracts still callable through public selectors, forgotten routers, old UIs, or direct contract interaction even though the product is no longer actively maintained?
- Which code path proves the buy-side quote is still a trustworthy payment certificate rather than merely a mathematical estimate, and what keeps that proof aligned with the sell-side reserve logic?
- Have reviewers fuzzed the final numerator / denominator assembly directly, not just the helper math feeding it, across max-size purchase requests and near-overflow states?
- If a contract is source-unverified or operationally dormant, who still owns the obligation to run modern invariant tests, reserve monitoring, and decommissioning work against it?
Lessons that should already be standard
- A reserve-backed quote helper is already custody-critical once mint logic treats its output as sufficient payment truth.
- The final arithmetic composition step is often the real solvency boundary. Safe-looking helper math does not matter if the last unchecked addition can still turn a giant purchase into a free mint.
- Same-state round-trip testing catches failures that local formula review misses.
Buy looks plausibleandsell looks plausibleare not enough when the protocol exposes both legs against one reserve. - Dormant reserve contracts are still production systems until reserves and authority are actually gone. Age, neglect, and missing source verification do not reduce exploitability.
- Spread design is not a safety proof. Once the buy side prints fiction and the sell side keeps honoring reality, the protocol’s own curve becomes the withdrawal rail.
Why this incident matters
- It preserves a clean
buy-side arithmetic fiction, sell-side reserve realityexploit path rather than a more familiar oracle, bridge, or reentrancy story. - It shows how economic asymmetry itself becomes the cash-out rail once only one side of a protocol’s pricing logic breaks.
- It is also a strong reminder that dormant or aging contracts are not merely operational debt; if they still hold reserves and expose public pricing or redemption logic, they remain live solvency surfaces.
- The incident is especially useful as a contrast case to AMM and lending arithmetic bugs: the attacker did not need to distort a market or exploit a cross-protocol assumption, only to make one protocol’s own pricing helper stop proving reserve reality.
Internal linkages
- Core recurring pattern: issuance-redemption-solvency-asymmetry.
- Healthy-side arithmetic companion: wide-integer-helper-trust-boundary-and-exact-overflow-discipline — Truebit is not a
checked_*helper bug in the Cetus sense, but it preserves the same broader lesson that a pricing helper becomes a reserve-boundary proof object once mint logic treats its output as canonical payment truth. - Useful contrast inside the same family: meta-pool-mpeth-erc4626-mint-without-asset-transfer-unbacked-mint-pool-drain-2025-06-17 — Truebit broke the buy-side arithmetic directly, while Meta Pool broke the receipt boundary of a shared ERC-4626 issuance primitive and still ended up with the same
claims printed under fiction, reserve paid out under realityoutcome. - Healthy-side reserve-boundedness companion: reserve-backed-claim-truth-security-object — the stronger positive-design note when the next question is what exact object should have kept value intake, claim quantity, entrypoint reach, reserve liability, and same-state round-trip bounds fused together.
- Sharper standards-level cousin: shared-standard-entrypoint-invariant-drift — the better subfamily when the bug is not unchecked math but a stale inherited issuance path that no longer proves backing.
- Adjacent arithmetic-solvency cousin: uranium-finance-v2-k-invariant-scaling-mismatch-drain-2021-04-28 — both incidents turned local arithmetic failure directly into real reserve outflow, though Uranium broke an AMM invariant check while Truebit broke a bonding-curve purchase quote.
- Adjacent legacy-risk framing: fresh-market-initialization-failure is not the same bug family, but it shares the broader lesson that operational assumptions (
old code has survived,launch runbook will be followed) are not security boundaries unless enforced onchain.
Control surface
-
The decisive leverage sat in Truebit’s buy-side price function, legacy arithmetic semantics, reserve-funded sell path, and the continued existence of a long-lived contract that still held meaningful ETH without publicly maintained source or repeated modern review.
-
Once the contract could quote gigantic TRU mints at zero or near-zero ETH while still redeeming those tokens against a real reserve, the bonding curve stopped being market design and became a direct withdrawal primitive.
-
Sources:
-
Last reviewed: 2026-07-25 UTC