diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index b2d2cb378b6d93..7bcb51807f309b 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -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 @@ -29,18 +29,15 @@ 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 @@ -48,9 +45,7 @@ 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 @@ -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`?