This repository was archived by the owner on Jul 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 270
CODECOPY opcode #148
Merged
Merged
CODECOPY opcode #148
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
4632d18
feat: copy code to memory
roynalnaruto a15ba0c
doc(CopyCodeToMemory): spec for copy code to memory gadget
roynalnaruto e5a468b
feat: codecopy test
roynalnaruto 332d8b7
specs for codecopy
roynalnaruto 71e427d
fix: update specs impl as per upstream updates
roynalnaruto dde5080
fix: type
roynalnaruto 518c8be
chore: minor refactor
roynalnaruto a35874d
feat: extend bytecode circuit with tags Length and Byte
roynalnaruto f6d81ac
fix: codecopy now uses code size from bytecode lookup
roynalnaruto 78948c6
fix: refactor, remove redundant/duplicate constraints, PR review fixes
roynalnaruto b513dda
fix: type check
roynalnaruto ec6bd14
fix: remove redundant constraint | fix other constraints
roynalnaruto 62ae3d3
fix: remove redundant constraint
roynalnaruto be96e29
fix: updates as per code review comments
roynalnaruto f7df2b0
fix: code source from current instruction
roynalnaruto 87f58bc
fix: specs are updated
roynalnaruto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # CODECOPY opcode | ||
|
|
||
| ## Procedure | ||
|
|
||
| The `CODECOPY` opcode pops `memory_offset`, `code_offset` and `size` from the stack. | ||
| It then copies `size` bytes of code running in the current environment from an offset `code_offset` to the memory at the address `memory_offset`. For out-of-bound scenarios where `size > len(code) - code_offset`, EVM pads 0 to the end of the copied bytes. | ||
|
|
||
| The gas cost of `CODECOPY` opcode consists of two parts: | ||
|
|
||
| 1. A constant gas cost: `3 gas` | ||
| 2. A dynamic gas cost: cost of memory expansion and copying (variable depending on the `size` copied to memory) | ||
|
|
||
| ## Circuit Behaviour | ||
|
|
||
| `CODECOPY` makes use of the internal execution step `CopyCodeToMemory` and loops over these steps iteratively until there are no more bytes to be copied. The `CODECOPY` circuit itself only constrains the values popped from stack and call context/account read lookups. | ||
|
|
||
| The gadget then transits to the internal state of `CopyCodeToMemory`. | ||
|
|
||
| ## Constraints | ||
|
|
||
| 1. opId = 0x39 | ||
| 2. State Transitions: | ||
| - rw_counter -> rw_counter + 3 (3 stack reads) | ||
| - stack_pointer -> stack_pointer + 3 | ||
| - pc -> pc + 1 | ||
| - gas -> 3 + dynamic_cost (memory expansion and copier cost when `size > 0`) | ||
| - memory_size | ||
| - `prev_memory_size` if `size = 0` | ||
| - `max(prev_memory_size, (memory_offset + size + 31) / 32)` if `size > 0` | ||
| 3. Lookups: | ||
| - `memory_offset` is at the top of the stack | ||
| - `code_offset` is at the second position of the stack | ||
| - `size` is at the third position of the stack | ||
| - `code_size` from the bytecode table | ||
|
|
||
| ## Exceptions | ||
|
|
||
| 1. Stack Underflow: `1021 <= stack_pointer <= 1024` | ||
| 2. Out-of-Gas: remaining gas is not enough | ||
|
|
||
| ## Code | ||
|
|
||
| Please refer to `src/zkevm_specs/evm/execution/codecopy.py` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # CopyCodeToMemory | ||
|
|
||
| ## Circuit Behaviour | ||
|
|
||
| `CopyCodeToMemory` is an internal execution state and doesn't correspond to an EVM opcode. It verifies that data from bytecode table has been written to memory. This gadget can in one iteration only copy `MAX_COPY_BYTES` number of bytes, hence for lengths longer than the bound the gadget loops itself until there are no more bytes to be copied. | ||
|
|
||
| The `CopyCodeToMemory` circuit uses the `BufferReaderGadget` to check if the access is out of bounds and needs 0 padding. | ||
|
|
||
| The `CopyCodeToMemory` circuit looks up the bytes read from buffer against both the bytecode table and the RW table (memory-write). An additional constraint checks whether or not the copying is finished, and if not, it constrains the next execution state to continue being `CopyCodeToMemory` while also adding constraints to the next step's auxiliary data. | ||
|
|
||
| ## Constraints | ||
|
|
||
| We define `n_bytes_read` as the number of bytes read from the bytecode table. `n_bytes_read <= MAX_COPY_BYTES`. | ||
|
|
||
| We define `n_bytes_written` as the number of bytes written to the memory. `n_bytes_written <= MAX_COPY_BYTES`. | ||
|
|
||
| `n_bytes_read` differs from `n_bytes_written` in out-of-bound cases where nothing is read from the bytecode table but a `0` is written to memory. | ||
|
|
||
| 1. State Transition: | ||
| - rw_counter: `n_bytes_written` | ||
| 2. Lookups: | ||
| - `n_bytes_read` lookups from bytecode table | ||
| - `n_bytes_written` lookups from RW table (memory-write) | ||
|
|
||
| ## Exceptions | ||
|
|
||
| No exceptions for `CopyCodeToMemory` since it is an internal state. | ||
|
|
||
| ## Code | ||
|
|
||
| Please refer to `src/zkevm_specs/evm/execution/copy_code_to_memory.py`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| from ...util import N_BYTES_MEMORY_ADDRESS, FQ | ||
| from ..execution_state import ExecutionState | ||
| from ..instruction import Instruction, Transition | ||
| from ..step import CopyCodeToMemoryAuxData | ||
| from ..table import RW, RWTableTag, CallContextFieldTag, AccountFieldTag | ||
|
|
||
|
|
||
| def codecopy(instruction: Instruction): | ||
| opcode = instruction.opcode_lookup(True) | ||
|
|
||
| memory_offset_word, code_offset_word, size_word = ( | ||
| instruction.stack_pop(), | ||
| instruction.stack_pop(), | ||
| instruction.stack_pop(), | ||
| ) | ||
|
|
||
| memory_offset, size = instruction.memory_offset_and_length(memory_offset_word, size_word) | ||
| code_offset = instruction.rlc_to_fq_exact(code_offset_word, N_BYTES_MEMORY_ADDRESS) | ||
|
|
||
| code_size = instruction.bytecode_length(instruction.curr.code_source) | ||
|
|
||
| next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( | ||
| memory_offset, size | ||
| ) | ||
| gas_cost = instruction.memory_copier_gas_cost(size, memory_expansion_gas_cost) | ||
|
|
||
| if instruction.is_zero(size) == FQ(0): | ||
| assert instruction.next is not None | ||
| instruction.constrain_equal( | ||
| instruction.next.execution_state, ExecutionState.CopyCodeToMemory | ||
| ) | ||
| next_aux = instruction.next.aux_data | ||
| assert isinstance(next_aux, CopyCodeToMemoryAuxData) | ||
| instruction.constrain_equal(next_aux.src_addr, code_offset) | ||
| instruction.constrain_equal(next_aux.dst_addr, memory_offset) | ||
| instruction.constrain_equal(next_aux.src_addr_end, code_size) | ||
| instruction.constrain_equal(next_aux.bytes_left, size) | ||
| instruction.constrain_equal(next_aux.code_source, instruction.curr.code_source) | ||
|
|
||
| instruction.step_state_transition_in_same_context( | ||
| opcode, | ||
| rw_counter=Transition.delta(instruction.rw_counter_offset), | ||
| program_counter=Transition.delta(1), | ||
| stack_pointer=Transition.delta(3), | ||
| memory_size=Transition.to(next_memory_size), | ||
| dynamic_gas_cost=gas_cost, | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import itertools | ||
| from typing import Iterator | ||
|
|
||
| from ...util import FQ, MAX_N_BYTES_COPY_CODE_TO_MEMORY, N_BYTES_MEMORY_SIZE, RLC | ||
| from ..execution_state import ExecutionState | ||
| from ..instruction import Instruction, Transition | ||
| from ..step import CopyCodeToMemoryAuxData | ||
| from ..table import RW | ||
| from ..util import BufferReaderGadget | ||
|
|
||
|
|
||
| def copy_code_to_memory(instruction: Instruction): | ||
| aux = instruction.curr.aux_data | ||
| assert isinstance(aux, CopyCodeToMemoryAuxData) | ||
|
|
||
| buffer_reader = BufferReaderGadget( | ||
| instruction, MAX_N_BYTES_COPY_CODE_TO_MEMORY, aux.src_addr, aux.src_addr_end, aux.bytes_left | ||
| ) | ||
|
|
||
| for idx in range(MAX_N_BYTES_COPY_CODE_TO_MEMORY): | ||
| if buffer_reader.read_flag(idx) == 1: | ||
| byte = instruction.bytecode_lookup( | ||
| aux.code_source, | ||
| aux.src_addr + idx, | ||
| ) | ||
| buffer_reader.constrain_byte(idx, byte) | ||
|
|
||
| for idx in range(MAX_N_BYTES_COPY_CODE_TO_MEMORY): | ||
| if buffer_reader.has_data(idx) == 1: | ||
| byte = instruction.memory_lookup(RW.Write, aux.dst_addr + idx) | ||
| buffer_reader.constrain_byte(idx, byte) | ||
|
|
||
| copied_bytes = buffer_reader.num_bytes() | ||
| lt, finished = instruction.compare(copied_bytes, aux.bytes_left, N_BYTES_MEMORY_SIZE) | ||
|
|
||
| # either copied bytes are less than the bytes left, or copying is finished | ||
| instruction.constrain_zero((1 - lt) * (1 - finished)) | ||
|
|
||
| if finished == 0: | ||
| assert instruction.next is not None | ||
| instruction.constrain_equal( | ||
| instruction.next.execution_state, ExecutionState.CopyCodeToMemory | ||
| ) | ||
| next_aux = instruction.next.aux_data | ||
| assert next_aux is not None and isinstance(next_aux, CopyCodeToMemoryAuxData) | ||
| instruction.constrain_equal(next_aux.src_addr, aux.src_addr + copied_bytes) | ||
| instruction.constrain_equal(next_aux.dst_addr, aux.dst_addr + copied_bytes) | ||
| instruction.constrain_equal(next_aux.bytes_left + copied_bytes, aux.bytes_left) | ||
| instruction.constrain_equal(next_aux.src_addr_end, aux.src_addr_end) | ||
| instruction.constrain_equal(next_aux.code_source, aux.code_source) | ||
|
|
||
| instruction.constrain_step_state_transition( | ||
| rw_counter=Transition.delta(instruction.rw_counter_offset), | ||
| call_id=Transition.same(), | ||
| is_root=Transition.same(), | ||
| is_create=Transition.same(), | ||
| code_source=Transition.same(), | ||
| program_counter=Transition.same(), | ||
| stack_pointer=Transition.same(), | ||
| memory_size=Transition.same(), | ||
| state_write_counter=Transition.same(), | ||
| ) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.