Prophet Project Rug Pull
What happened
This PRY token on Ethereum (contract 0xd7d84e9d…24b325) was an imitation of the Prophecy project: on 2020-12-18 the project's official account warned that a token using its old domain was a scam and gave its own contract as 0x3c81d482…d00521. The imitation's source gives its owner a hidden transfer restriction that can stop non-whitelisted holders from selling, while the owner's wallet is exempt.
On the same day the deployer 0xb3408AD8…B9182A added liquidity on Uniswap V2 (200 ETH and 10,000,000 PRY, receiving 44,721.3595 UNI-V2 LP tokens; 08:14 UTC) and about four hours later removed liquidity in a single transaction (12:20 UTC), receiving 270.847 ETH and 7,396,670.75 PRY. De.Fi's REKT database lists the incident as an exit scam/rugpull with $46,356 lost.
Add liquidity: Tx 1 0x1078…7d8d Remove liquidity: Tx 2 0xf634…0750
How it happened
- The constructor sets both control addresses to the deployer and mints the full supply to it:
_address0 = owner; _address1 = owner; _mint(_address0, initialSupply*(10**18));. While the two are equal, transfers are unrestricted. - The
safeCheckmodifier changes that on the owner's first outgoing transfer:if(sender==_address0 && _address0==_address1){_address1 = recipient;}. The deployer's first transfer to the Uniswap pool (the liquidity deposit, if no earlier transfer was made) therefore makes the pool the new_address1, which is the only way this restriction switches on. - From then on, any transfer where neither side is the owner must satisfy
if(recipient != _address0 && sender != _address0 && _address0!=_address1 && amount > _zero){require(sender == _address1 ||sender==_router || _Addressint[sender], "ERC20: transfer from the zero address");}. A buy comes from the pool (sender == _address1) and passes. A sale through the router is atransferFromwhosesenderis the holder, not the router or the pool, so it reverts unless the holder is whitelisted. The holder is debited nothing because the whole transfer fails. - The owner controls the exceptions:
upint(address addressn,uint8 Numb)whitelists an address,intnum(uint8 Numb)sets_zeroso amounts up to that size skip the check, andints(address addressn)replaces_address1.multiaddress(uint8 AllowN,address[] memory receivers, uint256[] memory amounts)sends tokens from the owner to a list and, for the firstAllowNreceivers, whitelists them and gives the router an unlimited allowance:_Addressint[receivers[i]] = true; _approve(receivers[i], _router, _valuehash);. All of these checkmsg.sender == _address0. Transfers to the owner (recipient != _address0) are not restricted. No function renounces the owner role. - What happened, per the transactions: on 2020-12-18 at 08:14 UTC the deployer called
addLiquidityETHon Uniswap V2 Router 2 with 200 ETH and 10,000,000 PRY and received 44,721.3595 LP tokens; at 12:20 UTC the same address calledremoveLiquidityETHWithPermitand received 270.847373774939170577 ETH and 7,396,670.7539 PRY from the pool. De.Fi's REKT database lists $46,356 lost. The sources opened do not show whether any holder's sale was blocked or which whitelist calls the owner made; the restriction above is what the code allows.
Protocol details
Evidence
- report Prophecy PRY warning about an imitation token, 2020-12-18 twitter.com
- report De.Fi REKT database listing for Prophet Project (as imported in meta.json) twitter.com
- transaction Etherscan transaction 0x107819b9e3060395e5bd4f45fd58e2491957c492370a8ad309bb6016bff87d8d (addLiquidityETH) etherscan.io
- transaction Etherscan transaction 0xf634b34feddbc2deca1a2b8018b6bc30e6b62398f09d3cc1001511ee2a7e0750 (removeLiquidityETHWithPermit) etherscan.io
- address Etherscan verified source: prophetproject.io (PRY) 0xd7d84e9df527882149d9c5908a3937107724b325 etherscan.io
- analysis Web Archive web.archive.org
- analysis Sourcify match: ERC20.sol repo.sourcify.dev
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.