EIP-7761: HASCODE instruction
Introduce HASCODE instruction to replace the EXTCODESIZE > 0 check in EOF
Abstract
Allow EOF contracts to discriminate between EOAs (Externally Owned Accounts) and contract accounts by introducing an HASCODE
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 succeedsafeTransfers
to contract accounts call anonERC721Received
(onERC1155Received
) on them and expect to get a special magic return value, otherwise the transfer reverts (on the assumption that such a receipient may not be able to interact with the token)
HASCODE
is aimed to fill this gap and bring back the possibility to easily implement ERC-721 and ERC-1155 standard contracts in EOF.
Specification
Parameters
Constant | Value |
---|---|
FORK_BLKNUM | tbd |
GAS_COLD_ACCOUNT_ACCESS | Defined as 2600 in the Ethereum Execution Layer Specs |
GAS_WARM_ACCESS | Defined as 100 in the Ethereum Execution Layer Specs |
We introduce a new EOFv1 instruction on the block number FORK_BLKNUM
: HASCODE
(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
HASCODE
- 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 - deduct
GAS_COLD_ACCOUNT_ACCESS - GAS_WARM_ACCESS
iftarget_address
is not inaccessed_addresses
and addtarget_address
toaccessed_addresses
- push
1
onto the stack iftarget_address.code
is not empty,0
otherwise
If target_address.code
contains an EIP-7702 delegation, the result of HASCODE
should follow the delegation and return a result according to the delegation designator account. Additional gas costs and rules concering accessed_addresses
apply as specified in EIP-7702 for code reading instructions like EXTCODESIZE
.
If target_address
points to an account with a contract mid-creation, it behaves aligned with similar instructions like EXTCODESIZE
and returns 0
.
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:
- Extra status code for
EXT*CALL
instruction - allowing to discriminate a result coming from calling an EOA - Extra argument for
EXT*CALL
(a "fail if EOA" flag) - Two return values from
EXT*CALL
(status code + whether it was EOA) EXT*CALL
setting a newcallstatus
register (+ a newCALLSTATUS
instruction)- Reenable
EXTCODESIZE
in EOF, keeping its behavior same as in legacy
HASCODE
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 HASCODE
A new opcode is prefered by a general policy to not reuse opcodes. Also HASCODE
can be rolled out in legacy EVM if desired.
Keep code introspection banned
Removing code introspection is one of the tenets of EOF and HASCODE
would be an exception from the principle. Without HASCODE
, ERC-721 and ERC-1155 standard implementations have to resort to either:
- 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) - Continuing to use legacy EVM themselves. This is sub-optimal, since EVM compilers are likely to at some point deprecate legacy EVM as compilation target
- Updating the standards to not rely on code introspection patterns in
safeTransfer
safe guards. 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
HASCODE
(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 counter arguments 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).
Relation to EIP-7702 "Set EOA account code"
After EIP-7702 is activated, the discrimination between EOAs and contract accounts using EXTCODESIZE
(or HASCODE
) 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.
Including safe guarding against proxy bricking
In parallel to the ERC-721 / ERC-1155 problem, another potential risk has been brought to attention. Since EOFv1 prohibits EXTDELEGATECALL
targetting 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.
One potential solution to this would be to have a generalized CONTRACT_KIND
instruction instead of HASCODE
which would further discriminate the account into legacy or EOFv1 with a 0
/ 1
/ 2
return value, thereby providing means for an additional safeguard against such a scenario.
This problem is potentially solvable on the application layer (proxy upgrade would include a direct check of the implementation contract being EXTDELEGATECALL
-able) or even by re-enabling EXTDELEGATECALL
to a legacy target in a future EVM upgrade.
Backwards Compatibility
HASCODE
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
Copyright and related rights waived via CC0.