From ebcfba7be7a9aa6090f06105add021bec0acaf38 Mon Sep 17 00:00:00 2001 From: KimiWu Date: Mon, 31 Jul 2023 14:01:10 +0800 Subject: [PATCH 1/5] feat: MD spec completed --- specs/error_state/ErrorInvalidCreationCode.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 specs/error_state/ErrorInvalidCreationCode.md diff --git a/specs/error_state/ErrorInvalidCreationCode.md b/specs/error_state/ErrorInvalidCreationCode.md new file mode 100644 index 000000000..66d150d82 --- /dev/null +++ b/specs/error_state/ErrorInvalidCreationCode.md @@ -0,0 +1,37 @@ +# 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 a contract creation transaction, initial bytecodes are executed and the current call context is `CREATE` or `CREATE2`. The last opcode to store for new contract is `RETURN`. It returns `memory[offset...offset + length]` content as contract bytecodes to store into 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 length and construct constraints with it. + +Overall it looks as the following: + +- Pop EVM word `offset` and `length` from the stack. +- Go to `ErrorInvalidCreationCode` state when the current call context is `CREATE` or `CREATE2` and the first byte of contract code 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. + +### Lookups + +- Byte code lookup. +- Stack reads for `offset` and `length`. +- 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`. \ No newline at end of file From 8e8ec2db659184c8e0a20185eb045875ada7df41 Mon Sep 17 00:00:00 2001 From: KimiWu Date: Mon, 31 Jul 2023 14:31:30 +0800 Subject: [PATCH 2/5] feat: impl ErrorInvalidCreationCode error state --- specs/error_state/ErrorInvalidCreationCode.md | 12 +++---- .../evm_circuit/execution/__init__.py | 2 ++ .../execution/error_invalid_creation_code.py | 32 +++++++++++++++++++ src/zkevm_specs/util/param.py | 2 ++ 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py diff --git a/specs/error_state/ErrorInvalidCreationCode.md b/specs/error_state/ErrorInvalidCreationCode.md index 66d150d82..2d5e300d8 100644 --- a/specs/error_state/ErrorInvalidCreationCode.md +++ b/specs/error_state/ErrorInvalidCreationCode.md @@ -6,29 +6,29 @@ InvalidCreationCode is an error related with code store. This error only occurs ### EVM behavior -When handling a contract creation transaction, initial bytecodes are executed and the current call context is `CREATE` or `CREATE2`. The last opcode to store for new contract is `RETURN`. It returns `memory[offset...offset + length]` content as contract bytecodes to store into state DB. +When handling a contract creation transaction, initial bytecode are executed and the current call context is `CREATE` or `CREATE2`. The last opcode to store for new contract is `RETURN`. It returns `memory[offset...offset + length]` content as contract bytecode to store into 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 length and construct constraints with it. +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` and `length` from the stack. -- Go to `ErrorInvalidCreationCode` state when the current call context is `CREATE` or `CREATE2` and the first byte of contract code is `0xEF`. +- 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. +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. ### Lookups -- Byte code lookup. -- Stack reads for `offset` and `length`. +- 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. diff --git a/src/zkevm_specs/evm_circuit/execution/__init__.py b/src/zkevm_specs/evm_circuit/execution/__init__.py index c66f1e047..b28c5b25f 100644 --- a/src/zkevm_specs/evm_circuit/execution/__init__.py +++ b/src/zkevm_specs/evm_circuit/execution/__init__.py @@ -68,6 +68,7 @@ from .error_oog_log import * from .error_write_protection import * from .error_oog_exp import * +from .error_invalid_creation_code import * EXECUTION_STATE_IMPL: Dict[ExecutionState, Callable] = { @@ -135,6 +136,7 @@ ExecutionState.ErrorOutOfGasLOG: error_oog_log, ExecutionState.ErrorWriteProtection: error_write_protection, ExecutionState.ErrorOutOfGasEXP: error_oog_exp, + ExecutionState.ErrorInvalidCreationCode: error_invalid_creation_code, # ExecutionState.ECRECOVER: , # ExecutionState.SHA256: , # ExecutionState.RIPEMD160: , diff --git a/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py b/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py new file mode 100644 index 000000000..dfc04f990 --- /dev/null +++ b/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py @@ -0,0 +1,32 @@ +from zkevm_specs.evm_circuit.table import RW, CallContextFieldTag +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): + # retrieve op code associated to oog constant error + opcode = instruction.opcode_lookup(True) + instruction.constrain_equal(opcode, Opcode.RETURN) + + # the call must be coming from CREATE or CREATE2 + 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) + + call_id = instruction.call_context_lookup(CallContextFieldTag.CallerId) + + # lookup the first byte of deployed bytecode from memory + first_byte = instruction.memory_lookup(RW.Read, return_offset, call_id) + + # verify if the first byte is `0xEF` + instruction.constrain_equal(first_byte, FQ(INVALID_FIRST_BYTE_CONTRACT_CODE)) + + instruction.constrain_error_state( + instruction.rw_counter_offset + instruction.curr.reversible_write_counter + ) diff --git a/src/zkevm_specs/util/param.py b/src/zkevm_specs/util/param.py index 191e9c16a..945d748c3 100644 --- a/src/zkevm_specs/util/param.py +++ b/src/zkevm_specs/util/param.py @@ -120,6 +120,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 From 581a37c16d80c82b5ea2b09c54a3cc2c4144f037 Mon Sep 17 00:00:00 2001 From: KimiWu Date: Mon, 31 Jul 2023 14:58:37 +0800 Subject: [PATCH 3/5] test: impl test cases for ErrorInvalidCreationCode --- specs/error_state/ErrorInvalidCreationCode.md | 2 +- .../execution/error_invalid_creation_code.py | 11 +- .../evm/test_error_invalild_creation_code.py | 105 ++++++++++++++++++ 3 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 tests/evm/test_error_invalild_creation_code.py diff --git a/specs/error_state/ErrorInvalidCreationCode.md b/specs/error_state/ErrorInvalidCreationCode.md index 2d5e300d8..1bb22c561 100644 --- a/specs/error_state/ErrorInvalidCreationCode.md +++ b/specs/error_state/ErrorInvalidCreationCode.md @@ -14,7 +14,7 @@ Even though this error occurs in `CREATE` or `CREATE2`, this error should be con Overall it looks as the following: -- Pop EVM word `offset` and `length` from the stack. +- 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 diff --git a/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py b/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py index dfc04f990..2abdbac8d 100644 --- a/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py +++ b/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py @@ -1,4 +1,4 @@ -from zkevm_specs.evm_circuit.table import RW, CallContextFieldTag +from zkevm_specs.evm_circuit.table import RW from zkevm_specs.util.param import ( INVALID_FIRST_BYTE_CONTRACT_CODE, N_BYTES_MEMORY_ADDRESS, @@ -9,8 +9,9 @@ def error_invalid_creation_code(instruction: Instruction): - # retrieve op code associated to oog constant error opcode = instruction.opcode_lookup(True) + + # opcode mut be `RETURN` instruction.constrain_equal(opcode, Opcode.RETURN) # the call must be coming from CREATE or CREATE2 @@ -19,12 +20,10 @@ def error_invalid_creation_code(instruction: Instruction): # pop offset from stack only return_offset = instruction.word_to_fq(instruction.stack_pop(), N_BYTES_MEMORY_ADDRESS) - call_id = instruction.call_context_lookup(CallContextFieldTag.CallerId) - # lookup the first byte of deployed bytecode from memory - first_byte = instruction.memory_lookup(RW.Read, return_offset, call_id) + first_byte = instruction.memory_lookup(RW.Read, return_offset) - # verify if the first byte is `0xEF` + # 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( diff --git a/tests/evm/test_error_invalild_creation_code.py b/tests/evm/test_error_invalild_creation_code.py new file mode 100644 index 000000000..e37798eb1 --- /dev/null +++ b/tests/evm/test_error_invalild_creation_code.py @@ -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, + ), + ], + ) From 74f01ff7571702da162659a07ddfa505db62385d Mon Sep 17 00:00:00 2001 From: Kimi Wu Date: Tue, 1 Aug 2023 12:23:04 +0800 Subject: [PATCH 4/5] Update src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py Co-authored-by: David Nevado --- .../evm_circuit/execution/error_invalid_creation_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py b/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py index 2abdbac8d..63e6b6235 100644 --- a/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py +++ b/src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py @@ -11,7 +11,7 @@ def error_invalid_creation_code(instruction: Instruction): opcode = instruction.opcode_lookup(True) - # opcode mut be `RETURN` + # opcode must be `RETURN` instruction.constrain_equal(opcode, Opcode.RETURN) # the call must be coming from CREATE or CREATE2 From 4a3594a0864cbb188e223aa433451b3fabc3f4b8 Mon Sep 17 00:00:00 2001 From: KimiWu Date: Wed, 2 Aug 2023 10:25:44 +0800 Subject: [PATCH 5/5] doc: refine MD spec --- specs/error_state/ErrorCodeStore.md | 2 +- specs/error_state/ErrorInvalidCreationCode.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/error_state/ErrorCodeStore.md b/specs/error_state/ErrorCodeStore.md index 04a7faa7c..601db78b2 100644 --- a/specs/error_state/ErrorCodeStore.md +++ b/specs/error_state/ErrorCodeStore.md @@ -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 diff --git a/specs/error_state/ErrorInvalidCreationCode.md b/specs/error_state/ErrorInvalidCreationCode.md index 1bb22c561..7391efbe5 100644 --- a/specs/error_state/ErrorInvalidCreationCode.md +++ b/specs/error_state/ErrorInvalidCreationCode.md @@ -6,7 +6,8 @@ InvalidCreationCode is an error related with code store. This error only occurs ### EVM behavior -When handling a contract creation transaction, initial bytecode are executed and the current call context is `CREATE` or `CREATE2`. The last opcode to store for new contract is `RETURN`. It returns `memory[offset...offset + length]` content as contract bytecode to store into state DB. +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.