RLToken Incentive Drain
Exploit Transactions
0x7f1f7ce49898eb45af498a090bbd6dd056e19e945360ca3fef6733a34763b43b0xe8851aa6172b0c4d0a41ce52a1249b01d33699747c0a9f2d0ca31b5fddc99ec50xf91478b3a31b8f75913ed4a0de53bdf06653c6603a971afd9705a8523d1d207a0x0d786162665e73c35afc7df3982836557647f9a6f0246aa172971cd56dc8872b0xe7aab6599706844ac13b0237e60f5c985b0446f947f637b230915086f94b09500xe15d261403612571edf8ea8be78458b88989cf1877f0b51af9159a76b74cb466Victim Addresses
0x4bbfae575dd47bcfd5770ab4bc54eb83db088888BSC0x335ddce3f07b0bdafc03f56c1b30d3b269366666BSC0xd9578d4009d9cc284b32d19fe58ffe5113c04a5eBSCLoss Breakdown
Similar Incidents
Public Liquidity Trigger Drain
35%OKC Flash-LP Reward Drain
34%CS Pair Balance Burn Drain
34%EEECOIN Public Helper LP Drain
34%AM Pending-Burn Drain
33%QiQi Reward Quote Override Drain
33%Root Cause Analysis
RLToken Incentive Drain
1. Incident Overview TL;DR
On BSC, attacker EOA 0x08e08f4b701d33c253ad846868424c1f3c9a4db3 prepared an attacker contract and helper network, then executed exploit transaction 0xe15d261403612571edf8ea8be78458b88989cf1877f0b51af9159a76b74cb466 to drain RL rewards from RLLpIncentive at 0x335ddce3f07b0bdafc03f56c1b30d3b269366666. The exploit used a DODO flash loan, rotated the same Pancake RL/USDT LP position across attacker-controlled helper addresses with stale reward checkpoints, repeatedly triggered reward settlement, and exited with a net gain of 9028495175881180406851 USDT units.
The root cause is stale per-address LP reward accounting. LpIncentive stores reward checkpoints in usersIndex, but it never synchronizes or settles those checkpoints when Pancake LP tokens move between holders. RLToken.transfer and RLToken.transferFrom call incentive.distributeAirdrop(...) on transfer participants, so once the attacker moves LP into a helper address with an old checkpoint, a zero-value RL transfer can force reward settlement against that helper's current LP balance and duplicate emissions that should only have been claimable once.
2. Key Background
RLToken at 0x4bbfae575dd47bcfd5770ab4bc54eb83db088888 is the reward token, and the RL/USDT Pancake pair at 0xd9578d4009d9cc284b32d19fe58ffe5113c04a5e is the LP token whose holders receive emissions. RLLpIncentive tracks a global emission index plus a per-user usersIndex checkpoint and computes accrued rewards as the index delta multiplied by the holder's current LP balance.
The critical design assumption is wrong: LP ownership is transferable, but reward checkpoints are pinned to addresses. If LP is moved from one address to another without synchronizing checkpoints, the destination can inherit a large LP balance together with an old usersIndex, making the same elapsed emissions claimable again.
The RL token itself exposes the settlement path during ordinary token transfers:
function transfer(address to, uint256 amount) public virtual override returns (bool) {
if (to != address(pancakeSwapV2Pair) && to != address(pancakeSwapV2Router)) {
incentive.distributeAirdrop(to);
}
if (msg.sender != address(pancakeSwapV2Pair) && msg.sender != address(pancakeSwapV2Router)) {
incentive.distributeAirdrop(msg.sender);
}
return super.transfer(to, amount);
}
This means an attacker can move LP first, then use an RL transfer to force distributeAirdrop on the new holder.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is an attack-class accounting flaw in the incentive system, not a privileged-admin failure and not a pure MEV opportunity. RLLpIncentive calculates rewards from address-local checkpoints instead of LP-position ownership. When LP tokens are transferred, the contract neither settles rewards for the sender nor initializes the receiver's checkpoint to the current global index. As a result, a receiver with stale or zero-initialized reward state can receive a large LP balance and immediately claim emissions accumulated before it ever held that LP. RLToken.transfer and transferFrom make this reachable because both functions call incentive.distributeAirdrop(...) for transfer participants before normal token side effects complete. The explicit broken invariant is: each unit of LP should earn each emission interval exactly once regardless of how often the LP is transferred. The concrete breakpoint is LpIncentive.getUserUnclaimedRewards, which uses the holder's current LP balance against usersIndex[user] without any LP-transfer synchronization.
4. Detailed Root Cause Analysis
The vulnerable reward formula is in LpIncentive.sol:
function getUserUnclaimedRewards(address user) public view returns (uint256) {
(uint256 newIndex,) = getNewIndex();
uint256 userIndex = usersIndex[user];
if (userIndex >= newIndex || userIndex == 0) {
return userUnclaimedRewards[user];
} else {
return userUnclaimedRewards[user] + (newIndex - userIndex) * lpToken.balanceOf(user) / PRECISION;
}
}
This function reads the holder's current LP balance at settlement time. No code in LpIncentive intercepts Pancake LP transfers, and no code migrates checkpoints when LP moves. distributeAirdrop then sets usersIndex[user] = globalAirdropInfo.index only after computing and paying rewards, so any stale helper address can monetize the full gap between its old checkpoint and the new global index once LP is moved in.
The exploit trace confirms the live transaction used flash-loan capital rather than prefunded attacker balances:
0xD7B7218D778338Ea05f5Ecce82f86D365E25dBCE::flashLoan(
0,
453357207388182310097710,
0x5EfD021Ab403B5b6bBD30fd2E3C26f83f03163d4,
...
)
...
0x5EfD021Ab403B5b6bBD30fd2E3C26f83f03163d4::DPPFlashLoanCall(...)
The same trace shows the attacker contract 0x5efd021ab403b5b6bbd30fd2e3c26f83f03163d4 iterating through many helper contracts and repeatedly calling the victim incentive after LP movement. A representative helper created by the attacker factory is 0x865f5c5d2632a8d87ebc3771455e63a9d27bddc3, whose creation metadata ties it back to the attacker factory.
The balance diff of the exploit transaction proves the victim-side depletion and attacker-side realization:
{
"token": "0x4bbfae575dd47bcfd5770ab4bc54eb83db088888",
"holder": "0x335ddce3f07b0bdafc03f56c1b30d3b269366666",
"delta": "-758474177835016259943400"
}
{
"token": "0x55d398326f99059ff775485246999027b3197955",
"holder": "0x08e08f4b701d33c253ad846868424c1f3c9a4db3",
"delta": "9028495175881180406851"
}
These artifacts match the claimed exploit path: duplicated RL emissions were extracted from RLLpIncentive, then sold through the RL/USDT pair for net USDT profit.
5. Adversary Flow Analysis
The attacker lifecycle is fully on-chain and permissionless:
- Transaction
0x7f1f7ce49898eb45af498a090bbd6dd056e19e945360ca3fef6733a34763b43bdeployed attacker contract0x5efd021ab403b5b6bbd30fd2e3c26f83f03163d4. - Transaction
0xe8851aa6172b0c4d0a41ce52a1249b01d33699747c0a9f2d0ca31b5fddc99ec5transferred inventory into the attacker contract. - Transactions
0xf91478b3a31b8f75913ed4a0de53bdf06653c6603a971afd9705a8523d1d207a,0x0d786162665e73c35afc7df3982836557647f9a6f0246aa172971cd56dc8872b, and0xe7aab6599706844ac13b0237e60f5c985b0446f947f637b230915086f94b0950called selector0xa1e2c7e5three times to batch-create the helper network. - Transaction
0xe15d261403612571edf8ea8be78458b88989cf1877f0b51af9159a76b74cb466borrowed453357207388182310097710USDT by flash loan, bought RL, minted RL/USDT LP, transferred that LP through attacker-controlled helpers, triggered repeated reward settlement, removed liquidity, sold RL back into USDT, repaid the flash loan, and forwarded profit to the attacker EOA.
The attacker did not need privileged ownership of RLToken or RLLpIncentive. The only requirements were public on-chain state, deployable helper contracts, and sufficient market liquidity to cycle RL, USDT, and LP through the vulnerable reward path.
6. Impact & Losses
The measurable losses are:
758474177835016259943400RL drained fromRLLpIncentive.9028495175881180406851USDT net removed from the RL/USDT pool and realized by the attacker EOA.
The seed balance diff also records the attacker EOA's native-gas cost as 155623900000000000 wei BNB. The exploit remained profitable after that gas spend. The affected parties were the reward inventory held by RLLpIncentive and the RL/USDT pool counterparties who absorbed the unwind into USDT.
7. References
- Exploit transaction:
0xe15d261403612571edf8ea8be78458b88989cf1877f0b51af9159a76b74cb466 - Attacker deployment transaction:
0x7f1f7ce49898eb45af498a090bbd6dd056e19e945360ca3fef6733a34763b43b - Helper preparation transactions:
0xf91478b3a31b8f75913ed4a0de53bdf06653c6603a971afd9705a8523d1d207a,0x0d786162665e73c35afc7df3982836557647f9a6f0246aa172971cd56dc8872b,0xe7aab6599706844ac13b0237e60f5c985b0446f947f637b230915086f94b0950 - Victim contracts:
RLToken0x4bbfae575dd47bcfd5770ab4bc54eb83db088888,RLLpIncentive0x335ddce3f07b0bdafc03f56c1b30d3b269366666, RL/USDT pair0xd9578d4009d9cc284b32d19fe58ffe5113c04a5e - Supporting evidence: collected verified source for
RLTokenandLpIncentive, attacker EOA transaction history, attacker and helper creation metadata, seed exploit trace, and seed exploit balance diff