DarkProtocol Rug Pull

Reported loss $174K
Rugpull

What happened

DARK token rugpull occurred on Nov 23, 2023, with the deployer removing liquidity worth 174,334 USD from the PancakeSwap pool, according to De.Fi's REKT database. DARK is a BEP-20 token trading on PancakeSwap, and the same listing says the funds, 737.52 WBNB, were transferred to another EOA and then distributed between multiple addresses. Deployer: 0xb88028a1…c60832.

De.Fi lists the liquidity adding transaction as 0xbc8b80ce…e5903c and the liquidity removal transaction as 0x7a5f057b…e82069. PeckShield Alert posted that DARK had dropped 100% and that the address 0xb880...0832 had swapped DARK for 976.53 WBNB (about $230.8K). In the verified code, the token stores no balances itself: balanceOf, transfer and transferFrom all call a separate accounting contract that the owner can set, so what a holder owns and whether a transfer succeeds depend on code outside the verified source.

How it happened

What the code allows (from the verified source):

  1. The token contract has no balance mapping. balanceOf reads from another contract: return Accounting(accounting).balanceOf(who);. The constructor only emits a mint event (emit Transfer(address(0), msg.sender, _totalSupply); with _totalSupply = 1000000 * 10 ** 9;) and never writes a balance.
  2. Every move of tokens is delegated: transfer runs return Accounting(accounting).doTransfer(msg.sender, msg.sender, to, amount); and transferFrom runs return Accounting(accounting).doTransfer(msg.sender, from, to, amount);. The Transfer event is emitted before the call, so the log shows a transfer regardless of what the accounting contract does with the balances. That contract can approve, reject, charge or ignore any transfer, including a sell into the pair, and it is not part of the verified source.
  3. The owner chooses that contract: function setAccountingAddress(address accountingAddress) public { require(msg.sender == owner); accounting = accountingAddress; }. While the owner holds the role it can point accounting at any contract at any time.
  4. The owner role can be given up: function renounceOwnership() public { require(msg.sender == owner); ... owner = address(0); }. A current read of the chain shows owner as the zero address, so ownership has been renounced at some point; which transaction did this, and what accounting pointed to when it did, was not checked. After renouncing, accounting can no longer be changed, but the code the address points to still decides every balance and transfer.

(reported by PeckShield Alert and the De.Fi listing):

  1. PeckShield Alert posted that DarkProtocol $DARK had dropped 100%, that address 0xb880...0832 swapped 100,100,100,100,100,100,100.1001 DARK for 976.53 WBNB (about $230.8K), and that the rugpull token may share a name with legitimate projects. That DARK amount is far above the 1,000,000 DARK _totalSupply set in the constructor, which fits balances being decided by the external accounting contract rather than by the token's own supply; the accounting contract was not read.
  2. De.Fi's listing says the deployer removed liquidity worth 174,334 USD (737.52 WBNB) from the PancakeSwap pool and moved the funds to another EOA, from which they were spread across multiple addresses. The liquidity add and removal transactions were not opened here.

Protocol details

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

Build your security review skills

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