Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.
2 changes: 1 addition & 1 deletion specs/error_state/ErrorCodeStore.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
`ErrorCodeStore` is a combined error code for handling the `CodeStoreOutOfGas` and `MaxCodeSizeExceeded` code store related errors. This type of error only occurs when executing `CREATE`/`CREATE2` opcode or a deployment transaction (tx.to = null).

### EVM behavior
When handling either a CREATE` or a`CREATE2` opcode, the initial bytecode is executed and the current call context is created. The contract bytecode will then be returned through the `RETURN` opcode as the execution result. More particularly, the contract bytecode will be defined as the memory chunk of length `length` starting at offset `offset`, that is the memory located at [`offset`...`offset` + `length`] is stored in the state db. The gas cost for storing the bytecode is:
When handling either a `CREATE` or a`CREATE2` opcode, the initial bytecode is executed and the current call context is created. The contract bytecode will then be returned through the `RETURN` opcode as the execution result. More particularly, the contract bytecode will be defined as the memory chunk of length `length` starting at offset `offset`, that is the memory located at [`offset`...`offset` + `length`] is stored in the state db. The gas cost for storing the bytecode is:

```
let CODE_DEPOSIT_BYTE_COST = 200
Expand Down
38 changes: 38 additions & 0 deletions specs/error_state/ErrorInvalidCreationCode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# ErrorInvalidCreationCode state

## Procedure

InvalidCreationCode is an error related with code store. This error only occurs when executing `CREATE` or `CREATE2` opcode or contract creation transaction (`tx.to == null`).

### EVM behavior

When handling either a `CREATE` or a`CREATE2` opcode, the initial bytecode is executed and the current call context is created. The contract bytecode will then be returned through the `RETURN` opcode as the execution result. More particularly, the contract bytecode will be defined as the memory chunk of length `length` starting at offset `offset`, that is the memory located at [`offset`...`offset` + `length`] is stored in the state db.


In bus-mapping, when the executing opcode is `RETURN` and the current call context is `CREATE` or `CREATE2`, check if the first byte of contract code is `0xEF` to identify this error.

Even though this error occurs in `CREATE` or `CREATE2`, this error should be constrained in the `RETURN` opcode. Since it is easy to get the available memory offset and construct constraints with it.

Overall it looks as the following:

- Pop EVM word `offset` from the stack.
- Go to `ErrorInvalidCreationCode` state when the current call context is `CREATE` or `CREATE2` and the first byte of the deployed bytecode is `0xEF`.

### Constraints

1. Current opcode is `RETURN` and `is_create` is True.
2. The first byte of contract code is `0xEF`.
3. Current call must be failed.
4. If it's a root call, it transits to `EndTx`.
5. if it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check that it's not a static call?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't. If it's a CREATE callcontext and also a static call, it would hit ErrorWriteProtection first. Correct me if I misunderstand error states handling mechanism.


### Lookups

- Memory lookup.
- Stack reads for `offset`.
- Call context lookups for `is_success` and `rw_counter_end_of_reversion`.
- Restore context lookups for non-root call.

## Code

