Ordinal DEX Rug Pull
What happened
ORDEX is an ERC20 token on Ethereum that charged a buy and sell tax and capped wallets at 2% of supply. Its transfer code contains a fee-exempt path in which a sender on the exempt list is debited nothing while the recipient is credited the full amount, so an exempt address can push tokens into the Uniswap pool without owning them. On 2023-12-25 PeckShield flagged ORDEX as down 100% after a swap of ORDEX for about 30.96 ETH (about 70.6K USD at the time).
De.Fi's REKT database lists the project as an exit scam / rug pull with a loss of 72,848 USD (30.95 WETH), and lists the deployer 0x45747155…43ec7A, the addresses 0x8a3F26Bb…252dCf, 0x69acbcdC…0A5403 and 0x1fBfE151…4f0991, and the transaction 0x0ebfe897…303397. De.Fi's text says the deployer and the scammer then sent funds to a single address and distributed them across several addresses. The Etherscan page for that transaction shows a swap of ORDEX for about 30.96 ETH by 0x8a3F26Bb…252dCf through the contract 0xea8180dE…839e31.
How it happened
- The constructor marks the deployer, the tax wallet passed to the constructor and the token contract itself as fee-exempt:
isFeeExempt[_owner] = true; isFeeExempt[_taxWallet] = true; isFeeExempt[address(this)] = true;. No function changes this mapping afterwards, and renouncing ownership does not remove it. createOrdinalTradecreates the pair and callsuniswapV2Router.addLiquidityETH{...}(address(this),balanceOf(address(this)),0,0,owner,block.timestamp). Theownerargument is the LP-token recipient, so the liquidity position is minted to the deployer, not locked or burned.enableOrdinalTradethen setsTradingOpen,feeEnabledandswapEnabledto true.- For a non-exempt holder the math is ordinary:
takeFeeis true, soamountX = rAmountis debited from the sender, and the recipient getsrAmountminus the tax. - For an exempt sender
takeFeeis false andfeeEnabledis true, soamountX = takeOrdinalAmountAfterFees(action, false, rAmount). Withtakefeefalse the percentage is1, and the function runsfeeAmounts = takefee ? feeAmounts : amounts * oxPercents;which makesfeeAmountsequal to the whole amount, so it returnsamounts.sub(feeAmounts)= 0. The sender is debited_balances[sender].sub(0, ...), which can never fail, while_balances[recipient].add(amountY)credits the fullrAmountbecauseamountY = rAmountfortakeFeefalse. The same call also addsamounts / 100to_balances[address(this)]. Balances can therefore be created without limit, and the fixed_totalSupplyof 100,000,000 stops describing the real circulating amount. - An exempt address that sends such tokens to the pair (directly or through the router, since
transferFromalso ends in_transferFrom) lets the pair swap them for ETH, drawing the ETH that buyers put in. This is what the code allows; the sources opened do not show which exempt address, if any, was used. - What happened: PeckShield's alert on 2023-12-25 reports ORDEX dropped 100% after 0xea8180dE…839e31 swapped ORDEX for 30.96 ETH, and De.Fi lists a loss of 72,848 USD.
Protocol details
Evidence
- report @PeckShieldAlert incident report twitter.com
- report PeckShieldAlert tweet on Ordinal Dex $ORDEX, 2023-12-25 twitter.com
- transaction Etherscan transaction 0x0ebfe897a6f86d5117deb1ef28707ba3942f4ea3a3c77bfe4c9efbcb3a303397 etherscan.io
- address Etherscan verified source, OrdinalDex 0x797885C0a6CfffCbc4D2e3C1ca0B4F07112dB6a3 etherscan.io
- address etherscan.io address 0x1fBf…0991 etherscan.io
- address etherscan.io address 0x4574…ec7A etherscan.io
- address etherscan.io address 0x69ac…5403 etherscan.io
- address etherscan.io address 0x8a3F…2dCf etherscan.io
- analysis Sourcify repository entry for OrdinalDex repo.sourcify.dev
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.