TRU reserve mispricing attack drains WBNB from pool
Exploit Transactions
0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014Victim Addresses
0x764c64b2a09b09acb100b80d8c505aa6a0302ef2EthereumLoss Breakdown
Similar Incidents
SBR reserve desynchronization exploit drains WETH from UniswapV2 pair
35%OMPxContract bonding-curve loop exploit drains ETH reserves
33%Pool16 lend/redeem accounting bug drains USDC without HOME backing
32%NOON Pool Drain via Public transfer
32%AnyswapV4Router WETH9 permit misuse drains WETH to ETH
32%SilicaPools decimal-manipulation bug drains WBTC flashloan collateral
32%Root Cause Analysis
TRU reserve mispricing attack drains WBNB from pool
1. Incident Overview TL;DR
On Ethereum mainnet block 24191019, EOA 0x6c8ec8f14be7c01672d31cfa5f2cefeab2562b50 called helper contract 0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8 with 0.01 ETH, triggering a scripted sequence of calls into the TRU reserve-backed proxy 0x764c64b2a09b09acb100b80d8c505aa6a0302ef2 (implementation 0xc186e6f0163e21be057e95aa135edd52508d14d3) and the TRU token proxy 0xf65b5c5104c4fafd4b709d9d60a185eae063276c. Within this single transaction, the helper contract buys TRU cheaply from the reserve-backed contract, then redeems a small TRU amount via a mispriced sellTRU/getRetirePrice path, and finally withdraws ETH, draining almost the entire reserve. The attacker EOA ends the transaction with a net ETH gain of 8,535.351939305962623673 ETH, while block.coinbase 0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97 receives a 0.101445247 ETH bribe.
The root cause is a deterministic pricing flaw in the victim’s reserve logic: sellTRU/getRetirePrice compute redemptions as (arg0 * reserve) / D and apply this amount both to the outgoing ETH transfer and to reducing the internal reserve variable stored in slot 0x9a, but buyTRU/getPurchasePrice do not keep reserve and the denominator D aligned with the true outstanding TRU supply. Under the on-chain configuration at block 24191019, this misalignment allows a small TRU position to redeem for nearly the entire ETH reserve in a single sequence, violating conservation of value for the reserve-backed asset.
2. Key Background
The victim system is an AdminUpgradeabilityProxy at address 0x764c64b2a09b09acb100b80d8c505aa6a0302ef2 pointing to implementation 0xc186e6f0163e21be057e95aa135edd52508d14d3. TRU itself is an ERC-20 token deployed as a separate proxy at 0xf65b5c5104c4fafd4b709d9d60a185eae063276c with implementation 0x18cedf1071ec25331130c82d7af71d393ccd4446. Etherscan metadata and Heimdall decompilation show that the implementation 0xc186e6… maintains:
- a
reservevariable that tracks ETH backing, - configuration parameters
THETAandOPEX_COST, and - public functions
donateToReserve(),buyTRU(uint256),sellTRU(uint256),getPurchasePrice(uint256),getRetirePrice(uint256),opex(),withdrawETH(), and AccessControl role-management functions.
The reserve-backed design is intended to ensure that ETH deposited via donateToReserve and buyTRU remains available to satisfy TRU redemptions, minus any explicitly configured operating expense (opex) withdrawals. PrestateTracer for the exploit transaction shows that for the victim proxy address 0x764c64b2a09b09acb100b80d8c505aa6a0302ef2, only one storage slot changes and it tracks ETH-balance-like quantities:
{
"pre": {
"0x764c64b2a09b09acb100b80d8c505aa6a0302ef2": {
"balance": "0x1ceecb63c13bfbfe337",
"storage": {
"0x000000000000000000000000000000000000000000000000000000000000009a": "0x0000000000000000000000000000000000000000000001ceec1aef842e54d9ee"
}
}
},
"post": {
"0x764c64b2a09b09acb100b80d8c505aa6a0302ef2": {
"balance": "0x377f8f00efa262c8",
"storage": {
"0x000000000000000000000000000000000000000000000000000000000000009a": "0x000000000000000000000000000000000000000000000000017de7f121a353f0"
}
}
}
}
Snippet: PrestateTracer pre/post state for victim proxy in tx 0xcd4755…014, showing ETH balance and storage slot 0x9a collapsing together (artifacts: prestate_tracer_diff.json).
Converting these hex values to 18-decimal ETH units, the victim’s ETH balance drops from 8,539.452648748044247863 ETH to 3.999072228311458504 ETH, while the slot 0x9a value drops from 8,539.40893594715035083 ETH-equivalent to 0.107496988964246512 ETH-equivalent. The fact that the final ETH balance and the final slot-0x9a value together match the small residual reserve shows that reserve is stored at slot 0x9a.
Non-adversarial buyTRU transactions collected at the same contract (e.g., txs 0x50de1e46c97f0b72eeac933da0dd7985b9fb05b5962e686dab0af86702b06b6c and 0x71496352b02f974a3898c1b743e9fc2befb935e6c2a3e421134ec09b63052f4b) show small positive ETH deltas into the proxy and modest increases in reserve, consistent with well-behaved purchases that increase backing rather than draining it.
3. Vulnerability Analysis & Root Cause Summary
This incident is an ATTACK-type anyone-can-take (ACT) opportunity with a profit predicate. The vulnerability is a mispricing bug in the reserve-backed TRU contract: redemptions in sellTRU/getRetirePrice are computed as (arg0 * reserve) / D and this amount is used both to transfer ETH to the caller and to decrement the internal reserve variable, while buyTRU/getPurchasePrice use a denominator D that is not kept consistent with the true outstanding TRU supply and reserve.
The intended invariant is that, starting from the pre-state at block 24191019, the sum of the victim contract’s ETH balance and its internal reserve (slot 0x9a) equals the net ETH paid in by external accounts minus any explicitly configured opex withdrawals. In particular, a trader who deposits only 0.01 ETH in the exploit transaction must not be able to increase their net ETH holdings by more than that 0.01 ETH (up to rounding) once the transaction completes.
The breakpoint is in the sellTRU/getRetirePrice path: the contract reads reserve directly from slot 0x9a, computes a redemption amount (arg0 * reserve) / D where D is derived from TRU totalSupply or an array length, then (i) transfers that full amount of ETH to msg.sender and (ii) reduces reserve by the same amount. Under the on-chain configuration observed in this incident, D is much smaller than the effective supply implied by reserve and the TRU token, so a small arg0 value causes the contract to pay out nearly the entire reserve in one redemption, breaking the invariant.
4. Detailed Root Cause Analysis
4.1 Victim reserve logic in code
Heimdall decompilation of implementation 0xc186e6f0163e21be057e95aa135edd52508d14d3 shows a reserve variable and pricing logic that uses it directly in redemption:
// Extract from decompiled implementation 0xc186e6f0..., sellTRU/getRetirePrice path
require(((arg0 * reserve) / arg0) == reserve, "SafeMath: multiplication overflow");
...
require(!(((arg0 * reserve) / var_d.length) > reserve), "SafeMath: division overflow");
reserve = reserve - ((arg0 * reserve) / var_d.length);
(bool success, bytes memory ret0) = address(msg.sender).transfer((arg0 * reserve) / var_d.length);
uint256 var_n = (arg0 * reserve) / var_d.length;
return (arg0 * reserve) / var_d.length;
Snippet: Decompiled victim implementation 0xc186e6… showing redemption amount (arg0 * reserve) / D (here var_d.length plays the role of D) applied both to reserve and an ETH transfer to the caller (artifact: 0xc186e6…-decompiled.sol).
This code path:
- multiplies the caller’s TRU amount
arg0by the currentreserve, - divides by a denominator
D(represented byvar_d.length, derived from TRU token data such astotalSupply), - ensures that the resulting amount is not greater than
reserve, and - then uses that amount both to decrement
reserveand to transfer ETH tomsg.sender.
Crucially, no additional check ensures that D accurately reflects the outstanding TRU supply against which reserve should be priced. The buyTRU/getPurchasePrice functions use a different formula involving THETA, OPEX_COST, and token supply; they do not enforce a tight coupling between reserve and D across the full lifecycle of deposits and redemptions.
4.2 Evidence from state diffs and balances
Balance diffs for the exploit transaction (balance_diff.json) show the ETH movements:
{
"native_balance_deltas": [
{
"address": "0x764c64b2a09b09acb100b80d8c505aa6a0302ef2",
"before_wei": "8539452648748044247863",
"after_wei": "3999072228311458504",
"delta_wei": "-8535453576519732789359"
},
{
"address": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
"before_wei": "15481272865175325228",
"after_wei": "15582718112175325228",
"delta_wei": "101445247000000000"
},
{
"address": "0x6c8ec8f14be7c01672d31cfa5f2cefeab2562b50",
"before_wei": "462932473584700624",
"after_wei": "8535814871779547324297",
"delta_wei": "8535351939305962623673"
}
]
}
Snippet: Seed transaction balance diff for tx 0xcd4755…014, showing the victim’s large negative ETH delta, the attacker’s large positive ETH delta, and the coinbase bribe (artifact: balance_diff.json).
From this:
- The victim proxy loses 8,535.453576519732789359 ETH (delta_wei
-8535453576519732789359). - The attacker EOA gains 8,535.351939305962623673 ETH (delta_wei
8535351939305962623673). - The block.coinbase gains 0.101445247 ETH (delta_wei
101445247000000000).
PrestateTracer confirms that the only changing storage slot on the victim is 0x9a, whose value collapses from approximately 8,539.4089 ETH-equivalent to 0.1075 ETH-equivalent, while the victim’s ETH balance collapses from approximately 8,539.4526 ETH to 3.9990 ETH. The sum of the final ETH balance and final slot-0x9a value matches the small remaining reserve, establishing that slot 0x9a is the contract’s internal reserve.
4.3 Exploit sequence and invariant violation
The helper contract 0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8 is decompiled and hard-codes both the victim proxy and TRU proxy addresses. Its attack(uint256) entrypoint invokes:
getPurchasePriceandbuyTRU(uint256)on the victim proxy to acquire TRU against the reserve at a favourable price, and- TRU token methods (via the TRU proxy) to manage allowances and transfers, then
- a
getRetirePrice/sellTRU-style redemption path on the victim implementation that computes a redemption amount(arg0 * reserve) / Das in the snippet above, followed bywithdrawETHto move the drained ETH to the attacker.
Under the on-chain parameterization at block 24191019, the denominator D used in the redemption formula is much smaller than the effective supply implied by reserve and the TRU token, so a relatively small arg0 (TRU amount) causes the computed redemption amount to be close to the entire reserve. When this amount is subtracted from reserve and simultaneously transferred as ETH to the attacker, the invariant that “ETH balance + reserve equals net ETH paid in minus opex” is broken:
- The attacker deposits only 0.01 ETH at the start of the transaction.
- After the
buyTRUand redemption sequence, the attacker’s ETH balance has increased by 8,535.351939305962623673 ETH net. - The victim’s
reserveand ETH balance have both collapsed to a small residual.
The invariant and breakpoint described in the analyzer’s root_cause.json are therefore realized concretely in the code, state diffs, and trace, and they fully explain how the mispricing and misaligned denominator enable an ACT adversary to drain the reserve in a single transaction.
5. Adversary Flow Analysis
The adversary strategy is a single-transaction, helper-orchestrated exploit that executes entirely through public entrypoints on the victim and TRU proxies. The relevant addresses and roles are:
- Attacker EOA:
0x6c8ec8f14be7c01672d31cfa5f2cefeab2562b50(seed transaction sender and profit recipient). - Helper/orchestrator contract:
0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8(hard-coded victim/TRU addresses and exploit sequence). - Victim reserve-backed proxy:
0x764c64b2a09b09acb100b80d8c505aa6a0302ef2(AdminUpgradeabilityProxy pointing to implementation0xc186e6…). - TRU ERC-20 proxy:
0xf65b5c5104c4fafd4b709d9d60a185eae063276c(implementation0x18cedf1071ec25331130c82d7af71d393ccd4446). - Block.coinbase:
0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97(receives explicit bribe).
The main lifecycle stage is:
- Adversary exploit transaction drains reserve (tx
0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014, block 24191019, chainid 1).
The attacker EOA sends 0.01 ETH to the helper contract withattack(uint256)calldata. The helper then:- Calls
getPurchasePriceandbuyTRU(uint256)on the victim proxy to acquire TRU cheaply against the reserve. - Interacts with the TRU proxy to set allowances and move TRU as needed for redemption.
- Calls
getRetirePrice/sellTRU-style functions on the victim implementation, which compute redemption as(arg0 * reserve) / Dand apply this both toreserveand to an ETH transfer to the helper/attacker. - Calls
withdrawETHor equivalent to move the remaining ETH balance to the attacker EOA. - Sends 0.101445247 ETH from the victim’s outflow as an explicit transfer to block.coinbase.
- Calls
Trace logs (trace.cast.log) show this call graph and the sequence of victim and TRU proxy calls, while the state and balance diffs confirm that the combined effect of this single transaction is to move approximately 8,535.45 ETH from the victim reserve-backed proxy to the attacker and the block.coinbase.
Because the helper contract and victim/TRU proxies expose only public functions, any unprivileged EOA observing the same pre-state σ_B at block 24191019 could submit an equivalent attack(uint256) transaction (or an equivalent hand-crafted sequence of calls) and achieve the same profit, making this a genuine anyone-can-take ACT opportunity.
6. Impact & Losses
The measurable impact on-chain is a large ETH loss from the victim reserve-backed contract:
- The victim proxy
0x764c64b2a09b09acb100b80d8c505aa6a0302ef2loses 8,535.453576519732789359 ETH, with its ETH balance dropping from 8,539.452648748044247863 ETH to 3.999072228311458504 ETH during the exploit transaction. - The attacker EOA
0x6c8ec8f14be7c01672d31cfa5f2cefeab2562b50gains 8,535.351939305962623673 ETH in the same transaction. - Block.coinbase
0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97receives a 0.101445247 ETH payment.
These values are computed directly from balance_diff.json and prestateTracer outputs. The reserve-backed contract is effectively drained, retaining only a small residual ETH balance that matches the post-exploit combination of ETH balance and internal reserve stored in slot 0x9a.
7. References
- Seed exploit transaction: Ethereum mainnet tx
0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014in block 24191019 (metadata and trace artifacts:metadata.json,trace.cast.log,prestate_tracer_diff.json,balance_diff.json). - Victim reserve-backed proxy:
0x764c64b2a09b09acb100b80d8c505aa6a0302ef2(AdminUpgradeabilityProxy) with implementation0xc186e6f0163e21be057e95aa135edd52508d14d3(Heimdall decompile). - TRU ERC-20 proxy:
0xf65b5c5104c4fafd4b709d9d60a185eae063276cwith implementation0x18cedf1071ec25331130c82d7af71d393ccd4446(Etherscan source and Heimdall decompile). - Helper/orchestrator contract:
0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8withattack(uint256)entrypoint hard-coding the victim proxy and TRU proxy (Heimdall decompile). - Non-adversarial reference transactions: txs
0x50de1e46c97f0b72eeac933da0dd7985b9fb05b5962e686dab0af86702b06b6cand0x71496352b02f974a3898c1b743e9fc2befb935e6c2a3e421134ec09b63052f4b(normalbuyTRUcalls confirming expected behaviour).