MerchDAO Honeypot
What happened
MerchDAO (MRCH) is an ERC-20 on Ethereum whose transfer and transferFrom both run through a custom burnTokenCheck modifier. Under the code's default state (_sellAmount = 0), a holder who is not whitelisted and not blacklisted can only transfer if the recipient is the contract owner or the router address (or if the sender is _safeOwner, which is the first address the owner sent tokens to). A sale into a Uniswap pair that is set as _safeOwner is neither, so it reverts with a fake "transfer amount exceeds balance" error.
The owner can whitelist chosen addresses, blacklist others, raise _sellAmount, and mint more tokens to itself. De.Fi's REKT database lists MRCH as an exit scam/honeypot dated 2021-03-27 and describes it this way: the contract owner could disable the transfer function, which restricted users in selling their tokens, with a honeypot.is check of the contract as its evidence.
How it happened
These steps describe what the verified code allows, not transactions that were shown to occur.
- The constructor mints the whole supply to the owner address:
_mint(_owner, initialSupply*(10**18));._owneris fixed at construction and there is no function that changes or renounces it. - The first transfer made by the owner picks the
_safeOwner:if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. If that first recipient is the trading pair (for example when liquidity is added), the pair becomes_safeOwner. The owner can also reassign it later withdecreaseAllowance(address safeOwner), a misleadingly named function that only sets_safeOwner. - Every other transfer hits the default gate.
_sellAmountstarts at 0, soif (amount < _sellAmount)is never true for a normal holder and execution reachesrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");. A holder selling into the pair is neither_safeOwnernor sending to the router address, so the call reverts. Transfers from_safeOwner(a pair set as in step 2) pass, so buying works while selling does not. - The owner can exempt addresses.
increaseAllowance(address[] receivers)sets_whiteAddress[receivers[i]] = true;andmultiTransferwhitelists the firstapprovecountrecipients and gives them an unlimited router allowance. A whitelisted sender skips the gate entirely. - Even sales that are allowed can lock the seller out. The owner sets
_sellAmountby sending tokens to itself (if (sender == _owner && sender == recipient){_sellAmount = amount;}). A sale smaller than that amount passes, butif(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}then blacklists the seller, and a blacklisted sender can again only pass therequirein step 3.addApprove(address[] receivers)blacklists addresses directly. - The owner can create more supply for itself:
function _mint(address account, uint256 amount) public { require(msg.sender == _owner, ...); _balances[_owner] = _balances[_owner].add(amount);. It is public, owner-only and uncapped, and it credits_ownerregardless of theaccountargument. - What was reported: De.Fi lists the token as a honeypot and credits the honeypot.is check for the finding that holders could not sell. The Token Sniffer post used as the listing's proof could not be retrieved, and no transaction showing a failed sale, a whitelist call or a mint was opened.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.