diff --git a/EIPS/eip-7761.md b/EIPS/eip-7761.md new file mode 100644 index 0000000000000..d42f574a7ee55 --- /dev/null +++ b/EIPS/eip-7761.md @@ -0,0 +1,107 @@ +--- +eip: 7761 +title: HASCODE instruction +description: Introduce HASCODE instruction to replace the EXTCODESIZE > 0 check in EOF +author: Andrei Maiboroda (@gumb0), Piotr Dobaczewski (@pdobacz), Danno Ferrin (@shemnon) +discussions-to: https://ethereum-magicians.org/t/eip-7761-is-contract-instruction/20936 +status: Draft +type: Standards Track +category: Core +created: 2024-09-01 +requires: 3540, 7692 +--- + +## 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](./eip-7692.md) removes code introspection capabilities from the EVM, including the `EXTCODESIZE` instruction. This makes it hard for [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) 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 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](https://github.com/ethereum/execution-specs/blob/fcd12750edd4443a91f138728689a1d0a503a7c1/src/ethereum/cancun/vm/gas.py#L64) | +| `GAS_WARM_ACCESS` | Defined as `100` in the [Ethereum Execution Layer Specs](https://github.com/ethereum/execution-specs/blob/fcd12750edd4443a91f138728689a1d0a503a7c1/src/ethereum/cancun/vm/gas.py#L65) | + +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` if `target_address` is not in `accessed_addresses` and add `target_address` to `accessed_addresses` +- push `1` onto the stack if `target_address.code` is not empty, `0` otherwise + +If `target_address.code` contains an [EIP-7702](./eip-7702.md) 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: + +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. 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: + +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 sub-optimal, 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 (`safeTransfer` safe guards) patterns, also unlikely to happen as ERC-721 and ERC-1155 are Final and adopted in practice. + +### "Endgame Account Abstraction" issues + +TBD - can someone verbalize it? + +### Relation to [EIP-7702](./eip-7702.md) "Set EOA account code" + +After [EIP-7702](./eip-7702.md) 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](../LICENSE.md).