-
-
Published
Linked with GitHub
# Write events
We define **write events** as follows. Note that when a write takes place, an access event also takes place (so the definition below should be a subset of the definition of access lists) A write event is of the form `(address, sub_key, leaf_key)`, determining what data is being written to.
#### Write events for account headers
When a nonzero-balance-sending `CALL` or `SELFDESTRUCT` with a given `sender` and `recipient` takes place, process these write events:
```python
(sender, 0, BALANCE_LEAF_KEY)
(recipient, 0, BALANCE_LEAF_KEY)
```
When a contract creation is initialized, process these write events:
```python
(contract_address, 0, VERSION_LEAF_KEY)
(contract_address, 0, NONCE_LEAF_KEY)
```
Only if the value sent with the creation is nonzero, also process:
```python
(contract_address, 0, BALANCE_LEAF_KEY)
```
When a contract is created, process these write events:
```python
(contract_address, 0, VERSION_LEAF_KEY)
(contract_address, 0, NONCE_LEAF_KEY)
(contract_address, 0, BALANCE_LEAF_KEY)
(contract_address, 0, CODE_KECCAK_LEAF_KEY)
(contract_address, 0, CODE_SIZE_LEAF_KEY)
```
#### Write events for storage
`SSTORE` opcodes with a given `address` and `key` process a write event of the form
```python
(address, tree_key, sub_key)
```
Where `tree_key` and `sub_key` are computed as follows:
```python
def get_storage_slot_tree_keys(storage_key: int) -> [int, int]:
if storage_key < (CODE_OFFSET - HEADER_STORAGE_OFFSET):
pos = HEADER_STORAGE_OFFSET + storage_key
else:
pos = MAIN_STORAGE_OFFSET + storage_key
return (
pos // 256,
pos % 256
)
```
#### Write events for code
When a contract is created, make write events:
```
(
address,
(CODE_OFFSET + i) // VERKLE_NODE_WIDTH,
(CODE_OFFSET + i) % VERKLE_NODE_WIDTH
)
```
For `i` in `0 ... (len(code)+30)//31`.
### Transactions
For a transaction, make these write events:
```python
(tx.origin, 0, NONCE_LEAF_KEY)
```
If `value` is nonzero:
```python
(tx.origin, 0, BALANCE_LEAF_KEY)
(tx.target, 0, BALANCE_LEAF_KEY)
```
### Access gas costs
Remove the following gas costs:
* Increased gas cost of `CALL` if it is nonzero-value-sending
* EIP-2200 `SSTORE` gas costs except for the `SLOAD_GAS`
* 200 per byte contract code cost
Reduce gas costs:
* `CREATE` to 1000
| Constant | Value |
| - | - |
| `SUBTREE_EDIT_COST` | 3000 |
| `CHUNK_EDIT_COST` | 500 |
| `CHUNK_FILL_COST` | 6200 |
When executing a transaction, maintain three sets:
* `edited_subtrees: Set[Tuple[address, int]]`
* `edited_leaves: Set[Tuple[address, int, int]]`
When a **write event** of `(address, sub_key, leaf_key)` occurs, perform the following checks:
* If `(address, sub_key)` is not in `edited_subtrees`, charge `SUBTREE_EDIT_COST` gas and add that tuple to `edited_subtrees`.
* If `leaf_key is not None` and `(address, sub_key, leaf_key)` is not in `edited_leaves`, charge `CHUNK_EDIT_COST` gas and add it to `edited_leaves`
* Additionally, if there was no value stored at `(address, sub_key, leaf_key)` (ie. the state held `None` at that position), charge `CHUNK_FILL_COST`
Note that tree keys can no longer be emptied: only the values `0...2**256-1` can be written to a tree key, and `0` is distinct from `None`. Once a tree key is changed from `None` to not-`None`, it can never go back to `None`.
### Alternative version (with refunds for re-setting storage)
When executing a transaction, maintain a map `edited_values: Map[Tuple[address, int] -> Set[int]]` that keeps track of which leaves were edited in each subtree.
Let the **original value** of some `(address, sub_key, leaf_key)` be the value of the corresponding trie key at the start of processing the transaction (similar to the definition in [EIP-2200](https://eips.ethereum.org/EIPS/eip-2200)).
When a **write event** writing a `value` to `(address, sub_key, leaf_key)` occurs, perform the following checks.
* If `value` equals the currently stored value, charge `SLOAD_GAS` and do nothing
* If `value` does not equal the currently stored value, and does not equal the original value, then:
* If `(address, sub_key)` is not in `edited_values`, charge `SUBTREE_EDIT_COST` gas and set `edited_values[(address, sub_key)]` to the empty set.
* If `leaf_key` is not in `edited_values[(address, sub_key)]`, charge `CHUNK_EDIT_COST` gas and add it to the set.
* If the original value is `None` (so, the tree had nothing stored in that position; remember that unlike in Patricia trees, in Verkle trees this is _distinct_ from storing `0`), then charge `CHUNK_FILL_COST`
* If `value` does not equal the currently stored value, and equals the original value, then:
* Remove `leaf_key` from `edited_values[(address, sub_key)]` and add a refund of `CHUNK_EDIT_COST` gas.
* If `edited_values[(address, sub_key)]` is now empty, remove `(address, sub_key)` from the map and refund `SUBTREE_EDIT_COST` gas.
Note that tree keys can no longer be emptied: only the values `0...2**256-1` can be written to a tree key, and `0` is distinct from `None`. Once a tree key is changed from `None` to not-`None`, it can never go back to `None`. Hence, `CHUNK_FILL_COST` can never be refunded.
Note also that refunds can only refund gas that was consumed in the same transaction.