-
Notifications
You must be signed in to change notification settings - Fork 77
add SLOAD & SSTORE spec
#11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
14ae61c
ab565fb
f2d5e2f
8b9e095
6b9581a
fd2a609
d91cd06
88fe454
143434e
3a79477
de1a710
d3ca0d4
8f3272e
9c20dc4
b0312bb
324b0da
9f2ee0a
8fe532a
7e5f2d9
b58c289
fcf7d21
7d13d3e
9be5d38
6be9d62
0106b38
967fd53
e1bb2b2
80cb839
464e231
23aa909
60a0e1e
f5866eb
d8f10cc
7914423
91cfd9a
93eb14c
1242b08
50cf47a
149a4bf
668acc8
aa6bc19
0f0b377
8f1f45e
34b5760
4c8230e
16ec3cb
475ff95
94398f0
d6e315a
b7fe613
0b35360
c9b35b0
e35e259
171a6bd
04d6e21
f5bfae9
05f35fe
d03ba15
0fe46ea
274d796
606747e
396eca4
073ba08
50d233a
aa48960
282ca28
045d1f2
4df61da
a0c274a
6a2b4e7
415ae8c
ca510da
c9446ff
d92b9ed
aa243fe
961c29f
d43948d
b1db976
7d59dc8
c21c31e
6f65116
d41ca4d
8ed213b
9d7c63b
b78d30c
51188a3
faec91e
b600178
b94667c
c435fea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,95 @@ | ||||||
| # SLOAD & SSTORE op code | ||||||
|
|
||||||
| ## Variables definition | ||||||
|
|
||||||
| | Name | Value | | ||||||
| | - | - | | ||||||
| | COLD_SLOAD_COST | 2100 | | ||||||
| | WARM_STORAGE_READ_COST | 100 | | ||||||
| | SLOAD_GAS | 100 | | ||||||
| | SSTORE_SET_GAS | 20000 | | ||||||
| | SSTORE_RESET_GAS | 2900 | | ||||||
| | SSTORE_CLEARS_SCHEDULE | 15000 | | ||||||
|
|
||||||
| ## Constraints | ||||||
|
|
||||||
| 1. opcodeId checks | ||||||
| 1. opId === OpcodeId(0x54) for `SLOAD` | ||||||
| 2. opId === OpcodeId(0x55) for `SSTORE` | ||||||
| 2. state transition: | ||||||
| - gc | ||||||
| - `SLOAD`: +5 (2 stack operations + 1 storage reads + 2 access_list reads/writes) | ||||||
| - `SSTORE`: +9 if persistent, +8 otherwise | ||||||
| + 2 stack operations | ||||||
| + 1 original value read | ||||||
| + 2 storage reads/writes | ||||||
| + 2 access_list reads/writes | ||||||
| + 2 gas_refund reads/writes if persistent, 1 otherwise | ||||||
| * 1 gas_refund read | ||||||
| * 1 gas_refund write if persistent | ||||||
| - stack_pointer | ||||||
| - `SLOAD`: remains the same | ||||||
| - `SSTORE`: -2 | ||||||
| - pc + 1 | ||||||
| - state_write_counter | ||||||
| - `SLOAD`: +1 | ||||||
| - `SSTORE`: +2 | ||||||
| - gas: | ||||||
| - `SLOAD`: | ||||||
| + the accessed address is warm: gas + WARM_STORAGE_READ_COST | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think we only need to mark how much gas needs to add for each step.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer keeping them as "gas + XXX", so that it's consistent with other markdown files. |
||||||
| + the accessed address is cold: gas + COLD_SLOAD_COST | ||||||
| - `SSTORE`: | ||||||
| + the accessed address is warm: | ||||||
| * `current_value == new_value`: gas + SLOAD_GAS | ||||||
| * `current_value != new_value`: | ||||||
| - `original_value == current_value`: | ||||||
| - `original_value == 0`: gas + SSTORE_SET_GAS | ||||||
| - `original_value != 0`: gas + SSTORE_RESET_GAS | ||||||
| - `original_value != current_value`: gas + SLOAD_GAS | ||||||
| + the accessed address is cold: | ||||||
| * `current_value == new_value`: gas + SLOAD_GAS + COLD_SLOAD_COST | ||||||
| * `current_value != new_value`: | ||||||
| - `original_value == current_value`: | ||||||
| - `original_value == 0`: gas + SSTORE_SET_GAS + COLD_SLOAD_COST | ||||||
| - `original_value != 0`: gas + SSTORE_RESET_GAS + COLD_SLOAD_COST | ||||||
| - `original_value != current_value`: gas + SLOAD_GAS + COLD_SLOAD_COST | ||||||
| * gas_refund: | ||||||
| - `SSTORE`: | ||||||
| + `current_value != new_value`: | ||||||
| * `original_value == current_value`: | ||||||
| * `original_value != 0` && `new_value == 0`: gas_refund + SSTORE_CLEARS_SCHEDULE | ||||||
| * `original_value != current_value`: | ||||||
| * `original_value != 0`: | ||||||
| - `current_value == 0`: gas_refund - SSTORE_CLEARS_SCHEDULE | ||||||
| - `new_value == 0`: gas_refund + SSTORE_CLEARS_SCHEDULE | ||||||
| * `original_value == new_value`: | ||||||
| - `original_value == 0`: gas_refund + SSTORE_SET_GAS - SLOAD_GAS | ||||||
| - `original_value != 0`: gas_refund + SSTORE_RESET_GAS - SLOAD_GAS | ||||||
| 3. lookups: | ||||||
|
0xmountaintop marked this conversation as resolved.
|
||||||
| - `SLOAD`: 5 busmapping lookups | ||||||
| - stack: | ||||||
| - `address` is popped off the top of the stack | ||||||
| - `value` is pushed on top of the stack | ||||||
| - storage: The 32 bytes of `value` are read from storage at `address` | ||||||
| - access_list: Whether the address is warm (accessed before), mark as warm afterward | ||||||
| - `SSTORE`: 9 busmapping lookups if persist, 8 otherwise | ||||||
| - stack: | ||||||
| - `address` is popped off the top of the stack | ||||||
| - `value` is popped off the top of the stack | ||||||
| - storage: | ||||||
| - Read the orignal value at `address` | ||||||
| - Read the current value at `address` | ||||||
| - The 32 bytes of new `value` are written to storage at `address` | ||||||
| - access_list: Whether the address is warm (accessed before), mark as warm afterward | ||||||
| - gas_refund: | ||||||
| + Read the accumulated gas_refund for this tx | ||||||
| + If persist, write the new accumulated gas_refund for this tx | ||||||
|
|
||||||
| ## Exceptions | ||||||
|
|
||||||
| 1. gas out: remaining gas is not enough | ||||||
| 2. stack underflow: | ||||||
| - the stack is empty: `1024 == stack_pointer` | ||||||
| - only for `SSTORE`: contains a single value: `1023 == stack_pointer` | ||||||
| 3. context error | ||||||
| - only for `SSTORE`: the current execution context is from a `STATICCALL` (since Byzantium fork). | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| COLD_SLOAD_COST = 2100 | ||
| WARM_STORAGE_READ_COST = 100 | ||
| SLOAD_GAS = 100 | ||
| SSTORE_SET_GAS = 20000 | ||
| SSTORE_RESET_GAS = 2900 | ||
| SSTORE_CLEARS_SCHEDULE = 15000 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| from ..instruction import Instruction, Transition | ||
| from ..opcode import Opcode | ||
| from ..table import CallContextFieldTag, TxContextFieldTag | ||
| from .gas import ( | ||
| COLD_SLOAD_COST, | ||
| WARM_STORAGE_READ_COST, | ||
| SLOAD_GAS, | ||
| SSTORE_SET_GAS, | ||
| SSTORE_RESET_GAS, | ||
| SSTORE_CLEARS_SCHEDULE, | ||
| ) | ||
|
|
||
| def sload(instruction: Instruction): | ||
| opcode = instruction.opcode_lookup(True) | ||
| instruction.constrain_equal(opcode, Opcode.SLOAD) | ||
|
|
||
| tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) | ||
| rw_counter_end_of_reversion = instruction.call_context_lookup(CallContextFieldTag.RWCounterEndOfReversion) | ||
| is_persistent = instruction.call_context_lookup(CallContextFieldTag.IsPersistent) | ||
| callee_address = instruction.tx_lookup(tx_id, TxContextFieldTag.CalleeAddress) | ||
|
|
||
| storage_slot = instruction.stack_pop() | ||
| warm = instruction.access_list_storage_slot_read(tx_id, callee_address, storage_slot) | ||
|
|
||
| # TODO: Use intrinsic gas (EIP 2028, 2930) | ||
| dynamic_gas_cost = WARM_STORAGE_READ_COST if warm else COLD_SLOAD_COST | ||
|
|
||
| instruction.storage_slot_read(callee_address, storage_slot) | ||
| instruction.add_storage_slot_to_access_list_with_reversion( | ||
| tx_id, callee_address, storage_slot, is_persistent, rw_counter_end_of_reversion | ||
| ) | ||
| instruction.stack_push() | ||
|
|
||
| instruction.constrain_same_context_state_transition( | ||
| opcode, | ||
| rw_counter=Transition.delta(5), | ||
| program_counter=Transition.delta(1), | ||
| stack_pointer=Transition.delta(0), | ||
| state_write_counter=Transition.delta(1), | ||
| dynamic_gas_cost=dynamic_gas_cost, | ||
| ) | ||
|
|
||
|
|
||
| def sstore(instruction: Instruction): | ||
| opcode = instruction.opcode_lookup(True) | ||
| instruction.constrain_equal(opcode, Opcode.SSTORE) | ||
|
|
||
| tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) | ||
| rw_counter_end_of_reversion = instruction.call_context_lookup(CallContextFieldTag.RWCounterEndOfReversion) | ||
| is_persistent = instruction.call_context_lookup(CallContextFieldTag.IsPersistent) | ||
| callee_address = instruction.tx_lookup(tx_id, TxContextFieldTag.CalleeAddress) | ||
|
|
||
| storage_slot = instruction.stack_pop() | ||
| new_value = instruction.stack_pop() | ||
| warm = instruction.access_list_storage_slot_read(tx_id, callee_address, storage_slot) | ||
| original_value = instruction.storage_slot_original_value_read(tx_id, callee_address, storage_slot) | ||
| current_value, _ = instruction.storage_slot_read(callee_address, storage_slot) | ||
|
|
||
| # TODO: Use intrinsic gas (EIP 2028, 2930) | ||
| if current_value == new_value: | ||
| dynamic_gas_cost = SLOAD_GAS | ||
| else: | ||
| if original_value == current_value: | ||
| if original_value == 0: | ||
| dynamic_gas_cost = SSTORE_SET_GAS | ||
| else: | ||
| dynamic_gas_cost = SSTORE_RESET_GAS | ||
| else: | ||
| dynamic_gas_cost = SLOAD_GAS | ||
| if not warm: | ||
| dynamic_gas_cost = dynamic_gas_cost + COLD_SLOAD_COST | ||
|
|
||
| gas_refund = instruction.gas_refund_read(tx_id) | ||
| if current_value != new_value: | ||
| if original_value == current_value: | ||
| if original_value != 0 and new_value == 0: | ||
| gas_refund = gas_refund + SSTORE_CLEARS_SCHEDULE | ||
| else: | ||
| if original_value != 0: | ||
| if current_value == 0: | ||
| gas_refund = gas_refund - SSTORE_CLEARS_SCHEDULE | ||
| if new_value == 0: | ||
| gas_refund = gas_refund + SSTORE_CLEARS_SCHEDULE | ||
| if original_value == new_value: | ||
| if original_value == 0: | ||
| gas_refund = gas_refund + SSTORE_SET_GAS - SLOAD_GAS | ||
| else: | ||
| gas_refund = gas_refund + SSTORE_RESET_GAS - SLOAD_GAS | ||
|
|
||
| instruction.storage_slot_write_with_reversion( | ||
| callee_address, storage_slot, is_persistent, rw_counter_end_of_reversion | ||
| ) | ||
| instruction.add_storage_slot_to_access_list_with_reversion( | ||
| tx_id, callee_address, storage_slot, is_persistent, rw_counter_end_of_reversion | ||
| ) | ||
| instruction.gas_refund_write_only_persistent(tx_id, is_persistent) | ||
|
|
||
| instruction.constrain_same_context_state_transition( | ||
| opcode, | ||
| rw_counter=Transition.delta(9) if is_persistent else Transition.delta(8), | ||
| program_counter=Transition.delta(1), | ||
| stack_pointer=Transition.delta(2), | ||
| state_write_counter=Transition.delta(2), | ||
| dynamic_gas_cost=dynamic_gas_cost, | ||
| ) |
Uh oh!
There was an error while loading. Please reload this page.