From 5cb47b40ca4a7ccd250221f2cd9f17c1e1bace5f Mon Sep 17 00:00:00 2001 From: lightclient Date: Fri, 4 Aug 2023 11:31:53 -0600 Subject: [PATCH 01/17] 4788: initial stab at v2 --- EIPS/eip-4788.md | 209 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 149 insertions(+), 60 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index bd1c9f709c33b1..541d62fb31367e 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -2,19 +2,20 @@ eip: 4788 title: Beacon block root in the EVM description: Expose beacon chain roots in the EVM -author: Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs), Danny Ryan (@djrtwo) +author: Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs), Danny Ryan (@djrtwo), lightclient (@lightclient) discussions-to: https://ethereum-magicians.org/t/eip-4788-beacon-root-in-evm/8281 status: Draft type: Standards Track category: Core created: 2022-02-10 +requires: 1559 --- ## Abstract Commit to the hash tree root of each beacon chain block in the corresponding execution payload header. -Store each of these roots in a stateful precompile. +Store each of these roots in a smart contract. ## Motivation @@ -25,18 +26,19 @@ restaking constructions, smart contract bridges, MEV mitigations and more. ## Specification -| constants | value | units -|--- |--- |--- +| constants | value | +|--- |--- | | `FORK_TIMESTAMP` | TBD | -| `HISTORY_STORAGE_ADDRESS` | `Bytes20(0xB)` | -| `G_beacon_root` | 4200 | gas -| `HISTORICAL_ROOTS_MODULUS` | 98304 | +| `HISTORICAL_ROOTS_MODULUS` | `98304` | +| `SYSTEM_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffe` | +| `BEACON_ROOTS_ADDRESS` | `0x502E02F5d91024A9AF0aB81fbF0a47Eb99a013aE` | ### 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, +The high-level idea is that each execution block contains the parent beacon block's 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, a small history of block roots -are stored in a stateful precompile. +are stored in the contract. + To bound the amount of storage this construction consumes, a ring buffer is used that mirrors a block root accumulator on the consensus layer. ### Block structure and validity @@ -77,76 +79,163 @@ When verifying a block, execution clients **MUST** ensure the root value in the For a genesis block with no existing parent beacon block root the 32 zero bytes are used as a root placeholder. -### EVM changes - -#### Block processing - -At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. before processing any transactions), -write the parent beacon root provided in the block header into the storage of the contract at `HISTORY_STORAGE_ADDRESS`. +#### Beacon roots contract -In order to bound the storage used by this precompile, two ring buffers are used: one to track the latest timestamp at a given index in the ring buffer and another to track -the latest root at a given index. +The beacon roots contract has two operations: `get` and `set`. The input itself is not used to determine which function to execute, for that the result of `caller` is used. If `caller` is equal to `SYSTEM_ADDRESS` then the operation to perform is `set`. Otherwise, `get`. -To derive the index `timestamp_index` into the timestamp ring buffer, the timestamp (a 64-bit unsigned integer value) is reduced modulo `HISTORICAL_ROOTS_MODULUS`. -To derive the index `root_index` into the root ring buffer, add `HISTORICAL_ROOTS_MODULUS` to the index into the timestamp ring buffer. -Both resulting 64-bit unsigned integers should be encoded as 32 bytes in big-endian format when writing to the storage. +###### `get` +* If `caller` is equal to `SYSTEM_ADDRESS`, the contract must revert. +* Callers provide the `timestamp` they are querying encoded as 32 bytes in big-endian format. +* If the input is not exactly 32 bytes, the contract must revert. +* Given `timestamp`, the contract computes the storage index in which the timestamp is stored by computing the modulo `timestamp % HISTORICAL_ROOTS_MODULUS` and reads the value. +* If the `timestamp` does not match, the contract must revert. +* Finally, the beacon root associated with the timestamp is accessed at returned to the user. It is stored at `timestamp % HISTORICAL_ROOTS_MODULUS + HISTORICAL_ROOTS_MODULUS`. -The timestamp from the header, encoded as 32 bytes in big-endian format, is the value to write behind the `timestamp_index`. -The 32 bytes of the `parent_beacon_block_root` (as provided) are the value to write behind the `root_index`. +###### `set` +* If `caller` is not equal to `SYSTEM_ADDRESS`, the contract must revert. +* Caller provides the parent beacon block root as calldata to the contract. +* Set the storage value at `header.timestamp % HISTORICAL_ROOTS_MODULUS` to be `header.timestamp` +* Set the storage value at `header.timestamp % HISTORICAL_ROOTS_MODULUS + HISTORICAL_ROOTS_MODULUS` to be `calldata[0:32]` -In Python pseudocode: +##### Pseudocode ```python -timestamp_reduced = block_header.timestamp % HISTORICAL_ROOTS_MODULUS -timestamp_extended = timestamp_reduced + HISTORICAL_ROOTS_MODULUS -timestamp_index = to_uint256_be(timestamp_reduced) -root_index = to_uint256_be(timestamp_extended) +if evm.caller == SYSTEM_ADDRESS: + set() +else: + get() -timestamp_as_uint256 = to_uint256_be(block_header.timestamp) -parent_beacon_block_root = block_header.parent_beacon_block_root +def get(): + if len(evm.calldata) != 32: + evm.revert() -sstore(HISTORY_STORAGE_ADDRESS, timestamp_index, timestamp_as_uint256) -sstore(HISTORY_STORAGE_ADDRESS, root_index, parent_beacon_block_root) -``` + timestamp_idx = to_uint256_be(timestamp) % HISTORICAL_ROOTS_MODULUS + timestamp = storage.get(timestamp_idx) -#### New stateful precompile + if timestamp != evm.calldata: + evm.revert() -Beginning at the execution timestamp `FORK_TIMESTAMP`, a "stateful" precompile is deployed at `HISTORY_STORAGE_ADDRESS`. + root_idx = timestamp_idx + HISTORICAL_ROOTS_MODULUS + root = storage.get(root_idx) + + evm.return(root) -Callers of the precompile should provide the `timestamp` they are querying encoded as 32 bytes in big-endian format. -Clients **MUST** sanitize this input call data to the precompile. -If the input is _more_ than 32 bytes, the precompile only takes the first 32 bytes of the input buffer and ignores the rest. -If the input is _less_ than 32 bytes, the precompile should revert. +def set(): + timestamp_idx = to_uint256_be(evm.timestamp) % HISTORICAL_ROOTS_MODULUS + root_idx = timestamp_idx + HISTORICAL_ROOTS_MODULUS -Given this input, the precompile reduces the `timestamp` in the same way during the write routine and first checks if -the `timestamp` recorded in the ring buffer matches the one supplied by the caller. + storage.set(timestamp_idx, evm.timestamp) + storage.set(root_idx, evm.calldata) +``` -If the `timestamp` **does NOT** match, the client **MUST** return the "zero" word -- the 32-byte value where each byte is `0x00`. +##### Bytecode + +The exact initcode to deploy is shared below. + +```asm +push1 0x5a +dup1 +push1 0x09 +push0 +codecopy +push0 +return + +caller +push20 0xfffffffffffffffffffffffffffffffffffffffe +eq +push1 0x42 +jumpi + +push1 0x20 +calldatasize +eq +push1 0x24 +jumpi + +push0 +push0 +revert + +jumpdest +push3 0x018000 +push0 +calldataload +mod +dup1 +sload +push0 +calldataload +eq +iszero +push1 0x3d +jumpi + +push3 0x018000 +add +sload +push0 +mstore + +jumpdest +push1 0x20 +push0 +return + +jumpdest +timestamp +push3 0x018000 +timestamp +mod +sstore +push0 +calldataload +push3 0x018000 +timestamp +mod +push3 0x018000 +add +sstore +stop +``` -If the `timestamp` **does** match, the client **MUST** read the root from the contract storage and return those 32 bytes in the caller's return buffer. +#### Deployment + +The beacon roots contract is deployed like any other smart contract. A special synthetic address is generated +by working backwards from the desired deployment transaction: + +```json +{ + "type": "0x2", + "chainId": "0x1", + "nonce": "0x0", + "to": null, + "gas": "0xd4f8", + "gasPrice": null, + "maxPriorityFeePerGas": "0x9c7652400", + "maxFeePerGas": "0xe8d4a51000", + "value": "0x0", + "input": "0x605a8060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604257602036146024575f5ffd5b620180005f350680545f351415603d576201800001545f525b60205ff35b42620180004206555f3562018000420662018000015500", + "accessList": [], + "v": "0x0", + "r": "0x539", + "s": "0x1337", + "hash": "0x8ecfe5753922d27aa737597d946f638a15b7e3b5f74fef9ef8cf1b510a1af1cc" + } +``` -In pseudocode: +The sender of the transaction can be calculated as `0x01d0610058aC7AEF1887d8877ee7f04B7645Dc95`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0x502E02F5d91024A9AF0aB81fbF0a47Eb99a013aE`. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). -```python -timestamp = evm.calldata[:32] -if len(timestamp) != 32: - evm.revert() - return +### Block processing -timestamp_reduced = to_uint64_be(timestamp) % HISTORICAL_ROOTS_MODULUS -timestamp_index = to_uint256_be(timestamp_reduced) +At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. before processing any transactions), call `BEACON_ROOTS_ADDRESS` as `SYSTEM_ADDRESS` with the 32-byte input of `header.parent_beacon_block_root`. This will trigger the `set()` routine of the beacon roots contract. This is a system operation and therefore: -recorded_timestamp = sload(HISTORY_STORAGE_ADDRESS, timestamp_index) -if recorded_timestamp != timestamp: - evm.returndata[:32].set(uint256(0)) -else: - timestamp_extended = timestamp_reduced + HISTORICAL_ROOTS_MODULUS - root_index = to_uint256_be(timestamp_extended) - root = sload(HISTORY_STORAGE_ADDRESS, root_index) - evm.returndata[:32].set(root) -``` +* the call must execute to completion, therefore the available gas can be considered as infinite +* the call does not count against the block's gas limit +* the call does not follow the [EIP-1559](./eip-1559.md) burn semantics - no value should be transferred as part of the call +* if no code exists at `BEACON_ROOTS_ADDRESS`, the call must fail silently -The precompile costs `G_beacon_root` gas to reflect the two (2) implicit `SLOAD`s from the precompile's state. +Client may decide to omit an explicit EVM call and directly set the storage values. ## Rationale From 68acfa51f165e7203734c19988aa8f60930b0f75 Mon Sep 17 00:00:00 2001 From: lightclient Date: Fri, 4 Aug 2023 11:50:39 -0600 Subject: [PATCH 02/17] 4788: fix markdown lints --- EIPS/eip-4788.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 541d62fb31367e..0c328309d51862 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -83,7 +83,8 @@ For a genesis block with no existing parent beacon block root the 32 zero bytes The beacon roots contract has two operations: `get` and `set`. The input itself is not used to determine which function to execute, for that the result of `caller` is used. If `caller` is equal to `SYSTEM_ADDRESS` then the operation to perform is `set`. Otherwise, `get`. -###### `get` +##### `get` + * If `caller` is equal to `SYSTEM_ADDRESS`, the contract must revert. * Callers provide the `timestamp` they are querying encoded as 32 bytes in big-endian format. * If the input is not exactly 32 bytes, the contract must revert. @@ -91,7 +92,8 @@ The beacon roots contract has two operations: `get` and `set`. The input itself * If the `timestamp` does not match, the contract must revert. * Finally, the beacon root associated with the timestamp is accessed at returned to the user. It is stored at `timestamp % HISTORICAL_ROOTS_MODULUS + HISTORICAL_ROOTS_MODULUS`. -###### `set` +##### `set` + * If `caller` is not equal to `SYSTEM_ADDRESS`, the contract must revert. * Caller provides the parent beacon block root as calldata to the contract. * Set the storage value at `header.timestamp % HISTORICAL_ROOTS_MODULUS` to be `header.timestamp` From 2eee682857904005f253c85aa68be58d0e328b02 Mon Sep 17 00:00:00 2001 From: lightclient Date: Fri, 4 Aug 2023 13:07:59 -0600 Subject: [PATCH 03/17] 4788: rm superfluous condition on get/set --- EIPS/eip-4788.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 0c328309d51862..c601aa87603971 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -85,7 +85,6 @@ The beacon roots contract has two operations: `get` and `set`. The input itself ##### `get` -* If `caller` is equal to `SYSTEM_ADDRESS`, the contract must revert. * Callers provide the `timestamp` they are querying encoded as 32 bytes in big-endian format. * If the input is not exactly 32 bytes, the contract must revert. * Given `timestamp`, the contract computes the storage index in which the timestamp is stored by computing the modulo `timestamp % HISTORICAL_ROOTS_MODULUS` and reads the value. @@ -94,7 +93,6 @@ The beacon roots contract has two operations: `get` and `set`. The input itself ##### `set` -* If `caller` is not equal to `SYSTEM_ADDRESS`, the contract must revert. * Caller provides the parent beacon block root as calldata to the contract. * Set the storage value at `header.timestamp % HISTORICAL_ROOTS_MODULUS` to be `header.timestamp` * Set the storage value at `header.timestamp % HISTORICAL_ROOTS_MODULUS + HISTORICAL_ROOTS_MODULUS` to be `calldata[0:32]` From 9f5179e5c642289b93e11224a70e814e9be6b9fa Mon Sep 17 00:00:00 2001 From: lightclient Date: Fri, 4 Aug 2023 14:23:49 -0600 Subject: [PATCH 04/17] 4788: add martin to coauthor --- EIPS/eip-4788.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index c601aa87603971..6f29ad87ed394e 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -2,7 +2,7 @@ eip: 4788 title: Beacon block root in the EVM description: Expose beacon chain roots in the EVM -author: Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs), Danny Ryan (@djrtwo), lightclient (@lightclient) +author: Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs), Danny Ryan (@djrtwo), Martin Holst Swende (@holiman), lightclient (@lightclient) discussions-to: https://ethereum-magicians.org/t/eip-4788-beacon-root-in-evm/8281 status: Draft type: Standards Track From c4caa4a7e4544ed64476da3c8a53896a7dc595aa Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Sun, 6 Aug 2023 20:25:19 -0600 Subject: [PATCH 05/17] 4788: fix typo Co-authored-by: Alex Stokes --- EIPS/eip-4788.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 6f29ad87ed394e..cf442ea337d7fb 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -89,7 +89,7 @@ The beacon roots contract has two operations: `get` and `set`. The input itself * If the input is not exactly 32 bytes, the contract must revert. * Given `timestamp`, the contract computes the storage index in which the timestamp is stored by computing the modulo `timestamp % HISTORICAL_ROOTS_MODULUS` and reads the value. * If the `timestamp` does not match, the contract must revert. -* Finally, the beacon root associated with the timestamp is accessed at returned to the user. It is stored at `timestamp % HISTORICAL_ROOTS_MODULUS + HISTORICAL_ROOTS_MODULUS`. +* Finally, the beacon root associated with the timestamp is returned to the user. It is stored at `timestamp % HISTORICAL_ROOTS_MODULUS + HISTORICAL_ROOTS_MODULUS`. ##### `set` From 9ae7e6f5ddc6346f1d6f3ebe4dad5203a947b4db Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:46:48 -0600 Subject: [PATCH 06/17] 4788: ymmv on syscall optimization Co-authored-by: Martin Holst Swende --- EIPS/eip-4788.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index cf442ea337d7fb..c592bc1d538262 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -235,7 +235,7 @@ At the start of processing any execution block where `block.timestamp >= FORK_TI * the call does not follow the [EIP-1559](./eip-1559.md) burn semantics - no value should be transferred as part of the call * if no code exists at `BEACON_ROOTS_ADDRESS`, the call must fail silently -Client may decide to omit an explicit EVM call and directly set the storage values. +Clients may decide to omit an explicit EVM call and directly set the storage values. Note: While this is a valid optimization for Ethereum mainnet, it could be problematic on non-mainnet situations in case a different contract is used. ## Rationale From 45b5803575d349fbf2dbc10fc9f79bbd07b78623 Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 9 Aug 2023 08:04:13 -0600 Subject: [PATCH 07/17] 4788: update code and deploy addr --- EIPS/eip-4788.md | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index c592bc1d538262..1572ea87f3732c 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -133,7 +133,7 @@ def set(): The exact initcode to deploy is shared below. ```asm -push1 0x5a +push1 0x58 dup1 push1 0x09 push0 @@ -144,7 +144,7 @@ return caller push20 0xfffffffffffffffffffffffffffffffffffffffe eq -push1 0x42 +push1 0x44 jumpi push1 0x20 @@ -167,32 +167,33 @@ sload push0 calldataload eq -iszero -push1 0x3d +push1 0x37 jumpi +push0 +push0 +revert + +jumpdest push3 0x018000 add sload push0 mstore - -jumpdest push1 0x20 push0 return jumpdest -timestamp push3 0x018000 timestamp mod +timestamp +dup2 sstore push0 calldataload -push3 0x018000 -timestamp -mod +swap1 push3 0x018000 add sstore @@ -206,25 +207,23 @@ by working backwards from the desired deployment transaction: ```json { - "type": "0x2", - "chainId": "0x1", + "type": "0x0", "nonce": "0x0", "to": null, - "gas": "0xd4f8", - "gasPrice": null, - "maxPriorityFeePerGas": "0x9c7652400", - "maxFeePerGas": "0xe8d4a51000", + "gas": "0x27eac", + "gasPrice": "0xe8d4a51000", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, "value": "0x0", - "input": "0x605a8060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604257602036146024575f5ffd5b620180005f350680545f351415603d576201800001545f525b60205ff35b42620180004206555f3562018000420662018000015500", - "accessList": [], - "v": "0x0", + "input": "0x60588060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", + "v": "0x1b", "r": "0x539", "s": "0x1337", - "hash": "0x8ecfe5753922d27aa737597d946f638a15b7e3b5f74fef9ef8cf1b510a1af1cc" - } + "hash": "0x629827765abe2a0dd202a4a1ffb6e9ad699c3221ec8e4bb882fd58d78c838fa3" +} ``` -The sender of the transaction can be calculated as `0x01d0610058aC7AEF1887d8877ee7f04B7645Dc95`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0x502E02F5d91024A9AF0aB81fbF0a47Eb99a013aE`. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). +The sender of the transaction can be calculated as `0x3A700F76Ddec97c4640FdAF5a0fF1632E50A10D8`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0x89e64Be8700cC37EB34f9209c96466DEEDc0d8a6`. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). ### Block processing From 72edbeece275114cbe34b7ee9df1da4ff0303c75 Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 9 Aug 2023 08:12:33 -0600 Subject: [PATCH 08/17] 4788: add address to 2929 warm list --- EIPS/eip-4788.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 1572ea87f3732c..3d25e97cffb508 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -8,7 +8,7 @@ status: Draft type: Standards Track category: Core created: 2022-02-10 -requires: 1559 +requires: 1559, 2929 --- ## Abstract @@ -31,7 +31,7 @@ restaking constructions, smart contract bridges, MEV mitigations and more. | `FORK_TIMESTAMP` | TBD | | `HISTORICAL_ROOTS_MODULUS` | `98304` | | `SYSTEM_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffe` | -| `BEACON_ROOTS_ADDRESS` | `0x502E02F5d91024A9AF0aB81fbF0a47Eb99a013aE` | +| `BEACON_ROOTS_ADDRESS` | `0x89e64Be8700cC37EB34f9209c96466DEEDc0d8a6` | ### Background @@ -223,7 +223,7 @@ by working backwards from the desired deployment transaction: } ``` -The sender of the transaction can be calculated as `0x3A700F76Ddec97c4640FdAF5a0fF1632E50A10D8`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0x89e64Be8700cC37EB34f9209c96466DEEDc0d8a6`. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). +The sender of the transaction can be calculated as `0x3A700F76Ddec97c4640FdAF5a0fF1632E50A10D8`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0x89e64Be8700cC37EB34f9209c96466DEEDc0d8a6`. This is how `BEACON_ROOTS_ADDRESS` is determined. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). ### Block processing @@ -236,6 +236,8 @@ At the start of processing any execution block where `block.timestamp >= FORK_TI Clients may decide to omit an explicit EVM call and directly set the storage values. Note: While this is a valid optimization for Ethereum mainnet, it could be problematic on non-mainnet situations in case a different contract is used. +Additionally, `BEACON_ROOTS_ADDRESS` must be added to the [EIP-2929](./eip-2929.md) `accessed_addresses` list at the start of transaction execution. + ## Rationale ### Gas cost of precompile From 6bf6dcad8caadf482c9d11c3cad789318735d4b9 Mon Sep 17 00:00:00 2001 From: lightclient Date: Fri, 11 Aug 2023 05:34:09 -0600 Subject: [PATCH 09/17] 4788: remove from 2929 warm list --- EIPS/eip-4788.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 3d25e97cffb508..5465b702433e36 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -8,7 +8,7 @@ status: Draft type: Standards Track category: Core created: 2022-02-10 -requires: 1559, 2929 +requires: 1559 --- ## Abstract @@ -236,8 +236,6 @@ At the start of processing any execution block where `block.timestamp >= FORK_TI Clients may decide to omit an explicit EVM call and directly set the storage values. Note: While this is a valid optimization for Ethereum mainnet, it could be problematic on non-mainnet situations in case a different contract is used. -Additionally, `BEACON_ROOTS_ADDRESS` must be added to the [EIP-2929](./eip-2929.md) `accessed_addresses` list at the start of transaction execution. - ## Rationale ### Gas cost of precompile From 9fdfa7f5041321ea4730feebb5e2461508b6d966 Mon Sep 17 00:00:00 2001 From: lightclient Date: Sat, 12 Aug 2023 20:43:39 -0600 Subject: [PATCH 10/17] 4788: disallow system tx in genesis blocks --- EIPS/eip-4788.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 5465b702433e36..4a5a37261c14b8 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -218,12 +218,12 @@ by working backwards from the desired deployment transaction: "input": "0x60588060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", "v": "0x1b", "r": "0x539", - "s": "0x1337", - "hash": "0x629827765abe2a0dd202a4a1ffb6e9ad699c3221ec8e4bb882fd58d78c838fa3" + "s": "0x1337007db5d301ac899c9e", + "hash": "0x6923750e3b16ef9bfe1a70820d0d0628d7d4e84906b19ef91840fe913d688759" } ``` -The sender of the transaction can be calculated as `0x3A700F76Ddec97c4640FdAF5a0fF1632E50A10D8`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0x89e64Be8700cC37EB34f9209c96466DEEDc0d8a6`. This is how `BEACON_ROOTS_ADDRESS` is determined. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). +The sender of the transaction can be calculated as `0x3A700F76Ddec97c4640FdAF5a0fF1632E50A10D8`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0xBeAC00541d49391ED88ABF392bfC1F4dEa8c4143`. This is how `BEACON_ROOTS_ADDRESS` is determined. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). ### Block processing @@ -236,6 +236,8 @@ At the start of processing any execution block where `block.timestamp >= FORK_TI Clients may decide to omit an explicit EVM call and directly set the storage values. Note: While this is a valid optimization for Ethereum mainnet, it could be problematic on non-mainnet situations in case a different contract is used. +If this EIP is active in a genesis block, the genesis header's `parent_beacon_block_root` must be `0x0` and no system transaction may occur. + ## Rationale ### Gas cost of precompile From ec64413bb662ec26c37cc33a602bf65d21f388bd Mon Sep 17 00:00:00 2001 From: lightclient Date: Thu, 17 Aug 2023 12:31:59 -0600 Subject: [PATCH 11/17] 4788: vanity address --- EIPS/eip-4788.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 4a5a37261c14b8..33b398a610990a 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -218,8 +218,8 @@ by working backwards from the desired deployment transaction: "input": "0x60588060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", "v": "0x1b", "r": "0x539", - "s": "0x1337007db5d301ac899c9e", - "hash": "0x6923750e3b16ef9bfe1a70820d0d0628d7d4e84906b19ef91840fe913d688759" + "s": "0x133700f3a77843802897db", + "hash": "0x14789a20c0508b81ab7a0287a12a3a41ca960aa18244af8e98689e37ed569f07" } ``` From 26e6ae7f98a4780e4d0ee732a911c391d643580a Mon Sep 17 00:00:00 2001 From: lightclient Date: Fri, 18 Aug 2023 12:10:46 -0600 Subject: [PATCH 12/17] 4788: fix beacon roots constant --- EIPS/eip-4788.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 33b398a610990a..60e7da043aa446 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -31,7 +31,7 @@ restaking constructions, smart contract bridges, MEV mitigations and more. | `FORK_TIMESTAMP` | TBD | | `HISTORICAL_ROOTS_MODULUS` | `98304` | | `SYSTEM_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffe` | -| `BEACON_ROOTS_ADDRESS` | `0x89e64Be8700cC37EB34f9209c96466DEEDc0d8a6` | +| `BEACON_ROOTS_ADDRESS` | `0xBeAC00541d49391ED88ABF392bfC1F4dEa8c4143` | ### Background From c02a3a917d7207ed7cad8579d5bbebbb27b6867e Mon Sep 17 00:00:00 2001 From: lightclient Date: Sun, 20 Aug 2023 16:33:09 -0600 Subject: [PATCH 13/17] 4788: fix typo in pseudocode --- EIPS/eip-4788.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 60e7da043aa446..1a1e9fb22cb6af 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -109,7 +109,7 @@ def get(): if len(evm.calldata) != 32: evm.revert() - timestamp_idx = to_uint256_be(timestamp) % HISTORICAL_ROOTS_MODULUS + timestamp_idx = to_uint256_be(evm.calldata) % HISTORICAL_ROOTS_MODULUS timestamp = storage.get(timestamp_idx) if timestamp != evm.calldata: From 998943a8d784bd4e47f9257a7c0393f784841177 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Mon, 21 Aug 2023 05:45:37 -0600 Subject: [PATCH 14/17] 4788: fix sender addr Co-authored-by: Martin Holst Swende --- EIPS/eip-4788.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 1a1e9fb22cb6af..387e7e7633c5d3 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -223,7 +223,7 @@ by working backwards from the desired deployment transaction: } ``` -The sender of the transaction can be calculated as `0x3A700F76Ddec97c4640FdAF5a0fF1632E50A10D8`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0xBeAC00541d49391ED88ABF392bfC1F4dEa8c4143`. This is how `BEACON_ROOTS_ADDRESS` is determined. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). +The sender of the transaction can be calculated as `0x3e266d3c3a70c238bdddafef1ba06fbd58958d70`. The address of the first contract deployed from the account is `rlp([sender, 0])` which equals `0xbEac00dDB15f3B6d645C48263dC93862413A222D`. This is how `BEACON_ROOTS_ADDRESS` is determined. Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode). ### Block processing From a5a404743578279c7a18a9c14cb9e8eb2e47eebf Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 21 Aug 2023 07:49:47 -0400 Subject: [PATCH 15/17] Update EIPS/eip-4788.md --- EIPS/eip-4788.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 387e7e7633c5d3..1602f974da63d9 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -31,7 +31,7 @@ restaking constructions, smart contract bridges, MEV mitigations and more. | `FORK_TIMESTAMP` | TBD | | `HISTORICAL_ROOTS_MODULUS` | `98304` | | `SYSTEM_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffe` | -| `BEACON_ROOTS_ADDRESS` | `0xBeAC00541d49391ED88ABF392bfC1F4dEa8c4143` | +| `BEACON_ROOTS_ADDRESS` | `0xbEac00dDB15f3B6d645C48263dC93862413A222D` | ### Background From 3d048adc99fde12ee7c8b4c8aebd3159a495c6c2 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 24 Aug 2023 11:35:42 -0600 Subject: [PATCH 16/17] add explicit gas limit to EIP-4788 system write --- EIPS/eip-4788.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 1602f974da63d9..e0678e4ca276ed 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -227,9 +227,9 @@ The sender of the transaction can be calculated as `0x3e266d3c3a70c238bdddafef1b ### Block processing -At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. before processing any transactions), call `BEACON_ROOTS_ADDRESS` as `SYSTEM_ADDRESS` with the 32-byte input of `header.parent_beacon_block_root`. This will trigger the `set()` routine of the beacon roots contract. This is a system operation and therefore: +At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. before processing any transactions), call `BEACON_ROOTS_ADDRESS` as `SYSTEM_ADDRESS` with the 32-byte input of `header.parent_beacon_block_root`, a gas limit of `30_000_000`, and `0` value. This will trigger the `set()` routine of the beacon roots contract. This is a system operation and therefore: -* the call must execute to completion, therefore the available gas can be considered as infinite +* the call must execute to completion * the call does not count against the block's gas limit * the call does not follow the [EIP-1559](./eip-1559.md) burn semantics - no value should be transferred as part of the call * if no code exists at `BEACON_ROOTS_ADDRESS`, the call must fail silently From bb28b8d7136720eb257cad806ab2d1eaaff0254f Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 24 Aug 2023 11:47:06 -0600 Subject: [PATCH 17/17] change name of length of the EIP-4788 ring buffer --- EIPS/eip-4788.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 1602f974da63d9..66a2a8b60f415f 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -29,7 +29,7 @@ restaking constructions, smart contract bridges, MEV mitigations and more. | constants | value | |--- |--- | | `FORK_TIMESTAMP` | TBD | -| `HISTORICAL_ROOTS_MODULUS` | `98304` | +| `HISTORY_BUFFER_LENGTH` | `98304` | | `SYSTEM_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffe` | | `BEACON_ROOTS_ADDRESS` | `0xbEac00dDB15f3B6d645C48263dC93862413A222D` | @@ -87,15 +87,15 @@ The beacon roots contract has two operations: `get` and `set`. The input itself * Callers provide the `timestamp` they are querying encoded as 32 bytes in big-endian format. * If the input is not exactly 32 bytes, the contract must revert. -* Given `timestamp`, the contract computes the storage index in which the timestamp is stored by computing the modulo `timestamp % HISTORICAL_ROOTS_MODULUS` and reads the value. +* Given `timestamp`, the contract computes the storage index in which the timestamp is stored by computing the modulo `timestamp % HISTORY_BUFFER_LENGTH` and reads the value. * If the `timestamp` does not match, the contract must revert. -* Finally, the beacon root associated with the timestamp is returned to the user. It is stored at `timestamp % HISTORICAL_ROOTS_MODULUS + HISTORICAL_ROOTS_MODULUS`. +* Finally, the beacon root associated with the timestamp is returned to the user. It is stored at `timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH`. ##### `set` * Caller provides the parent beacon block root as calldata to the contract. -* Set the storage value at `header.timestamp % HISTORICAL_ROOTS_MODULUS` to be `header.timestamp` -* Set the storage value at `header.timestamp % HISTORICAL_ROOTS_MODULUS + HISTORICAL_ROOTS_MODULUS` to be `calldata[0:32]` +* Set the storage value at `header.timestamp % HISTORY_BUFFER_LENGTH` to be `header.timestamp` +* Set the storage value at `header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH` to be `calldata[0:32]` ##### Pseudocode @@ -109,20 +109,20 @@ def get(): if len(evm.calldata) != 32: evm.revert() - timestamp_idx = to_uint256_be(evm.calldata) % HISTORICAL_ROOTS_MODULUS + timestamp_idx = to_uint256_be(evm.calldata) % HISTORY_BUFFER_LENGTH timestamp = storage.get(timestamp_idx) if timestamp != evm.calldata: evm.revert() - root_idx = timestamp_idx + HISTORICAL_ROOTS_MODULUS + root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH root = storage.get(root_idx) evm.return(root) def set(): - timestamp_idx = to_uint256_be(evm.timestamp) % HISTORICAL_ROOTS_MODULUS - root_idx = timestamp_idx + HISTORICAL_ROOTS_MODULUS + timestamp_idx = to_uint256_be(evm.timestamp) % HISTORY_BUFFER_LENGTH + root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH storage.set(timestamp_idx, evm.timestamp) storage.set(root_idx, evm.calldata) @@ -260,7 +260,7 @@ e.g. with a singleton state root contract that caches the proof per slot). ### Why two ring buffers? -The first ring buffer only tracks `HISTORICAL_ROOTS_MODULUS` worth of roots and so for all possible timestamp values would consume a constant amount of storage. +The first ring buffer only tracks `HISTORY_BUFFER_LENGTH` worth of roots and so for all possible timestamp values would consume a constant amount of storage. However, this design opens the precompile to an attack where a skipped slot that has the same value modulo the ring buffer length would return an old root value, rather than the most recent one.