Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7761: EXTCODETYPE instruction

Add EXTCODETYPE instruction to EOF to address common uses of EXTCODE* instructions

Authors Andrei Maiboroda (@gumb0), Piotr Dobaczewski (@pdobacz), Danno Ferrin (@shemnon)
Created 2024-09-01
Discussion Link https://ethereum-magicians.org/t/eip-7761-is-contract-instruction/20936
Requires EIP-3540, EIP-7692

Abstract

Allow EOF contracts to discriminate between EOAs (Externally Owned Accounts) and contract accounts by introducing an EXTCODETYPE instruction.

Motivation

EOFv1 as scoped in EIP-7692 removes code introspection capabilities from the EVM, including the EXTCODESIZE instruction (in EIP-3540). This makes it hard for ERC-721 and ERC-1155 standard contracts to be implemented, as they rely on discovering whether a token’s safeTransfer call target was an EOA or a contract account:

  • safeTransfers to EOAs succeed
  • safeTransfers to contract accounts call an onERC721Received (onERC1155Received) on them and expect to get a special magic return value, otherwise the transfer reverts (on the assumption that such a recipient may not be able to interact with the token)

Application and library developers are also concerned about dynamic proxies written in EOF accidentally pointing to legacy accounts which cannot safely be called with EXTDELEGATECALL. They would like a way to differentiate between EOF and legacy contracts.

EXTCODETYPE is aimed to fill this gap and bring back the possibility to easily implement ERC-721 and ERC-1155 standard contracts in EOF as well as preserve EOF proxy contract safety.

Specification

Parameters

Constant Value
FORK_BLKNUM tbd
GAS_COLD_ACCOUNT_ACCESS Defined as 2600 in the [Ethereum Execution Layer Spec Constants]
GAS_WARM_ACCESS Defined as 100 in the [Ethereum Execution Layer Spec Constants]
TYPE_NONE 0
TYPE_LEGACY_CONTRACT 1
TYPE_EOF_CONTRACT 2

We introduce a new EOFv1 instruction on the block number FORK_BLKNUM: EXTCODETYPE (0xe9)

EOF code which contains this instruction before FORK_BLKNUM is considered invalid. Beginning with block FORK_BLKNUM 0xe9 is added to the set of valid EOFv1 instructions.

Execution Semantics

EXTCODETYPE

  • deduct GAS_WARM_ACCESS gas
  • pop 1 argument target_address from the stack
  • if target_address has any of the high 12 bytes set to a non-zero value (i.e. it does not contain a 20-byte address), then halt with an exceptional failure
    • Notice: Future expansion of the EVM address space may remove or reduce the number of bytes in this check.
  • deduct GAS_COLD_ACCOUNT_ACCESS - GAS_WARM_ACCESS if target_address is not in accessed_addresses and add target_address to accessed_addresses
  • Load the code from target_address and refer to it as loaded_code
  • if loaded_code is empty (does not exist or has zero length), push TYPE_NONE onto the stack and stop processing the operation
  • If loaded_code indicates a delegation designator (for example, 0xef0100 as defined in EIP-7702),
    • replace loaded_code with the delegated code.
    • deduct gas in accordance with the delegation designation rules
  • If loaded_code indicates an EOFv1 packaged contract (starts with the bytes 0xef0001) push TYPE_EOF_CONTRACT onto the stack and stop processing the operation
  • Otherwise, push TYPE_LEGACY_CONTRACT onto the stack and stop processing the operation

Note: if there is not enough gas to deduct for delegation designation the whole message frame will halt, making updating the accessed_addresses irrelevant.

Note: If target_address or the delegation designator points to an account with a contract mid-creation then the code is empty and returns 0 (TYPE_NONE). This is aligned with similar behavior of instructions like EXTCODESIZE.

Rationale

Alternative solutions

There have been other solutions proposed to alleviate the problems related to lack of code introspection required for ERC-721 and ERC-1155 standards:

  1. Extra status code for EXT*CALL instruction - allowing to discriminate a result coming from calling an EOA
  2. Extra argument for EXT*CALL (a “fail if EOA” flag)
  3. Two return values from EXT*CALL (status code + whether it was EOA)
  4. EXT*CALL setting a new callstatus register (+ a new CALLSTATUS instruction)
  5. Re-enable EXTCODESIZE in EOF, keeping its behavior same as in legacy

