Back to all stories
Blogs
Tech & Dev
Pectra’s EIP-7702: Redefining Trust Assumptions of Externally Owned Accounts (EOAs) in EVM
5/6/2025
Pectra’s EIP-7702: Redefining Trust Assumptions of Externally Owned Accounts (EOAs) in EVM

The Pectra upgrade, which introduces a set of significant changes to the Ethereum Virtual Machine (EVM), has been scheduled on Ethereum for May 7, 2025. Binance Smart Chain has already performed a similar upgrade called Pascal on March 30, 2025. Other EVM-compatible chains may follow this update in the near future. One of Pectra’s major features is EIP-7702, a proposal that fundamentally redefines the behavior of externally owned accounts (EOAs), allowing them to include executable logic.

On April 20, CertiK detected several suspicious transactions on BSC involving unknown addresses that appeared to leverage the EIP-7702 upgrade. One particularly alarming case involved a function that relied on the condition tx.origin == msg.sender, a pattern we've occasionally seen in smart contracts that assume that EOAs cannot execute smart contract code. While this assumption previously held true, EIP-7702 fundamentally changes that trust assumption.

We’ve observed that some deployed contracts still rely on tx.origin == msg.sender as a form of runtime check to block contract-originated calls, typically intended as a defense against reentrancy or flash loan attacks. While this pattern was occasionally used under tight development timelines or specific assumptions, it has never been considered a security best practice. More importantly, the underlying assumption that EOAs cannot have executable logic no longer holds, significantly increasing the potential for misuse and bypass.

In this post, we’ll examine how EIP-7702 reshapes core EVM assumptions, spotlight mocked examples, and provide actionable guidance for developers to assess whether their existing contracts may be vulnerable.

Background – A New Transaction Type

EIP-7702 introduces a new transaction type (SET_CODE_TX_TYPE 0x04), allowing an EOA to authorize setting its own account code. This isn't arbitrary code; instead, the EOA's code is set to a special "delegation indicator" bytecode (0xef0100 followed by the 20-byte address of the delegate contract).

Pectra Code 1

