← Back to Blog

Rug Pull Detection: On-Chain Red Flags in Smart Contract Code

2026-04-17 rug-pull solidity security erc-20 defi token audit red-flags 2026

Rug pulls remain the most common form of crypto theft by volume. In 2025, rug pull scams accounted for over $2.8B in losses — most from tokens that had auditable smart contract code containing obvious red flags that no one checked.

This post is for both developers (don't write these patterns) and users (know what to look for before buying).


What Makes a Rug Pull Possible

A rug pull requires that the deployer — or someone they control — can do at least one of:

  1. Drain liquidity directly (withdraw LP tokens they control)
  2. Mint unlimited tokens to dilute investors
  3. Prevent selling (honeypot — let people buy, block all sells)
  4. Pause or freeze user funds
  5. Upgrade the contract to add any of the above

Each of these has a corresponding code pattern that can be detected in the source.


Red Flag 1: Hidden or Unrestricted Mint Function

// RED FLAG: owner can mint unlimited tokens at any time
contract ScamToken is ERC20, Ownable {
    constructor() ERC20("ScamCoin", "SCAM") {
        _mint(msg.sender, 1_000_000 * 1e18);
    }

    // This one function enables infinite dilution
    function mint(address to, uint256 amount) external onlyOwner {
        _mint(to, amount);
    }
}

A token with an owner-callable mint() function can have its supply inflated to infinity at any point. The initial 1M token supply becomes worthless when the deployer mints 1 trillion and sells.

What to look for:
- Any function calling _mint() that isn't in the constructor
- mint() or mintTo() functions with onlyOwner modifier
- Uncapped supply — no MAX_SUPPLY check

Safer pattern:

// Fixed supply — minting only in constructor
constructor() ERC20("SafeToken", "SAFE") {
    _mint(msg.sender, 21_000_000 * 1e18);
    // No mint function — supply is fixed forever
}

Red Flag 2: Owner-Controlled Blacklist / Transfer Block

// RED FLAG: owner can prevent any address from selling
contract HoneypotToken is ERC20, Ownable {
    mapping(address => bool) public blacklisted;

    function blacklist(address account) external onlyOwner {
        blacklisted[account] = true;
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
        require(!blacklisted[from], "Address blacklisted");
        super._beforeTokenTransfer(from, to, amount);
    }
}

The deployer lets buyers accumulate tokens, then blacklists the DEX router address or individual buyer addresses before the planned dump. Buyers find they can't sell while the deployer exits.

Variant: whitelist-only transfer (even more restrictive):

// ALSO RED FLAG: only whitelisted addresses can transfer
mapping(address => bool) public whitelisted;
bool public transfersLocked = true;

function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
    if (transfersLocked) {
        require(whitelisted[from] || from == address(0), "Transfers locked");
    }
}

If transfersLocked starts as true and only the owner can call setWhitelist(), this is a trap.


Red Flag 3: High Transfer Tax with Owner-Adjustable Rate

// RED FLAG: owner can set fee to 99% at any time
contract TaxToken is ERC20, Ownable {
    uint256 public buyFee = 5;   // 5%
    uint256 public sellFee = 5;  // 5%
    address public feeWallet;

    function setFees(uint256 _buy, uint256 _sell) external onlyOwner {
        // No upper bound — owner can set to 100%
        buyFee = _buy;
        sellFee = _sell;
    }

    function _transfer(address from, address to, uint256 amount) internal override {
        uint256 fee = (to == pair) ? (amount * sellFee / 100) : 0;
        super._transfer(from, feeWallet, fee);
        super._transfer(from, to, amount - fee);
    }
}

Rug pull pattern: launch with 5% tax to appear legitimate, then raise sellFee to 95% just before the dump. Everyone who tries to sell loses 95% to the fee wallet (controlled by deployer).

What to look for:
- Fee-setting functions with no upper bound (e.g., no require(_sell <= 10, "Max 10%"))
- Fees that apply asymmetrically to sells but not buys (honeypot indicator)
- Fee wallet controlled by owner EOA (not a timelock or DAO)


Red Flag 4: Emergency Withdraw / Drain Function

