Skip to content

Commit

Permalink
Update EIP-4788: favor stateful precompile over opcode
Browse files Browse the repository at this point in the history
Merged by EIP-Bot.
  • Loading branch information
ralexstokes authored May 24, 2023
1 parent ef5fe54 commit d810df0
Showing 1 changed file with 19 additions and 19 deletions.
38 changes: 19 additions & 19 deletions EIPS/eip-4788.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ created: 2022-02-10

Commit to the hash tree root of each beacon chain block in the corresponding execution payload header.

Store each of these roots in a contract that lives in the execution state and add a new opcode that reads this contract.
Store each of these roots in a stateful precompile.

## Motivation

Expand All @@ -29,28 +29,23 @@ restaking constructions, smart contract bridges, MEV mitigations and more.
|--- |--- |---
| `FORK_TIMESTAMP` | TBD |
| `HISTORY_STORAGE_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffd` |
| `OPCODE_VALUE` | `0x4A` |
| `G_beacon_root` | 20 | gas
| `G_beacon_root` | 2100 | gas
| `SLOTS_PER_HISTORICAL_ROOT` | 8192 | slot(s)

### Background

The high-level idea is that each execution block contains the parent beacon block root. Even in the event of missed slots since the previous block root does not change,
we only need a constant amount of space to represent this "oracle" in each execution block. To improve the usability of this oracle, block roots are stored
in a canonical place in the execution state analogous to a `SSTORE` in the given contract's storage for each update.
Roots are keyed by the slot(s) they pertain to.
we only need a constant amount of space to represent this "oracle" in each execution block. To improve the usability of this oracle, a small history of block roots
are stored in a stateful precompile.
To bound the amount of storage this construction consumes, a ring buffer is used that mirrors a block root accumulator on the consensus layer.
The method for exposing the root data via opcode is inspired by [EIP-2935](./eip-2935.md).

### Block structure and validity

Beginning at the execution timestamp `FORK_TIMESTAMP`, execution clients **MUST**:

1. set 32 bytes of the execution block header after the `excess_data_gas` to the 32 byte [hash tree root](https://github.com/ethereum/consensus-specs/blob/fa09d896484bbe240334fa21ffaa454bafe5842e/ssz/simple-serialize.md#merkleization) of the parent beacon block.

2. set the 8 bytes after the previous 32 byte extension to the slot number as a big-endian uint64 of the current slot.

*NOTE*: these fields are appended to the current block header structure with this EIP so that the size of the header grows after (and including) the `FORK_TIMESTAMP`.
*NOTE*: this field is appended to the current block header structure with this EIP so that the size of the header grows after (and including) the `FORK_TIMESTAMP`.

### EVM changes

Expand Down Expand Up @@ -90,26 +85,31 @@ sstore(HISTORY_STORAGE_ADDRESS, SLOTS_PER_HISTORICAL_ROOT, end_slot)

When using any slot value as a key to the storage, the value under consideration must be converted to 32 bytes with big-endian encoding.

#### New opcode
#### New stateful precompile

Beginning at the execution timestamp `FORK_TIMESTAMP`, the code and storage at `HISTORY_STORAGE_ADDRESS` constitute a "stateful" precompile.

Callers of the precompile should provide the `slot` they are querying encoding a 64-bit unsigned integer as 256-bit big-endian data.
Recall this `slot` number should be reduced modulo the `SLOTS_PER_HISTORICAL_ROOT` constant to derive the correct key into the ring buffer structure.

Beginning at the execution timestamp `FORK_TIMESTAMP`, introduce a new opcode `BEACON_ROOT` at `OPCODE_VALUE`.
This opcode consumes one word from the stack encoding the slot number for the desired root under big-endian discipline.
The opcode has a gas cost of `G_beacon_state_root`.
Alongside the existing gas for calling the precompile, there is an additional gas cost of `G_beacon_root` cost to reflect the implicit `SLOAD` from
the precompile's state. The root is returned as 32 bytes in the caller's provided return buffer.

The result of executing this opcode leaves one word on the stack corresponding to a read of the history contract's storage; in pseudocode:
In pseudocode:

```python
slot = evm.stack.pop()
sload(HISTORY_STORAGE_ADDRESS, slot % SLOTS_PER_HISTORICAL_ROOT)
slot = to_uint64(evm.calldata[:32])
root = sload(HISTORY_STORAGE_ADDRESS, slot % SLOTS_PER_HISTORICAL_ROOT)
evm.returndata[:32].set(root)
```

If there is no root stored at the requested slot number, the opcode follows the existing EVM semantics of `sload` returning `0`.

## Rationale

### Gas cost of opcode
### Gas cost of precompile

The suggested gas cost is just using the value for the `BLOCKHASH` opcode as `BEACON_ROOT` is an analogous operation.
The suggested gas cost reflects a cold `SLOAD` analogous to the operation performed while executing the precompile's logic.

### Why not repurpose `BLOCKHASH`?

Expand Down

0 comments on commit d810df0

Please sign in to comment.