TL;DR
On January 8, 2026, Truebit Protocol lost ~$26.2 million (8,535 ETH) to a single-transaction exploit. The attacker found an integer overflow in the buy-side pricing function of Truebit's bonding curve, compiled with Solidity 0.6.10 — a version that doesn't enforce overflow checks. By supplying a carefully crafted purchase amount, the computed price wrapped around to zero, allowing the attacker to mint ~240 million TRU tokens for free and immediately sell them back for ETH at fair market value. The entire attack was a textbook lesson in why legacy contracts without SafeMath are ticking time bombs.
The Protocol: What Truebit Was Supposed to Do
Truebit provides off-chain computation services for Ethereum, using interactive verification to ensure correctness. Its TRU token serves as the economic backbone — staking, payments, and protocol coordination all flow through it.
The token economics relied on a bonding curve model:
- Buying TRU uses a convex pricing curve (the more you buy, the more expensive each additional unit)
- Selling TRU uses linear redemption (proportional share of reserves)
This asymmetry was by design: making large buy→sell arbitrage unattractive under normal conditions.
The key word there? Normal.
The Vulnerability: When Math Wraps Around
The _getPurchasePrice() function calculates how much ETH you need to pay for a given amount of TRU. The formula looks like this:
purchasePrice = (100 × amount² × reserve + 200 × totalSupply × amount × reserve) / ((100 - θ) × totalSupply²)
Where:
-
amount= TRU tokens to purchase -
reserve= contract's ETH reserves -
totalSupply= current TRU supply -
θ= coefficient fixed at 75
The numerator involves multiplying several large uint256 values together. For a sufficiently large amount, the intermediate computation 100 × amount² × reserve exceeds 2^256.
In Solidity ≥ 0.8, this would revert. But Truebit was compiled with Solidity 0.6.10. No automatic overflow checks. No SafeMath on this critical operation. The value silently wraps around modulo 2^256.
The Proof
>>> _reserve = 0x1ceec1aef842e54d9ee # ~8,535 ETH in wei
>>> totalSupply = 161753242367424992669183203
>>> amount = 240442509453545333947284131
>>> numerator = int(100 * amount * _reserve * (amount + 2 * totalSupply))
>>> numerator > 2**256
True
>>> denominator = (100 - 75) * totalSupply**2
>>> purchasePrice = (numerator - 2**256) / denominator
>>> purchasePrice
0.00025775798757211426
>>> int(purchasePrice) # Truncates to integer
0
A purchase price of zero ETH for 240 million TRU tokens. The math literally broke.
The Attack: Systematic Drainage in One Transaction
The attacker executed everything in a single transaction, repeating a simple cycle:
-
Query
getPurchasePrice()with a crafted amount -
Buy via
buyTRU()— paying 0 ETH (or near-zero in later rounds) -
Sell via
sellTRU()— receiving proportional ETH from reserves
Round 1: The Free Lunch
- Input: 240,442,509.45 TRU (carefully chosen to trigger overflow)
- Purchase price computed: 0 ETH
- Sold for: 5,105 ETH (~$15.3M at the time)
The sell function worked correctly — it's linear, simply dividing amount × reserve / totalSupply. No overflow, honest math. The attacker got paid the fair proportional value of the reserves they didn't pay for.
Subsequent Rounds
The attacker repeated the cycle multiple times. As reserves decreased and total supply changed, some rounds required small non-zero payments, but the overflow consistently kept purchase prices far below the corresponding sell returns. After several iterations, the contract was empty.
Total extracted: 8,535 ETH (~$26.2M)
The funds were subsequently routed through Tornado Cash.
Why This Was Devastating
1. The Asymmetric Design Backfired
The convex buy / linear sell model was meant to discourage speculation. But when the buy-side breaks (computing zero instead of expensive), the sell-side still works perfectly. The attacker exploited exactly this asymmetry — the buy price was broken, the sell price was honest.
2. Unverified Source Code
The implementation contract's source code was never publicly verified on Etherscan. Security researchers had to work from decompiled bytecode. This means:
- No public audit trail
- Community couldn't review the math
- The overflow went unnoticed for the contract's entire lifetime
3. Legacy Solidity Without SafeMath
The contract used Solidity 0.6.10. Prior to Solidity 0.8 (released in December 2020), arithmetic overflow and underflow were silent — no revert, no error, just wrong numbers. The SafeMath library was the standard mitigation, but Truebit's critical pricing function used raw arithmetic for the numerator addition.
The irony: some parts of the contract did use safe math (_SafeAdd, _SafeSub, _SafeDiv appear in the decompiled code). But the critical numerator computation — where two large intermediate values are added — used a raw + operation.
The Broader Pattern: Legacy Contract Time Bombs
Truebit isn't alone. There's a growing class of "zombie contract" exploits targeting protocols that:
- Deployed years ago with older Solidity versions
- Hold significant value (sometimes forgotten value)
- Have teams that have moved on or dissolved
- Were never upgraded or migrated
The Hit List of Pre-0.8 Risks
| Risk | Pre-0.8 Behavior | Post-0.8 Behavior |
|---|---|---|
| Integer overflow | Silent wrap to 0 | Automatic revert |
| Integer underflow | Silent wrap to 2^256 | Automatic revert |
| Division by zero | Returns 0 | Reverts |
Every contract deployed before Solidity 0.8 that performs arithmetic on user-controlled inputs without explicit SafeMath is potentially vulnerable.
How to Find Them
-
Check the compiler version:
solcversion in contract metadata -
Look for raw arithmetic:
+,-,*without SafeMath wrappers - Map user-controlled inputs to arithmetic paths: Can a user influence operands in multiplication chains?
-
Test boundary values: What happens when inputs approach
2^256 / known_multiplier?
# Quick check using cast (Foundry)
cast call <CONTRACT> "getPurchasePrice(uint256)" \
$(python3 -c "print(2**128)")
# If this returns 0 or suspiciously small → investigate
Defense Patterns
For Existing Pre-0.8 Contracts
If you maintain a legacy contract that can't be easily upgraded:
- Audit every arithmetic operation against SafeMath usage
- Add input validation: Cap maximum amounts in user-facing functions
- Deploy a guardian contract: A wrapper that validates inputs before forwarding calls
- Consider migration: Move liquidity to a new, properly compiled contract
For New Development
- Always use Solidity ≥ 0.8 — automatic overflow checks are non-negotiable
-
Even with 0.8+, use
uncheckedblocks sparingly and only with explicit justification - Test arithmetic boundaries in your invariant tests:
// Foundry invariant test
function invariant_purchasePriceNeverZero() public {
uint256 amount = handler.lastPurchaseAmount();
if (amount > 0) {
uint256 price = protocol.getPurchasePrice(amount);
assertGt(price, 0, "Purchase price should never be zero");
}
}
- Verify and publish source code — always. Unverified contracts are untrusted contracts.
The $26M Checklist: Lessons Learned
- ☐ Compiler version matters. Solidity < 0.8 is a red flag for any contract holding value.
- ☐ SafeMath gaps are exploitable. Partial SafeMath usage is almost worse than none — it creates false confidence.
- ☐ Asymmetric economic models amplify bugs. When buy and sell use different formulas, a bug in one side creates arbitrage.
- ☐ Bonding curves need overflow analysis. Polynomial pricing functions multiply user inputs multiple times — overflow risk grows exponentially.
- ☐ Unverified source = unauditable. If the community can't read your code, nobody's watching for bugs.
- ☐ Legacy contracts need monitoring. If a contract holds value and the team has moved on, someone should still be watching.
Timeline
| Date | Event |
|---|---|
| Pre-2021 | Truebit deploys Purchase contract (Solidity 0.6.10) |
| Jan 8, 2026 | Attacker drains 8,535 ETH in a single transaction |
| Jan 8, 2026 | CertiK, BlockSec flag the exploit |
| Jan 8, 2026 | Truebit acknowledges the incident |
| Jan 14, 2026 | BlockSec publishes detailed technical analysis |
| Post-exploit | Stolen funds routed through Tornado Cash; unrecovered |
Final Thought
The Truebit exploit wasn't sophisticated. It was arithmetic. A single missing SafeMath wrapper on a + operation in a years-old contract turned a theoretical overflow into a $26 million withdrawal. The attacker didn't need a flash loan, didn't need to manipulate an oracle, didn't need to exploit cross-contract composability. They just needed a calculator and the patience to find the right input value.
Every Solidity < 0.8 contract with user-controlled inputs in multiplication chains is a potential Truebit. The question isn't whether more will be found. It's how much they're holding when someone looks.
References: BlockSec Technical Analysis, Truebit Official Statement, QuillAudits Analysis, SlowMist Analysis
Top comments (0)