EXTCODETYPE has been chosen as the most elegant and minimal solution satisfying the requirements at hand and still able to be introduced in EOFv1.

Reuse the 0x3b (EXTCODESIZE) opcode for EXTCODETYPE

A new opcode is preferred by a general policy to not reuse opcodes. Also EXTCODETYPE can be rolled out in legacy EVM if desired.

Keep code introspection banned

Removing code introspection is one of the tenets of EOF and EXTCODETYPE would be an exception from the principle. Without EXTCODETYPE, ERC-721 and ERC-1155 standard implementations have to resort to either:

  1. Leveraging a “booster contract” which would be legacy and would call EXTCODESIZE for them. This has been deemed inelegant and inconvenient from the point of view of library implementers, requiring them to hard code an address of such a contract (with the usual address-related problems arising on different EVM chains)
  2. Continuing to use legacy EVM themselves. This is suboptimal, since EVM compilers are likely to at some point deprecate legacy EVM as compilation target
  3. Updating the standards to not rely on code introspection patterns in safeTransfer safeguards. This can be accomplished for example by leveraging ERC-165, leaving out only contracts which do not implement ERC-165, and at the same time have non-throwing fallback functions, as indistinguishable from EOAs. This is not easy to achieve since ERC-721 and ERC-1155 are Final and adopted in practice.

“Endgame Account Abstraction” issues

EXTCODETYPE (and earlier EXTCODESIZE available in legacy EVM) are claimed to slow down AA adoption, because they encourage patterns which discriminate between smart contract and EOA accounts, e.g. not allowing the former to interact. However, there are counterarguments that it is up to other factors which slow down AA adoption (assumption that accounts can produce ECDSA signatures, and the lack of adoption of smart contract signatures).

Including safeguarding against proxy bricking

In parallel to the ERC-721 / ERC-1155 problem, another potential risk has been brought to attention. Since EOFv1 prohibits EXTDELEGATECALL targeting legacy contracts, there exists a scenario where an EOF proxy contract accidentally upgrades its implementation to a legacy EVM one. Since reverting this or upgrading again (using current proxy standards) requires the implementation contract to be called, it would effectively render the contract unusable.

For this reason it was decided to have a generalized EXTCODETYPE instruction instead of a HASCODE instruction. This provides the means to safeguard against such a scenario that a simpler instruction could not.

Relation to EIP-7702 “Set EOA account code”

After EIP-7702 is activated, the discrimination between EOAs and contract accounts using EXTCODESIZE (or EXTCODETYPE) has an edge case: Whenever an EOA sets its code to a contract account which does not respond as expected to an onERC721Received (onERC1155Received) callback, transfers to it will revert, despite the recipient being able to interact with the token. This has been deemed unlikely to be a problem, as for the intended real-world uses of EIP-7702, those callbacks will be implemented by designator codes.

Because of the requirement that EOF proxy contracts be able to determine if the new target is safe we cannot return a value for indicating that the account itself is delegated without also providing the delegated address. Instead, we return the code for the account that has the delegated code, applying the delegation. This is because the executable code is what matters for proxy updates.

This differs from EXTCODECOPY and EXTCODEHASH behavior which do return a hash and short code indicating the proxy. The behavior of EXTCODETYPE is closer to a “code executing operation” in purpose as it is meant to describe the execution behavior of the account.

Backwards Compatibility

EXTCODETYPE at 0xe9 can be introduced in a backwards compatible manner into EOFv1 (no bump to version), because 0xe9 has been rejected by EOF validation before FORK_BLKNUM and there are no EOF contracts on-chain with a 0xe9 which would have their behavior altered.

Security Considerations

Needs discussion

Copyright and related rights waived via CC0

[Ethereum Execution Layer Spec Constants] :https://github.com/ethereum/execution-specs/blob/1adcc1bfe774798bcacc685aebc17bd9935078c3/src/ethereum/cancun/vm/gas.py#L65-L66

Citation

Please cite this document as:

Andrei Maiboroda (@gumb0), Piotr Dobaczewski (@pdobacz), Danno Ferrin (@shemnon), "EIP-7761: EXTCODETYPE instruction [DRAFT]," Ethereum Improvement Proposals, no. 7761, September 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7761.