This is a lower bound: only assets with reliable historical USD prices are counted, so the actual loss may be higher.
0x32f9188d6d86bf88dbac3ceee5958adf1aa609dfBSCOn BNB Smart Chain, attacker EOA 0x36fb87c3e65ec608d37e38bd556fb6ebdb3d8a39 deployed helper contract 0x3e1c5ddd39801c1e72e5ab7e19c614fd398747f8, opened a 100 USDT-equivalent pledge in victim contract 0x32F9188d6D86Bf88dbAc3ceEe5958aDf1aa609df, waited out the fixed four-hour delay, and then executed exploit transaction 0x12e8c24dec36a29fdd9b9d7a8b587b3abd2519089b6438c194e6e5eb357b68d8. In that transaction, the attacker flash-borrowed almost all USDT from Pancake pair 0xBb33668bAe76A6394683DeEf645487e333b8fC45, called the victim claim path fd5a466f(), received 1005546137044666873719593 TGC from the victim, sold the TGC back into the market, and realized 36574031608362256830655 USDT in profit.
The root cause is a manipulable spot-price oracle inside the victim. Both joinPledge(uint256) and fd5a466f() derive pricing from instantaneous token balances of the public TGC/USDT pair. Because fd5a466f() recomputes the claim amount at redemption time from live pair balances instead of immutable pledge economics, temporarily draining pair USDT in the same transaction turns a 100 USDT pledge into a claim on more than one million TGC.
The victim is an unverified pledge contract, so the analysis relies on the collected decompilation plus direct storage reads. The constructor transaction and storage snapshot show that the victim stores:
1: 0x12e8c24dec36a29fdd9b9d7a8b587b3abd2519089b6438c194e6e5eb357b68d80x55d398326f99059ff775485246999027b31979552: 0x523aa213fe806778ffa597b6409382fffcc12de23: 0xbb33668bae76a6394683deef645487e333b8fc455: 0xc31cc21095597e43e97a08d5eaf904e800dadacd6: 0x3840 = 14400 seconds7: 0x03ee = 1006Relevant victim code recovered from the collected decompilation:
function Unresolved_2ccebc28(uint256 arg0) public payable returns (uint256) {
(bool success, bytes memory ret0) = address(store_e).Unresolved_70a08231(address(store_d));
(bool success, bytes memory ret0) = address(store_f).Unresolved_70a08231(address(store_d));
return (arg0 * var_c.length) / var_c.length;
}
function Unresolved_fd5a466f() public payable returns (bool) {
...
require(!(block.timestamp < (storage_map_b[var_a] + store_g)), "atm: 4 hour not finished");
(bool success, bytes memory ret0) = address(store_f).{ value: var_u ether }Unresolved_a9059cbb(var_s);
...
}
The important point is not the decompiler names; it is the data flow. Both the preview routine 0x2ccebc28 and the claim routine fd5a466f() read live token balances from the pair and use those balances in pricing calculations.
This is an ATTACK root cause, not a benign arbitrage. The victim exposes a permissionless deposit path joinPledge(uint256) and a permissionless claim path fd5a466f(). The contract records a caller-specific timestamp and amount, then later computes the payout from live pair balances plus a fixed 1006/1000 multiplier. That design violates a basic invariant: a delayed redemption must remain bounded by the originally committed pledge economics and must not depend on attacker-manipulable AMM spot balances at claim time.
The code-level breakpoint is the claim calculation inside fd5a466f(). During the exploit, the attacker temporarily removes nearly all pair USDT just before the victim reads the pair balances. Because the denominator becomes tiny while the pair still holds a large TGC balance, the victim overestimates how much TGC corresponds to the stored pledge amount and transfers a massive amount of TGC to the attacker helper. The exploit succeeds without privileged roles, private keys, or non-public information; any unprivileged actor that can fund the setup call and obtain same-transaction liquidity can reproduce the path.
The setup transaction 0x57002225ca6d1e96bde79fb643d2eb5da3d32af136380356c75a03aa07ed7aa4 shows the attacker helper calling the public victim entrypoint with a 100 USDT-equivalent pledge:
0x3E1c5Ddd39801C1e72e5aB7E19c614fd398747f8::joinPledge(100000000000000000000)
0x32F9188d6D86Bf88dbAc3ceEe5958aDf1aa609df::joinPledge(100000000000000000000)
At honest setup time, the collected trace records pair balances of 29525571260222056736207 TGC and 29826539556276300144846 USDT. That implies the pledge required about 98990937934699462496 TGC, which matches the setup trace.
The exploit transaction 0x12e8c24dec36a29fdd9b9d7a8b587b3abd2519089b6438c194e6e5eb357b68d8 then changes the observable pair ratio inside the same transaction before the claim:
0x32F9188d6D86Bf88dbAc3ceEe5958aDf1aa609df::fd5a466f()
stack values around pricing path:
pair TGC balance = 40003621810134850539992
pair USDT balance = 38080802193260983363359
multiplier = 1006
divisor = 1000
The root-cause calculation is therefore:
storedAmount * currentPairTGC * 1006 / currentPairUSDT / 1000
= 100e18 * 38063621810134850539992 * 1006 / 3808080219326098337 / 1000
= 1005546137044666873719593 TGC
That computed payout is confirmed by the post-state balance diff:
{
"token": "0x523aa213fe806778ffa597b6409382fffcc12de2",
"holder": "0x32f9188d6d86bf88dbac3ceee5958adf1aa609df",
"delta": "-1005546137044666873719593"
}
The same balance diff shows the profit realization:
{
"token": "0x55d398326f99059ff775485246999027b3197955",
"holder": "0x36fb87c3e65ec608d37e38bd556fb6ebdb3d8a39",
"delta": "36574031608362256830655"
}
This proves the exploit is not merely a temporary accounting distortion. The manipulated claim causes a real TGC transfer from the victim, that TGC is monetized through the same market, and the attacker exits with a large positive USDT balance.
The adversary flow is three-stage and fully on-chain:
0xc236b467624863bee5f8a5e877d5a14f9ddf1ebec5004ba29a43405ade59622e deploys helper 0x3e1c5ddd39801c1e72e5ab7e19c614fd398747f8. The deployment bytecode hard-codes the victim, TGC, USDT, Pancake router, and pair addresses.0x57002225ca6d1e96bde79fb643d2eb5da3d32af136380356c75a03aa07ed7aa4 calls joinPledge(100e18), transferring the required TGC and recording the helper’s claimable position after the timer starts.0x12e8c24dec36a29fdd9b9d7a8b587b3abd2519089b6438c194e6e5eb357b68d8 calls the helper’s exploit path. Inside that path, the helper borrows nearly all pair USDT, invokes fd5a466f() while the pair’s USDT balance is depleted, receives the oversized TGC payout, repays the flash leg, sells the claimed TGC, and returns the profit to the attacker EOA.The ACT predicate holds because each step uses public interfaces: deployment is permissionless, joinPledge(uint256) is public, fd5a466f() is public after the timer elapses, and Pancake flash swap liquidity is public. The only practical constraints are enough capital to prepare the initial pledge, enough same-transaction liquidity to distort the pair, and enough TGC inventory remaining in the victim to satisfy the manipulated payout.
The victim lost 1005546137044666873719593 TGC in the exploit transaction. The attacker realized 36574031608362256830655 USDT of profit in the same transaction, while paying only 3039291000000000 wei of BNB gas separately.
Two impacts matter:
36574031608362256830655 USDT by selling back into the manipulated market and exiting with net profit.0x12e8c24dec36a29fdd9b9d7a8b587b3abd2519089b6438c194e6e5eb357b68d80x57002225ca6d1e96bde79fb643d2eb5da3d32af136380356c75a03aa07ed7aa40xc236b467624863bee5f8a5e877d5a14f9ddf1ebec5004ba29a43405ade59622e0xdae8cafa9832b416e90ba2eed00557efa6d965cd0b3fca9374ea7a2bd8df4abejoinPledge, fd5a466f, and 0x2ccebc280 through 9