// RED FLAG: owner can drain all ETH and tokens from the contract
contract LiquidityTrap is Ownable {
    IERC20 public token;

    // "Emergency" function that drains everything
    function emergencyWithdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
        token.transfer(owner(), token.balanceOf(address(this)));
    }

    // Also dangerous: token rescue that works on the project's own token
    function rescueTokens(address tokenAddr, uint256 amount) external onlyOwner {
        IERC20(tokenAddr).transfer(owner(), amount);
    }
}

"Rescue" or "emergency withdraw" functions are legitimate for recovering accidentally sent tokens — but they become rug vectors when they can operate on the protocol's own liquidity token or paired assets.

Check: Does the emergency function exclude the protocol's own LP token, governance token, or paired stablecoin? If not, it's a backdoor.


Red Flag 5: Upgradeable Proxy Without Timelock

// RED FLAG: proxy with instant upgrade capability
contract UpgradeableToken is ERC20Upgradeable, UUPSUpgradeable, OwnableUpgradeable {
    function _authorizeUpgrade(address newImplementation)
        internal override onlyOwner
    {
        // No timelock — owner can upgrade instantly to any logic
    }
}

An upgradeable token with no timelock on upgrades means the deployer can replace all contract logic at any time. They can push an implementation that transfers all balances to themselves in a single transaction.

What makes it safer:
- _authorizeUpgrade requires governance vote, not just onlyOwner
- Upgrade goes through a TimelockController (2-48 hour delay)
- Contract is non-upgradeable (verified in code: no proxy pattern)


Red Flag 6: Owner Can Pause All Transfers

// RED FLAG if pause is indefinite and only owner-controlled
contract PausableToken is ERC20, Pausable, Ownable {
    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal override whenNotPaused
    {
        super._beforeTokenTransfer(from, to, amount);
    }
}

Pausing is legitimate for emergency response — but if a single owner EOA can pause indefinitely with no automatic timeout or governance check, it's a rug vector.


Automated Detection Capability

Red Flag Slither Semgrep AI
Owner-callable mint() with no cap
Transfer-blocking _beforeTokenTransfer hook ⚠️ Partial
Fee function with no upper bound
Emergency drain that works on own token
Upgradeable proxy with no timelock ⚠️
Asymmetric buy/sell restrictions

Most rug pull patterns are semantic — Slither can find mint() but can't determine whether the fee cap check is missing or whether the rescueTokens function covers the protocol's own liquidity. That reasoning requires understanding the contract's purpose and normal behavior — exactly what AI-based analysis provides.


Quick Token Checklist

Before buying a token or auditing one for deployment:

Supply:
- [ ] Is the total supply fixed (minted only in constructor)?
- [ ] If mint() exists, is it capped and access-controlled via governance?

Transfers:
- [ ] No blacklist or whitelist that owner can modify unilaterally
- [ ] If fees exist, is there an immutable max fee percentage?
- [ ] Sell fees ≤ buy fees (asymmetric = honeypot signal)

Admin functions:
- [ ] No emergencyWithdraw that operates on the token or LP pair
- [ ] If upgradeable, upgrades require timelock or governance
- [ ] pause() has an automatic timeout or requires governance

Liquidity:
- [ ] LP tokens locked in a time-lock contract (not held by deployer EOA)
- [ ] Lock duration is verifiable on-chain


The Bottom Line

Every rug pull listed above has code that can be read before investing. The patterns are not subtle — they are explicit, owner-privileged functions that any code review would flag.

The question is whether that review happens. Automated scanning catches the structural patterns (uncapped mint, missing fee bounds) in seconds — but the deeper semantic patterns (emergency withdraw that covers own token, asymmetric transfer hooks) require understanding contract intent.

ContractScan specifically flags rug pull indicators as part of its AI analysis — reporting owner-controlled restrictions, uncapped mint functions, and emergency withdrawal patterns to give investors and deployers a fast first-pass assessment.


Related: Token Approval Security: The Infinite Allowance Problem — a related category of token-level security risks for DeFi users.

Related: Access Control Failures: $953M in Losses — how insufficient access control extends beyond rug pulls to protocol exploits.

Scan your contract now
Slither + AI analysis — Unlimited quick scans. No signup required.
Try Free Scan →