Calculated from recorded token losses using historical USD prices at the incident time.
0xe15d6f7fa891c2626819209edf2d5ded6948310eaada067b400062aa022ce7180x278ce7151bfd1b035e8bc99e15b4d9773969d4edBSC0xbcbcb0e7e28414e084c4a40c1ccc30b75629a7deBSC0xe83d8a3c45b77d95c850c00aca53a57ce9d49314BSCOn BSC block 37857764, transaction 0xe15d6f7fa891c2626819209edf2d5ded6948310eaada067b400062aa022ce718 exploited the publicly callable reward subsystem behind BEP20Token at 0x278ce7151bfd1b035e8bc99e15b4d9773969d4ed. The attacker first fabricated a reward entry for its own address, then converted that fabricated accounting entry into a real token payout from distributor 0xe83d8a3c45b77d95c850c00aca53a57ce9d49314, and finally dumped the drained inventory into the live PancakeSwap token/USDT pool 0x875ac38bc56e2c6fbeda4354ac085cb94d0d2d2f.
The root cause is missing authorization on Reward.setReward and Reward.generateReward, combined with a claim path in BEP20Token._transfer that trusts reward.getWaitReleaseCoin(sender) when the sender transfers exactly 10000 token units to the token contract. Because those reward balances are publicly writable, any unprivileged caller can mint itself an arbitrary pending reward and cash it out of the distributor wallet.
The victim system consists of three protocol-side components:
BEP20Token at 0x278ce7151bfd1b035e8bc99e15b4d9773969d4edReward at 0xbcbcb0e7e28414e084c4a40c1ccc30b75629a7deTokenDistributor at 0xe83d8a3c45b77d95c850c00aca53a57ce9d49314The distributor starts with the mining inventory of 8,000,000 tokens. The intended flow is that legitimate pawn operations inside the token contract create reward entries through protocol logic, after which users can later claim accrued rewards. Claiming is encoded in an unusual way: a holder sends exactly 10000 raw token units to the token contract, which routes execution through a special reward-release branch.
The verified source shows that the reward contract does not enforce any caller restrictions on reward creation. This means the protocol-side accounting that is supposed to represent earned mining rewards is directly writable by arbitrary users, while the token contract still treats that accounting as authoritative during payout.
This is an ATTACK-class ACT vulnerability caused by externally writable reward accounting. The core invariant should be: only protocol-authorized pawn operations may create reward entries, and claimable balances must remain bounded by real user deposits and release schedules. The deployed code breaks that invariant because Reward.setReward accepts arbitrary rewardSender, amount, remain, and price values from any caller, and Reward.generateReward materializes those attacker-controlled values into waitRelease balances without provenance checks.
Once a forged waitRelease balance exists, BEP20Token._transfer makes the bug exploitable. If recipient == contractAddress and amount == releaseAmount, the token contract calls reward.releaseCoin(sender) and transfers the corresponding distributor inventory to the sender. That turns fabricated accounting state into a real asset transfer from the distributor wallet. Because the attacker can then immediately swap the drained tokens into USDT through PancakeSwap, the full exploit is permissionless, single-transaction, and profit-realizing.
The code-level breakpoint is therefore the combination of:
function setReward(address rewardSender, uint256 amount, uint256 remain, uint256 price) public {
if(reward[rewardSender].length == 0) {
rewardKeys.push(rewardSender);
}
reward[rewardSender].push(RewardData(rewardSender, amount, remain, price, block.timestamp));
_totalRemainCnt += remain;
}
function generateReward(uint256 coinPrice) public {
...
waitRelease[rewardKeys[i]] += release;
...
}
and:
if (recipient == contractAddress) {
if (amount == releaseAmount) {
uint256 waitRelease = reward.getWaitReleaseCoin(sender);
uint256 poolBalance = _balances[address(_tokenDistributor)];
if (poolBalance < waitRelease) {
waitRelease = poolBalance;
}
reward.releaseCoin(sender);
_basicTransfer(address(_tokenDistributor), sender, waitRelease);
_basicTransfer(sender, recipient, amount);
}
}
The exploit starts from the pre-state immediately before the adversary transaction in block 37857764. At that point, the distributor still holds the full 8,000,000 token mining pool, the reward and token contracts are publicly callable, and the PancakeSwap token/USDT pair has live liquidity. Those conditions are sufficient for an unprivileged adversary to realize the opportunity.
In the verified Reward code, setReward is public and performs no authorization or caller-origin checks. The attacker can therefore inject an arbitrary reward entry for itself. In the incident trace, the attacker-controlled helper 0xbf65cadb3f637e87271a4d9a62f03a634a61a19c called:
Reward::setReward(
0xbF65cADB3F637e87271A4D9a62F03a634A61A19C,
400000000000000000000000000,
400000000000000000000000000,
1000000000000000000
)
Reward::generateReward(1000000000000000000)
emit CoinReward(..., 8000000000000000000000000, ...)
Because generateReward computes release amounts directly from the attacker-provided record, the forged entry produces exactly 8,000,000 claimable tokens in waitRelease. The trace confirms this with getWaitReleaseCoin(attacker) returning 8000000000000000000000000.
The attacker then triggers the payout branch in BEP20Token._transfer by sending exactly 10000 raw token units to the token contract. That branch reads the forged pending reward, caps it only by the distributor balance, calls reward.releaseCoin(sender), and transfers the distributor inventory to the attacker. The exploit trace shows the decisive state transition:
BEP20Token::transfer(BEP20Token, 10000)
Reward::releaseCoin(attacker)
emit Transfer(
from: 0xE83d8a3C45b77d95C850C00aca53A57cE9D49314,
to: 0xbF65cADB3F637e87271A4D9a62F03a634A61A19C,
value: 8000000000000000000000000
)
From there, the exploit is simply monetization. The attacker approves PancakeSwap router 0x10ed43c718714eb63d5aa57b78b54704e256024e and dumps the drained tokens into the live token/USDT market. The trace shows swapExactTokensForTokensSupportingFeeOnTransferTokens, and the balance diff confirms that the attacker helper ends the transaction with 14697577434492608060644 raw USDT while the pair loses the same amount.
The identified adversary cluster is:
0x0740100ad48d22bc648901520a313b46557f8ada, which submitted the exploit transaction0xbf65cadb3f637e87271a4d9a62f03a634a61a19c, which received the fabricated reward, executed the dump, and retained the profitThe transaction sequence is a single adversary-crafted call:
Reward.setReward with attacker-chosen values.Reward.generateReward(1 ether), creating 8,000,000 pending tokens.10000 token units to BEP20Token, which causes reward.releaseCoin(sender) and a distributor-to-attacker transfer of nearly the entire mining inventory.The key trace fragment captures the whole ACT realization:
Reward::setReward(attacker, 4e26, 4e26, 1e18)
Reward::generateReward(1e18)
emit CoinReward(attacker, 8e24, ...)
BEP20Token::transfer(BEP20Token, 10000)
Reward::releaseCoin(attacker)
emit Transfer(distributor, attacker, 8e24)
Router::swapExactTokensForTokensSupportingFeeOnTransferTokens(...)
This sequence is permissionless. No privileged key, governance action, private orderflow, or attacker-side artifact from the original incident is required to reproduce the exploit semantics.
The distributor 0xe83d8a3c45b77d95c850c00aca53a57ce9d49314 lost its full 8,000,000 token reward inventory in the transaction. The downstream market impact was realized in the PancakeSwap pair, which lost 14697577434492608060644 raw USDT units to the attacker-controlled helper during liquidation.
The measured loss captured in the artifacts is:
USDT: 14697577434492608060644 raw units (14697.577434492608060644 USDT at 18 decimals)The gas payer EOA spent 805227000000000 wei in BNB, but the exploit still realized a large positive profit in the reference asset.
The validation and report rely on the following concrete evidence:
0xe15d6f7fa891c2626819209edf2d5ded6948310eaada067b400062aa022ce718BEP20Token / Reward source for 0x278ce7151bfd1b035e8bc99e15b4d9773969d4ed0xbcbcb0e7e28414e084c4a40c1ccc30b75629a7de0xe83d8a3c45b77d95c850c00aca53a57ce9d493140x875ac38bc56e2c6fbeda4354ac085cb94d0d2d2f0x10ed43c718714eb63d5aa57b78b54704e256024e