Rimau Finance Rug Pull

Reported loss Not disclosed
Rugpull

What happened

Rimau Finance's MasterChef farm (0x1ef43a30…250B51 on BNB Smart Chain) contains the migrator hook from the original MasterChef template. The owner can call setMigrator(IMigratorChef _migrator) to point it at any contract, and the public migrate(uint256 _pid) then approves that contract to take the farm's entire LP-token balance for the pool. The only check afterwards is that the new LP token reports the same balance for the farm.

The owner also controls an unbounded reward multiplier. De.Fi's REKT database lists Rimau Finance as an exit scam/rugpull dated 2021-08-12 with no loss figure. The owner role reads as the zero address in a 2026-09-30 call, so ownership has been renounced at some point; the source does not show whether a migrator was set before then.

How it happened

These steps describe what the code allows, not a recorded sequence of transactions.

  1. Users stake LP tokens with deposit(_pid, _amount), which runs pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount); and credits user.amount. The LP tokens sit in the MasterChef contract.
  2. The owner can call setMigrator(IMigratorChef _migrator), which is onlyOwner and does migrator = _migrator; with no validation of the target.
  3. Anyone can then call migrate(_pid). It reads uint256 bal = lpToken.balanceOf(address(this));, grants the migrator an allowance of everything with lpToken.safeApprove(address(migrator), bal); and calls IBEP20 newLpToken = migrator.migrate(lpToken);.
  4. The migrator is external code, so it can pull the approved LP tokens out of the farm. The only guard is require(bal == newLpToken.balanceOf(address(this)), "migrate: bad");, which trusts the balance reported by whatever token the migrator returns. The pool is then re-pointed with pool.lpToken = newLpToken;, and later withdraw and emergencyWithdraw calls pay out of that token instead of the original LP.
  5. Separately, updateMultiplier(uint256 multiplierNumber) sets BONUS_MULTIPLIER = multiplierNumber; with no cap, and updatePool() mints cake.mint(devaddr, cakeReward.div(10)); plus cake.mint(address(syrup), cakeReward); scaled by it.
  6. The owner role can be given up with renounceOwnership(). The contract owner reads as 0x00000000…000000 in an eth_call on 2026-09-30 (current state, not historical), so setMigrator and updateMultiplier are no longer callable today. If a migrator was set before that, migrate stays callable by anyone.

Protocol details

Classification Token
Protocol Type Exit Scam/Rugpull
Affected asset / contract Rimau Finance
Protocol links Website

Build your security review skills

Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.