Update EIP-8141: Add explicit second dimension for state gas to frames - #12062
Update EIP-8141: Add explicit second dimension for state gas to frames#12062lightclient wants to merge 5 commits into
Conversation
|
✅ All reviewers have approved. |
|
The commit 9c0b23f (as a parent of 9fa3fb6) contains errors. |
jochem-brouwer
left a comment
There was a problem hiding this comment.
Saw this PR and gave it a read, some comments.
Allowing state gas introspection to other (earlier) frames means we can (in some specific cases) verify if such frame created a slot. This thus adds a subset of journal introspection to the EVM.
We could fix this by pinning the state gas used of previous frames to the value at the start of the currently executing frame. Any refills can thus not be measured by the EVM, while they are accounted for in the end.
The other thing is that not allowing state gas to be paid with execution gas also means that deep call frames can exhaust all state gas and therefore not allow later operations to create state (as this would attempt to put state gas below 0). This is something to think about, it might be the same argument for sending all your gas to untrusted accounts, but this definitely is something which "rogue contracts" can use to attack. The only way to then regain the "rights" to create state is to revert the current frame and therefore rollback the state creation by the rogue account.
Note that in EIP-8037 execution gas would pay for it if the state gas / state reservoir is empty.
Also note that we now have two gas journaling modes, one for frame txs and the other for non-frame txs.
Lemme know if you have some questions if I made a comment which is unclear 😄 👍
Exciting work!! 😄 🚀
| | `STANDARD_TOKEN_COST` | `4` | [EIP-7623](./eip-7623.md) | | ||
| | `TOTAL_COST_FLOOR_PER_TOKEN` | `10` | [EIP-7623](./eip-7623.md) | |
There was a problem hiding this comment.
| | `STANDARD_TOKEN_COST` | `4` | [EIP-7623](./eip-7623.md) | | |
| | `TOTAL_COST_FLOOR_PER_TOKEN` | `10` | [EIP-7623](./eip-7623.md) | | |
| | `STANDARD_TOKEN_COST` | `4` | [EIP-7976](./eip-7976.md) | | |
| | `TOTAL_COST_FLOOR_PER_TOKEN` | `16` | [EIP-7976](./eip-7976.md) | |
| [chain_id, nonce, sender, frames, signatures, max_priority_fee_per_gas, max_fee_per_gas, max_fee_per_blob_gas, blob_versioned_hashes] | ||
|
|
||
| frames = [[mode, flags, target, gas_limit, value, data], ...] | ||
| frames = [[mode, flags, target, execution_gas_limit, state_gas_limit, value, data], ...] |
There was a problem hiding this comment.
What happens if we introduce another gas dimension? How would we pack this in frame txs?
Is it necessary to split gas limit in these two dimensions? I would initially say that this is not necessary: all gas is execution gas. If a state operation happens and it gets cancelled, then this moves into the reservoir. However, not allowing this splitting means that a tx which has an intended split of execution/state gas does not fit in the block anymore because one (or both) of the gas dimensions would exceed the block gas limit. So I guess there is indeed reason to add this feature here.
Only worried if we go to multidimensional gas, then this is not forward compatible, and then we need to introduced "typed frame transactions" 😜
| assert frame.flags < 8 | ||
| assert frame.target is None or len(frame.target) == 20 | ||
| assert frame.gas_limit <= 2**64 - 1 | ||
| assert frame.state_gas_limit <= 2**64 - 1 |
There was a problem hiding this comment.
There is no check for execution_gas_limt
| assert frame.state_gas_limit <= 2**64 - 1 | |
| assert frame.execution_gas_limit <= 2**64 - 1 | |
| assert frame.state_gas_limit <= 2**64 - 1 |
There was a problem hiding this comment.
Ah ok, maybe this is not necessary, because this is implicitly checked by the EIP-7825 check?
| assert i + 1 < len(tx.frames) # must not be last frame | ||
| assert tx.frames[i + 1].mode != VERIFY # batches never contain VERIFY frames | ||
|
|
||
| # Intrinsic gas must fit the EIP-7825 transaction cap. |
There was a problem hiding this comment.
| # Intrinsic gas must fit the EIP-7825 transaction cap. | |
| # Intrinsic and execution gas must fit the EIP-7825 transaction cap. |
| - Logs emitted by frames that executed before the failure are discarded together with their state changes when the batch is unrolled. Those frame receipts retain their execution status and gas used, with empty logs. | ||
| - Since skipped frames are not executed, the gas value allotted to them is refunded at the end of the transaction. | ||
| - Logs emitted by frames that executed before the failure are discarded together with their state changes when the batch is unrolled. Change to any frame receipt's `state_gas_used` during the batch is reverted, as the batch's state modifications are reverted. Executed frame receipts retain their execution status and execution gas used, with empty logs and zero state gas used. | ||
| - Since skipped frames are not executed, the gas and state gas allotted to them is refunded at the end of the transaction. |
There was a problem hiding this comment.
The word "refunded" here is ambiguous. Is this an EIP-3529 refund (thus capped at 20%?)
If not the case refunded->returned?
| | **Total** | 141 | | ||
|
|
||
| Notes: Nonce assumes < 65536 prior sends. Fees assume < 1099 gwei. Validation frame target is 1 byte because target is `tx.sender`. Validation gas assumes <= 65,536 gas. Validation frame value is zero. Execution frame target is encoded directly as the destination address. Execution frame value assumes a compact 5-byte encoding. The execution frame data is empty for a plain ETH transfer. The signature is a secp256k1 entry with empty `msg` using a 65-byte ECDSA signature. Blob fields assume no blobs (empty list, zero max fee). | ||
| Notes: Nonce assumes < 65536 prior sends. Fees assume < 1099 gwei. Validation frame target is 1 byte because target is `tx.sender`. Validation gas assumes <= 65,536 gas. Validation frame value is zero. Execution frame target is encoded directly as the destination address. Execution frame value assumes a compact 5-byte encoding. The execution frame data is empty for a plain ETH transfer. The signature is a secp256k1 entry with empty `msg` using a 65-byte ECDSA signature. Blob fields assume no blobs (empty list, zero max fee). State gas limits are zero, assuming the destination exists; a transfer that creates a new account instead requires a 183,600 state gas budget on the execution frame (three additional bytes). |
There was a problem hiding this comment.
| Notes: Nonce assumes < 65536 prior sends. Fees assume < 1099 gwei. Validation frame target is 1 byte because target is `tx.sender`. Validation gas assumes <= 65,536 gas. Validation frame value is zero. Execution frame target is encoded directly as the destination address. Execution frame value assumes a compact 5-byte encoding. The execution frame data is empty for a plain ETH transfer. The signature is a secp256k1 entry with empty `msg` using a 65-byte ECDSA signature. Blob fields assume no blobs (empty list, zero max fee). State gas limits are zero, assuming the destination exists; a transfer that creates a new account instead requires a 183,600 state gas budget on the execution frame (three additional bytes). | |
| Notes: Nonce assumes < 65536 prior sends. Fees assume < 1099 gwei. Validation frame target is 1 byte because target is `tx.sender`. Validation gas assumes <= 65,536 gas. Validation frame value is zero. Execution frame target is encoded directly as the destination address. Execution frame value assumes a compact 5-byte encoding. The execution frame data is empty for a plain ETH transfer. The signature is a secp256k1 entry with empty `msg` using a 65-byte ECDSA signature. Blob fields assume no blobs (empty list, zero max fee). State gas limits are zero, assuming the destination exists; a transfer that creates a new account instead requires a ``STATE_BYTES_PER_NEW_ACCOUNT × CPSB = 183,600` state gas budget on the execution frame (three additional bytes). |
(the 183,600 should be defined as a constant somewhere, now it is a magic number for readers not deep in EIP-8037)
| | Execution frame: target | 20 | | ||
| | Execution frame: gas | 1 | | ||
| | Execution frame: execution gas | 1 | | ||
| | Execution frame: state gas | 1 | |
There was a problem hiding this comment.
| | Execution frame: state gas | 1 | | |
| | Execution frame: state gas | 3 | |
If this has to cover the account creation cost it should be 3 bytes as described in the Notes below
| | **Total** | 141 | | ||
|
|
||
| Notes: Nonce assumes < 65536 prior sends. Fees assume < 1099 gwei. Validation frame target is 1 byte because target is `tx.sender`. Validation gas assumes <= 65,536 gas. Validation frame value is zero. Execution frame target is encoded directly as the destination address. Execution frame value assumes a compact 5-byte encoding. The execution frame data is empty for a plain ETH transfer. The signature is a secp256k1 entry with empty `msg` using a 65-byte ECDSA signature. Blob fields assume no blobs (empty list, zero max fee). | ||
| Notes: Nonce assumes < 65536 prior sends. Fees assume < 1099 gwei. Validation frame target is 1 byte because target is `tx.sender`. Validation gas assumes <= 65,536 gas. Validation frame value is zero. Execution frame target is encoded directly as the destination address. Execution frame value assumes a compact 5-byte encoding. The execution frame data is empty for a plain ETH transfer. The signature is a secp256k1 entry with empty `msg` using a 65-byte ECDSA signature. Blob fields assume no blobs (empty list, zero max fee). State gas limits are zero, assuming the destination exists; a transfer that creates a new account instead requires a 183,600 state gas budget on the execution frame (three additional bytes). |
There was a problem hiding this comment.
What is this compact 5 byte encoding? With 5 bytes I can send slightly more than 0.000001 ETH 🤔
| | Execution frame: flags | 1 | | ||
| | Execution frame: target | 20 | | ||
| | Execution frame: gas | 1 | | ||
| | Execution frame: execution gas | 1 | |
There was a problem hiding this comment.
255 gas is not a lot, but I guess that fallback/receive methods consume much less, or have some "tricks" to ensure that receiving ether is cheap in gas units?
| | **Total additional** | 150 | | ||
|
|
||
| Notes: Sponsor can read info from other fields. ERC-20 transfer call is 68 bytes. | ||
| Notes: Sponsor can read info from other fields. ERC-20 transfer call is 68 bytes. State gas fields assume no new storage slots are created; an ERC-20 transfer into a fresh balance slot requires a 97,920 state gas budget (three additional bytes). |
There was a problem hiding this comment.
This 97,920 should also be referenced to EIP-8037's New slot constant.
|
Also, have to admit, this EIP is super well written already. Also nice to see how quickly this is changing, and great to see that the spec is made Glamsterdam-ready now! 😍 |
There was a problem hiding this comment.
assert max(
frame_tx_intrinsic_gas + total_frame_execution_gas,
calldata_floor_gas,
) <= TX_MAX_GAS_LIMIT
As I understand it, frames are equivalent to individual transactions that are grouped together, so static analysis capabilities are still maintained. Therefore, shouldn't we only apply limits per frame instead of across all frames?
This splits
gas_limitfield in the frame into:execution_gas_limitstate_gas_limitThere are important distinctions between the two, but most notable is that execution gas continues to (generally) be a callframe level concept while state gas is treated as a frame level concept.
Hopefully this illuminates the remaining edge cases around frames and 2780, 7623, 7708, 7825, and 8037!