The purpose of this document is to:
Both upgrades, especially EOF, are complex. We encourage readers to research them independently, but this document will attempt to explain “code introspection” as it relates to each upgrade, and separate out a notion of “delegation introspection” from that of “code introspection”.
We believe that EXTCODE*
opcodes should act on the full EIP-7702 "delegation designator" for delegated EOAs instead of just the prefix because it would make the EIP far more useful and wouldn't change the behavior of EOF contracts.
EIP-7702 — a new transaction type that allows EOAs to set persistent runtime bytecode in their account (ref)
EVM Object Format (EOF) — a new smart contract bytecode format defined by a collection of EIPs which aim to improve smart contract structure, efficiency, security, and EVM forward compatibility. (ref)
“Code introspection” refers to a legacy contract’s ability to inspect its own bytecode or the bytecode of an external contract and change behavior based on that information. This is enabled by CODE*
opcodes for self-inspection and EXTCODE*
opcodes for external inspection.
However, “code introspection” is not supported in EOF contracts. There are a variety of reasons for this decision, including:
Under the hood, EIP-7702 works by setting a “delegation designator” in the EOA’s code
field with the structure: 0xef0100 || delegate_address
; where delegate_address
is the address of a singleton contract. All code-executing operations called on the EOA will load the runtime bytecode of this contract and execute it in the context of the EOA.
The structure of the code
field for each account type is as follows:
code field structure |
|
---|---|
legacy contract | pre-EOF legacy bytecode |
EOF contract | EOF structured bytecode |
un-delegated EOA | 0x |
EOA delegated to a legacy contract | 0xef0100 || delegate_address |
EOA delegated to an EOF contract | 0xef0100 || delegate_address |
Note: the code
field structure of this kind of account is clearly distinct from that of any contract.
Here’s a table describing CODECOPY
behavior for each account type:
CODECOPY behavior |
|
---|---|
legacy contract | pre-EOF legacy bytecode |
EOF contract | not possible |
EOA delegated to a legacy contract | 0xef0100 || delegate_address |
EOA delegated to an EOF contract | not possible |
*CODESIZE
returns the size of CODECOPY
(if possible)
And, here's a table describing current EXTCODECOPY
behavior for each account type. The rows represent the type of account calling EXTCODECOPY
, the columns represent the account type of the target, and each entry is the return value of the opcode:
legacy contract (target) | EOF contract (target) | EOA delegated to a legacy contract (target) | EOA delegated to an EOF contract (target) | |
---|---|---|---|---|
legacy contract (caller) | pre-EOF legacy bytecode | 0xef00 |
0xef01 |
0xef01 |
EOF contract (caller) | not possible | not possible | not possible | not possible |
EOA delegated to a legacy contract (caller) | pre-EOF legacy bytecode | 0xef00 |
0xef01 |
0xef01 |
EOA delegated to an EOF contract (caller) | not possible | not possible | not possible | not possible |
*EXTCODEHASH
and EXTCODESIZE
return the keccak256
hash and the size of EXTCODECOPY
respectively (if possible)
Note:
delegate_address
is hidden on-chain unnecessarily for legacy-contract-based accounts even though the information is readily available in the account’s code
CODE*
and EXTCODE*
behavior is different when referencing the same account’s code
EXTCODE*
behavior?Developers are being nudged to use EOF contracts in conjunction with EIP-7702. Since EOF contracts can’t contain CODE*
and EXTCODE*
opcodes, the spec disallows legacy-contract-based accounts from leveraging these opcodes in a useful manner.
However, this rationale takes an unnecessarily forceful stance on EOF adoption and tangles EOF and EIP-7702 when they are not explicitly connected. While EOF will be a massive improvement for the EVM and should be used in conjunction with EIP-7702 when beneficial, the simple fact is that EOF in its initial form will be an optional feature.
There might come a day when legacy contract deployment is deprecated entirely and replaced by EOF. However, such a fundamental change would require extensive discussion across the entire ecosystem and would need to involve a substantial time buffer to allow developers to migrate to this format. Not to mention, EOF itself would likely see new iterations before that time.
In the meantime, AA teams building on EIP-7702 should have the choice of developing legacy contracts, EOF contracts, or a combination as they see fit. We believe the use of EXTCODE*
opcodes to check an EOA’s “delegation designator” at runtime (aka “delegation introspection”) is an incredibly useful tool for building on EIP-7702 and we should not disallow legacy contracts from leveraging this feature regardless of EOF’s stance on “code introspection”.
Listed in EIP-7702’s “Motivation” section are the following (ref):
- Sponsorship: account X pays for a transaction on behalf of account Y. Account X could be paid in some other ERC-20 for this service, or it could be an application operator including the transactions of its users for free.
- Privilege de-escalation: users can sign sub-keys and give them specific permissions that are much weaker than global access to the account. For example, you could imagine a permission to spend ERC-20 tokens but not ETH, or to spend up to 1% of the total balance per day, or to interact only with a specific application.
Neither of these goals can be accomplished without relayers or other external accounts being able to safely submit transactions on behalf of users.
The use case outlined here relies on account X (either an application relayer or other external account) being able to submit a transaction on behalf of account Y. However, if account X cannot be sure at runtime that account Y is delegated to the agreed upon implementation, account Y is free to take advantage of account X by front-running the transaction:
This would be improved by allowing account X to check account Y’s delegation designator at runtime via EXTCODECOPY
because it would allow account X to revert execution at the protocol level instead of falling into an unknown execution path as a result of the attack.
Similarly, sub-keys can only be used reliably by external accounts that trust the delegated EOA. There is currently no way to ensure that the sub-key holder’s transaction will behave as intended.
This would be improved by allowing the sub-key holder to check the EOA’s delegation designator at runtime via EXTCODECOPY
for the same reason.
EXTCODE*
behaviorWe propose EXTCODE*
behavior to be changed to the following:
Legacy Contract (target) | EOF Contract (target) | EOA delegated to Legacy Contract (target) | EOA delegated to EOF Contract (target) | |
---|---|---|---|---|
Legacy Contract (caller) | pre-EOF legacy bytecode | 0xef00 |
0xef0100 || delegate_address |
0xef0100 || delegate_address |
EOF Contract (caller) | not possible | not possible | not possible | not possible |
EOA delegated to Legacy Contract (caller) | pre-EOF legacy bytecode | 0xef00 |
0xef0100 || delegate_address |
0xef0100 || delegate_address |
EOA delegated to EOF Contract (caller) | not possible | not possible | not possible | not possible |
*EXTCODEHASH
and EXTCODESIZE
return the keccak256
hash and the size of EXTCODECOPY
EXTCODE*
behaviorCODE*
and EXTCODE*
opcodes behave exactly the same for all account typesNo. A “delegation designator” is NOT bytecode. This value does not reveal any internal implementation details of the EOA’s delegate contract. While it is stored in the same place where bytecode is typically stored for contract accounts, it’s actually just a pointer to an implementation contract, not actual bytecode. The ability to query an EOA’s delegation designator on-chain would allow legacy contracts to know the general behavior of the delegated EOA, but this would still be fundamentally different than true “code introspection”. Considering the utility of “delegation introspection”, it might be beneficial to enable this functionality somehow in the next version of EOF: continuing to block “code introspection” but enabling “delegation introspection”.
“Delegation introspection” would be a very useful EIP-7702 feature. While EOF contracts wouldn’t be able to use this feature directly, EOF compliance won't be required when the upgrade lands. It doesn’t make sense to limit legacy contracts when the utility of EOF is unchanged. AA teams building on EIP-7702 should have the choice to develop legacy contracts, EOF contracts, or a combination to maximize EIP-7702’s utility while both EIP-7702 and EOF are still relatively new.
Let’s untangle these upgrades. Let’s implement both in their maximally useful forms and figure out how to better integrate them later via the EIP process.