This repository has been archived by the owner on Jul 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 272
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* doc: MD spec finished * feat: impl ErrorReturnDataOutOfBound * test: test cases finished * chore: fix for review feedbacks
- Loading branch information
Showing
4 changed files
with
186 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,28 @@ | ||
# ErrorReturnDataOutOfBound state | ||
|
||
## Procedure | ||
|
||
This type of error only occurs when executing op code is `RETURNDATACOPY`. | ||
|
||
### EVM behavior | ||
|
||
The `RETURNDATACOPY` opcode pops `memOffset`,`dataOffset`, and `length` from the stack. A return data out of bound error is thrown if one of the following condition is met: | ||
1. `dataOffset`is u64 overflow, | ||
2. `dataOffset` + `length` is u64 overflow, | ||
3. `dataOffset` + `length` is larger than the length of last callee's return data. | ||
|
||
### Constraints | ||
1. current opcode is `RETURNDATACOPY` | ||
2. at least one of below conditions is met: | ||
- `dataOffset`is u64 overflow, | ||
- `dataOffset` + `length` is u64 overflow | ||
- `dataOffset` + `length` is larger than the length of last callee's return data | ||
3. common error constraints: | ||
- current call fails. | ||
- constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. | ||
- If it's a root call, it transits to `EndTx`. | ||
- if it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. | ||
|
||
## Code | ||
|
||
Please refer to src/zkevm_specs/evm_circuit/execution/error_return_data_out_of_bound.py |
This file contains 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
32 changes: 32 additions & 0 deletions
32
src/zkevm_specs/evm_circuit/execution/error_return_data_out_of_bound.py
This file contains 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,32 @@ | ||
from zkevm_specs.evm_circuit.table import CallContextFieldTag, RW | ||
from zkevm_specs.util.param import MAX_N_BYTES | ||
from ..instruction import Instruction | ||
from ..opcode import Opcode | ||
|
||
|
||
def error_return_data_out_of_bound(instruction: Instruction): | ||
opcode = instruction.opcode_lookup(True) | ||
instruction.constrain_equal(opcode, Opcode.RETURNDATACOPY) | ||
|
||
# skip first stack value, `memOffset` | ||
data_offset = instruction.word_to_fq(instruction.stack_lookup(RW.Read, 1), MAX_N_BYTES) | ||
length = instruction.word_to_fq(instruction.stack_lookup(RW.Read, 2), MAX_N_BYTES) | ||
|
||
# get the length of last callee's return data | ||
return_data_length = instruction.call_context_lookup( | ||
CallContextFieldTag.LastCalleeReturnDataLength, RW.Read | ||
) | ||
|
||
# verify if this call meets any one of error conditions | ||
end = data_offset + length | ||
is_data_offset_u64_overflow = instruction.is_u64_overflow(data_offset) | ||
is_end_u64_overflow = instruction.is_u64_overflow(end) | ||
is_end_over_return_data_len, _ = instruction.compare(return_data_length, end, MAX_N_BYTES) | ||
|
||
instruction.constrain_not_zero( | ||
is_data_offset_u64_overflow + is_end_u64_overflow + is_end_over_return_data_len | ||
) | ||
|
||
instruction.constrain_error_state( | ||
instruction.rw_counter_offset + instruction.curr.reversible_write_counter | ||
) |
This file contains 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,124 @@ | ||
import pytest | ||
|
||
from zkevm_specs.util import Word | ||
from zkevm_specs.evm_circuit import ( | ||
ExecutionState, | ||
StepState, | ||
verify_steps, | ||
Tables, | ||
CallContextFieldTag, | ||
Block, | ||
Bytecode, | ||
RWDictionary, | ||
) | ||
from zkevm_specs.util.param import MAX_U64 | ||
|
||
|
||
TESTING_DATA = ( | ||
## is_root is True | ||
# is_data_offset_u64_overflow is True | ||
(MAX_U64 + 1, 0, True), | ||
# is_end_u64_overflow is True | ||
(MAX_U64, 1, True), | ||
(1, MAX_U64, True), | ||
(0, MAX_U64 + 1, True), | ||
# is_end_over_return_data_len is True (as we set return_data_length to 320) | ||
(321, 0, True), | ||
(320, 1, True), | ||
## Same cases, with is_root set to False | ||
(MAX_U64 + 1, 0, False), | ||
(MAX_U64, 1, False), | ||
(1, MAX_U64, False), | ||
(0, MAX_U64 + 1, False), | ||
(321, 0, False), | ||
(320, 1, False), | ||
) | ||
|
||
|
||
@pytest.mark.parametrize("data_offset, length, is_root", TESTING_DATA) | ||
def test_error_return_data_out_of_bound( | ||
data_offset: int, | ||
length: int, | ||
is_root: bool, | ||
): | ||
bytecode = Bytecode().push32(32).push32(data_offset).push32(length).returndatacopy() | ||
bytecode_hash = Word(bytecode.hash()) | ||
|
||
current_call_id = 1 if is_root else 2 | ||
stack_pointer = 1021 | ||
pc = 99 | ||
reversible_write_counter = 2 | ||
rw_counter = 4 if is_root is True else 16 | ||
|
||
# Only need `size` from RETURN opcode | ||
rw_table = ( | ||
RWDictionary(rw_counter) | ||
.stack_read(current_call_id, stack_pointer + 1, Word(data_offset)) | ||
.stack_read(current_call_id, stack_pointer + 2, Word(length)) | ||
) | ||
|
||
rw_table.call_context_read(current_call_id, CallContextFieldTag.LastCalleeReturnDataLength, 320) | ||
|
||
rw_table.call_context_read(current_call_id, CallContextFieldTag.IsSuccess, 0) | ||
|
||
if not is_root: | ||
# fmt: off | ||
rw_table \ | ||
.call_context_read(current_call_id, CallContextFieldTag.CallerId, 1) \ | ||
.call_context_read(1, CallContextFieldTag.IsRoot, False) \ | ||
.call_context_read(1, CallContextFieldTag.IsCreate, False) \ | ||
.call_context_read(1, CallContextFieldTag.CodeHash, bytecode_hash) \ | ||
.call_context_read(1, CallContextFieldTag.ProgramCounter, pc + 1) \ | ||
.call_context_read(1, CallContextFieldTag.StackPointer, 1024) \ | ||
.call_context_read(1, CallContextFieldTag.GasLeft, 0) \ | ||
.call_context_read(1, CallContextFieldTag.MemorySize, 0) \ | ||
.call_context_read(1, CallContextFieldTag.ReversibleWriteCounter, reversible_write_counter) \ | ||
.call_context_write(1, CallContextFieldTag.LastCalleeId, 2) \ | ||
.call_context_write(1, CallContextFieldTag.LastCalleeReturnDataOffset, 0) \ | ||
.call_context_write(1, CallContextFieldTag.LastCalleeReturnDataLength, 0) | ||
# fmt: on | ||
|
||
tables = Tables( | ||
block_table=set(Block().table_assignments()), | ||
tx_table=set(), | ||
bytecode_table=set(bytecode.table_assignments()), | ||
rw_table=set(rw_table.rws), | ||
) | ||
|
||
verify_steps( | ||
tables=tables, | ||
steps=[ | ||
StepState( | ||
execution_state=ExecutionState.ErrorReturnDataOutOfBound, | ||
rw_counter=rw_counter, | ||
call_id=current_call_id, | ||
is_root=is_root, | ||
is_create=False, | ||
code_hash=bytecode_hash, | ||
program_counter=pc, | ||
stack_pointer=stack_pointer, | ||
gas_left=100, | ||
memory_word_size=0, | ||
reversible_write_counter=reversible_write_counter, | ||
), | ||
StepState( | ||
execution_state=ExecutionState.EndTx, | ||
rw_counter=rw_table.rw_counter + reversible_write_counter, | ||
call_id=1, | ||
) | ||
if is_root is True | ||
else StepState( | ||
execution_state=ExecutionState.STOP, | ||
rw_counter=rw_table.rw_counter + reversible_write_counter, | ||
call_id=1, | ||
is_root=is_root, | ||
is_create=False, | ||
code_hash=bytecode_hash, | ||
program_counter=pc + 1, | ||
stack_pointer=1024, | ||
gas_left=0, | ||
memory_word_size=0, | ||
reversible_write_counter=reversible_write_counter, | ||
), | ||
], | ||
) |