Calculated from recorded token losses using historical USD prices at the incident time.
0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb0140x764c64b2a09b09acb100b80d8c505aa6a0302ef2EthereumOn 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.
The victim system is an AdminUpgradeabilityProxy at address pointing to implementation . TRU itself is an ERC-20 token deployed as a separate proxy at with implementation . Etherscan metadata and Heimdall decompilation show that the implementation maintains:
0x764c64b2a09b09acb100b80d8c505aa6a0302ef20xc186e6f0163e21be057e95aa135edd52508d14d30xf65b5c5104c4fafd4b709d9d60a185eae063276c0x18cedf1071ec25331130c82d7af71d393ccd44460xc186e6…reserve variable that tracks ETH backing,THETA and OPEX_COST, anddonateToReserve(), 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.
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.
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:
arg0 by the current reserve,D (represented by var_d.length, derived from TRU token data such as totalSupply),reserve, andreserve and to transfer ETH to msg.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.
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:
-8535453576519732789359).8535351939305962623673).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.
The helper contract 0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8 is decompiled and hard-codes both the victim proxy and TRU proxy addresses. Its attack(uint256) entrypoint invokes:
getPurchasePrice and buyTRU(uint256) on the victim proxy to acquire TRU against the reserve at a favourable price, andgetRetirePrice/sellTRU-style redemption path on the victim implementation that computes a redemption amount (arg0 * reserve) / D as in the snippet above, followed by withdrawETH to 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:
buyTRU and redemption sequence, the attacker’s ETH balance has increased by 8,535.351939305962623673 ETH net.reserve and 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.
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:
0x6c8ec8f14be7c01672d31cfa5f2cefeab2562b50 (seed transaction sender and profit recipient).0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8 (hard-coded victim/TRU addresses and exploit sequence).0x764c64b2a09b09acb100b80d8c505aa6a0302ef2 (AdminUpgradeabilityProxy pointing to implementation 0xc186e6…).0xf65b5c5104c4fafd4b709d9d60a185eae063276c (implementation 0x18cedf1071ec25331130c82d7af71d393ccd4446).0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97 (receives explicit bribe).The main lifecycle stage is:
0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014, block 24191019, chainid 1).attack(uint256) calldata. The helper then:
getPurchasePrice and buyTRU(uint256) on the victim proxy to acquire TRU cheaply against the reserve.getRetirePrice/sellTRU-style functions on the victim implementation, which compute redemption as (arg0 * reserve) / D and apply this both to reserve and to an ETH transfer to the helper/attacker.withdrawETH or equivalent to move the remaining ETH balance to the attacker EOA.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.
The measurable impact on-chain is a large ETH loss from the victim reserve-backed contract:
0x764c64b2a09b09acb100b80d8c505aa6a0302ef2 loses 8,535.453576519732789359 ETH, with its ETH balance dropping from 8,539.452648748044247863 ETH to 3.999072228311458504 ETH during the exploit transaction.0x6c8ec8f14be7c01672d31cfa5f2cefeab2562b50 gains 8,535.351939305962623673 ETH in the same transaction.0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97 receives 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.
0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014 in block 24191019 (metadata and trace artifacts: metadata.json, trace.cast.log, prestate_tracer_diff.json, balance_diff.json).0x764c64b2a09b09acb100b80d8c505aa6a0302ef2 (AdminUpgradeabilityProxy) with implementation 0xc186e6f0163e21be057e95aa135edd52508d14d3 (Heimdall decompile).0xf65b5c5104c4fafd4b709d9d60a185eae063276c with implementation 0x18cedf1071ec25331130c82d7af71d393ccd4446 (Etherscan source and Heimdall decompile).0x1de399967b206e446b4e9aeeb3cb0a0991bf11b8 with attack(uint256) entrypoint hard-coding the victim proxy and TRU proxy (Heimdall decompile).0x50de1e46c97f0b72eeac933da0dd7985b9fb05b5962e686dab0af86702b06b6c and 0x71496352b02f974a3898c1b743e9fc2befb935e6c2a3e421134ec09b63052f4b (normal buyTRU calls confirming expected behaviour).