GDAO Rug Pull

Reported loss $90K
Rugpull

What happened

GDAO (contract 0xeB0dafA8…fDb904) is a BEP-20 token trading on PancakeSwap; CertiK's alert of 2022-10-28 called it an exit scam with 96% slippage and about 90K USD taken, and warned that roughly 30 other tokens share the GDAO symbol, so the address must be checked. De.Fi's listing, which describes it as a copycat token, says the deployer sent 1,000,000 GDAO to an externally owned account that drained the liquidity and moved the funds through TransitSwap for a 90,302 USD profit. The verified source gives the owner several powers over holders: it can lock any address so it cannot transfer or approve, close the buy gate with a lock time, and set recorded purchase amounts that route a holder's sales through a fee branch, while the token contract contains no liquidity lock; no opened source shows any of these being used.

Addresses listed by De.Fi: 0x7f12728d…6d5d71 and 0xb67b4aa8…878c77. Token transfer transaction listed by De.Fi: 0xb0ed3561…85dbf3.

How it happened

  1. The owner can freeze a holder: function setLockAccount(address target, uint256 lockenddate) public onlyOwner { writes an end time, and transfer, approve, increaseAllowance and decreaseAllowance check the caller while transferFrom checks the token holder: require(block.timestamp>lockAccount[sender]. A router sale calls transferFrom with the holder as sender, so a locked holder cannot sell, and cannot approve the router either.
  2. The trade logic keys on capitalPoolAddr, which is address(0) until function setCapitalPoolAddr(address _address) public onlyOwner{ sets it. For buys from that address the contract requires require(locktime<=block.timestamp || (getNftWhites(to)==true && buyUamount <= 301*10**18), "Purchase after adding whitelist, please purchase 300U quota");, and the owner can push the date out with function setLockTimeOut(uint256 endtime) public onlyOwner {, leaving only owner-whitelisted buyers with a 300 USDT limit.
  3. For sales into capitalPoolAddr, if(balance.sub(amount) < totalBuyAmount) { sends the sale through a fee branch that splits the amount among the dead address, marketAddr and changeAddr, and forwards to the pool only uint256 toAmount = amount1.mul(sellMulFee).div(10000);. The seller is debited each slice. The rates are constructor arguments not visible in the source, and the owner can force this branch for any address with function setBuyAmountListOut(address _address,uint256 _amount) public onlyOwner {.
  4. Fee-exempt status is not protected: function excludeFromFees(address account, bool excluded) public { has no access modifier, so anyone can mark any address exempt and skip the fee branch.
  5. The token contract has no function that locks or burns liquidity-provider tokens: the addLiquidity helper sends LP to address(0) but is reachable only through if(AmountLiquidityFee > 0) swapAndLiquify(AmountLiquidityFee);, and AmountLiquidityFee is a private variable that is never assigned. Custody of the LP tokens cannot be read from the source.
  6. What happened, per CertiK and De.Fi: the pool was drained, CertiK measured 96% slippage and about 90K USD was taken, with 1,000,000 GDAO sent from the deployer to an externally owned account first. No opened source shows which of the owner functions above, if any, was called.

Protocol details

Classification Token
Protocol Type Exit Scam/Rugpull
Affected asset / contract GDAO

Build your security review skills

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