Skip to content

fix: replace tx.origin EOA guard with code-length check for EIP-7702 compatibility#7

Open
Ridwannurudeen wants to merge 1 commit intoProjectOpenSea:mainfrom
Ridwannurudeen:fix/eip7702-activatetstore-guard
Open

fix: replace tx.origin EOA guard with code-length check for EIP-7702 compatibility#7
Ridwannurudeen wants to merge 1 commit intoProjectOpenSea:mainfrom
Ridwannurudeen:fix/eip7702-activatetstore-guard

Conversation

@Ridwannurudeen
Copy link

Summary

Fixes #5msg.sender != tx.origin check in __activateTstore() is no longer safe after EIP-7702.

Problem

EIP-7702, introduced in Ethereum's Pectra upgrade, allows EOAs to temporarily delegate code execution to smart contracts via SET_CODE_TX. This breaks the invariant that msg.sender == tx.origin implies no code is executing.

The current guard in __activateTstore():

if (msg.sender != tx.origin) {
    revert OnlyDirectCalls();
}

A 7702-delegated EOA can bypass this because msg.sender == tx.origin remains true even while executing delegated code. This enables the following attack:

  1. EOA delegates to a malicious contract via SET_CODE_TX
  2. EOA enters a tstorish-inheriting contract (reentrancy guard set via SSTORE)
  3. Delegated code calls __activateTstore() — guard passes since msg.sender == tx.origin
  4. _tstoreSupport flips to true, switching reads from SSTORE to TLOAD
  5. Reentrancy guard was written via SSTORE but is now read via TLOAD (returns 0)
  6. Reentrancy guard is bypassed

Fix

Replace msg.sender != tx.origin with msg.sender.code.length != 0:

- if (msg.sender != tx.origin) {
+ if (msg.sender.code.length != 0) {
      revert OnlyDirectCalls();
  }

This works because:

  • Pure EOAs: code.length == 0 — allowed (correct)
  • Contracts: code.length > 0 — rejected (correct)
  • 7702-delegated EOAs: code.length == 23 (the delegation designator 0xef0100 || address) — rejected (correct, this is the fix)

Changes

  • src/Tstorish.sol: Replace msg.sender != tx.origin with msg.sender.code.length != 0, update natspec comment
  • test/foundry/TestTstorish.t.sol:
    • Fix test_fail_activateTstore_alreadyActivated to use a pure EOA (the old test used address(this) which now correctly hits OnlyDirectCalls before TStoreAlreadyActivated)
    • Add test_fail_activateTstore_rejectsDelegatedEOA — uses vm.etch to simulate a 23-byte delegation designator on an EOA and verifies it reverts with OnlyDirectCalls

References

After EIP-7702 (Pectra), EOAs can delegate code execution via
SET_CODE_TX while maintaining msg.sender == tx.origin. This breaks
the __activateTstore() guard which relied on that invariant to
restrict calls to pure EOAs.

Replace `msg.sender != tx.origin` with `msg.sender.code.length != 0`
which correctly rejects both contracts and 7702-delegated EOAs
(code.length == 23 for delegation designators).

Add test for delegated EOA rejection using vm.etch to simulate the
23-byte delegation designator (0xef0100 || address).

Closes ProjectOpenSea#5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

msg.sender != tx.origin check in __activateTstore() is no longer safe after EIP-7702

1 participant