Please refer to `src/zkevm_specs/evm/execution/error_invalid_creation_code.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 @@ -71,6 +71,7 @@
from .error_oog_account_access import *
from .error_code_store import *
from .error_oog_exp import *
from .error_invalid_creation_code import *
from .error_oog_sha3 import *
from .error_oog_static_memory_expansion import *
from .error_oog_sload_sstore import *
Expand Down Expand Up @@ -143,6 +144,7 @@
ExecutionState.ErrorMaxCodeSizeExceeded: error_code_store,
ExecutionState.ErrorOutOfGasCodeStore: error_code_store,
ExecutionState.ErrorOutOfGasEXP: error_oog_exp,
ExecutionState.ErrorInvalidCreationCode: error_invalid_creation_code,
ExecutionState.ErrorOutOfGasSHA3: error_oog_sha3,
ExecutionState.ErrorOutOfGasAccountAccess: error_oog_account_access,
ExecutionState.ErrorOutOfGasStaticMemoryExpansion: error_oog_static_memory_expansion,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from zkevm_specs.evm_circuit.table import RW
from zkevm_specs.util.param import (
INVALID_FIRST_BYTE_CONTRACT_CODE,
N_BYTES_MEMORY_ADDRESS,
)
from ...util import FQ
from ..instruction import Instruction
from ..opcode import Opcode


def error_invalid_creation_code(instruction: Instruction):
opcode = instruction.opcode_lookup(True)

# opcode must be `RETURN`
instruction.constrain_equal(opcode, Opcode.RETURN)

# the call must be coming from CREATE or CREATE2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it also be that tx.to == null as it is mentioned in the MD?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we couldn't add this constraint. There are two routes for this error, a.) it's a CREATE/CREATE2 opcode execution and b.) it's a contract deployment transaction (which is tx.to == null). If we add this constraint, then the first case a.) would be failed.

instruction.constrain_equal(FQ(instruction.curr.is_create), FQ(1))

# pop offset from stack only
return_offset = instruction.word_to_fq(instruction.stack_pop(), N_BYTES_MEMORY_ADDRESS)

# lookup the first byte of deployed bytecode from memory
first_byte = instruction.memory_lookup(RW.Read, return_offset)

# verify if the first byte is `0xEF`, which is introduced in EIP-3541
instruction.constrain_equal(first_byte, FQ(INVALID_FIRST_BYTE_CONTRACT_CODE))

instruction.constrain_error_state(
instruction.rw_counter_offset + instruction.curr.reversible_write_counter
)
2 changes: 2 additions & 0 deletions src/zkevm_specs/util/param.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@
PUBLIC_INPUTS_EXTRA_LEN = 3 * 2 # Length of fields that don't belong to any table
PUBLIC_INPUTS_TX_LEN = 10 # Length of tx public data (without calldata)

# EIP-3541, Reject new contract code starting with the 0xEF byte
INVALID_FIRST_BYTE_CONTRACT_CODE = 0xEF

# Precompiled contract gas prices

Expand Down
105 changes: 105 additions & 0 deletions tests/evm/test_error_invalild_creation_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import pytest

from zkevm_specs.evm_circuit import (
Block,
Bytecode,
CallContextFieldTag,
ExecutionState,
RWDictionary,
StepState,
Tables,
verify_steps,
)
from zkevm_specs.util import Word
from zkevm_specs.util.param import INVALID_FIRST_BYTE_CONTRACT_CODE


TESTING_DATA = (
(0, True), # is_root
(0, False), # not is_root
(200, True), # is_root
(200, False), # not is_root
)


@pytest.mark.parametrize("offset, is_root", TESTING_DATA)
def test_error_invalid_creation_code(offset: int, is_root: bool):
bytecode = Bytecode().push32(32).push32(offset).return_()
bytecode_hash = Word(bytecode.hash())

current_call_id = 1 if is_root else 2
stack_pointer = 1023
pc = 66
reversible_write_counter = 2
rw_counter = 3 if is_root is True else 15

# Only need `size` from RETURN opcode
rw_table = RWDictionary(rw_counter).stack_read(current_call_id, stack_pointer, Word(offset))

# assign invalid byte `0xEF` to the first byte of memory
rw_table.memory_read(current_call_id, offset, INVALID_FIRST_BYTE_CONTRACT_CODE)

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, True) \
.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.ErrorInvalidCreationCode,
rw_counter=rw_counter,
call_id=current_call_id,
is_root=is_root,
is_create=True,
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,
gas_left=0,
)
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=True,
code_hash=bytecode_hash,
program_counter=pc + 1,
stack_pointer=1024,
gas_left=0,
memory_word_size=0,
reversible_write_counter=reversible_write_counter,
),
],
)