# 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.