AlaskanDoge 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
- 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.ownerLockis not set there, so it starts asfalseand the checks below do nothing until the owner enables it. - The owner enables the trap with
function setOwnerLock(bool _ownerLock) onlyOwner public { ownerLock = _ownerLock; }; it can switch it off the same way.transfercarriestransferMiddleware(recipient,ownerLock)andtransferFromcarriestransferFromMiddleware(sender, recipient, ownerLock). - With the lock on, the first
transferFromwhileprevOwneris empty stores its recipient without any check:if(from != address(0) && prevOwner == address(0)) prevOwner = to;. A router liquidity deposit pulls tokens withtransferFrom, so in that flow the pair would becomeprevOwner; the contract itself never names the pair. - After that, every
transferFromrunselse require(to != prevOwner, "Order TransferFromMiddleware");and everytransferrunsrequire(to != prevOwner, "Handling Request");. A router sell callstransferFrom(holder, pair, amount), so ifprevOwneris the pair the call reverts before_balances[sender]is debited. The owner is not exempt from these requires. - 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 overwritesprevOwner. Pointing it at an unused address lifts the block, and setting it to the zero address re-arms the capture in step 3. TheonlyOwnercheck isrequire((_owner == _msgSender() || _owner == prevOwner), ...), so it also passes for any caller wheneverprevOwnerequals_owner. - Other code in the contract is not part of the block:
_transfercaps non-owner transfers at_maxTxAmount = _totalSupply / 2, and the publicburn(uint _percent)burns a percentage of total supply from the caller's own balance.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.