We do not have a reliable USD price for the recorded assets yet.
0x72f8dd2bcfe2c9fbf0d933678170417802ac8a0d8995ff9a56bfbabe3aa712d60xaF0CA21363219C8f3D8050E7B61Bb5f04e02F8D4BSC0x7ef6E527969054afbc0980E00C51D2E645b4A5efBSCOn BNB Smart Chain block 29365172, EOA 0x69810917928b80636178b1bb011c746efe61770d drove attacker contract 0xcdb3d057ca0cfdf630baf3f90e9045ddeb9ea4cc through transaction 0x72f8dd2bcfe2c9fbf0d933678170417802ac8a0d8995ff9a56bfbabe3aa712d6. The transaction borrowed 40 WBNB from the public DODO pool 0x81917eb96b397dfb1c6000d28a5bc08c0f05fc1d, bought SHIDO v1 from public Pancake liquidity, migrated that SHIDO v1 through ShidoLock at 0xaF0CA21363219C8f3D8050E7B61Bb5f04e02F8D4, sold the resulting SHIDO v2 for WBNB, repaid the flash loan, and transferred 976.975297305562276696 WBNB to the EOA. After subtracting the EOA's 0.0456318 BNB gas cost, the adversary cluster still netted about 976.929665505562276696 WBNB.
The root cause is a permissionless fixed-rate migration left live after unlock. ShidoLock allows any caller to deposit whatever SHIDO v1 balance they currently hold and, once the timestamp gate has passed, redeem that balance at a hard-coded * 10**9 ratio into SHIDO v2 from an approved reward wallet. Because the reward wallet still held 11355556538099569186000000001 SHIDO v2 units and had left an unlimited allowance to ShidoLock, any market discount in publicly tradable SHIDO v1 became a deterministic arbitrage against that treasury inventory.
ShidoLock is a migration contract with four critical public parameters:
0x733Af324146DCfe743515D8D77DC25140a07F9e00xa963eE460Cf4b474c35ded8fFF91c4eC011FB6400x7ef6E527969054afbc0980E00C51D2E645b4A5ef1687528800The verified ShidoLock source shows that the contract is not a router, AMM, or pricing oracle. Its only job is to record a caller's SHIDO v1 balance and later redeem it into SHIDO v2 from the reward wallet. That makes the security boundary simple: if arbitrary secondary-market SHIDO v1 can be turned into claimable migration state, the migration program itself becomes a public conversion venue.
Immediately before the incident block, the collected pre-state observations show:
11355556538099569186000000001 SHIDO v2 units;ShidoLock an unlimited SHIDO v2 allowance;40 WBNB.The attacker also touched a public helper contract at 0x9869674E80D632F93c338bd398408273D20a6C8e. Explorer data shows that this helper is a generic AddRemoveLiquidityForFeeOnTransferTokens contract created months earlier by 0x85d30747868a5081f53bc7b9450301e761620a4f, not an attacker-only artifact. Its role in this incident is operational staging for a taxed token, not the economic source of profit.
This incident is an MEV-style ACT opportunity, not an invariant break in Pancake, DODO, or the attacker contract. The vulnerable business logic is the post-unlock migration flow in ShidoLock: once block.timestamp >= lockTimestamp, any caller can lock their current SHIDO v1 balance and later claim SHIDO v2 from the reward wallet at a fixed nominal ratio. There is no snapshot of legitimate migration entitlements, no allowlist, no claim cap, and no mechanism tying the redeemable amount to a pre-existing holder record. The reward wallet approval kept the program fully settleable, so the only remaining question for an adversary was market pricing. When SHIDO v1 traded below the value of the SHIDO v2 obtainable through claimTokens(), the contract exposed a public buy-cheap, redeem-fixed, sell-high arbitrage. The trace confirms that the attacker realized exactly that path, and the reward wallet funded the resulting SHIDO v2 transfer.
The violated safety invariant is straightforward: after unlock, SHIDO v2 distributed by the migration program should correspond to intended migration entitlement, not to arbitrary SHIDO v1 inventory sourced later from public AMMs at whatever market discount is available. The code-level breakpoint is the composition of lockTokens() and claimTokens().
The verified ShidoLock source contains the entire exploit surface:
function lockTokens() external {
uint256 amount = IERC20(shidoV1).balanceOf(msg.sender);
if (amount == 0) revert ZeroAmount();
userShidoV1[msg.sender] += amount;
IERC20(shidoV1).transferFrom(msg.sender, rewardWallet, amount);
}
function claimTokens() external {
if (block.timestamp < lockTimestamp) revert WaitNotOver();
uint256 amount = userShidoV1[msg.sender] * 10 ** 9;
if (amount == 0) revert ZeroAmount();
userShidoV1[msg.sender] = 0;
userShidoV2[msg.sender] += amount;
IERC20(shidoV2).transferFrom(rewardWallet, msg.sender, amount);
}
Two consequences follow directly from that code:
lockTokens() trusts the caller's live SHIDO v1 balance at call time. It does not require a historical snapshot, merkle proof, or governance-approved claim record.claimTokens() uses a fixed conversion formula, userShidoV1[msg.sender] * 10**9, and sources the payout from the reward wallet via transferFrom.That logic was fully armed in the incident pre-state. The reward wallet still had enough SHIDO v2 inventory to honor a massive claim, and the allowance to ShidoLock was still the maximum uint256. The trace and balance-diff artifacts then show the exact exploit breakpoint:
ShidoLock::lockTokens()
AntiBotLiquidityGeneratorToken::transferFrom(
0xcDb3D057CA0cFDf630BAF3f90e9045dDEb9EA4cc,
0x7ef6E527969054afbc0980E00C51D2E645b4A5ef,
10436986704133494387
)
ShidoLock::claimTokens()
StandardToken::transferFrom(
0x7ef6E527969054afbc0980E00C51D2E645b4A5ef,
0xcDb3D057CA0cFDf630BAF3f90e9045dDEb9EA4cc,
10436986704133494387000000000
)
PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(
10436986704133494387000000000,
500000000000000000000,
[SHIDOv2, WBNB],
0xcDb3D057CA0cFDf630BAF3f90e9045dDEb9EA4cc,
...
)
The balance-diff artifact independently confirms the treasury loss:
{
"token": "0xa963ee460cf4b474c35ded8fff91c4ec011fb640",
"holder": "0x7ef6e527969054afbc0980e00c51d2e645b4a5ef",
"delta": "-10436986704133494387000000000"
}
Nothing in this path requires privileged access. The adversary only needed four public conditions to hold at the same time:
block.timestamp >= lockTimestampShidoLock remains openThose conditions are exactly the ACT opportunity described in root_cause.json, and every one of them is evidenced by the collected pre-state or the seed trace.
The adversary flow is a single transaction with three economic stages:
40 WBNB from DODO pool 0x81917eb96b397dfb1c6000d28a5bc08c0f05fc1d.39 WBNB, performs a smaller 0.1 WBNB buy to the attacker contract, routes the larger position through the public liquidity helper, and ends with 10436986704133494387 SHIDO v1 units under attacker control for migration.ShidoLock::lockTokens() and ShidoLock::claimTokens(), receives 10436986704133494387000000000 SHIDO v2 units from the reward wallet, sells that SHIDO v2 back into WBNB, repays the 40 WBNB flash loan, and transfers the remaining WBNB to the EOA.The attacker-side source recovered from the explorer matches the trace structure: it flash-borrows WBNB, buys SHIDO v1, interacts with the public helper, calls the migration contract, and finally sells SHIDO v2 for WBNB. The important validator conclusion is that the attacker contract is only orchestration. The profit source sits entirely in ShidoLock's open fixed-rate redemption and the reward wallet approval.
The collected seed trace also shows that the final transfer to the EOA is explicit and on-chain:
... WBNB::transfer(
0x69810917928B80636178b1BB011c746efe61770D,
976975297305562276696
)
That call happens only after the flash-loan repayment, which is why the WBNB remainder is the realized profit of the transaction.
The directly depleted asset was SHIDO v2 held by the reward wallet. The measured loss is:
10436986704133494387000000000 raw SHIDO v2 units10,436,986,704.133494387 SHIDO v2 at 18 decimalsThe reward wallet balance fell from 11355556538099569186000000001 to 918569833966074799000000001 SHIDO v2 units in one transaction. The attacker contract then liquidated the redeemed SHIDO v2 into public WBNB liquidity and sent 976.975297305562276696 WBNB to the controlling EOA. The seed native-balance diff shows the EOA separately paid 45631800000000000 wei in gas, yielding an approximate post-gas gain of 976.929665505562276696 WBNB.
This is why the incident belongs in the MEV / economic-ACT class: the protocol treasury was drained through a permissionless public price discrepancy, not through unauthorized ownership changes or broken arithmetic.
0x72f8dd2bcfe2c9fbf0d933678170417802ac8a0d8995ff9a56bfbabe3aa712d60xaF0CA21363219C8f3D8050E7B61Bb5f04e02F8D40x733Af324146DCfe743515D8D77DC25140a07F9e00xa963eE460Cf4b474c35ded8fFF91c4eC011FB6400x7ef6E527969054afbc0980E00C51D2E645b4A5ef0xcdb3d057ca0cfdf630baf3f90e9045ddeb9ea4cc0x9869674E80D632F93c338bd398408273D20a6C8e29365171