Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP: HASCODE instruction #8838

Merged
merged 7 commits into from
Sep 9, 2024
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions EIPS/eip-7761.md
pdobacz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
eip: 7761
title: IS_CONTRACT instruction
description: Introduce IS_CONTRACT instruction
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 `IS_CONTRACT` 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)

`IS_CONTRACT` 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`: `IS_CONTRACT` (`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

#### `IS_CONTRACT`

- 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 `IS_CONTRACT` 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

`IS_CONTRACT` 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 `IS_CONTRACT`

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

### Keep code introspection banned

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that the purpose of removing code introspection is to enable future semantics-preserving transformations of code already deployed on chain. In this sense IS_CONTRACT would not be an obstacle at all, so I think it's debatable whether it would be an exception to that principle.


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?
pdobacz marked this conversation as resolved.
Show resolved Hide resolved

### 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 `IS_CONTRACT`) 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 `IS_CONTRACT` 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

`IS_CONTRACT` 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

TBA
pdobacz marked this conversation as resolved.
Show resolved Hide resolved

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
Loading