Crucially, this means an EOA, identified by its address, can now execute complex smart contract logic as itself (msg.sender will be the EOA's address), even when called deeper within a transaction's call stack, not just at the top level.

This change breaks a fundamental invariant that many developers, consciously or unconsciously, rely upon: tx.origin == msg.sender is no longer a guarantee that the current execution context is the first frame initiated directly by an EOA without any intermediate contract calls within the same transaction.

Security Considerations For Existing Contracts

Some existing contracts using tx.origin == msg.sender for certain security checks may become vulnerable after EIP-7702 is deployed. Furthermore, EIP-7702 may affect the expectations related to contract detection and token transfers.

Broken Reentrancy Protection (Anti-Pattern)

Although extremely rare and considered poor design, a contract might have used require(tx.origin == msg.sender) attempting to prevent reentrancy, assuming any reentrant call would necessarily come from another contract (thus msg.sender would differ from tx.origin). For example, the VulnerableRewards contract below tries to utilize tx.origin to protect against reentrancy issues.

Pectra Code 2

EIP-7702 breaks this assumption. Now, an EOA can make a call with a delegated code that includes a fallback function, making reentrant calls possible where they weren’t before. For instance, the Foundry test below demonstrates the attacker’s ability to drain all the ethers from the VulnerableRewards contract.

Pectra Code 3

Broken Flashloan / Atomic Sandwich Attack Protection

Contracts sometimes used require(tx.origin == msg.sender, "Externally owned accounts only"); to prevent intermediary contracts from manipulating state before and after calling the target contract within the same atomic transaction. This was often seen as a defense against flash loan-style sandwich attacks executed within a single transaction block. For example, the VulnerableGovernance contract utilizes this EOA check to ensure that the user who votes actually owns the ether for voting. The full code is available in the Appendix section.

Pectra Code 4

This check can be bypassed under EIP-7702. An attacker’s EOA can delegate to a malicious contract and then call the target function; both msg.sender and tx.origin will still be the attacker’s EOA address, passing the check. Previous anti-flash loan measures that rely on tx.origin thus become ineffective. Therefore, the user can delegate itself to a contract as shown below, borrow enough ethers from other protocols that support flash loans, and immediately execute a malicious proposal of the VulnerableGovernance contract above. The complete Proof-of-Concept is provided in the Appendix section.

Pectra Code 5

Potential Incorrect Contract Address Detection

It has been common to differentiate EOAs from contracts in on-chain logic — for instance, using extcodesize/code.length (via an isContract() helper) to check if an address has code. Many protocols would change behavior based on caller type, for example, not allowing contracts to call certain functions or applying different rules to them.

With EIP-7702, these assumptions can fail. A delegated EOA technically has bytecode (the delegation stub) in its account, so extcodesize on an EOA could return a non-zero value. Therefore, a function like isContract(address) might return true for what is actually an EOA address.

Unexpected Revert When Receiving Tokens

Under the previous assumption, sending ETH or tokens to an EOA was straightforward — an EOA has no code, so it cannot reject a transfer or execute logic on receive. EIP-7702 breaks this expectation. If an EOA has delegated code, any call to that address will invoke the code of the delegate contract. This means that a simple ETH transfer to what appears to be an EOA might actually trigger a fallback function (in the delegated contract), which could consume gas or even revert the transaction.

For example, in the Foundry test shown below, a contract performing a simple EOA.call{value: 1 ether}("") or payable(user).transfer(1 ether) might expect the call to succeed unconditionally. But with EIP-7702, if the EOA has delegated logic, that fallback function might revert or consume unexpected gas, leading to failed transfers.

Pectra Code 6

Similarly, token transfers can break assumptions. Standards like ERC-721 (NFTs) and ERC-777/ERC-677 treat any address with code as a contract and attempt to call receiver hook functions on them. A wallet that enables delegation will be seen as a contract, so a safe NFT transfer (safeTransferFrom) to such an address will try to invoke onERC721Received. If the delegated contract does not implement the expected callback, the NFT transfer will revert, whereas previously sending to an EOA would never require a callback. The same goes for ERC-777 tokens expecting tokensReceived.

In short, EOA with delegated code are no longer passive recipients — without careful handling, they can introduce revert conditions and other side effects that existing contracts didn’t expect.

Mitigations

Developers and security engineers should take proactive steps to adapt to the changes brought by EIP-7702. Key mitigation strategies include:

Flash Loan Protection

For flash loan or bot mitigation, it’s more effective to enforce logical constraints (e.g., minimum time delays, fees, or invariants that make flash-loaned arbitrage unprofitable) rather than trying to block contract callers.

Reentrancy Protection

For reentrancy protection, use established methods like the checks-effects-interactions (CEI) pattern or a reentrancy guard mutex, instead of relying on the caller being an EOA.

Rethink EOA vs. Contract Checks

If your contract uses code size checks or a similar assumption to treat EOAs differently, refactor this logic. Going forward, assume any address might have code or might eventually gain code. Nevertheless, certain checks remain effective:

  • Checking for code: address.code.length == 0 is still a valid way to check whether an address currently has no deployed code. However, this check does not guarantee that the address is permanently code-free — contracts in their constructor phase will also return zero code length, and with mechanisms like EIP-7702, EOAs can temporarily execute code even if code.length is zero.
  • Checking if an address initiated the transaction: tx.origin == addressToCheck is still valid for this purpose.
  • Checking if the immediate caller initiated the transaction: tx.origin == msg.sender is still valid for this purpose, but be aware of the broken assumptions discussed above if this check passes.
  • Checking if an address is an EOA address: tx.origin is still to be an EOA address, but cannot be used to verify whether the address has code or not.

Conclusion

Security is an evolving target, especially with fundamental changes like EIP-7702. Keep an eye on community discussions and guidelines surrounding this EIP. Make sure your development team is aware that EOAs are no longer guaranteed to be simple. Design new contracts under the assumption that “code-free” EOAs may eventually not exist. Finally, consider an external security review that focuses on the impacts of EIP-7702 if your protocol heavily relies on tx.origin or EOA assumptions.

References

Appendix - VulnerableGovernance Contract and Foundry Test

carbon

carbon (1)