0xd9e0014a32d96cfc8b72864988a6e1664a9b6a2e90aeaa895fcd42da11cc34900xf573748637e0576387289f1914627d716927f90fBSCRoulettePotV2 was exploited on BSC in transaction 0xd9e0014a32d96cfc8b72864988a6e1664a9b6a2e90aeaa895fcd42da11cc3490 at block 45668286. The attacker used flash liquidity to distort PancakeSwap reserves, then triggered the public swapProfitFees() maintenance routine so the protocol sold treasury-held assets at manipulated prices. The routine trusted spot AMM quotes and executed swaps with amountOutMin = 0, so the protocol accepted hostile execution prices. The same transaction then unwound the manipulated market state and realized profit. The measured sender-side gain was 19.520332279709631513 BNB net of gas.
RoulettePotV2 stores casino liquidity, profit, and LINK funding state on-chain for each casino configuration. Its maintenance path later converts accrued value into BNB, LINK, and BNBP. That design is only safe if the price source and execution path are manipulation resistant.
The verified victim code instead uses PancakeRouter spot-reserve functions:
function getTokenAmountForLink(address tokenAddr, uint256 linkAmount) public view returns (uint256) {
...
return router.getAmountsIn(linkAmount, path)[0];
}
function getLinkAmountForToken(address tokenAddr, uint256 tokenAmount) public view returns (uint256) {
...
return router.getAmountsOut(tokenAmount, path)[isBNB ? 1 : 2];
}
Those quotes are derived from current pool reserves and can be changed inside a single transaction with sufficient liquidity. Because swapProfitFees() is externally callable, any unprivileged actor can pair reserve distortion with victim execution.
The root cause is a public treasury-conversion function that depends on manipulable Pancake spot quotes and performs zero-slippage swaps. In , RoulettePotV2 calculates how much of each casino asset should be sold to replenish LINK budget using , which itself calls over live reserves. The function then approves and sells protocol assets through , and later buys LINK and BNBP through . No trusted executor restriction, TWAP, oracle sanity bound, or minimum-output protection exists on these paths. That means the contract will accept whatever price exists in the manipulated pool at the instant the attacker calls it. The on-chain incident matches this exact mechanism: flash liquidity and swaps occur before victim execution, RoulettePotV2 emits and , and the balance diff shows treasury depletion and attacker profit afterward.
swapProfitFees()getTokenAmountForLink()router.getAmountsIn()swapExactTokensForETH(..., 0, ...)swapExactETHForTokens(..., 0, ...)RoundFinishedSuppliedLinkThe decisive victim code is:
function swapProfitFees() external {
...
uint256 amountForLinkFee = getTokenAmountForLink(casinoInfo.tokenAddress, linkSpent[i]);
...
uint256[] memory swappedAmounts = router.swapExactTokensForETH(
gameFee + amountForLinkFee,
0,
path,
address(this),
block.timestamp
);
...
uint256 linkAmount = router.swapExactETHForTokens{ value: totalBNBForLink }(
0,
path,
address(this),
block.timestamp
)[1];
...
LinkTokenInterface(link677TokenAddr).transferAndCall(coordinatorAddr, linkAmount, abi.encode(subscriptionId));
}
This violates the invariant that an unprivileged caller must not be able to force protocol-owned inventory through attacker-set prices. The pricing input and the execution venue are the same manipulable AMM, and every swap uses zero slippage protection.
The incident evidence is consistent end to end. The receipt summary records one Flash event, seven Swap events, one RoundFinished, one SuppliedLink, and one SubscriptionFunded in the exploit transaction. The balance diff shows RoulettePotV2 lost all tracked BUSD (9442741234770661142), all tracked BNBP (324850208759244488266486), most tracked CAKE (325721336343852503289), and almost all of its native BNB balance (-4171603472025223867 wei). The same artifact shows the sender EOA rising from 1379794158320757963 wei to 20900126438030389476 wei, a net gain of 19520332279709631513 wei.
The LINK-funding side effect also matches the claimed mechanism. swapProfitFees() swaps BNB into LINK, converts LINK into ERC677 LINK through PegSwap, and funds the Chainlink VRF coordinator. The receipt contains both a PegSwap TokensSwapped event and RoulettePotV2 SuppliedLink, and the validator replay confirms the coordinator's ERC677 LINK balance increases during the exploit sequence.
Exploitability is permissionless. The transaction sender 0x0000000000004f3d8aaf9175fd824cb00ad4bf80 is an EOA, the helper target 0x000000000000bb1b11e5ac8099e92e366b64c133 appears only in the incident transaction window, and the vulnerable call path itself has no access control. That makes the opportunity ACT rather than privilege dependent.
The on-chain flow is:
0x172fcd41e0913e95784454622d1c3724f546f849 and distorts Pancake reserves on the relevant routes.FinishedBet, LiquidityChanged, and RoundFinished, consistent with the helper first settling the round and then entering the conversion path.swapProfitFees() sells protocol-owned BUSD, CAKE, and BNBP into WBNB at manipulated prices, converts part of the proceeds into LINK, swaps LINK to ERC677 LINK through PegSwap, and funds the VRF coordinator.The account clustering is defensible from transaction-local evidence:
0x0000000000004f3d8aaf9175fd824cb00ad4bf80 is the sender EOA and realizes the net native profit.0x000000000000bb1b11e5ac8099e92e366b64c133 is the direct transaction target and orchestrator.0xdfac7733c205c3a2a5e202293ebb37e4633bc286 receives 20 BNB during the exploit transaction and appears in the victim settlement events.The measurable victim-side losses recorded in the collector balance diff are:
9442741234770661142 base units325721336343852503289 base units324850208759244488266486 base unitsRoulettePotV2 also lost native BNB during the conversion path, with its native balance falling by 4171603472025223867 wei. On the attacker side, the incident sender EOA finished with 19.520332279709631513 BNB more than it started with, net of gas.
0xd9e0014a32d96cfc8b72864988a6e1664a9b6a2e90aeaa895fcd42da11cc34900xf573748637e0576387289f1914627d716927f90f on BSCcontracts/Roulette/RouletteV2.soltrace.cast.log for the incident transactionreceipt_summary.json, receipt.json, and decoded_topics.jsonbalance_diff.json0x1fcc3b22955e76ca48bf025f1a6993685975bb9e