All incidents

RLToken Incentive Drain

Share
Sep 30, 2022 10:43 UTCAttackLoss: 758,474.18 RL, 9,028.5 USDTPending manual check6 exploit txWindow: 13h 14m
Estimated Impact
758,474.18 RL, 9,028.5 USDT
Label
Attack
Exploit Tx
6
Addresses
3
Attack Window
13h 14m
Sep 30, 2022 10:43 UTC → Sep 30, 2022 23:57 UTC

Exploit Transactions

TX 1BSC
0x7f1f7ce49898eb45af498a090bbd6dd056e19e945360ca3fef6733a34763b43b
Sep 30, 2022 10:43 UTCExplorer
TX 2BSC
0xe8851aa6172b0c4d0a41ce52a1249b01d33699747c0a9f2d0ca31b5fddc99ec5
Sep 30, 2022 10:45 UTCExplorer
TX 3BSC
0xf91478b3a31b8f75913ed4a0de53bdf06653c6603a971afd9705a8523d1d207a
Sep 30, 2022 10:53 UTCExplorer
TX 4BSC
0x0d786162665e73c35afc7df3982836557647f9a6f0246aa172971cd56dc8872b
Sep 30, 2022 10:53 UTCExplorer
TX 5BSC
0xe7aab6599706844ac13b0237e60f5c985b0446f947f637b230915086f94b0950
Sep 30, 2022 10:53 UTCExplorer
TX 6BSC
0xe15d261403612571edf8ea8be78458b88989cf1877f0b51af9159a76b74cb466
Sep 30, 2022 23:57 UTCExplorer

Victim Addresses

0x4bbfae575dd47bcfd5770ab4bc54eb83db088888BSC
0x335ddce3f07b0bdafc03f56c1b30d3b269366666BSC
0xd9578d4009d9cc284b32d19fe58ffe5113c04a5eBSC

Loss Breakdown

758,474.18RL
9,028.5USDT

Similar Incidents

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:

  1. Transaction 0x7f1f7ce49898eb45af498a090bbd6dd056e19e945360ca3fef6733a34763b43b deployed attacker contract 0x5efd021ab403b5b6bbd30fd2e3c26f83f03163d4.
  2. Transaction 0xe8851aa6172b0c4d0a41ce52a1249b01d33699747c0a9f2d0ca31b5fddc99ec5 transferred inventory into the attacker contract.
  3. Transactions 0xf91478b3a31b8f75913ed4a0de53bdf06653c6603a971afd9705a8523d1d207a, 0x0d786162665e73c35afc7df3982836557647f9a6f0246aa172971cd56dc8872b, and 0xe7aab6599706844ac13b0237e60f5c985b0446f947f637b230915086f94b0950 called selector 0xa1e2c7e5 three times to batch-create the helper network.
  4. Transaction 0xe15d261403612571edf8ea8be78458b88989cf1877f0b51af9159a76b74cb466 borrowed 453357207388182310097710 USDT 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:

  • 758474177835016259943400 RL drained from RLLpIncentive.
  • 9028495175881180406851 USDT 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: RLToken 0x4bbfae575dd47bcfd5770ab4bc54eb83db088888, RLLpIncentive 0x335ddce3f07b0bdafc03f56c1b30d3b269366666, RL/USDT pair 0xd9578d4009d9cc284b32d19fe58ffe5113c04a5e
  • Supporting evidence: collected verified source for RLToken and LpIncentive, attacker EOA transaction history, attacker and helper creation metadata, seed exploit trace, and seed exploit balance diff