SeaShell.Financ Rug Pull
What happened
SeaShell.Finance was a yield farm on BNB Smart Chain: a MasterChef contract that paid out SHELL tokens for staked LP tokens. De.Fi's REKT database lists SHELL as an exit scam / rug pull dated 2021-08-26 and gives no loss figure. The verified farm code contains three controls that work against stakers.
updatePool contains a line that, when called by feeAddress, transfers the pool's entire LP token balance to feeAddress. withdraw and emergencyWithdraw debit the user's full recorded stake but pay back only one hundred-thousandth of it. withdraw also pays only half of the pending SHELL reward.
How it happened
- Stakers deposit LP tokens through
deposit. The contract takes the full amount (pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);) and creditsuser.amount, minus anydepositFeeBPfee, which the owner can set up to 10000 (100%) throughadd/set(require(_depositFeeBP <= 10000, "set: invalid deposit fee basis points");).feeAddressis set to the deployer in the constructor. updatePoolis public and runs insidedepositandwithdraw. Before minting rewards it containsif(address(msg.sender) == feeAddress){pool.lpToken.transfer(feeAddress,lpSupply);}, wherelpSupply = pool.lpToken.balanceOf(address(this)). WhenfeeAddressitself callsupdatePool(directly, or through a function that calls it) on a pool with stake and a non-zeroallocPoint, the full LP balance of that pool moves tofeeAddress.user.amountis not reduced, so the records still show the stake after the tokens are gone.- A regular staker's exit is also reduced by the code.
withdrawrunsuser.amount = user.amount.sub(_amount);for the full amount and thenpool.lpToken.safeTransfer(address(msg.sender), _amount.div(1e5));, returning 0.001% of the amount withdrawn. For amounts below 100000 base units the integer division returns zero. emergencyWithdrawhas the same shape:user.amount = 0;followed bypool.lpToken.safeTransfer(address(msg.sender), amount.div(1e5));.- Rewards are cut as well:
withdrawsendssafeShellTransfer(msg.sender, pending.div(2));, half of the pending amount (after any early-harvest penalty), and then resetsrewardDebt, so the other half is not paid to the user.updatePoolalso mints a further 1% of each reward todevAddress(shell.mint(devAddress, shellReward.div(100)););ShellToken.mintisonlyOwner, so that works only while the farm owns the token, which the supplied source does not show. - What happened: De.Fi's REKT database lists SHELL as an exit scam / rug pull dated 2021-08-26 with no funds-lost value. No transaction was opened showing the
updatePoolline being triggered byfeeAddressor any user's reduced withdrawal, so the steps above describe what the code allowed, not a confirmed sequence.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.