Rimau Finance Rug Pull
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.
- Users stake LP tokens with
deposit(_pid, _amount), which runspool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);and creditsuser.amount. The LP tokens sit in the MasterChef contract. - The owner can call
setMigrator(IMigratorChef _migrator), which isonlyOwnerand doesmigrator = _migrator;with no validation of the target. - Anyone can then call
migrate(_pid). It readsuint256 bal = lpToken.balanceOf(address(this));, grants the migrator an allowance of everything withlpToken.safeApprove(address(migrator), bal);and callsIBEP20 newLpToken = migrator.migrate(lpToken);. - 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 withpool.lpToken = newLpToken;, and laterwithdrawandemergencyWithdrawcalls pay out of that token instead of the original LP. - Separately,
updateMultiplier(uint256 multiplierNumber)setsBONUS_MULTIPLIER = multiplierNumber;with no cap, andupdatePool()mintscake.mint(devaddr, cakeReward.div(10));pluscake.mint(address(syrup), cakeReward);scaled by it. - 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), sosetMigratorandupdateMultiplierare no longer callable today. If a migrator was set before that,migratestays callable by anyone.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.