Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Feat/#446 implement ErrorReturnDataOutOfBound error state (#479)
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
KimiWu123 authored Aug 3, 2023
1 parent 78f22a4 commit f67c2e0
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 0 deletions.
28 changes: 28 additions & 0 deletions specs/error_state/ErrorReturnDataOutOfBound.md
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
2 changes: 2 additions & 0 deletions src/zkevm_specs/evm_circuit/execution/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
from .error_oog_dynamic_memory_expansion import *
from .error_oog_memory_copy import *
from .error_oog_log import *
from .error_return_data_out_of_bound import *
from .error_write_protection import *
from .error_oog_account_access import *
from .error_code_store import *
Expand Down Expand Up @@ -146,6 +147,7 @@
ExecutionState.ErrorOutOfGasAccountAccess: error_oog_account_access,
ExecutionState.ErrorOutOfGasStaticMemoryExpansion: error_oog_static_memory_expansion,
ExecutionState.ErrorOutOfGasSloadSstore: error_oog_sload_sstore,
ExecutionState.ErrorReturnDataOutOfBound: error_return_data_out_of_bound,
# ExecutionState.ECRECOVER: ,
# ExecutionState.SHA256: ,
# ExecutionState.RIPEMD160: ,
Expand Down
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
)
124 changes: 124 additions & 0 deletions tests/evm/test_error_return_data_out_of_bound.py
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,
),
],
)

0 comments on commit f67c2e0

Please sign in to comment.