AlaskanDoge Honeypot

Reported loss Not disclosed
BNB Chain
Honeypot

What happened

AlaskanDoge (ADG) is a 9-decimal BEP20 token on BNB Smart Chain with an owner-controlled switch, ownerLock, that is off by default. When the owner turns it on with setOwnerLock(true), transfer and transferFrom start rejecting any recipient equal to a stored address called prevOwner, which the contract fills from the first transferFrom recipient and the owner can also set directly through transferOwnership. If prevOwner holds the liquidity pair, a holder's sell into the pair reverts with "Order TransferFromMiddleware".

De.Fi's REKT database lists ADG as an Exit Scam/Honeypot dated 2021-05-12 and says the contract owner could disable the transfer function, which restricted users in selling their tokens. The contract was deployed on 2021-05-12 at 17:47:27 UTC by 0x931d36c7…fFea7d in transaction 0xd3f35fff…2fd8e3.

How it happened

  1. The constructor gives the whole supply to the deployer: _totalSupply = 1 * 10**12 * 10**9; and _balances[msg.sender] = _totalSupply;, which is 10^12 tokens at 9 decimals. ownerLock is not set there, so it starts as false and the checks below do nothing until the owner enables it.
  2. The owner enables the trap with function setOwnerLock(bool _ownerLock) onlyOwner public { ownerLock = _ownerLock; }; it can switch it off the same way. transfer carries transferMiddleware(recipient,ownerLock) and transferFrom carries transferFromMiddleware(sender, recipient, ownerLock).
  3. With the lock on, the first transferFrom while prevOwner is empty stores its recipient without any check: if(from != address(0) && prevOwner == address(0)) prevOwner = to;. A router liquidity deposit pulls tokens with transferFrom, so in that flow the pair would become prevOwner; the contract itself never names the pair.
  4. After that, every transferFrom runs else require(to != prevOwner, "Order TransferFromMiddleware"); and every transfer runs require(to != prevOwner, "Handling Request");. A router sell calls transferFrom(holder, pair, amount), so if prevOwner is the pair the call reverts before _balances[sender] is debited. The owner is not exempt from these requires.
  5. The owner can also set the blocked address directly: function transferOwnership(address newOwner) public onlyOwner { prevOwner = newOwner; }. Despite its name it does not change _owner; it only overwrites prevOwner. Pointing it at an unused address lifts the block, and setting it to the zero address re-arms the capture in step 3. The onlyOwner check is require((_owner == _msgSender() || _owner == prevOwner), ...), so it also passes for any caller whenever prevOwner equals _owner.
  6. Other code in the contract is not part of the block: _transfer caps non-owner transfers at _maxTxAmount = _totalSupply / 2, and the public burn(uint _percent) burns a percentage of total supply from the caller's own balance.

Protocol details

Classification Token
Protocol Type Exit Scam/Honeypot
Affected asset / contract ADG

Build your security review skills

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